diff --git a/pandas/core/interchange/column.py b/pandas/core/interchange/column.py index 9ba237a94b8fe..83f57d5bb8d3e 100644 --- a/pandas/core/interchange/column.py +++ b/pandas/core/interchange/column.py @@ -146,15 +146,18 @@ def describe_categorical(self): """ If the dtype is categorical, there are two options: - There are only values in the data buffer. - - There is a separate dictionary-style encoding for categorical values. - Raises RuntimeError if the dtype is not categorical + - There is a separate non-categorical Column encoding for categorical values. + + Raises TypeError if the dtype is not categorical + Content of returned dict: - "is_ordered" : bool, whether the ordering of dictionary indices is semantically meaningful. - "is_dictionary" : bool, whether a dictionary-style mapping of categorical values to other objects exists - - "mapping" : dict, Python-level only (e.g. ``{int: str}``). - None if not a dictionary-style categorical. + - "categories" : Column representing the (implicit) mapping of indices to + category values (e.g. an array of cat1, cat2, ...). + None if not a dictionary-style categorical. """ if not self.dtype[0] == DtypeKind.CATEGORICAL: raise TypeError( @@ -164,7 +167,7 @@ def describe_categorical(self): return { "is_ordered": self._col.cat.ordered, "is_dictionary": True, - "mapping": dict(enumerate(self._col.cat.categories)), + "categories": PandasColumn(pd.Series(self._col.cat.categories)), } @property diff --git a/pandas/core/interchange/dataframe_protocol.py b/pandas/core/interchange/dataframe_protocol.py index 6f4ec673f03e0..3ab87d9a60399 100644 --- a/pandas/core/interchange/dataframe_protocol.py +++ b/pandas/core/interchange/dataframe_protocol.py @@ -110,7 +110,7 @@ class CategoricalDescription(TypedDict): is_dictionary: bool # Python-level only (e.g. ``{int: str}``). # None if not a dictionary-style categorical. - mapping: dict | None + categories: Column | None class Buffer(ABC): @@ -274,17 +274,18 @@ def describe_categorical(self) -> CategoricalDescription: """ If the dtype is categorical, there are two options: - There are only values in the data buffer. - - There is a separate dictionary-style encoding for categorical values. + - There is a separate non-categorical Column encoding for categorical values. Raises TypeError if the dtype is not categorical Returns the dictionary with description on how to interpret the data buffer: - "is_ordered" : bool, whether the ordering of dictionary indices is semantically meaningful. - - "is_dictionary" : bool, whether a dictionary-style mapping of + - "is_dictionary" : bool, whether a mapping of categorical values to other objects exists - - "mapping" : dict, Python-level only (e.g. ``{int: str}``). - None if not a dictionary-style categorical. + - "categories" : Column representing the (implicit) mapping of indices to + category values (e.g. an array of cat1, cat2, ...). + None if not a dictionary-style categorical. TBD: are there any other in-memory representations that are needed? """ diff --git a/pandas/core/interchange/from_dataframe.py b/pandas/core/interchange/from_dataframe.py index a430e0c66a988..6e1b2de10e8e6 100644 --- a/pandas/core/interchange/from_dataframe.py +++ b/pandas/core/interchange/from_dataframe.py @@ -7,6 +7,7 @@ import numpy as np import pandas as pd +from pandas.core.interchange.column import PandasColumn from pandas.core.interchange.dataframe_protocol import ( Buffer, Column, @@ -179,9 +180,10 @@ def categorical_column_to_series(col: Column) -> tuple[pd.Series, Any]: if not categorical["is_dictionary"]: raise NotImplementedError("Non-dictionary categoricals not supported yet") - mapping = categorical["mapping"] - assert isinstance(mapping, dict), "Categorical mapping must be a dict" - categories = np.array(tuple(mapping[k] for k in sorted(mapping))) + cat_column = categorical["categories"] + # for mypy/pyright + assert isinstance(cat_column, PandasColumn), "categories must be a PandasColumn" + categories = np.array(cat_column._col) buffers = col.get_buffers() codes_buff, codes_dtype = buffers["data"] diff --git a/pandas/tests/interchange/test_impl.py b/pandas/tests/interchange/test_impl.py index 0c2cffff8a159..b4c27ba31317b 100644 --- a/pandas/tests/interchange/test_impl.py +++ b/pandas/tests/interchange/test_impl.py @@ -8,6 +8,7 @@ import pandas as pd import pandas._testing as tm +from pandas.core.interchange.column import PandasColumn from pandas.core.interchange.dataframe_protocol import ( ColumnNullType, DtypeKind, @@ -61,11 +62,13 @@ def test_categorical_dtype(data): assert col.null_count == 0 assert col.describe_null == (ColumnNullType.USE_SENTINEL, -1) assert col.num_chunks() == 1 - assert col.describe_categorical == { - "is_ordered": data[1], - "is_dictionary": True, - "mapping": {0: "a", 1: "d", 2: "e", 3: "s", 4: "t"}, - } + desc_cat = col.describe_categorical + assert desc_cat["is_ordered"] == data[1] + assert desc_cat["is_dictionary"] is True + assert isinstance(desc_cat["categories"], PandasColumn) + tm.assert_series_equal( + desc_cat["categories"]._col, pd.Series(["a", "d", "e", "s", "t"]) + ) tm.assert_frame_equal(df, from_dataframe(df.__dataframe__()))