Pipelines & ColumnTransformer
Definition
A
Pipelinechains preprocessing steps and a final estimator into a single object with one.fit()/.predict()call β preventing data leakage and making cross-validation/deployment trivial.ColumnTransformerapplies different preprocessing to different columns (e.g. scale numeric, one-hot encode categorical) within that same pipeline.
Basic Pipeline
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) # fits scaler, then model, in sequence
pipe.predict(X_test) # applies scaler.transform, then model.predict
pipe.score(X_test, y_test)Why use a Pipeline instead of manual steps
A Pipeline guarantees the exact same preprocessing is applied consistently at train and predict time, and β critically β it lets
cross_val_score/GridSearchCVrefit the scaler correctly within each fold, avoiding data leakage. See 17-Common-Errors-Gotchas.
make_pipeline() β Shorthand (Auto-Named Steps)
from sklearn.pipeline import make_pipeline
pipe = make_pipeline(StandardScaler(), LogisticRegression())
# step names auto-generated: 'standardscaler', 'logisticregression'ColumnTransformer β Different Preprocessing per Column Type
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.impute import SimpleImputer
from sklearn.pipeline import Pipeline
numeric_features = ["age", "income"]
categorical_features = ["city", "gender"]
numeric_transformer = Pipeline([
("imputer", SimpleImputer(strategy="median")),
("scaler", StandardScaler())
])
categorical_transformer = Pipeline([
("imputer", SimpleImputer(strategy="most_frequent")),
("onehot", OneHotEncoder(handle_unknown="ignore"))
])
preprocessor = ColumnTransformer([
("num", numeric_transformer, numeric_features),
("cat", categorical_transformer, categorical_features)
])
full_pipe = Pipeline([
("preprocessor", preprocessor),
("model", LogisticRegression())
])
full_pipe.fit(X_train, y_train)graph TD A[Raw DataFrame] --> B[ColumnTransformer] B --> C["Numeric cols Impute -> Scale"] B --> D["Categorical cols Impute -> OneHot"] C --> E[Combined feature matrix] D --> E E --> F[Model]
Accessing Pipeline Steps
pipe.named_steps["scaler"] # access a specific step by name
pipe["scaler"] # shorthand equivalent
pipe.named_steps["model"].coef_ # reach into the fitted model's learned attributes
pipe[:-1].transform(X_test) # apply all steps EXCEPT the final estimator (useful for inspection)get_feature_names_out() β Track Column Names Through the Pipeline
preprocessor.get_feature_names_out()
# array(['num__age', 'num__income', 'cat__city_Mumbai', 'cat__city_Pune', ...])set_output(transform="pandas") β Keep DataFrames Throughout (sklearn β₯ 1.2)
preprocessor.set_output(transform="pandas")
X_transformed = preprocessor.fit_transform(X_train) # returns a DataFrame, not a NumPy arrayFeature Union (Combine Multiple Transformers on the SAME Columns)
from sklearn.pipeline import FeatureUnion
from sklearn.decomposition import PCA
from sklearn.feature_selection import SelectKBest
combined = FeatureUnion([
("pca", PCA(n_components=2)),
("select_best", SelectKBest(k=3))
])
ColumnTransformervsFeatureUnion
ColumnTransformerapplies different transformers to different columns;FeatureUnionapplies different transformers to the same data and concatenates their outputs side by side.
Using a Pipeline with GridSearchCV
from sklearn.model_selection import GridSearchCV
param_grid = {
"model__C": [0.1, 1, 10], # note the "stepname__paramname" syntax
"preprocessor__num__imputer__strategy": ["mean", "median"]
}
grid = GridSearchCV(full_pipe, param_grid, cv=5, scoring="accuracy")
grid.fit(X_train, y_train)See 12-Hyperparameter-Tuning for the full GridSearchCV/RandomizedSearchCV reference.