{"record":{"id":"9a1e4f6fbbf3808d","repo":"pandas-dev/pandas","slug":"can-only-use-cat-accessor-with-a-category-dtype","errorCode":null,"errorMessage":"Can only use .cat accessor with a 'category' dtype","messagePattern":"Can only use \\.cat accessor with a 'category' dtype","errorType":"exception","errorClass":"AttributeError","httpStatus":null,"severity":"error","filePath":"pandas/core/arrays/categorical.py","lineNumber":3067,"sourceCode":"    2    b\n    3    c\n    4    c\n    5    c\n    dtype: category\n    Categories (3, str): ['a', 'b', 'c']\n    \"\"\"\n\n    def __init__(self, data) -> None:\n        self._validate(data)\n        self._parent = data.values\n        self._index = data.index\n        self._name = data.name\n        self._freeze()\n\n    @staticmethod\n    def _validate(data) -> None:\n        if not isinstance(data.dtype, CategoricalDtype):\n            raise AttributeError(\"Can only use .cat accessor with a 'category' dtype\")\n\n    def _delegate_property_get(self, name: str):\n        return getattr(self._parent, name)\n\n    def _delegate_property_set(self, name: str, new_values) -> None:\n        setattr(self._parent, name, new_values)\n\n    @property\n    def codes(self) -> Series:\n        \"\"\"\n        Return Series of codes as well as the index.\n\n        The codes are integer indicators for the position of each value in\n        the categories. Uncategorized values (i.e., NaN) are assigned a code\n        of ``-1``.\n\n        See Also\n        --------","sourceCodeStart":3049,"sourceCodeEnd":3085,"githubUrl":"https://github.com/pandas-dev/pandas/blob/3b7651241d4da534b3559b60ef128e1c34f54116/pandas/core/arrays/categorical.py#L3049-L3085","documentation":"Raised by the .cat accessor's _validate when the Series dtype is not CategoricalDtype. The .cat accessor (Series.cat) is registered only for categorical data; accessing it on any other dtype (int, object, datetime, string) raises AttributeError mirroring how pandas accessors gate on dtype. This is a property-accessor guard, not a value check.","triggerScenarios":"s.cat.codes where s.dtype != 'category'. df['col'].cat.categories on an object-typed column. Chaining .cat.rename_categories on a column that was never converted. Accessing .cat after an operation that returned object dtype (e.g., .astype(object)).","commonSituations":"Column was read as object/string instead of category due to mixed types or missing astype. A prior .astype('category') was removed in a refactor. Using pyarrow-backed string dtype ('string[pyarrow]') and expecting .cat to work (it does not — that is a different accessor).","solutions":["Convert the Series to categorical first: df['col'] = df['col'].astype('category').","Check dtype before accessing: if isinstance(df['col'].dtype, pd.CategoricalDtype): ...","Verify the column was not silently widened to object by a previous operation (concat, merge, where).","For arrow-backed categoricals, ensure dtype reads as CategoricalDtype, not ArrowDtype."],"exampleFix":"# before\ns = pd.Series(['a','b','a'])\ns.cat.categories  # AttributeError\n\n# after\ns = s.astype('category')\ns.cat.categories","handlingStrategy":"type-guard","validationCode":"if not isinstance(df['col'].dtype, pd.CategoricalDtype):\n    df['col'] = df['col'].astype('category')\ndf['col'].cat.categories","typeGuard":"import pandas as pd\n\ndef is_categorical(s: pd.Series) -> bool:\n    return isinstance(s.dtype, pd.CategoricalDtype)","tryCatchPattern":null,"preventionTips":["Convert columns to 'category' right after loading: df['col'] = df['col'].astype('category').","After merge/concat, re-check dtypes; categoricals may widen to object."],"tags":["categorical","accessor","dtype","attribute-error"],"backgroundTag":null,"analyzedSha":"3b7651241d4da534b3559b60ef128e1c34f54116","analyzedAt":"2026-08-11T22:10:44.015Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}