pandas-dev/pandas · error · AttributeError

Can only use .cat accessor with a 'category' dtype

Error message

Can only use .cat accessor with a 'category' dtype

What it means

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.

Source

Thrown at pandas/core/arrays/categorical.py:3067

    2    b
    3    c
    4    c
    5    c
    dtype: category
    Categories (3, str): ['a', 'b', 'c']
    """

    def __init__(self, data) -> None:
        self._validate(data)
        self._parent = data.values
        self._index = data.index
        self._name = data.name
        self._freeze()

    @staticmethod
    def _validate(data) -> None:
        if not isinstance(data.dtype, CategoricalDtype):
            raise AttributeError("Can only use .cat accessor with a 'category' dtype")

    def _delegate_property_get(self, name: str):
        return getattr(self._parent, name)

    def _delegate_property_set(self, name: str, new_values) -> None:
        setattr(self._parent, name, new_values)

    @property
    def codes(self) -> Series:
        """
        Return Series of codes as well as the index.

        The codes are integer indicators for the position of each value in
        the categories. Uncategorized values (i.e., NaN) are assigned a code
        of ``-1``.

        See Also
        --------

View on GitHub (pinned to 71959b8cb9)

Solutions

  1. Convert the Series to category first: s = s.astype('category').
  2. Check dtype before accessing .cat: if isinstance(s.dtype, pd.CategoricalDtype): ...
  3. Investigate upstream operations (merge, concat, astype) that may have stripped the category dtype.

Example fix

// before
s = pd.Series(['a','b','a'])
s.cat.categories  # AttributeError: Can only use .cat accessor with a 'category' dtype

// after
s = s.astype('category')
s.cat.categories
Defensive patterns

Strategy: type-guard

Validate before calling

def require_category(s):
    import pandas as pd
    if not isinstance(s.dtype, pd.CategoricalDtype):
        s = s.astype('category')
    return s

Type guard

import pandas as pd
from typing import Any

def is_category_dtype(obj: Any) -> bool:
    return isinstance(getattr(obj, 'dtype', None), pd.CategoricalDtype)

Try / catch

try:
    s.cat.categories
except AttributeError as e:
    if 'category' in str(e) and 'accessor' in str(e):
        s = s.astype('category')
    else:
        raise

Prevention

When it happens

Trigger: 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.

Common situations: 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').

Related errors


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