{"record":{"id":"2b2861f2abc1a5ec","repo":"pandas-dev/pandas","slug":"cannot-perform-how-with-non-ordered-categorical","errorCode":null,"errorMessage":"Cannot perform {how} with non-ordered Categorical","messagePattern":"Cannot perform (.+?) with non-ordered Categorical","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"pandas/core/arrays/categorical.py","lineNumber":2876,"sourceCode":"        has_dropped_na: bool,\n        min_count: int,\n        ngroups: int,\n        ids: npt.NDArray[np.intp],\n        **kwargs,\n    ):\n        from pandas.core.groupby.ops import WrappedCythonOp\n\n        kind = WrappedCythonOp.get_kind_from_how(how)\n        op = WrappedCythonOp(how=how, kind=kind, has_dropped_na=has_dropped_na)\n\n        dtype = self.dtype\n        if how in [\"sum\", \"prod\", \"cumsum\", \"cumprod\", \"skew\", \"kurt\"]:\n            raise TypeError(f\"{dtype} type does not support {how} operations\")\n        if how in [\"min\", \"max\", \"rank\", \"idxmin\", \"idxmax\"] and not dtype.ordered:\n            # raise TypeError instead of NotImplementedError to ensure we\n            #  don't go down a group-by-group path, since in the empty-groups\n            #  case that would fail to raise\n            raise TypeError(f\"Cannot perform {how} with non-ordered Categorical\")\n        if how not in [\n            \"rank\",\n            \"any\",\n            \"all\",\n            \"first\",\n            \"last\",\n            \"min\",\n            \"max\",\n            \"idxmin\",\n            \"idxmax\",\n        ]:\n            if kind == \"transform\":\n                raise TypeError(f\"{dtype} type does not support {how} operations\")\n            raise TypeError(f\"{dtype} dtype does not support aggregation '{how}'\")\n\n        result_mask = None\n        mask = self.isna()\n        if how == \"rank\":","sourceCodeStart":2858,"sourceCodeEnd":2894,"githubUrl":"https://github.com/pandas-dev/pandas/blob/3b7651241d4da534b3559b60ef128e1c34f54116/pandas/core/arrays/categorical.py#L2858-L2894","documentation":"Raised in Categorical._groupby_op when how is min, max, rank, idxmin, or idxmax and the categorical dtype is not ordered. Same rationale as error 240: these operations need a total order, which an unordered categorical does not provide. Raised as TypeError (not NotImplementedError) deliberately so the group-by-group fallback path is not taken.","triggerScenarios":"df.groupby('g')['catcol'].min() / .max() / .rank() / .idxmin() / .idxmax() on an unordered categorical. df.groupby('g').agg({'catcol': 'min'}). Resample min/max on a categorical value column.","commonSituations":"Categorical group key created without ordered=True; user then asks for the min category per group. Grouping survey Likert-scale data stored as unordered categorical and requesting max. Multi-column agg where one categorical column receives a min/max spec.","solutions":["Make the categorical ordered: df['col'] = df['col'].cat.as_ordered() (optionally set_categories in rank order first).","If the category order is meaningful, define it: set_categories(['low','med','high'], ordered=True).","Use count/size/first instead of min/max when ordering is genuinely absent.","Separate categorical columns out of min/max aggregations."],"exampleFix":"# before\ndf = pd.DataFrame({'g': ['x','x','y'], 'c': pd.Categorical(['b','a','c'])})\ndf.groupby('g')['c'].min()  # TypeError\n\n# after\ndf['c'] = df['c'].cat.as_ordered()\ndf.groupby('g')['c'].min()","handlingStrategy":"validation","validationCode":"how = 'min'\norder_hows = {'min','max','rank','idxmin','idxmax'}\ncol = 'catcol'\nif how in order_hows and isinstance(df[col].dtype, pd.CategoricalDtype) and not df[col].cat.ordered:\n    df[col] = df[col].cat.as_ordered()\ndf.groupby('g')[col].agg(how)","typeGuard":"def groupby_min_ok(s: pd.Series) -> bool:\n    return not (isinstance(s.dtype, pd.CategoricalDtype) and not s.cat.ordered)","tryCatchPattern":"try:\n    df.groupby('g')['catcol'].min()\nexcept TypeError as e:\n    if 'non-ordered Categorical' in str(e):\n        df['catcol'] = df['catcol'].cat.as_ordered()\n        df.groupby('g')['catcol'].min()\n    else:\n        raise","preventionTips":["Mark Likert/ordinal columns ordered=True at ingestion.","Audit groupby specs for min/max/rank against categorical columns before running."],"tags":["categorical","groupby","ordering","min-max"],"backgroundTag":null,"analyzedSha":"3b7651241d4da534b3559b60ef128e1c34f54116","analyzedAt":"2026-08-11T22:10:44.015Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}