Skip to content

Commit

Permalink
Make .describe_categoricals["mapping"] a Column
Browse files Browse the repository at this point in the history
  • Loading branch information
vnlitvinov committed Jun 1, 2022
1 parent 1b42205 commit 91ed593
Show file tree
Hide file tree
Showing 2 changed files with 22 additions and 29 deletions.
22 changes: 12 additions & 10 deletions protocol/dataframe_protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,21 +170,23 @@ def dtype(self) -> Tuple[enum.IntEnum, int, str, str]:
pass

@property
def describe_categorical(self) -> dict[bool, bool, Optional[dict]]:
def describe_categorical(self) -> dict[bool, bool, Optional[Column]]:
"""
If the dtype is categorical, there are two options:
- There are only values in the data buffer.
- The data buffer stores encoded values, while the (single)
child column stores the categorical values themselves.
- There is a separate non-categortical Column encoding categorical values.
Raises RuntimeError 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 the data is integer encoded
- "is_dictionary" : bool, whether a mapping of
categorical values to other objects exists
- "mapping" : Column representing the mapping of indices to category values.
None if not a dictionary-style categorical.
TBD: are there any other in-memory representations that are needed?
"""
Expand Down Expand Up @@ -263,12 +265,12 @@ def get_buffers(self) -> dict[Tuple[Buffer, Any], Optional[Tuple[Buffer, Any]],
"""
pass

def get_children(self) -> Iterable[Column]:
"""
Children columns underneath the column, each object in this iterator
must adhere to the column specification.
"""
pass
# def get_children(self) -> Iterable[Column]:
# """
# Children columns underneath the column, each object in this iterator
# must adhere to the column specification.
# """
# pass


class DataFrame:
Expand Down
29 changes: 10 additions & 19 deletions protocol/pandas_implementation.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,15 +145,14 @@ def convert_categorical_column(col : ColumnObject) -> pd.Series:
"""
Convert a categorical column to a Series instance.
"""
ordered, is_dict = col.describe_categorical
ordered, is_dict, mapping = col.describe_categorical
if not is_dict:
raise NotImplementedError('Non-dictionary categoricals not supported yet')

# If you want to cheat for testing (can't use `_col` in real-world code):
# categories = col._col.values.categories.values
# codes = col._col.values.codes
categories_column, = col.get_children() # need to keep a reference to the child
categories = convert_column_to_ndarray(categories_column)[0]
categories = convert_column_to_ndarray(mapping)
codes_buffer, codes_dtype = col.get_buffers()["data"]
codes = buffer_to_ndarray(codes_buffer, codes_dtype)
values = categories[codes]
Expand Down Expand Up @@ -457,20 +456,19 @@ def describe_categorical(self) -> Dict[str, Any]:
- "is_ordered" : bool, whether the ordering of dictionary indices is
semantically meaningful.
- "is_dictionary" : bool, whether the data is integer encoded
- "mapping" : Column representing the mapping of indices to category values.
None if not a dictionary-style categorical.
"""
if not self.dtype[0] == _DtypeKind.CATEGORICAL:
raise TypeError("`describe_categorical only works on a column with "
"categorical dtype!")

ordered = self._col.dtype.ordered
is_dictionary = True
# NOTE: this shows the children approach is better, transforming
# `categories` to a "mapping" dict is inefficient
codes = self._col.values.codes # ndarray, length `self.size`
# categories.values is ndarray of length n_categories
categories = self._col.values.categories.values
return ordered, is_dictionary
categories = _PandasColumn(self._col.dtype.categories.to_series())
return {"is_ordered": ordered,
"is_dictionary": is_dictionary,
"mapping": categories}

@property
def describe_null(self) -> Tuple[int, Any]:
Expand Down Expand Up @@ -692,14 +690,6 @@ def _get_offsets_buffer(self) -> Tuple[_PandasBuffer, Any]:

return buffer, dtype

def get_children(self):
if self.dtype[0] == _DtypeKind.CATEGORICAL:
if self.describe_categorical[1]:
# return the categories as a child Column
return (_PandasColumn(self._col.dtype.categories.to_series()),)
else:
return tuple()


class _PandasDataFrame:
"""
Expand Down Expand Up @@ -847,7 +837,8 @@ def test_categorical_dtype():
assert col.null_count == 1
assert col.describe_null == (2, -1) # sentinel value -1
assert col.num_chunks() == 1
assert col.describe_categorical == (False, True)
assert col.describe_categorical["is_ordered"] == False
assert col.describe_categorical["is_dictionary"] == True

df2 = from_dataframe(df)
assert_dataframe_equal(df.__dataframe__(), df)
Expand Down

0 comments on commit 91ed593

Please sign in to comment.