pandas-dev/pandas · error · ValueError

invalid na_position

Error message

invalid na_position: {na_position}

What it means

Raised by ArrowExtensionArray.argsort when na_position is not 'last' or 'first'. The method maps na_position to pyarrow's null_placement enum and only those two strings are valid; any typo or alternate spelling (e.g. 'end', 'at_end', 'keep') is rejected.

Solutions

  1. Use exactly 'first' or 'last' for na_position.
  2. If you need nulls at the end (default), omit na_position entirely.
  3. Wrap user-facing API parameters and validate against {'first','last'} before forwarding.
  4. Check the value at runtime: assert na_position in {'first','last'}.

Example fix

# before
arr = pd.array([1, None, 2], dtype="int64[pyarrow]")
arr.argsort(na_position='end')  # raises ValueError

# after
arr.argsort(na_position='last')
Defensive patterns

Strategy: validation

Validate before calling

VALID_NA_POSITIONS = {'first', 'last'}

def safe_argsort(arr, na_position='last', **kw):
    if na_position not in VALID_NA_POSITIONS:
        raise ValueError(f"na_position must be one of {VALID_NA_POSITIONS}, got {na_position!r}")
    return arr.argsort(na_position=na_position, **kw)

Type guard

def is_valid_na_position(s: str) -> bool:
    return s in {'first', 'last'}

Prevention

When it happens

Trigger: arr.argsort(na_position='end'); sort_values(na_position='at_end') on a pyarrow-backed Series; passing na_position='Keep' or 'NONE'.

Common situations: Users more familiar with numpy/pandas spelling variations; copying na_position values from a different API (e.g. pyarrow's null_placement names); case sensitivity ('Last' vs 'last').

Related errors


AI-assisted analysis of pandas-dev/pandas@3b7651241d (2026-08-11). Data as JSON: /api/errors/5c39aee9369356b5. Report an issue: GitHub.

Appendix: source

Thrown at pandas/core/arrays/arrow/array.py:1546

        >>> pd.array([True, False, pd.NA], dtype="boolean[pyarrow]").all(skipna=False)
        False
        >>> pd.array([1, 0, pd.NA], dtype="boolean[pyarrow]").all(skipna=False)
        False
        """
        return self._reduce("all", skipna=skipna, **kwargs)

    def argsort(
        self,
        *,
        ascending: bool = True,
        kind: SortKind = "quicksort",
        na_position: str = "last",
        **kwargs,
    ) -> np.ndarray:
        order = "ascending" if ascending else "descending"
        null_placement = {"last": "at_end", "first": "at_start"}.get(na_position, None)
        if null_placement is None:
            raise ValueError(f"invalid na_position: {na_position}")

        result = pc.array_sort_indices(
            self._pa_array, order=order, null_placement=null_placement
        )
        np_result = result.to_numpy()
        return np_result.astype(np.intp, copy=False)

    def sort(
        self,
        *,
        ascending: bool = True,
        kind: SortKind = "quicksort",
        na_position: str = "last",
    ) -> None:
        # This override replaces self._pa_array directly, bypassing __setitem__,
        # so enforce the read-only guard here to stay consistent with it and
        # with the base ExtensionArray.sort.
        if self._readonly:

View on GitHub (pinned to 3b7651241d)