Un Pipeline de scikit-learn conecta el preprocesamiento y un estimador como un solo objeto. La regla directa para evitar data leakage es: divide las observaciones, coloca toda transformación que aprende parámetros dentro del pipeline y pasa el pipeline completo a la validación cruzada. Nunca ajustes imputadores, escaladores, selectores o codificadores en el conjunto completo antes de validar.

Esta estructura mantiene coherentes entrenamiento e inferencia y facilita la búsqueda de hiperparámetros. Complementa las guías de pandas y machine learning con Python.

El problema que resuelve Pipeline

Supón una tabla de clientes con edad, ingresos, ciudad, canal y el objetivo abandono. Las columnas numéricas requieren imputación y escala; las categóricas, imputación y codificación one-hot. Si ajustas estas operaciones antes de separar, medianas, medias y vocabularios incorporan información de prueba. La métrica ya no representa observaciones realmente desconocidas.

Un pipeline coordina fit, transform y predict. En fit, los transformadores aprenden solo de los registros entregados. En predict, reutilizan sus parámetros sin recalcularlos. Esa separación sostiene la prevención de leakage.

Reserva primero la prueba final

import pandas as pd
from sklearn.model_selection import train_test_split

datos = pd.read_parquet('clientes.parquet') X = datos.drop(columns='abandono') y = datos['abandono']

X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42, stratify=y, )

stratify=y conserva aproximadamente las clases. Para filas del mismo cliente o dispositivo, divide por grupos; para predecir el futuro, divide cronológicamente. Mantén intacta la prueba hasta elegir el pipeline.

Procesa columnas heterogéneas

ColumnTransformer aplica flujos diferentes a subconjuntos de columnas y concatena sus salidas. Las listas explícitas convierten el esquema de entrada en un contrato revisable:

from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder, StandardScaler

numericas = ['edad', 'ingreso_mensual', 'meses_activo'] categoricas = ['ciudad', 'canal']

pasos_numericos = Pipeline([ ('imputar', SimpleImputer(strategy='median')), ('escalar', StandardScaler()), ])

pasos_categoricos = Pipeline([ ('imputar', SimpleImputer(strategy='most_frequent')), ('codificar', OneHotEncoder(handle_unknown='ignore')), ])

preprocesar = ColumnTransformer([ ('num', pasos_numericos, numericas), ('cat', pasos_categoricos, categoricas), ])

handle_unknown='ignore' permite inferencia cuando una categoría no apareció en entrenamiento y la representa con ceros en el bloque correspondiente. Evita un error, pero no sustituye el monitoreo de cambios. La documentación oficial de ColumnTransformer con datos heterogéneos muestra más selectores.

Conecta el estimador

from sklearn.linear_model import LogisticRegression

modelo = Pipeline([ ('preprocesar', preprocesar), ('clasificar', LogisticRegression( max_iter=1000, class_weight='balanced', )), ])

modelo.fit(X_train, y_train) probabilidades = modelo.predict_proba(X_test)[:, 1]

El mismo objeto recibe las columnas originales durante entrenamiento e inferencia. No ejecutes antes preprocesar.fit_transform(X). Las llamadas separadas a pd.get_dummies también pueden producir columnas distintas en entrenamiento y producción. La guía oficial de Pipeline documenta el encadenamiento y los parámetros anidados.

Validación cruzada correcta

La validación cruzada estima la variación del rendimiento en varias divisiones. Pasa el pipeline completo a cross_validate. Para cada fold, scikit-learn clona el objeto y ajusta imputación, codificación, escala y clasificador únicamente en la partición de entrenamiento:

from sklearn.model_selection import StratifiedKFold, cross_validate

cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) resultados = cross_validate( modelo, X_train, y_train, cv=cv, scoring={'roc_auc': 'roc_auc', 'f1': 'f1'}, return_train_score=True, n_jobs=-1, )

print(resultados['test_roc_auc'].mean()) print(resultados['test_roc_auc'].std())

Informa centro y dispersión, no solo el mejor fold. Compara entrenamiento y validación para investigar sobreajuste. Elige métricas según los costes: ROC AUC no elige un umbral y la exactitud puede ocultar fallos en una clase rara. Usa GroupKFold o StratifiedGroupKFold para entidades agrupadas y considera TimeSeriesSplit para observaciones ordenadas.

Ajusta hiperparámetros sin contaminación

Los nombres anidados usan dos guiones bajos. La búsqueda recibe el pipeline, no una matriz transformada de antemano:

from sklearn.model_selection import GridSearchCV

parametros = { 'preprocesarnumimputarstrategy': ['mean', 'median'], 'clasificarC': [0.1, 1.0, 10.0], }

busqueda = GridSearchCV( modelo, param_grid=parametros, scoring='roc_auc', cv=cv, n_jobs=-1, refit=True, ) busqueda.fit(X_train, y_train)

mejor_modelo = busqueda.bestestimator

Con refit=True, la mejor configuración se vuelve a ajustar en todo el entrenamiento. Evalúala una vez en la prueba reservada. Consultar repetidamente esa prueba para escoger variables, umbral o parámetros la convierte en validación y crea una estimación optimista.

Evaluación final y persistencia

from sklearn.metrics import classification_report, roc_auc_score
import joblib

proba = mejor_modelo.predict_proba(X_test)[:, 1] pred = mejor_modelo.predict(X_test)

print(roc_auc_score(y_test, proba)) print(classification_report(y_test, pred, zero_division=0)) joblib.dump(mejor_modelo, 'pipeline_abandono.joblib')

Guarda el pipeline completo porque contiene transformaciones y estimador. Carga artefactos solo desde fuentes fiables: los formatos basados en pickle pueden ejecutar código. Registra versiones, tipos de variables, objetivo y periodo. Aplica prácticas sostenibles de Python y usa pytest para cubrir nulos, categorías inéditas y solicitudes inválidas.

Leakage que Pipeline no corrige por sí solo

  • Variables posteriores: la fecha de baja revela el abandono aunque pase por el pipeline.
  • Agregaciones globales: promedios calculados con eventos futuros contaminan filas históricas.
  • Entidades duplicadas: la misma persona en entrenamiento y validación expone patrones individuales.
  • Selección manual en prueba: cambiar decisiones tras cada métrica final sobreajusta la prueba.
  • División temporal aleatoria: aprender del futuro para predecir el pasado genera una evaluación irreal.

El capítulo oficial de errores comunes y prácticas recomendadas detalla el preprocesamiento inconsistente y el leakage. La prevención correcta exige saber cuándo estará disponible cada variable en el proceso real.

Errores comunes

  • Ajustar StandardScaler, un imputador o selector antes de dividir.
  • Pasar solo el clasificador, no el pipeline completo, a validación cruzada.
  • Incluir entre los predictores una variable derivada del objetivo o posterior al evento.
  • Usar K-fold aleatorio para grupos relacionados o registros cronológicos.
  • Ajustar y presentar como rendimiento final el mejor resultado de la misma validación.
  • Guardar solo el estimador y reconstruir el preprocesamiento en la aplicación.

Checklist antes de entrenar

  1. Define el objetivo, el instante de predicción y la información disponible entonces.
  2. Reserva la prueba con una estrategia adecuada para clases, grupos y tiempo.
  3. Coloca cada transformación aprendida dentro de Pipeline.
  4. Usa ColumnTransformer para contratos numéricos y categóricos explícitos.
  5. Valida el pipeline completo con métricas relevantes para la decisión.
  6. Ajusta con entrenamiento y evalúa la prueba final una sola vez.
  7. Guarda el pipeline completo y registra dependencias y esquema de entrada.
  8. Monitorea categorías, distribuciones, calidad y rendimiento tras desplegar.

Las variables originales entran en un pipeline y las predicciones salen de él. Los transformadores aprenden en cada fold sin consultar la prueba final, y producción usa el mismo objeto. El razonamiento temporal y causal sigue siendo indispensable.