Категориальные данные: one-hot кодирование в Python

Раздел: Предобработка данных -> One-hot кодирование

Основные подходы к one-hot кодированию

One-hot кодирование преобразует категориальные признаки в бинарные векторы. Каждая категория становится отдельным столбцом со значением 1 для данной строки и 0 для остальных. Рассмотрим несколько вариантов реализации на Python.

Как выполнить one-hot кодирование с помощью sklearn OneHotEncoder?

Этот подход считается основным и наиболее эффективным, так как интегрируется в пайплайны и корректно обрабатывает неизвестные категории на новых данных.


import pandas as pd
from sklearn.preprocessing import OneHotEncoder

# Исходные данные
df = pd.DataFrame({'цвет': ['красный', 'синий', 'зеленый', 'синий']})

encoder = OneHotEncoder(sparse_output=False, handle_unknown='ignore')
encoded = encoder.fit_transform(df[['цвет']])
# Превращаем в DataFrame с названиями столбцов
result = pd.DataFrame(encoded, columns=encoder.get_feature_names_out(['цвет']))
print(result)

One hot python (one-hot кодирование в python)

   цвет_зеленый  цвет_красный  цвет_синий
0           0.0           1.0          0.0
1           0.0           0.0          1.0
2           1.0           0.0          0.0
3           0.0           0.0          1.0

Преимущества:

  • Обработка неизвестных категорий – параметр handle_unknown='ignore' позволяет не выбрасывать ошибку при появлении новой категории в тесте.
  • Совместимость с Pipeline – можно использовать в цепочке преобразований вместе с масштабированием и моделью.
  • Управление разреженностью – для больших данных sparse_output=True экономит память.

Проблема: результат – массив NumPy, столбцы не сразу удобны для анализа. Решение – преобразовать в DataFrame с помощью get_feature_names_out.

Типичная ошибка: забыть указать sparse_output=False и затем пытаться конкатенировать с pandas DataFrame. Решение – либо использовать sparse матрицу явно, либо конвертировать её в плотную.

Как сделать one-hot кодирование с pandas get_dummies?

Простой и наглядный способ, подходит для исследовательского анализа, но не интегрируется в пайплайны и не обрабатывает новые категории.


import pandas as pd

df = pd.DataFrame({'цвет': ['красный', 'синий', 'зеленый', 'синий']})
df_encoded = pd.get_dummies(df, columns=['цвет'])
print(df_encoded)
   цвет_зеленый  цвет_красный  цвет_синий
0         False          True        False
1         False         False         True
2          True         False        False
3         False         False         True

Цель использования:

  • Быстрая генерация фиктивных переменных для моделирования без дополнительных библиотек.
  • Автоматическое создание имён столбцов на основе значений.

Проблема: при разных наборах категорий в тренировочных и тестовых данных возникает несовпадение столбцов. Решение – вручную выровнять столбцы с помощью reindex или использовать OneHotEncoder.

Другая ошибка: get_dummies возвращает булевы значения, а не числа 0/1. Для некоторых моделей (например, линейная регрессия) это не критично, но может вызвать неявное приведение типов. Решение – явно преобразовать в int: astype(int).

Как сделать one-hot кодирование вручную с помощью словаря и numpy?

Этот вариант полезен для понимания внутреннего механизма или при работе с очень большими разреженными данными без дополнительных библиотек.


import numpy as np
import pandas as pd

df = pd.DataFrame({'цвет': ['красный', 'синий', 'зеленый', 'синий']})
categories = sorted(df['цвет'].unique())
cat_to_idx = {cat: i for i, cat in enumerate(categories)}
n_rows = len(df)
n_cats = len(categories)
one_hot = np.zeros((n_rows, n_cats), dtype=int)
for row, cat in enumerate(df['цвет']):
    one_hot[row, cat_to_idx[cat]] = 1
result = pd.DataFrame(one_hot, columns=categories)
print(result)
   зеленый  красный  синий
0        0        1      0
1        0        0      1
2        1        0      0
3        0        0      1

Цель использования:

  • Образовательная – понять, как работает one-hot.
  • Специфические сценарии, когда нужно полностью контролировать отображение.

Проблема: при большом количестве категорий массив может быть огромным. Решение – использовать разреженные матрицы из scipy.sparse.

Ошибка: порядок категорий не сохраняется, если не отсортировать его явно. Решение – всегда определять порядок перед кодированием.

Как использовать category_encoders для one-hot кодирования?

Библиотека category_encoders предоставляет единый интерфейс для многих кодировщиков, включая OneHotEncoder с возможностью drop первого уровня (избегание мультиколлинеарности).


import pandas as pd
import category_encoders as ce

df = pd.DataFrame({'цвет': ['красный', 'синий', 'зеленый', 'синий']})
encoder = ce.OneHotEncoder(use_cat_names=True, drop_invariant=False)
encoded = encoder.fit_transform(df['цвет'])
print(encoded)
   цвет_зеленый  цвет_красный  цвет_синий
0             0             1           0
1             0             0           1
2             1             0           0
3             0             0           1

Цель использования:

  • Единый синтаксис для разных типов кодирования (target, ordinal и т.д.).
  • Автоматическая обработка пропусков.

Проблема: при добавлении новых категорий в тестовых данных encoder выдаёт ошибку. Решение – использовать параметр handle_unknown (в версии 2.0+).

Ошибка: если не задать use_cat_names=True, имена столбцов будут сгенерированы как целые числа – сложнее интерпретировать.

Как сделать one-hot кодирование для меток классов с помощью Keras?

В задачах классификации часто преобразуют метки (целые числа) в one-hot векторы. Для этого удобна функция to_categorical из Keras.


import numpy as np
from tensorflow.keras.utils import to_categorical

labels = np.array([0, 1, 2, 1])
encoded = to_categorical(labels, num_classes=3)
print(encoded)
[[1. 0. 0.]
 [0. 1. 0.]
 [0. 0. 1.]
 [0. 1. 0.]]

Цель использования:

  • Подготовка целевых переменных для нейронных сетей с категориальной кросс-энтропией.
  • Простота и встроенная поддержка разреженных меток.

Проблема: если метки не последовательны (пропущены значения), num_classes нужно задавать явно, иначе он определится как max_label+1, что может быть меньше реального числа классов. Решение – передать список всех возможных классов.

Ошибка: to_categorical возвращает float32, что может быть неожиданно для целочисленных моделей. Решение – конвертировать astype(int) при необходимости.

Расширенные примеры one-hot кодирования

1. One-hot для нескольких столбцов и работа с разреженной матрицей

На практике часто кодируют сразу несколько категориальных признаков. Покажем, как это сделать с OneHotEncoder и конвертировать разреженную матрицу в DataFrame для анализа.

Пример

import pandas as pd
from sklearn.preprocessing import OneHotEncoder

# Данные с двумя категориальными столбцами
df = pd.DataFrame({
    'цвет': ['красный', 'синий', 'зеленый', 'синий'],
    'размер': ['S', 'M', 'L', 'M']
})

encoder = OneHotEncoder(sparse_output=True, handle_unknown='ignore')
encoded_sparse = encoder.fit_transform(df[['цвет', 'размер']])
print('Тип:', type(encoded_sparse))
print('Размер:', encoded_sparse.shape)

# Преобразуем в плотный DataFrame
df_encoded = pd.DataFrame(
    encoded_sparse.toarray(),
    columns=encoder.get_feature_names_out(['цвет', 'размер'])
)
print(df_encoded)
Тип: 
Размер: (4, 6)
   цвет_зеленый  цвет_красный  цвет_синий  размер_L  размер_M  размер_S
0           0.0           1.0          0.0       0.0       0.0       1.0
1           0.0           0.0          1.0       0.0       1.0       0.0
2           1.0           0.0          0.0       1.0       0.0       0.0
3           0.0           0.0          1.0       0.0       1.0       0.0

2. Избегание ловушки фиктивных переменных (dummy trap)

Чтобы убрать один из столбцов (устранить идеальную мультиколлинеарность), используют параметр drop='first' в OneHotEncoder или drop_first=True в pd.get_dummies.

Пример

import pandas as pd
from sklearn.preprocessing import OneHotEncoder

df = pd.DataFrame({'цвет': ['красный', 'синий', 'зеленый', 'синий']})

# sklearn
enc_drop = OneHotEncoder(drop='first', sparse_output=False)
arr = enc_drop.fit_transform(df[['цвет']])
df_sk = pd.DataFrame(arr, columns=enc_drop.get_feature_names_out(['цвет']))
print('С drop first (sklearn):')
print(df_sk)

# pandas
df_pd = pd.get_dummies(df, columns=['цвет'], drop_first=True)
print('\nС drop first (pandas):')
print(df_pd)
С drop first (sklearn):
   цвет_красный  цвет_синий
0           1.0          0.0
1           0.0          1.0
2           0.0          0.0
3           0.0          1.0

С drop first (pandas):
   цвет_синий  цвет_зеленый
0       False         False
1        True         False
2       False          True
3        True         False

Примечание:

pandas с drop_first=True даёт булевы значения и порядок столбцов может отличаться; рекомендуется преобразовать в int и при необходимости отсортировать.

3. Интеграция OneHotEncoder в Pipeline

Для автоматизации предобработки и масштабирования OneHotEncoder комбинируют с ColumnTransformer.

Пример

import pandas as pd
import numpy as np
from sklearn.preprocessing import OneHotEncoder, StandardScaler
from sklearn.compose import ColumnTransformer
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline

# Данные
X = pd.DataFrame({
    'цвет': ['красный', 'синий', 'зеленый'],
    'число': [10, 20, 30]
})
y = [0, 1, 0]

# Пайплайн
preprocessor = ColumnTransformer(
    transformers=[
        ('cat', OneHotEncoder(sparse_output=False), ['цвет']),
        ('num', StandardScaler(), ['число'])
    ]
)
pipeline = Pipeline(steps=[
    ('preprocessor', preprocessor),
    ('classifier', LogisticRegression())
])

pipeline.fit(X, y)
preds = pipeline.predict(X)
print('Предсказания:', preds)
Предсказания: [0 1 0]

Объяснение: ColumnTransformer применяет one-hot кодирование к столбцу 'цвет' и стандартизацию к 'число'. Затем модель обучается на объединённых признаках.

4. Обратное преобразование one-hot в исходные категории

Метод inverse_transform OneHotEncoder восстанавливает исходные значения из бинарной матрицы.

Пример

import pandas as pd
from sklearn.preprocessing import OneHotEncoder

categories = ['A', 'B', 'C']
encoder = OneHotEncoder(sparse_output=False)
encoded = encoder.fit_transform(pd.DataFrame({'cat': categories}))
print('Закодировано:')
print(encoded)

# Обратное преобразование
decoded = encoder.inverse_transform(encoded)
print('\nВосстановлено:', decoded.flatten())
Закодировано:
[[1. 0. 0.]
 [0. 1. 0.]
 [0. 0. 1.]]

Восстановлено: ['A' 'B' 'C']

Примечание:

Обратное преобразование работает корректно только если матрица содержит ровно одну единицу на строку; если были удалены столбцы (drop='first'), результат будет неполным.

5. One-hot с пропущенными значениями

Если в данных есть NaN, поведение разных кодировщиков различается. OneHotEncoder по умолчанию считает NaN отдельной категорией (если handle_unknown='ignore', то NaN кодируется как все нули).

Пример

import pandas as pd
from sklearn.preprocessing import OneHotEncoder

df = pd.DataFrame({'цвет': ['красный', None, 'синий', None]})
encoder = OneHotEncoder(sparse_output=False, handle_unknown='ignore')
encoded = encoder.fit_transform(df[['цвет']])
result = pd.DataFrame(encoded, columns=encoder.get_feature_names_out(['цвет']))
print(result)
   цвет_красный  цвет_синий
0           1.0          0.0
1           0.0          0.0
2           0.0          1.0
3           0.0          0.0

NaN превратились в строку из нулей – модель не получит информацию о пропуске. Если нужно явно кодировать пропуски, можно заменить NaN на специальную строку (например, 'missing') перед кодированием.

6. Работа с очень большим числом категорий

Когда категорий тысячи, one-hot может быть неэффективен. Но если всё же нужно, используйте разреженные матрицы. Пример с случайными данными (1000 строк, 500 категорий).

Пример

import numpy as np
import pandas as pd
from sklearn.preprocessing import OneHotEncoder

# Генерация данных: 1000 строк, 500 уникальных категорий
np.random.seed(42)
categories = [f'cat_{i}' for i in range(500)]
data = np.random.choice(categories, size=1000)
df = pd.DataFrame({'big_cat': data})

encoder = OneHotEncoder(sparse_output=True)
encoded = encoder.fit_transform(df[['big_cat']])
print('Размер разреженной матрицы:', encoded.shape)
print('Занимаемая память (байт):', encoded.data.nbytes)
# Сравнение с плотной
print('Плотная заняла бы (байт):', 1000 * 500 * 8)
Размер разреженной матрицы: (1000, 500)
Занимаемая память (байт): 8000
Плотная заняла бы (байт): 4000000

Разреженная матрица экономит память, так как хранит только ненулевые элементы.

One-hot кодирование в Python - comments

En
One hot python (python)