Think of a scikit-learn Pipeline as a “recipe” for your machine learning workflow: it lists the steps (cleaning, transforming, modeling) in order and runs them automatically every time you train or predict. This keeps your code tidy, prevents mistakes, and makes your models easier to reuse.
What Problem Do Pipelines Solve?
Without pipelines, you might:
- Impute missing values, then scale, then train a model—using separate commands.
- Accidentally fit preprocessing on the whole dataset (including test data), which leaks information.
- Repeat the same steps for training, validation, and deployment.
Pipelines fix this by:
- Bundling all steps into one object.
- Ensuring preprocessing is fit only on training data during cross-validation.
- Letting you call
fitandpredictonce on the whole workflow.
The Basic Idea: Steps in Order
A pipeline is just an ordered list of named steps:
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
pipe = Pipeline([
("scaler", StandardScaler()),
("model", LogisticRegression())
])
pipe.fit(X_train, y_train)
y_pred = pipe.predict(X_test)
What happens inside:
- On
fit:StandardScalerlearns mean and std fromX_train, transformsX_train, thenLogisticRegressionfits on the scaled data. - On
predict:StandardScalertransformsX_testusing the training statistics, thenLogisticRegressionpredicts.
You never manually scale test data; the pipeline does it consistently.
A Simpler Constructor: make_pipeline
If you don’t care about step names, use make_pipeline:
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
pipe = make_pipeline(StandardScaler(), LogisticRegression())
scikit-learn names the steps automatically (standardscaler, logisticregression).
Handling Different Column Types: ColumnTransformer
Real data has numeric and categorical columns that need different treatment. ColumnTransformer lets you apply different preprocessing to different columns, then combines them.
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import OneHotEncoder, StandardScaler
from sklearn.impute import SimpleImputer
from sklearn.pipeline import Pipeline
numeric_features = ["age", "income"]
categorical_features = ["city", "subscription"]
numeric_pipe = Pipeline([
("imputer", SimpleImputer(strategy="median")),
("scaler", StandardScaler())
])
categorical_pipe = Pipeline([
("imputer", SimpleImputer(strategy="most_frequent")),
("onehot", OneHotEncoder(handle_unknown="ignore"))
])
preprocess = ColumnTransformer([
("num", numeric_pipe, numeric_features),
("cat", categorical_pipe, categorical_features)
])
model = Pipeline([
("preprocess", preprocess),
("clf", LogisticRegression())
])
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
Key idea: define “what to do” for each group of columns once, then reuse.
Why This Prevents Data Leakage
Data leakage happens when information from the test set influences training. Common mistake:
# Wrong: scaling before split or on full X
scaler = StandardScaler().fit(X)
X_scaled = scaler.transform(X)
Correct pattern with pipelines:
model = Pipeline([
("scaler", StandardScaler()),
("clf", LogisticRegression())
])
model.fit(X_train, y_train) # scaler learns only from X_train
y_pred = model.predict(X_test) # scaler uses training stats on X_test
During cross-validation, scikit-learn refits the entire pipeline on each training fold, so preprocessing never sees the validation fold.
Tuning Hyperparameters Across Steps
Pipelines let you tune parameters from any step using stepname__param syntax.
from sklearn.model_selection import GridSearchCV
param_grid = {
"scaler__with_mean": [True, False],
"clf__C": [0.1, 1, 10]
}
grid = GridSearchCV(model, param_grid, cv=5)
grid.fit(X_train, y_train)
This searches over both scaling options and model regularization jointly.
Custom Steps When You Need Them
Sometimes you need your own transformation. You can:
- Wrap a simple function with
FunctionTransformer. - Or write a small class that follows scikit-learn’s transformer interface.
Simple function example:
from sklearn.preprocessing import FunctionTransformer
import numpy as np
pipe = Pipeline([
("log", FunctionTransformer(np.log1p, validate=False)),
("clf", LogisticRegression())
])
For more complex logic, define a class with fit and transform methods and use it like any other step.
Saving and Reusing Your Model
Once your pipeline is trained, save the whole thing:
import joblib
joblib.dump(model, "model_pipeline.joblib")
# Later, in another script or service:
loaded = joblib.load("model_pipeline.joblib")
y_pred = loaded.predict(X_new)
You don’t need to re-implement preprocessing; it’s all inside the saved pipeline.
Minimal Complete Example
from sklearn.datasets import fetch_openml
from sklearn.model_selection import train_test_split
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import OneHotEncoder, StandardScaler
from sklearn.impute import SimpleImputer
from sklearn.pipeline import Pipeline
from sklearn.ensemble import RandomForestClassifier
import pandas as pd
# Load a sample dataset
data = fetch_openml("adult", version=2, as_frame=True)
X = data.data
y = (data.target == ">50K").astype(int)
num_cols = X.select_dtypes(include=["int64", "float64"]).columns
cat_cols = X.select_dtypes(include=["object", "category"]).columns
num_pipe = Pipeline([
("imputer", SimpleImputer(strategy="median")),
("scaler", StandardScaler())
])
cat_pipe = Pipeline([
("imputer", SimpleImputer(strategy="most_frequent")),
("onehot", OneHotEncoder(handle_unknown="ignore"))
])
preprocess = ColumnTransformer([
("num", num_pipe, num_cols),
("cat", cat_pipe, cat_cols)
])
model = Pipeline([
("preprocess", preprocess),
("clf", RandomForestClassifier(n_estimators=200, random_state=42))
])
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
model.fit(X_train, y_train)
print("Test accuracy:", model.score(X_test, y_test))
This single model object handles imputation, encoding, scaling, and prediction.
Quick Checklist for Using Pipelines
- Put all preprocessing inside the pipeline, not outside.
- Fit only on
X_train,y_train; use the pipeline to predict on new data. - Use
ColumnTransformerwhen you have mixed column types. - Use
make_pipelinefor quick experiments; usePipelinewhen you want clear step names. - Save the entire pipeline for deployment.
Top comments (0)