pandas-dev/pandas · error · ValueError

invalid na_position: {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 mapping dict at line 1519 only accepts those two strings; any other value (typo, 'Last', 'end', None, 'at_end') yields null_placement None and raises ValueError. Used by argsort/sort/sort_values pipelines.

Source

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

        >>> 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 71959b8cb9)

Solutions

  1. Use only 'first' or 'last' (lowercase).
  2. Normalize user input: na_position = str(na_position).lower(); assert in {'first','last'}.
  3. Map synonyms: {'at_start':'first','at_end':'last','head':'first','tail':'last'}.
  4. Default to 'last' when input is missing/invalid.

Example fix

# before
idx = arr.argsort(na_position=cfg.get('na', 'at_end'))  # ValueError
# after
NA_MAP = {'at_start':'first','at_end':'last','first':'first','last':'last'}
na = NA_MAP[str(cfg.get('na','last')).lower()]
idx = arr.argsort(na_position=na)
Defensive patterns

Strategy: validation

Validate before calling

VALID_NA_POSITIONS = {'first', 'last'}
SYNONYMS = {'at_start': 'first', 'at_end': 'last', 'head': 'first', 'tail': 'last', 'top': 'first', 'bottom': 'last'}

def normalize_na_position(x):
    if x is None:
        return 'last'
    key = str(x).lower()
    return SYNONYMS.get(key, key) if key in SYNONYMS else (key if key in VALID_NA_POSITIONS else 'last')

na = normalize_na_position(cfg.get('na_position'))
idx = arr.argsort(na_position=na)

Type guard

def is_valid_na_position(x) -> bool:
    return isinstance(x, str) and x.lower() in {'first', 'last'}

Try / catch

try:
    idx = arr.argsort(na_position=na)
except ValueError as e:
    if 'invalid na_position' in str(e):
        idx = arr.argsort(na_position='last')
    else:
        raise

Prevention

When it happens

Trigger: `arr.argsort(na_position='end')`, `arr.argsort(na_position=None)`, `arr.argsort(na_position='LAST')`, or passing a user-controlled config value without validation. Case-sensitive: only lowercase 'first'/'last'.

Common situations: Config-driven sort parameters from YAML/JSON where values differ ('at_end', 'tail'); case mismatches; passing pandas-internal null_placement strings ('at_start'/'at_end') that pyarrow uses but argsort doesn't accept at the public API.

Related errors


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