pandas-dev/pandas · error · TypeError

Cannot compare a Categorical for op

Error message

Cannot compare a Categorical for op {opname} with type {type(other)}.
If you want to compare values, use 'np.asarray(cat) <op> other'.

What it means

Raised when a Categorical is compared with a non-hashable, non-Categorical list-like (e.g. `collections.deque`) using an operator other than `==`/`!=`. For such positional list-likes only equality is supported; ordering comparisons have no meaningful semantics and are rejected. The message points users at `np.asarray(cat) <op> other` for raw value comparison.

Solutions

  1. Convert the list-like to a plain list/ndarray first: `cat < list(deque_val)` — though note this still requires equal length and ordered=True for ordering ops.
  2. Compare values directly via `np.asarray(cat) < np.asarray(other)` as the message suggests.
  3. Switch to equality (`==`/`!=`) which is the only op supported for raw list-likes here.
  4. Wrap the Categorical in a Series for richer alignment semantics.

Example fix

# before
from collections import deque
cat = pd.Categorical([1, 2, 3], ordered=True)
cat < deque([2, 3, 4])  # TypeError

# after
import numpy as np
np.asarray(cat) < np.asarray(deque([2, 3, 4]))
Defensive patterns

Strategy: validation

Validate before calling

from collections.abc import Iterable
import numpy as np

def materialize_for_compare(other):
    if isinstance(other, (list, tuple, np.ndarray)):
        return other
    if isinstance(other, Iterable):
        return list(other)
    return other

Type guard

def is_plain_list_like(other) -> bool:
    return isinstance(other, (list, tuple, np.ndarray))

Try / catch

try:
    result = cat < other
except TypeError as e:
    if 'Cannot compare a Categorical' in str(e):
        import numpy as np
        result = np.asarray(cat) < np.asarray(other)
    else:
        raise

Prevention

When it happens

Trigger: Using `<`, `>`, `<=`, `>=` between a Categorical and a `deque`, custom non-ndarray sequence, or other exotic list-like that is not hashable and not a Categorical.

Common situations: Migrating code that compared against a list and later swapped the list for a `deque` (GH#62423); passing generator-derived sequences that lost hashability; interop with non-pandas containers.

Related errors


AI-assisted analysis of pandas-dev/pandas@3b7651241d (2026-08-11). Data as JSON: /api/errors/75953ba57d27e82a. Report an issue: GitHub.

Appendix: source

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

                if opname not in {"__eq__", "__ge__", "__gt__"}:
                    # GH#29820 performance trick; get_loc will always give i>=0,
                    #  so in the cases (__ne__, __le__, __lt__) the setting
                    #  here is a no-op, so can be skipped.
                    mask = self._codes == -1
                    ret[mask] = fill_value
                return ret
            else:
                return ops.invalid_comparison(self, other, op)
        else:
            # allow categorical vs object dtype array comparisons for equality
            # these are only positional comparisons
            # (hashable list-likes such as tuple/range take the branch above and
            #  are already treated as scalar-like, so only non-standard
            #  positional list-likes like ``deque`` warn here, GH#62423)
            ops.maybe_warn_listlike(other)
            if opname not in ["__eq__", "__ne__"]:
                raise TypeError(
                    f"Cannot compare a Categorical for op {opname} with "
                    f"type {type(other)}.\nIf you want to compare values, "
                    "use 'np.asarray(cat) <op> other'."
                )

            if isinstance(other, ExtensionArray) and needs_i8_conversion(other.dtype):
                # We would return NotImplemented here, but that messes up
                #  ExtensionIndex's wrapped methods
                return op(other, self)
            return getattr(np.array(self), opname)(np.array(other))

    func.__name__ = opname

    return func


def contains(cat, key, container) -> bool:
    """

View on GitHub (pinned to 3b7651241d)