ingeniería de características

Variables categóricas de alta cardinalidad

¿Qué pasa cuando hay muchas categorías?

Variables categóricas de alta cardinalidad

Ejemplo:

  • código postal
  • ID de usuario
  • nombre de producto

Problema

One-hot encoding aquí es un desastre.

Problema

  • demasiadas columnas

  • modelo ineficiente

  • riesgo de sobreajuste

Alternativa

Target encoding

Idea

Convertir cada categoría en un número…

Idea

Convertir cada categoría en un número

basado en el target.

Ejemplo

Categoría → promedio del target

Ejemplo

A → 0.66

B → 0.25

Ejemplo

Cada fila recibe ese valor.

Construcción

Paso 1: calcular promedio en train

Construcción

Paso 1: calcular promedio en train
Paso 2: aplicar esos valores al test

Construcción

Paso 1: calcular promedio en train
Paso 2: aplicar esos valores al test
Paso 3: usar como nueva variable

Código básico

mean_encoding = train.groupby("categoria")["target"].mean()

train["cat_enc"] = train["categoria"].map(mean_encoding)
test["cat_enc"] = test["categoria"].map(mean_encoding)

Problema grave

Esto parece correcto…

Problema grave

Pero está MAL.

Problema grave

Estás usando el target de la misma fila.

Problema grave

Eso genera:

fuga de información

Solución

Usar validación por folds

Solución

Para cada fila:

NO usar su propio target

Solución

Dividir el dataset en K partes

Solución

Para cada fold:

  • calcular promedio con los otros folds

  • aplicar al fold actual

Código (out-of-fold)

from sklearn.model_selection import KFold
import numpy as np

kf = KFold(n_splits=5, shuffle=True, random_state=123)

train["cat_enc"] = np.nan

for train_idx, val_idx in kf.split(train):
    
    X_train, X_val = train.iloc[train_idx], train.iloc[val_idx]
    
    means = X_train.groupby("categoria")["target"].mean()
    
    train.loc[val_idx, "cat_enc"] = X_val["categoria"].map(means)

Aplicación al test

global_means = train.groupby("categoria")["target"].mean()

test["cat_enc"] = test["categoria"].map(global_means)

Otro problema

Categorías con pocos datos

Otro problema

Ejemplo:

una categoría aparece 1 vez

Otro problema

El promedio es inestable

Solución

Suavizado

Suavizado

Combinar:

  • promedio de la categoría

  • promedio global

Fórmula

\[ \text{codificación} = \frac{\text{suma}_i + \alpha \cdot \text{media global}}{n_i + \alpha} \]

Parámetro clave

\[ \alpha \in [5, 10] \]

Código con suavizado

global_mean = train["target"].mean()
alpha = 10

stats = train.groupby("categoria")["target"].agg(["mean", "count"])

smooth = (stats["mean"] * stats["count"] + global_mean * alpha) / (stats["count"] + alpha)

train["cat_enc"] = train["categoria"].map(smooth)
test["cat_enc"] = test["categoria"].map(smooth)

Último problema

Categorías nuevas en test

Último problema

No existen en train

Solución

test["cat_enc"].fillna(global_mean, inplace=True)

Resumen final

  • One-hot → pocas categorías
  • Label → binarias
  • Target encoding → muchas categorías

Idea clave

La ingeniería de variables NO es automática.

Idea clave

Es donde realmente se gana Kaggle.