pola-rs/polars · error

Categorical physical must be one of pl.UInt(8|16|32)

Error message

Categorical physical must be one of pl.UInt(8|16|32)

What it means

The `Categories` class stores categorical data with a fixed physical representation that must be one of the Polars unsigned integer types UInt8, UInt16, or UInt32. Any other physical dtype passed to the constructor raises this TypeError.

Solutions

  1. Pass exactly `pl.UInt8`, `pl.UInt16`, or `pl.UInt32` as `physical`
  2. Choose the width by category count: UInt8 for <=255, UInt16 for <=65535, otherwise UInt32
  3. Do not pass dtype names as strings; import and pass the classes

Example fix

// before
pl.Categories(physical=pl.Int32)
// after
pl.Categories(physical=pl.UInt32)
Defensive patterns

Strategy: validation

Validate before calling

import polars as pl
ALLOWED_PHYSICAL = (pl.UInt8, pl.UInt16, pl.UInt32)
if physical not in ALLOWED_PHYSICAL:
    raise TypeError(f'physical must be one of {ALLOWED_PHYSICAL}')

Type guard

def is_valid_physical(p) -> bool:
    import polars as pl
    return p in (pl.UInt8, pl.UInt16, pl.UInt32)

Try / catch

try:
    cats = pl.Categories(physical=physical)
except TypeError as e:
    if 'Categorical physical' in str(e):
        cats = pl.Categories(physical=pl.UInt32)
    else:
        raise

Prevention

When it happens

Trigger: `pl.Categories(physical=pl.Int32)` or passing a string, `pl.Categorical`, or signed integer dtype as `physical`.

Common situations: Developers assume any integer dtype works as physical backing, or pass the dtype name as a string instead of the class.

Understand the failure class

Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.

Related errors


AI-assisted analysis of pola-rs/polars@fe841f959e (2026-09-18). Data as JSON: /api/errors/3e04bd3c79e5bc35. Report an issue: GitHub.

Appendix: source

Thrown at py-polars/src/polars/datatypes/classes.py:778

        physical: PolarsDataType = pldt.UInt32,
    ) -> None:
        if name is None or name == "":
            assert namespace == "", "global categories may not specify a namespace"
            assert physical == pldt.UInt32, (
                "global categories may not specify a physical type"
            )
            self._categories = PyCategories.global_categories()
            return

        if physical == pldt.UInt32:
            internal_phys = "u32"
        elif physical == pldt.UInt16:
            internal_phys = "u16"
        elif physical == pldt.UInt8:
            internal_phys = "u8"
        else:
            msg = "Categorical physical must be one of pl.UInt(8|16|32)"
            raise TypeError(msg)

        self._categories = PyCategories(name, namespace, internal_phys)

    @staticmethod
    def _from_py_categories(py_categories: PyCategories) -> Categories:
        self = Categories.__new__(Categories)
        self._categories = py_categories
        return self

    @staticmethod
    def random(
        namespace: str = "", physical: PolarsDataType = pldt.UInt32
    ) -> Categories:
        """
        Creates a new `Categories` with a random name.

        Parameters
        ----------

View on GitHub (pinned to fe841f959e)