"""Component that imputes missing data according to a specified imputation strategy."""
import pandas as pd
import woodwork
from sklearn.impute import SimpleImputer as SkImputer
from woodwork.logical_types import Double
from evalml.pipelines.components.transformers import Transformer
from evalml.pipelines.components.utils import (
drop_natural_language_columns,
set_boolean_columns_to_integer,
)
from evalml.utils import infer_feature_types
from evalml.utils.gen_utils import is_categorical_actually_boolean
[docs]class SimpleImputer(Transformer):
"""Imputes missing data according to a specified imputation strategy. Natural language columns are ignored.
Args:
impute_strategy (string): Impute strategy to use. Valid values include "mean", "median", "most_frequent", "constant" for
numerical data, and "most_frequent", "constant" for object data types.
fill_value (string): When impute_strategy == "constant", fill_value is used to replace missing data.
Defaults to 0 when imputing numerical data and "missing_value" for strings or object data types.
random_seed (int): Seed for the random number generator. Defaults to 0.
"""
name = "Simple Imputer"
hyperparameter_ranges = {"impute_strategy": ["mean", "median", "most_frequent"]}
"""{
"impute_strategy": ["mean", "median", "most_frequent"]
}"""
def __init__(
self, impute_strategy="most_frequent", fill_value=None, random_seed=0, **kwargs
):
parameters = {"impute_strategy": impute_strategy, "fill_value": fill_value}
parameters.update(kwargs)
self.impute_strategy = impute_strategy
imputer = SkImputer(
strategy=impute_strategy,
fill_value=fill_value,
missing_values=pd.NA,
**kwargs,
)
self._all_null_cols = None
super().__init__(
parameters=parameters,
component_obj=imputer,
random_seed=random_seed,
)
[docs] def fit(self, X, y=None):
"""Fits imputer to data. 'None' values are converted to np.nan before imputation and are treated as the same.
Args:
X (pd.DataFrame or np.ndarray): the input training data of shape [n_samples, n_features]
y (pd.Series, optional): the target training data of length [n_samples]
Returns:
self
Raises:
ValueError: if the SimpleImputer receives a dataframe with both Boolean and Categorical data.
"""
X = infer_feature_types(X)
if set([lt.type_string for lt in X.ww.logical_types.values()]) == {
"boolean",
"categorical",
} and not all(
[
is_categorical_actually_boolean(X, col)
for col in X.ww.select("Categorical")
],
):
raise ValueError(
"SimpleImputer cannot handle dataframes with both boolean and categorical features. Use Imputer instead.",
)
nan_ratio = X.isna().sum() / X.shape[0]
self._all_null_cols = nan_ratio[nan_ratio == 1].index.tolist()
X, _ = drop_natural_language_columns(X)
# Convert any boolean columns to IntegerNullable, but keep track of the columns so they can be converted back
self._boolean_cols = list(
X.ww.select(
include=["Boolean", "BooleanNullable"],
return_schema=True,
).columns,
)
# Make sure we're tracking Categorical columns that should be boolean as well
self._boolean_cols.extend(
[
col
for col in X.ww.select("Categorical")
if is_categorical_actually_boolean(X, col)
],
)
X = set_boolean_columns_to_integer(X)
# If the Dataframe only had natural language columns, do nothing.
if X.shape[1] == 0:
return self
self._component_obj.fit(X, y)
return self