-
Notifications
You must be signed in to change notification settings - Fork 1
Add experimental infer_stypes(_low_cardinality: bool)
#987
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -58,6 +58,7 @@ def infer_stypes( | |
| *, | ||
| text: Literal["off", "infer", "drop"] = "off", | ||
| id: Literal["off", "infer", "drop"] = "off", | ||
| _low_cardinality: Literal["off", "infer"] = "off", | ||
| unsupported: Literal["error", "warn", "drop"] = "error", | ||
| ) -> dict[str, StypeLike]: | ||
| r"""Infer semantic types from raw data statistics. | ||
|
|
@@ -103,7 +104,13 @@ def infer_stypes( | |
| """ | ||
| overrides = overrides or {} | ||
|
|
||
| fn: Callable[[str, object, Policy, Policy], Stype | None] | None = None | ||
| fn: ( | ||
| Callable[ | ||
| [str, object, Policy, Policy, Literal["off", "infer"]], | ||
| Stype | None, | ||
| ] | ||
| | None | ||
| ) = None | ||
| columns: Iterable[tuple[Hashable, object]] | None = None | ||
| if isinstance(table, pa.Table): | ||
| fn = _infer_arrow_stype | ||
|
|
@@ -136,7 +143,7 @@ def infer_stypes( | |
| continue | ||
|
|
||
| try: | ||
| stype = fn(name, column, text, id) | ||
| stype = fn(name, column, text, id, _low_cardinality) | ||
| except TypeError: | ||
| if unsupported == "error": | ||
| raise | ||
|
|
@@ -169,6 +176,7 @@ def _infer_arrow_stype( | |
| array: object, | ||
| text: Policy, | ||
| id: Policy, | ||
| low_cardinality: Literal["off", "infer"], | ||
| ) -> Stype | None: | ||
| assert isinstance(array, pa.Array | pa.ChunkedArray) | ||
| dtype = array.type | ||
|
|
@@ -189,6 +197,8 @@ def _infer_arrow_stype( | |
| or pa.types.is_floating(dtype) | ||
| or pa.types.is_decimal(dtype) | ||
| ): | ||
| if low_cardinality != "off" and _is_arrow_low_cardinality(array): | ||
| return Stype.categorical | ||
| return Stype.numerical | ||
|
|
||
| if pa.types.is_boolean(dtype) or pa.types.is_dictionary(dtype): | ||
|
|
@@ -210,6 +220,7 @@ def _infer_pandas_stype( | |
| ser: object, | ||
| text: Policy, | ||
| id: Policy, | ||
| low_cardinality: Literal["off", "infer"], | ||
| ) -> Stype | None: | ||
| import pandas as pd | ||
| from pandas.api.types import ( | ||
|
|
@@ -237,6 +248,8 @@ def _infer_pandas_stype( | |
| return None if id == "drop" else Stype.id | ||
|
|
||
| if is_integer_dtype(dtype) or is_float_dtype(dtype): | ||
| if low_cardinality != "off" and _is_series_low_cardinality(ser): | ||
| return Stype.categorical | ||
| return Stype.numerical | ||
|
|
||
| if is_bool_dtype(dtype) or isinstance(dtype, pd.CategoricalDtype): | ||
|
|
@@ -258,6 +271,7 @@ def _infer_cudf_stype( | |
| ser: object, | ||
| text: Policy, | ||
| id: Policy, | ||
| low_cardinality: Literal["off", "infer"], | ||
| ) -> Stype | None: | ||
| import cudf | ||
| from cudf.api.types import ( | ||
|
|
@@ -284,6 +298,8 @@ def _infer_cudf_stype( | |
| or is_float_dtype(dtype) | ||
| or is_decimal_dtype(dtype) | ||
| ): | ||
| if low_cardinality != "off" and _is_series_low_cardinality(ser): | ||
| return Stype.categorical | ||
| return Stype.numerical | ||
|
|
||
| if is_bool_dtype(dtype) or isinstance(dtype, cudf.CategoricalDtype): | ||
|
|
@@ -343,3 +359,38 @@ def _is_cudf_text(ser: cudf.Series) -> bool: | |
| unique = ser.dropna().unique() | ||
| avg_words = unique.str.token_count().mean() | ||
| return avg_words >= _TEXT_MIN_AVERAGE_WORD_COUNT | ||
|
|
||
|
|
||
| _LOW_CARDINALITY_MIN_ROWS = 151 | ||
| _LOW_CARDINALITY_MAX_UNIQUE_VALUES = 3 | ||
| _LOW_CARDINALITY_PREFIX_ROWS = 1024 | ||
|
|
||
|
|
||
| def _is_arrow_low_cardinality(array: pa.Array | pa.ChunkedArray) -> bool: | ||
| if len(array) < _LOW_CARDINALITY_MIN_ROWS: | ||
| return False | ||
|
|
||
| # A prefix holds a subset of the distinct values, so most columns are | ||
| # ruled out without a full pass. | ||
| options = pc.CountOptions(mode="all") | ||
| prefix = array.slice(0, _LOW_CARDINALITY_PREFIX_ROWS) | ||
| num_unique = pc.call_function("count_distinct", [prefix], options).as_py() | ||
|
Comment on lines
+375
to
+377
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Count NaN and null as one missing value in Arrow. For an Arrow float column containing 🤖 Prompt for AI Agents |
||
| if num_unique > _LOW_CARDINALITY_MAX_UNIQUE_VALUES: | ||
| return False | ||
|
|
||
| num_unique = pc.call_function("count_distinct", [array], options).as_py() | ||
| return 1 < num_unique <= _LOW_CARDINALITY_MAX_UNIQUE_VALUES | ||
|
|
||
|
|
||
| def _is_series_low_cardinality(ser: pd.Series | cudf.Series) -> bool: | ||
| if len(ser) < _LOW_CARDINALITY_MIN_ROWS: | ||
| return False | ||
|
|
||
| # A prefix holds a subset of the distinct values, so most columns are | ||
| # ruled out without a full pass. | ||
| prefix = ser.iloc[:_LOW_CARDINALITY_PREFIX_ROWS] | ||
| if prefix.nunique(dropna=False) > _LOW_CARDINALITY_MAX_UNIQUE_VALUES: | ||
| return False | ||
|
|
||
| num_unique = ser.nunique(dropna=False) | ||
| return 1 < num_unique <= _LOW_CARDINALITY_MAX_UNIQUE_VALUES | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Support pandas decimal columns in the new policy.
When a pandas column contains
Decimalvalues, its object dtype does not enter this numeric branch._infer_pandas_stypethen raisesTypeError, even if 151 rows contain only two decimal values. Detect decimal-valued object columns forlow_cardinality="infer", or narrow the documented backend contract. Pandas identifies these values as"decimal"throughinfer_dtype. (pandas.pydata.org)🤖 Prompt for AI Agents