{"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/71959b8cb9b2459c16e14b34f28b178ccfe14735/pandas/core/arrays/categorical.py#L3049-L3085","documentation":"Raised by CategoricalAccessor._validate when the .cat accessor is invoked on a Series whose dtype is not CategoricalDtype. The accessor is registered only for category dtype; accessing .cat on any other dtype triggers this AttributeError. It is the standard pandas 'wrong accessor for dtype' guard.","triggerScenarios":"s.cat.categories / s.cat.ordered / s.cat.codes on a Series that is object, int, str, or datetime dtype instead of category. Common after the Series dtype is reset by an operation that drops the category type.","commonSituations":"After .astype('str') or .to_numpy()-roundtrip that loses category dtype; after merge/join that may coerce category to object; or accessing .cat before converting with astype('category').","solutions":["Convert the Series to category first: s = s.astype('category').","Check dtype before accessing .cat: if isinstance(s.dtype, pd.CategoricalDtype): ...","Investigate upstream operations (merge, concat, astype) that may have stripped the category dtype."],"exampleFix":"// before\ns = pd.Series(['a','b','a'])\ns.cat.categories  # AttributeError: Can only use .cat accessor with a 'category' dtype\n\n// after\ns = s.astype('category')\ns.cat.categories","handlingStrategy":"type-guard","validationCode":"def require_category(s):\n    import pandas as pd\n    if not isinstance(s.dtype, pd.CategoricalDtype):\n        s = s.astype('category')\n    return s","typeGuard":"import pandas as pd\nfrom typing import Any\n\ndef is_category_dtype(obj: Any) -> bool:\n    return isinstance(getattr(obj, 'dtype', None), pd.CategoricalDtype)","tryCatchPattern":"try:\n    s.cat.categories\nexcept AttributeError as e:\n    if 'category' in str(e) and 'accessor' in str(e):\n        s = s.astype('category')\n    else:\n        raise","preventionTips":["Always call .astype('category') before using .cat.","Re-check dtype after merge/concat that may strip category type."],"tags":["categorical","accessor","dtype"],"analyzedSha":"71959b8cb9b2459c16e14b34f28b178ccfe14735","analyzedAt":"2026-08-07T01:30:20.476Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}