Как обрабатывать пропущенные значения в scikit-learn с помощью SimpleImputer и IterativeImputer?
SimpleImputer заполняет NaN одним статистическим значением (mean/median/most_frequent/constant), вычисленным при fit; IterativeImputer моделирует каждый признак по остальным итеративно — точнее при MCAR/MAR, но медленнее и чувствительнее к порядку признаков.
SimpleImputer: быстрое заполнение одним значением
SimpleImputer вычисляет одну статистику (mean, median, most_frequent, constant) на обучающей выборке и заменяет все NaN этим значением. Это stateless после fit: при transform используются только сохранённые statistics_.
import numpy as np
import pandas as pd
from sklearn.impute import SimpleImputer, IterativeImputer
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.ensemble import HistGradientBoostingClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
# Синтетический датасет с пропусками
np.random.seed(42)
n = 1000
df = pd.DataFrame({
"age": np.random.randint(18, 65, n).astype(float),
"income": np.random.exponential(50000, n),
"tenure": np.random.randint(0, 20, n).astype(float),
"country": np.random.choice(["US", "DE", "BR", "IN"], n),
"plan": np.random.choice(["free", "pro", "enterprise"], n),
})
y = (df["income"] > 60000).astype(int)
# Вносим пропуски
for col in ["age", "income", "tenure"]:
mask = np.random.rand(n) < 0.15 # 15% NaN
df.loc[mask, col] = np.nan
X_train, X_test, y_train, y_test = train_test_split(
df, y, test_size=0.2, stratify=y, random_state=42
)
# SimpleImputer в Pipeline
num_cols = ["age", "income", "tenure"]
cat_cols = ["country", "plan"]
preprocessor = ColumnTransformer([
("num", Pipeline([
("impute", SimpleImputer(strategy="median")), # median устойчив к выбросам
("scale", StandardScaler()),
]), num_cols),
("cat", Pipeline([
("impute", SimpleImputer(strategy="most_frequent")),
("encode", OneHotEncoder(handle_unknown="ignore", sparse_output=False)),
]), cat_cols),
])
pipeline = Pipeline([
("prep", preprocessor),
("clf", HistGradientBoostingClassifier(random_state=42)),
])
pipeline.fit(X_train, y_train)
# Проверяем сохранённые статистики
simple_imp = pipeline.named_steps["prep"].named_transformers_["num"].named_steps["impute"]
print("statistics_ (median):", simple_imp.statistics_) # array с медианами по каждому признаку
y_pred = pipeline.predict(X_test)
print(classification_report(y_test, y_pred))
IterativeImputer: мультивариантное восстановление
IterativeImputer моделирует каждый признак как функцию остальных признаков. На каждой итерации для каждого столбца с NaN обучается регрессор (по умолчанию BayesianRidge) на строках без пропусков и предсказывает пропущенные значения. Процесс повторяется max_iter раз до сходимости.
from sklearn.impute import IterativeImputer
from sklearn.linear_model import BayesianRidge
from sklearn.experimental import enable_iterative_imputer # обязательный импорт
# IterativeImputer только для числовых признаков
iter_imputer = IterativeImputer(
estimator=BayesianRidge(), # внутренний регрессор
max_iter=10, # число раундов
initial_strategy="mean", # инициализация NaN перед первой итерацией
imputation_order="ascending", # по возрастанию доли NaN
random_state=42,
verbose=0,
)
# Только числовые колонки
X_num_train = X_train[num_cols].values
X_num_test = X_test[num_cols].values
iter_imputer.fit(X_num_train)
X_num_imputed = iter_imputer.transform(X_num_test)
print("NaN после IterativeImputer:", np.isnan(X_num_imputed).sum()) # 0
# В полном пайплайне:
preprocessor_iter = ColumnTransformer([
("num", Pipeline([
("impute", IterativeImputer(max_iter=10, random_state=42)),
("scale", StandardScaler()),
]), num_cols),
("cat", Pipeline([
("impute", SimpleImputer(strategy="most_frequent")),
("encode", OneHotEncoder(handle_unknown="ignore", sparse_output=False)),
]), cat_cols),
])
pipeline_iter = Pipeline([
("prep", preprocessor_iter),
("clf", HistGradientBoostingClassifier(random_state=42)),
])
pipeline_iter.fit(X_train, y_train)
KNNImputer как альтернатива
from sklearn.impute import KNNImputer
# Заполняет NaN средним по k ближайшим соседям в пространстве признаков
knn_imp = KNNImputer(n_neighbors=5, weights="distance")
X_knn = knn_imp.fit_transform(X_num_train)
# MissingIndicator: добавляем бинарный признак «был ли NaN»
from sklearn.impute import MissingIndicator
from sklearn.pipeline import FeatureUnion
indicator = MissingIndicator(features="missing-only")
indicator.fit(X_num_train)
print("Признаки с NaN:", indicator.features_) # индексы столбцов с пропусками
Подводные камни
enable_iterative_imputerнужно импортировать явно — без негоfrom sklearn.impute import IterativeImputerвызываетImportError.SimpleImputer(strategy='mean')не работает на строковых колонках; для категориальных используйте'most_frequent'или'constant'.IterativeImputerвызываетfitвнутреннего регрессора на каждой итерации для каждого признака — при большом числе признаков иmax_iter=10время fit резко растёт.- Если
fitвызван на данных без NaN,SimpleImputerсохраняет статистики но не создаёт проблем; однако при transform с NaN применит сохранённые значения — это ожидаемое, не ошибочное поведение. IterativeImputerне поддерживает категориальные признаки напрямую; передавайте только числовые или используйте OrdinalEncoder перед импутацией.- При использовании
MissingIndicatorвнутри Pipeline позиция трансформера важна: индикатор должен видеть исходные NaN до импутации. SimpleImputerвозвращает numpy array, не DataFrame — имена столбцов теряются; оборачивайте вColumnTransformerили используйтеset_output(transform='pandas')(sklearn >= 1.2).- Лечение MNAR (Missing Not At Random) импутацией приводит к bias: если пропуск сам по себе несёт информацию (например, пациент не измерял давление именно при гипертонии), добавляйте бинарный индикатор через
MissingIndicator.
Common mistakes
- Объяснять
imputers missing valuesтолько синтаксисом без shape, dtype, состояния или режима выполнения. - Игнорировать leakage, воспроизводимость, пустые входы и скрытые копии данных.
- Не проверять production-симптомы: latency, память, ретраи, дрейф качества и несовпадение версий.
What the interviewer is testing
- Может ли связать
imputers missing valuesс реальным контрактом входов и выходов. - Упоминает ли тесты, метрики, reproducibility и диагностику ошибок.
- Видит ли различие между demo-кодом в ноутбуке и production-пайплайном.