{"record":{"id":"26d3a71a6780d3e6","repo":"pandas-dev/pandas","slug":"invalid-na-position-na-position-r","errorCode":null,"errorMessage":"invalid na_position: {na_position!r}","messagePattern":"invalid na_position: (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"pandas/core/arrays/categorical.py","lineNumber":2159,"sourceCode":"        >>> c\n        [NaN, 2, 2, NaN, 5]\n        Categories (2, int64): [2, 5]\n        >>> c.sort_values()\n        [2, 2, 5, NaN, NaN]\n        Categories (2, int64): [2, 5]\n        >>> c.sort_values(ascending=False)\n        [5, 2, 2, NaN, NaN]\n        Categories (2, int64): [2, 5]\n        >>> c.sort_values(na_position=\"first\")\n        [NaN, NaN, 2, 2, 5]\n        Categories (2, int64): [2, 5]\n        >>> c.sort_values(ascending=False, na_position=\"first\")\n        [NaN, NaN, 5, 2, 2]\n        Categories (2, int64): [2, 5]\n        \"\"\"\n        inplace = validate_bool_kwarg(inplace, \"inplace\")\n        if na_position not in [\"last\", \"first\"]:\n            raise ValueError(f\"invalid na_position: {na_position!r}\")\n\n        sorted_idx = nargsort(self, ascending=ascending, na_position=na_position)\n\n        if not inplace:\n            codes = self._codes[sorted_idx]\n            return self._from_backing_data(codes)\n        self._codes[:] = self._codes[sorted_idx]\n        return None\n\n    def _rank(\n        self,\n        *,\n        axis: AxisInt = 0,\n        method: RankMethod = \"average\",\n        na_option: RankNaOption = \"keep\",\n        ascending: bool = True,\n        pct: bool = False,\n    ):","sourceCodeStart":2141,"sourceCodeEnd":2177,"githubUrl":"https://github.com/pandas-dev/pandas/blob/71959b8cb9b2459c16e14b34f28b178ccfe14735/pandas/core/arrays/categorical.py#L2141-L2177","documentation":"Raised by Categorical.sort_values when na_position is not exactly 'last' or 'first'. The numpy-backed sort path (nargsort) only understands these two placements of missing values, so any other string is rejected before sorting begins. This is a strict input-contract violation surfaced as a ValueError.","triggerScenarios":"Calling .sort_values(na_position=...) with a typo like 'Last', 'FIRST', 'top', 'bottom', None, or an empty string. Also triggered by dynamically passing a user-supplied value without normalization.","commonSituations":"User-facing code that forwards a config parameter straight to sort_values; locale-dependent casing; or confusion with DataFrame.sort_values which also only accepts 'first'/'last'.","solutions":["Pass exactly 'last' (default) or 'first' to na_position.","Normalize external input with .lower() and validate against {'first','last'} before forwarding to sort_values."],"exampleFix":"// before\ncat.sort_values(na_position='Last')  # ValueError\n\n// after\ncat.sort_values(na_position='last')","handlingStrategy":"validation","validationCode":"def normalize_na_position(value):\n    if value not in ('first', 'last'):\n        raise ValueError(\"na_position must be 'first' or 'last'\")\n    return value","typeGuard":"from typing import Literal\n\nNaPosition = Literal['first', 'last']\n\ndef is_na_position(v: str) -> bool:\n    return v in ('first', 'last')","tryCatchPattern":"try:\n    cat.sort_values(na_position=pos)\nexcept ValueError as e:\n    if 'invalid na_position' in str(e):\n        cat.sort_values(na_position='last')\n    else:\n        raise","preventionTips":["Whitelist na_position input to {'first','last'} before forwarding to sort_values.","Lowercase and strip user-supplied sort options."],"tags":["categorical","sort","validation"],"analyzedSha":"71959b8cb9b2459c16e14b34f28b178ccfe14735","analyzedAt":"2026-08-07T01:30:20.476Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}