Ingeniería de características
La ingeniería de características transforma las columnas crudas en representaciones que los modelos pueden aprender. La codificación de categóricas, el escalado de numéricas y la composición de transformaciones con ColumnTransformer son los patrones centrales para el ML tabular.
Receta
Tarjeta de receta de referencia rápida, lista para copiar y pegar.
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import OneHotEncoder, StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.ensemble import RandomForestClassifier
preprocessor = ColumnTransformer([
("num", StandardScaler(), numeric_cols),
("cat", OneHotEncoder(handle_unknown="ignore"), categorical_cols),
])
pipe = Pipeline([("prep", preprocessor), ("clf", RandomForestClassifier())])Cuándo usar esto:
- Columnas numéricas y categóricas mixtas en el mismo conjunto de datos.
- Modelos sensibles a la escala de características (SVM, regresión logística, redes neuronales en datos tabulares).
- Categóricas de alta cardinalidad que necesitan hashing o codificación de destino.
- Preprocesamiento reproducible empaquetado con el modelo para su implementación.
Ejemplo de trabajo
"""feature_engineering.py - preprocesamiento de tipos mixtos con ColumnTransformer."""
from __future__ import annotations
import pandas as pd
from sklearn.compose import ColumnTransformer
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.metrics import classification_report
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder, StandardScaler
df = pd.DataFrame({
"age": [25, 34, 45, 29, 52],
"income": [45000, 62000, 88000, 51000, 95000],
"city": ["nyc", "la", "nyc", "chicago", "la"],
"plan": ["basic", "pro", "pro", "basic", "enterprise"],
"churned": [0, 0, 1, 0, 1],
})
X = df.drop(columns=["churned"])
y = df["churned"]
numeric_cols = ["age", "income"]
categorical_cols = ["city", "plan"]
preprocessor = ColumnTransformer(
transformers=[
("num", StandardScaler(), numeric_cols),
("cat", OneHotEncoder(handle_unknown="ignore", sparse_output=False), categorical_cols),
],
remainder="drop",
)
pipe = Pipeline([
("prep", preprocessor),
("clf", GradientBoostingClassifier(random_state=42)),
])
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.4, random_state=42)
pipe.fit(X_train, y_train)
print(classification_report(y_test, pipe.predict(X_test)))Lo que esto demuestra:
- Diferentes transformaciones aplicadas a grupos de columnas numéricas y categóricas.
OneHotEncoderconhandle_unknown="ignore"para seguridad en producción.sparse_output=Falsepara salida densa compatible con modelos de árboles y lineales.- Serialización completa de la canalización: el preprocesamiento viaja con el clasificador.
Inmersión profunda
Cómo funciona
- Escalado centra y escala las columnas numéricas para que los modelos basados en gradientes converjan más rápido.
- Codificación one-hot expande las categóricas en columnas indicadoras binarias por categoría.
- ColumnTransformer ejecuta cada subtransformador en sus columnas asignadas y concatena los resultados.
- Pipelines llaman a
fiten los datos de entrenamiento por cada pliegue de CV, evitando la fuga de estadísticas. - Los nombres de características se pueden recuperar con
get_feature_names_out()después de ajustar.
Transformaciones comunes
| Transformación | Tipo de columna | Clase sklearn |
|---|---|---|
| Escalado estándar | Numérico | StandardScaler |
| Escalado Min-Max | Numérico acotado | MinMaxScaler |
| Transformación logarítmica | Numérico sesgado | FunctionTransformer(np.log1p) |
| One-hot | Categórica de baja cardinalidad | OneHotEncoder |
| Ordinal | Categorías ordenadas | OrdinalEncoder |
| Imputación | Valores faltantes | SimpleImputer |
Notas de Python
from sklearn.preprocessing import FunctionTransformer
import numpy as np
log_transform = FunctionTransformer(np.log1p, validate=False)
# Encadenar imputación antes del escalado en la rama numérica:
from sklearn.impute import SimpleImputer
from sklearn.pipeline import Pipeline as SkPipeline
numeric_pipe = SkPipeline([
("impute", SimpleImputer(strategy="median")),
("scale", StandardScaler()),
])Errores comunes
- Ajustar en datos completos - calcular la media/std o las listas de categorías a partir de filas de prueba filtra información. Solución: ajustar dentro de una canalización en pliegues de entrenamiento solamente.
- Categorías no vistas en la inferencia - los nuevos valores de categoría bloquean un
OneHotEncodersimple. Solución:handle_unknown="ignore"ohandle_unknown="infrequent_if_exist". - Fuga de codificación de destino - codificar categorías con la media de destino usando todos los datos infla las puntuaciones de CV. Solución: usar
TargetEncoderdentro de una canalización con CV adecuado. - Escalar modelos de árboles innecesariamente - los bosques aleatorios y el aumento de gradiente son invariantes a la escala. Solución: omitir el escalado para conjuntos de árboles; aún así codificar las categóricas.
- One-hot de alta cardinalidad - miles de columnas dummy ralentizan el entrenamiento y sobreajustan. Solución: hashing (
FeatureHasher), codificación de frecuencia o embeddings. - Explosión polinómica -
PolynomialFeatures(degree=3)en 20 columnas crea miles de características. Solución: limitar el grado, usar selección de características o modo de solo interacción.
Alternativas
| Alternativa | Usar cuándo | No usar cuándo |
|---|---|---|
ColumnTransformer | Tipos de columna mixtos en un modelo | Todas las columnas son del mismo tipo (usar un solo transformador) |
pandas.get_dummies | EDA rápida en notebooks | Canalizaciones de producción (no serializable, sin manejo de desconocidos) |
| Codificación de destino | Categóricas de alta cardinalidad con suficientes datos | Conjuntos de datos pequeños (riesgo severo de sobreajuste) |
| Hashing de características | Cardinalidad muy alta, restricciones de memoria | Necesitas nombres de características interpretables |
Preguntas frecuentes
¿Por qué usar ColumnTransformer en lugar de operaciones manuales de pandas?
- Serializable con
joblibpara implementación. - Se integra con
Pipeliney validación cruzada sin fugas. - Maneja categorías desconocidas y valores faltantes de manera consistente en la inferencia.
¿Debo escalar características para RandomForest?
- Los modelos de árboles se dividen por orden de rango; el escalado no cambia las divisiones.
- Aún así, codifica categóricas e imputa valores faltantes.
- Escala para regresión logística, SVM y k-NN en el mismo conjunto de datos.
¿Cómo obtengo los nombres de las características después de la codificación one-hot?
pipe.fit(X_train, y_train)
names = pipe.named_steps["prep"].get_feature_names_out()
print(list(names))- Los nombres siguen el patrón
cat__city_nyc,num__age.
¿Qué significa remainder="drop"?
- Las columnas no listadas en ningún transformador se descartan.
- Usa
remainder="passthrough"para mantener las columnas no listadas tal cual. - Las listas de columnas explícitas evitan la inclusión accidental de columnas de ID.
¿Cómo manejo los valores faltantes?
from sklearn.impute import SimpleImputer
num_pipe = Pipeline([("impute", SimpleImputer(strategy="median")), ("scale", StandardScaler())])- Imputa antes de escalar: escalar en columnas NaN falla.
strategy="most_frequent"funciona para la imputación categórica conSimpleImputer+p tipo de cadena.
¿Cuándo debería usar OrdinalEncoder vs OneHotEncoder?
- Ordinal para categorías con orden natural (bajo/medio/alto).
- One-hot para categorías nominales sin orden (nombres de ciudades).
- Los modelos de árboles manejan ordinales codificados como etiquetas; los modelos lineales prefieren one-hot.
¿Puedo aplicar funciones personalizadas de pandas?
- Usa
FunctionTransformerpara envolver cualquier función compatible con numpy. - Evita cierres que capturan estado externo; rompen la serialización.
- Prefiere los incorporados de sklearn cuando estén disponibles.
¿Cómo ingenierizo características de fecha?
df["order_year"] = df["order_date"].dt.year
df["order_dow"] = df["order_date"].dt.dayofweek- Extrae año, mes, día de la semana, es fin de semana como columnas numéricas.
- Descarta la columna datetime cruda de las entradas del modelo.
¿Qué pasa con las columnas de texto?
- ML Clásico:
TfidfVectorizeren una rama deColumnTransformer. - Era LLM: precalcula embeddings y almacena como columnas numéricas.
- Ver Embeddings y Similitud para características basadas en embeddings.
¿Cómo evito la fuga de datos con la codificación de destino?
- Nunca calcules medias de destino usando el conjunto de datos completo antes de dividir.
- Usa
category_encoders.TargetEncoderdentro de unaPipelinecon CV. - O calcula las codificaciones solo dentro de cada pliegue de CV.
¿Debería normalizar o estandarizar?
- Estandariza (
StandardScaler) cuando las características tienen diferentes unidades y escalas. - Normaliza (
MinMaxScaler) cuando necesitas entradas acotadas [0,1]. - RobustScaler para conjuntos de datos con valores atípicos.
¿Cómo inspecciono la forma de la salida transformada?
Xt = preprocessor.fit_transform(X_train)
print(Xt.shape) # filas x características codificadas totales- One-hot expande columnas: 3 categorías se convierten en 3 columnas binarias.
Relacionado
- Pipelines - encadenamiento de transformaciones y modelos
- Conjuntos de datos y divisiones - ajustar transformaciones solo en entrenamiento
- Aprendizaje supervisado - necesidades de preprocesamiento específicas del modelo
- Datos desequilibrados - remuestreo dentro de pipelines
- Aumento de gradiente - modelos de árboles y categóricas
Versiones de la pila: Esta página fue escrita para Python 3.14.0 (estable 3.14, mantenimiento 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, y uv 0.6+.