
Scikit-Learn (Parte 2)
Flujo completo de un Pipeline: cómo automatizar tu ML como un profesional
En la Parte 1 vimos los tres pilares de Scikit-Learn:
- Estimators
- Transformers
- Predictors
Funcionan, sí. Pero escribirlos a mano es frágil, repetitivo y propenso a errores. Y en cuanto tu pipeline crece a 5, 8 o 12 pasos… se vuelve directamente inmanejable.

Por eso Scikit-Learn tiene una de las ideas más elegantes de toda su API:
El Pipeline.
Un objeto que:
- encadena pasos
- evita leakage
- mantiene la consistencia entre train y test
- facilita la validación
- permite automatizar la búsqueda de hiperparámetros
- y te deja un sistema listo para producción
Hoy vas a aprender a construir uno de verdad. No el típico ejemplo de dos líneas que se ve en cualquier tutorial.
Vamos paso a paso.
¿Qué problema resuelve un Pipeline?
Imagina este flujo manual:
scaler = StandardScaler()
pca = PCA(n_components=5)
model = LogisticRegression()
scaler.fit(X_train)
X_train_scaled = scaler.transform(X_train)
X_test_scaled = scaler.transform(X_test)
pca.fit(X_train_scaled)
X_train_pca = pca.transform(X_train_scaled)
X_test_pca = pca.transform(X_test_scaled)
model.fit(X_train_pca, y_train)
y_pred = model.predict(X_test_pca)
Funciona, sí. Pero tiene problemas serios:
- Si cambias un paso, tienes que tocar 6 líneas.
- Si añades un paso, tienes que acordarte de aplicarlo en train y en test.
- Si quieres cross-validation, tienes que reescribir medio script.
- Si quieres GridSearch, tienes que anidar funciones a mano.
- Y un solo
.fit_transform()mal puesto → data leakage silencioso.
Un Pipeline elimina todo eso de un plumazo.
¿Qué es un Pipeline?
Un Pipeline es un Estimator compuesto.
Es decir:
- tiene
.fit() - tiene
.predict() - y por dentro ejecuta todos los pasos en el orden correcto
from sklearn.pipeline import Pipeline
pipeline = Pipeline([
('scaler', StandardScaler()),
('pca', PCA(n_components=5)),
('model', LogisticRegression())
])
Ahora todo tu flujo es un único objeto:
pipeline.fit(X_train, y_train)
y_pred = pipeline.predict(X_test)
Y Scikit-Learn se encarga de:
- aplicar
.fit()solo donde corresponde - aplicar
.transform()en cada paso intermedio - pasar los datos de un paso al siguiente
- mantener el orden
- evitar leakage
Tú no escribes nada de eso. Lo gestiona la librería.
La regla de oro del Pipeline
En un Pipeline:
- Todos los pasos menos el último deben ser Transformers.
- El último paso debe ser un Predictor.
Ejemplo válido:
Scaler → PCA → LogisticRegression
Ejemplo inválido:
Scaler → LogisticRegression → PCA
¿Por qué? Porque un Predictor no tiene.transform(), así que no puede pasar datos al siguiente paso. El último eslabón siempre es el que decide.
Cómo funciona internamente
Cuando llamas a:
pipeline.fit(X_train, y_train)
Scikit-Learn ejecuta por dentro:
scaler.fit(X_train)X1 = scaler.transform(X_train)pca.fit(X1)X2 = pca.transform(X1)model.fit(X2, y_train)
Y cuando llamas a:
pipeline.predict(X_test)
Hace:
X1 = scaler.transform(X_test)X2 = pca.transform(X1)model.predict(X2)
Sin que tú escribas una sola línea extra. Y sin posibilidad de equivocarte de orden.
ColumnTransformer: el arma secreta
En proyectos reales, no todas las columnas se procesan igual:
- unas necesitan escalado
- otras one-hot encoding
- otras imputación de nulos
- y otras se quedan tal cual
Para eso existeColumnTransformer.
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
preprocess = ColumnTransformer([
('num', StandardScaler(), ['age', 'salary']),
('cat', OneHotEncoder(), ['city', 'gender'])
])
Y lo metes dentro del Pipeline como un paso más:
pipeline = Pipeline([
('prep', preprocess),
('model', LogisticRegression())
])
Ahora tu pipeline, en un único objeto:
- escala las numéricas
- codifica las categóricas
- entrena el modelo
- y se queda listo para predecir sobre datos nuevos con el mismo tratamiento
Esto es lo que separa un notebook de juguete de un pipeline real.
Cross-validation con Pipelines (la forma correcta)
Antes, cuando hacías:
from sklearn.model_selection import cross_val_score
cross_val_score(model, X, y, cv=5)
estabas validando el modelo, pero no el preprocesamiento. Y si escalaste antes confit_transformsobre todoX, ya hubo leakage en el momento que lo hiciste.
Con un Pipeline el problema desaparece:
cross_val_score(pipeline, X, y, cv=5)
Ahora, en cada fold, Scikit-Learn:
- reentrena el scaler solo con el train del fold
- reentrena el PCA solo con el train del fold
- reentrena el modelo solo con el train del fold
- evalúa sobre el validation del fold
Validación honesta. Sin leakage. Sin dolor.
GridSearchCV con Pipelines
Aquí es donde los Pipelines brillan de verdad.
from sklearn.model_selection import GridSearchCV
params = {
'prep__num__with_mean': [True, False],
'model__C': [0.1, 1, 10]
}
grid = GridSearchCV(pipeline, params, cv=5)
grid.fit(X, y)
La sintaxisprep__num__with_meanse lee así:
pipeline → paso 'prep' → bloque 'num' → parámetro 'with_mean'
Es decir: dobles guiones bajos (__) para bajar de nivel. Con esto puedes tunear cualquier parámetro de cualquier paso, incluido el preprocesamiento, todo en una sola llamada.
Y lo mejor: cada combinación se evalúa con cross-validation honesto, porque el reentrenamiento ocurre dentro de cada fold.
Pipeline listo para producción
Una vez entrenado, lo guardas:
import joblib
joblib.dump(pipeline, 'model.pkl')
Y en producción solo necesitas:
pipeline = joblib.load('model.pkl')
pipeline.predict(new_data)
Sin preocuparte por:
- escalado
- encoding
- orden de pasos
- consistencia entre train y producción
Todo está dentro del Pipeline. Esa es justamente la idea: lo que despliegas no es un modelo, es el flujo completo.
Errores comunes (y cómo evitarlos)
❌ Error 1: Hacer.fit()fuera del Pipeline
scaler.fit(X_train)
En el momento que haces esto por tu cuenta, ya rompiste la idea del Pipeline. Deja que el objeto se encargue.
❌ Error 2: Mezclar.fit_transform()con Pipelines
Nunca mezcles flujos manuales con Pipelines. O todo dentro, o todo fuera. Lo demás es pedir leakage a gritos.
❌ Error 3: Procesar columnas a mano en lugar de usarColumnTransformer
Si separas numéricas y categóricas con slicing manual, pierdes modularidad y reproducibilidad. Y un día lo pagarás caro.
❌ Error 4: Hacer GridSearch sin Pipeline
Si el preprocesamiento queda fuera del objeto que validas, casi seguro que hay leakage. La regla es simple: todo lo que se aprende de los datos, dentro del Pipeline.
Ejemplo final: un Pipeline completo y real
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.pipeline import Pipeline
from sklearn.linear_model import LogisticRegression
preprocess = ColumnTransformer([
('num', StandardScaler(), ['age', 'salary']),
('cat', OneHotEncoder(handle_unknown='ignore'), ['city', 'gender'])
])
pipeline = Pipeline([
('prep', preprocess),
('model', LogisticRegression())
])
pipeline.fit(X_train, y_train)
y_pred = pipeline.predict(X_test)
Esto ya es nivel producción. Modular, validable, serializable y sin leakage. Justamente lo que veníamos buscando desde la Parte 1.
TL;DR
- Un Pipeline encadena pasos y evita leakage automáticamente.
- Todos los pasos menos el último deben ser Transformers; el último, un Predictor.
ColumnTransformerte permite aplicar transformaciones distintas a columnas distintas.- Cross-validation y GridSearch funcionan correctamente solo cuando el preprocesamiento vive dentro del Pipeline.
- Un Pipeline es el objeto que se guarda con
jobliby se despliega en producción. - Si entiendes bien el Pipeline, dejas de tener «scripts de ML» y empiezas a tener sistemas de ML.



