Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion supervised/base_automl.py
Original file line number Diff line number Diff line change
Expand Up @@ -673,7 +673,13 @@ def _handle_drastic_imbalance(
] = new_sensitive_features.loc[j]

def _save_data_info(self, X, y, sample_weight=None, sensitive_features=None):
target_is_numeric = pd.api.types.is_numeric_dtype(y)
# Resolve category dtype before numeric check so that targets like
# pd.Series([0, 1, 1]).astype('category') are correctly identified
# as numeric (the categories themselves are numeric).
if isinstance(y, pd.Series) and isinstance(y.dtype, pd.CategoricalDtype):
target_is_numeric = pd.api.types.is_numeric_dtype(y.cat.categories)
else:
target_is_numeric = pd.api.types.is_numeric_dtype(y)
if self._ml_task == MULTICLASS_CLASSIFICATION:
y = y.astype(str)

Expand Down Expand Up @@ -792,6 +798,19 @@ def _build_dataframe(self, X, y=None, sample_weight=None, sensitive_features=Non
y = check_array(y, ensure_2d=False)
y = pd.Series(np.array(y), name="target")

# If y is a pd.Series with category dtype (e.g. user called
# df[col].astype('category') on the target column), convert it to
# the underlying dtype so that downstream code (LightGBM, sklearn,
# dtype checks, etc.) can handle it correctly.
if isinstance(y, pd.Series) and isinstance(y.dtype, pd.CategoricalDtype):
if pd.api.types.is_numeric_dtype(y.cat.categories):
# Numeric categories (e.g. 0, 1, 2 stored as category) —
# restore to the actual numeric dtype.
y = y.astype(y.cat.categories.dtype)
else:
# String/object categories — convert to plain object dtype.
y = y.astype(object)

if sample_weight is not None:
if isinstance(sample_weight, np.ndarray):
sample_weight = check_array(sample_weight, ensure_2d=False)
Expand Down
66 changes: 66 additions & 0 deletions tests/tests_automl/test_data_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,69 @@ def test_encoding_strange_characters(self):
start_random_models=1,
)
automl.fit(X, y)

def test_numeric_category_target(self):
"""Regression test: target stored as numeric category dtype should not
raise 'ValueError: pandas dtypes must be int, float or bool'.
See: https://github.com/mljar/mljar-supervised/issues/XXX
"""
X = np.random.rand(self.rows, 3)
X = pd.DataFrame(X, columns=[f"f{i}" for i in range(3)])
# Binary target with integer values stored as category dtype
y = pd.Series(np.random.randint(0, 2, self.rows)).astype("category")

automl = AutoML(
results_path=self.automl_dir,
total_time_limit=1,
algorithms=["Baseline"],
train_ensemble=False,
explain_level=0,
start_random_models=1,
)
automl.fit(X, y)

def test_numeric_category_features_and_target(self):
"""Regression test: both X feature columns and y target stored as
numeric category dtype must be handled without errors.
"""
X = pd.DataFrame(
{
"f0": np.random.rand(self.rows),
# Numeric-valued categorical feature — the exact scenario from the issue
"f1": pd.Series(
np.random.randint(1, 5, self.rows)
).astype("category"),
"f2": pd.Series(
np.random.randint(0, 3, self.rows)
).astype("category"),
}
)
y = pd.Series(np.random.randint(0, 2, self.rows)).astype("category")

automl = AutoML(
results_path=self.automl_dir,
total_time_limit=1,
algorithms=["Baseline"],
train_ensemble=False,
explain_level=0,
start_random_models=1,
)
automl.fit(X, y)

def test_string_category_target(self):
"""Regression test: target with string-valued category dtype is handled."""
X = np.random.rand(self.rows, 3)
X = pd.DataFrame(X, columns=[f"f{i}" for i in range(3)])
y = pd.Series(
np.random.choice(["cat", "dog"], size=self.rows)
).astype("category")

automl = AutoML(
results_path=self.automl_dir,
total_time_limit=1,
algorithms=["Baseline"],
train_ensemble=False,
explain_level=0,
start_random_models=1,
)
automl.fit(X, y)