Source code for evalml.pipelines.components.transformers.imputers.knn_imputer

"""Component that imputes missing data according to a specified imputation strategy."""
import numpy as np
import pandas as pd
import woodwork
from sklearn.impute import KNNImputer as Sk_KNNImputer

from evalml.pipelines.components.transformers import Transformer
from evalml.utils import infer_feature_types
from evalml.utils.nullable_type_utils import _get_new_logical_types_for_imputed_data


[docs]class KNNImputer(Transformer): """Imputes missing data using KNN according to a specified number of neighbors. Natural language columns are ignored. Args: number_neighbors (int): Number of nearest neighbors for KNN to search for. Defaults to 3. random_seed (int): Seed for the random number generator. Defaults to 0. """ name = "KNN Imputer" def __init__(self, number_neighbors=3, random_seed=0, **kwargs): parameters = {"number_neighbors": number_neighbors} parameters.update(kwargs) imputer = Sk_KNNImputer( n_neighbors=number_neighbors, missing_values=np.nan, **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 KNNImputer receives a dataframe with both Boolean and Categorical data. """ X = infer_feature_types(X) nan_ratio = X.isna().sum() / X.shape[0] # Keep track of the different types of data in X self._all_null_cols = nan_ratio[nan_ratio == 1].index.tolist() self._natural_language_cols = list( X.ww.select("NaturalLanguage", return_schema=True).columns.keys(), ) # Only impute data that is not natural language columns or fully null self._cols_to_impute = [ col for col in X.columns if col not in self._natural_language_cols and col not in self._all_null_cols ] # If the Dataframe only had natural language columns, do nothing. if not self._cols_to_impute: return self self._component_obj.fit(X[self._cols_to_impute], y) return self
[docs] def transform(self, X, y=None): """Transforms input by imputing missing values. 'None' and np.nan values are treated as the same. Args: X (pd.DataFrame): Data to transform. y (pd.Series, optional): Ignored. Returns: pd.DataFrame: Transformed X """ # Record original data X = infer_feature_types(X) original_index = X.index original_schema = X.ww.schema # separate out just the columns we are imputing X_t = X[self._cols_to_impute] if not self._cols_to_impute: not_all_null_cols = [ col for col in X.columns if col not in self._all_null_cols ] return X.ww[not_all_null_cols] # Transform the data X_t = self._component_obj.transform(X_t) X_t = pd.DataFrame(X_t, columns=self._cols_to_impute) # Reinit woodwork, maintaining original types where possible imputed_schema = original_schema.get_subset_schema(self._cols_to_impute) new_ltypes = _get_new_logical_types_for_imputed_data( impute_strategy="knn", original_schema=imputed_schema, ) X_t.ww.init(schema=imputed_schema, logical_types=new_ltypes) # Add back in the unchanged original natural language columns that we want to keep if len(self._natural_language_cols) > 0: X_t = woodwork.concat_columns([X_t, X.ww[self._natural_language_cols]]) # reorder columns to match original X_t = X_t.ww[[col for col in original_schema.columns if col in X_t.columns]] if self._cols_to_impute: X_t.index = original_index return X_t
[docs] def fit_transform(self, X, y=None): """Fits on X and transforms X. Args: X (pd.DataFrame): Data to fit and transform y (pd.Series, optional): Target data. Returns: pd.DataFrame: Transformed X """ return self.fit(X, y).transform(X, y)