Категориальные данные
Узнайте, как кодировать категориальные данные в Python с помощью Label Encoding, Ordinal Encoding, One-Hot Encoding и pd.get_dummies на примерах scikit-learn.
Категориальные данные — это любые данные, принимающие ограниченный фиксированный набор значений: например, "red", "blue", "green" для столбца цвета или "low", "medium", "high" для уровня серьёзности. Большинство алгоритмов машинного обучения работают с числами, поэтому категориальные столбцы необходимо преобразовать в числовое представление перед обучением.
В этой главе рассматриваются основные стратегии кодирования, когда следует выбирать каждую из них, и как правильно применять их в Python с помощью pandas и scikit-learn, не допуская утечки информации из тестовой выборки в модель.
Почему кодирование важно
Передача исходных строковых значений в оценщик scikit-learn вызывает ошибку ValueError. Даже когда столбец уже содержит числа — например, 1, 2, 3, представляющие "small", "medium", "large" — алгоритм, трактующий значения признаков как непрерывные числа, выведет ложную зависимость (например, что "large" втрое больше "small"). Кодирование позволяет точно представить реальную взаимосвязь.
Выбор кодирования зависит от двух вопросов:
- Есть ли естественный порядок? Цвет не имеет естественного порядка (номинальный признак). Размер футболки имеет естественный порядок (порядковый признак). Правильное кодирование сохраняет порядок там, где он существует, и игнорирует его там, где его нет.
- Сколько различных значений (кардинальность) содержит столбец? Столбцы с высокой кардинальностью (сотни уникальных городов, идентификаторов товаров) при One-Hot Encoding могут создавать тысячи фиктивных столбцов, что негативно сказывается как на памяти, так и на производительности модели.
Подготовка тестового набора данных
В примерах ниже используется небольшой набор данных об одежде, чтобы можно было точно проследить результат.
import pandas as pd
data = {
"color": ["red", "green", "blue", "green", "red"],
"size": ["S", "M", "L", "S", "M"],
"price": [10, 20, 30, 10, 20],
"in_stock": [True, True, False, True, False],
}
df = pd.DataFrame(data)
print(df)Вывод:
color size price in_stock
0 red S 10 True
1 green M 20 True
2 blue L 30 False
3 green S 10 True
4 red M 20 FalseLabel Encoding
Label Encoding заменяет каждую категорию целым числом. LabelEncoder из scikit-learn назначает целые числа в алфавитном порядке.
from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
df["color_encoded"] = le.fit_transform(df["color"])
print(df[["color", "color_encoded"]])
print("Classes:", list(le.classes_))Вывод:
color color_encoded
0 red 2
1 green 1
2 blue 0
3 green 1
4 red 2
Classes: ['blue', 'green', 'red']blue → 0, green → 1, red → 2 (алфавитный порядок).
Когда использовать: Label Encoding предназначен для целевой переменной (y), а не для входных признаков. При применении к номинальному столбцу признаков закодированные целые числа подразумевают несуществующий порядок, что вводит в заблуждение модели на основе деревьев и вредит линейным моделям.
Обратное преобразование кодирования:
decoded = le.inverse_transform([0, 1, 2])
print(decoded) # ['blue' 'green' 'red']Ordinal Encoding
Ordinal Encoding похож на Label Encoding, но позволяет задавать точный порядок категорий. Используйте его для признаков, где порядок имеет значение.
from sklearn.preprocessing import OrdinalEncoder
# Define the order explicitly: S < M < L
oe = OrdinalEncoder(categories=[["S", "M", "L"]])
df["size_encoded"] = oe.fit_transform(df[["size"]])
print(df[["size", "size_encoded"]])Вывод:
size size_encoded
0 S 0.0
1 M 1.0
2 L 2.0
3 S 0.0
4 M 1.0Модель теперь может правильно определить, что L (2) > M (1) > S (0).
Обработка неизвестных категорий при предсказании:
# Use handle_unknown='use_encoded_value' with unknown_value=-1
oe_safe = OrdinalEncoder(
categories=[["S", "M", "L"]],
handle_unknown="use_encoded_value",
unknown_value=-1,
)
oe_safe.fit(df[["size"]])
print(oe_safe.transform([["XL"]])) # [[-1.]]One-Hot Encoding
One-Hot Encoding создаёт по одному бинарному столбцу для каждой категории. Значение 1 в столбце означает, что строка принадлежит этой категории; во всех остальных столбцах стоит 0. Это стандартный выбор для номинальных (неупорядоченных) признаков, подаваемых в линейные модели, SVM и нейронные сети.
from sklearn.preprocessing import OneHotEncoder
import numpy as np
ohe = OneHotEncoder(sparse_output=False, handle_unknown="ignore")
color_encoded = ohe.fit_transform(df[["color"]])
# Build a labelled DataFrame from the result
col_names = ohe.get_feature_names_out(["color"])
color_df = pd.DataFrame(color_encoded, columns=col_names, dtype=int)
print(color_df)Вывод:
color_blue color_green color_red
0 0 0 1
1 0 1 0
2 1 0 0
3 0 1 0
4 0 0 1handle_unknown='ignore' заполняет неизвестные категории нулями вместо того, чтобы вызывать ошибку при появлении новых данных на этапе предсказания.
Удаление одного столбца во избежание мультиколлинеарности
При трёх категориях создаются три бинарных столбца, однако третий полностью предсказывается из двух других (blue = 1 − green − red). Эта ловушка фиктивных переменных может вызывать проблемы в линейных моделях. Удалите один столбец с помощью drop='first':
ohe_nodrop = OneHotEncoder(sparse_output=False, drop="first", handle_unknown="ignore")
reduced = ohe_nodrop.fit_transform(df[["color"]])
print(pd.DataFrame(reduced, columns=ohe_nodrop.get_feature_names_out(["color"]), dtype=int))Вывод (один столбец удалён):
color_green color_red
0 0 1
1 1 0
2 0 0
3 1 0
4 0 1Модели на основе деревьев (деревья решений, случайный лес, градиентный бустинг) невосприимчивы к ловушке фиктивных переменных, поэтому для них удаление столбца необязательно.
pd.get_dummies — быстрая альтернатива pandas
Для исследовательской работы pd.get_dummies() — самый быстрый способ выполнить one-hot кодирование DataFrame:
dummies = pd.get_dummies(df[["color", "size"]], dtype=int)
print(dummies)Вывод:
color_blue color_green color_red size_L size_M size_S
0 0 0 1 0 0 1
1 0 1 0 0 1 0
2 1 0 0 1 0 0
3 0 1 0 0 0 1
4 0 0 1 0 1 0Ограничение: pd.get_dummies() не является обученным преобразователем. Он не гарантирует одинаковый набор столбцов между обучающей и тестовой выборками и не поддерживает handle_unknown. Для производственных конвейеров предпочтительнее использовать OneHotEncoder внутри Pipeline scikit-learn.
Предотвращение утечки данных
Утечка данных происходит, когда информация из тестовой выборки влияет на подготовку обучающих данных. Результатом является чрезмерно оптимистичная оценка, не отражающая реальную производительность.
Правильный подход:
- Сначала разделите данные на обучающую и тестовую выборки.
- Обучите любой кодировщик только на обучающей выборке.
- Используйте
transform()(а неfit_transform()) для тестовой выборки.
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import OneHotEncoder
X = df[["color", "size"]]
y = df["price"]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.4, random_state=42)
ohe = OneHotEncoder(sparse_output=False, handle_unknown="ignore")
ohe.fit(X_train) # fit on training data only
X_train_enc = ohe.transform(X_train) # transform training set
X_test_enc = ohe.transform(X_test) # transform test set using training-fit encoderПодробнее о разделении на обучающую и тестовую выборки читайте в главе Train/Test Split.
Использование Pipeline для объединения кодирования с моделью
Pipeline scikit-learn объединяет преобразователь и оценщик в цепочку. Это гарантирует, что кодировщик всегда обучается только на обучающих данных, даже при кросс-валидации.
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder
from sklearn.linear_model import LinearRegression
from sklearn.compose import ColumnTransformer
from sklearn.model_selection import train_test_split
categorical_cols = ["color", "size"]
numeric_cols = ["in_stock"]
X_full = df[categorical_cols + numeric_cols]
y_full = df["price"]
X_tr, X_te, y_tr, y_te = train_test_split(X_full, y_full, test_size=0.4, random_state=42)
preprocessor = ColumnTransformer(transformers=[
("ohe", OneHotEncoder(handle_unknown="ignore"), categorical_cols),
("pass", "passthrough", numeric_cols),
])
pipe = Pipeline(steps=[
("preprocessor", preprocessor),
("model", LinearRegression()),
])
pipe.fit(X_tr, y_tr)
print("Test predictions:", pipe.predict(X_te))ColumnTransformer применяет различные шаги предобработки к разным столбцам за один проход. Pipeline — рекомендуемый подход для всех производственных рабочих процессов машинного обучения.
Выбор правильного кодирования
| Ситуация | Рекомендуемое кодирование |
|---|---|
| Целевая переменная (y) | LabelEncoder |
| Порядковый признак (существует естественный порядок) | OrdinalEncoder с явным указанием categories |
| Номинальный признак с низкой кардинальностью | OneHotEncoder (или pd.get_dummies для исследования) |
| Номинальный признак с высокой кардинальностью | Target encoding или частотное кодирование (см. примечание ниже) |
| Производственный конвейер | OneHotEncoder внутри Pipeline / ColumnTransformer |
Target encoding заменяет каждую категорию средним значением целевой переменной для данной категории. Он хорошо справляется с высокой кардинальностью, но особенно подвержен утечке данных — всегда применяйте его с фолдами кросс-валидации или используйте библиотечную реализацию (например, category_encoders.TargetEncoder), которая обрабатывает это автоматически.
Связанные главы
- Scale — нормализация и стандартизация числовых признаков перед моделированием
- Train/Test Split — правильное разделение данных до любых шагов предобработки
- Linear Regression — модель, которая выигрывает от правильного кодирования категориальных признаков
- Cross Validation — надёжная оценка моделей в сочетании с конвейерами кодирования
- Confusion Matrix — измерение производительности классификационной модели после кодирования целевых переменных
- Pandas Tutorial — основы pandas, включая создание и манипуляцию DataFrame