Подготовка данных в scikit-learn: пропуски, категории и масштабирование

Большинство моделей принимает числовую матрицу без неоднозначных пропусков. Подготовка данных превращает исходные столбцы в такое представление, сохраняя одинаковые правила для…

Большинство моделей принимает числовую матрицу без неоднозначных пропусков. Подготовка данных превращает исходные столбцы в такое представление, сохраняя одинаковые правила для обучения и будущих запросов. Важно не только выполнить преобразования, но и обучить их без утечки.

Три распространённые операции

Числовые пропуски можно заполнить медианой обучающей выборки. Категории превращают в индикаторные столбцы с помощью one-hot encoding. Признаки разного масштаба стандартизуют, если алгоритм опирается на расстояния или регуляризацию.

Дереву решений масштабирование обычно не требуется, а k-NN без него может считать доход важнее возраста только из-за больших чисел. Подготовка выбирается вместе с моделью, а не применяется автоматически ко всем столбцам.

ColumnTransformer и Pipeline для разных типов полей

import pandas as pd

from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import OneHotEncoder, StandardScaler

X_train = pd.DataFrame({
    'age': [22, 35, 41, None, 29, 54, 47, 31],
    'income': [55_000, 92_000, 120_000, 74_000, None, 160_000, 105_000, 68_000],
    'city': ['Казань', 'Москва', 'Москва', 'Казань', 'Омск', 'Москва', 'Омск', 'Казань'],
    'device': ['mobile', 'desktop', 'mobile', 'tablet', 'mobile', 'desktop', 'tablet', 'mobile'],
})
y_train = [0, 1, 1, 0, 0, 1, 1, 0]
X_test = pd.DataFrame({
    'age': [38, None],
    'income': [98_000, 81_000],
    'city': ['Томск', 'Казань'],  # «Томск» не встречался при fit.
    'device': ['mobile', 'watch'],
})

numeric_columns = ['age', 'income']
category_columns = ['city', 'device']

numeric = make_pipeline(
    SimpleImputer(strategy='median'),
    StandardScaler(),
)
categories = make_pipeline(
    SimpleImputer(strategy='most_frequent'),
    OneHotEncoder(handle_unknown='ignore'),
)

preprocessing = ColumnTransformer([
    ('numeric', numeric, numeric_columns),
    ('categories', categories, category_columns),
])

model = make_pipeline(
    preprocessing,
    LogisticRegression(max_iter=1_000),
)
model.fit(X_train, y_train)
predictions = model.predict(X_test)
print(predictions)

Параметр handle_unknown='ignore' позволяет обработать новую категорию без аварии. Это не гарантирует хороший прогноз для неё: мониторинг всё равно должен показать, насколько часто возникают неизвестные значения.

Почему Pipeline является частью корректности

Если отдельно подготовить всю таблицу, а затем запустить кросс-валидацию, imputer и scaler уже увидят статистику проверочных фолдов. Объедините преобразование и модель, чтобы каждый фолд обучал все шаги только на своей train-части.

Тот же объект принимает исходные столбцы при fit и predict, поэтому порядок операций нельзя случайно забыть в приложении.

Смысл пропуска важнее стратегии

Медиана уместна не всегда. Отсутствующая температура из-за сломанного датчика и отсутствие второго автомобиля у семьи — разные события. Иногда полезен индикатор is_missing, иногда нужна отдельная категория, а иногда строку нельзя использовать вовсе.

Проверьте долю пропусков по времени и источникам. Если в production она резко выросла, прежний imputer скроет техническую проблему, но не исправит распределение данных.

Практика: соберите преобразователь

Создайте маленький DataFrame с возрастом, доходом, городом и типом устройства. Добавьте пропуски и категорию, которая встречается только в test. Соберите ColumnTransformer, обучите его на train и убедитесь, что transform теста завершается. Затем объясните, почему после one-hot число столбцов отличается от исходного и почему выходная ширина одинакова для train и test.

Что важно запомнить

Источники