Scikit-Learn Pipelines in 2026: Feature Engineering and Interview Questions

Master scikit-learn pipelines with ColumnTransformer for feature engineering. Learn preprocessing best practices, avoid data leakage, and prepare for machine learning interviews with practical code examples.

Scikit-Learn Pipelines feature engineering machine learning Python

Scikit-learn pipelines transform chaotic preprocessing code into reproducible, production-ready workflows. With version 1.9 released in June 2026, Pipeline and ColumnTransformer remain the foundation of any serious machine learning project, now with callback monitoring, enhanced HTML visualization, and Array API support.

Interview Insight

Interviewers often ask: "How do you prevent data leakage during cross-validation?" The answer is pipelines. Fitting preprocessing steps inside the CV loop ensures test data never influences scaling or encoding decisions.

Why Pipelines Eliminate Data Leakage

Data leakage occurs when information from the test set influences preprocessing. A common mistake: fitting StandardScaler on the entire dataset before splitting. The scaler learns mean and variance from test samples, inflating cross-validation scores.

Pipelines fix this by chaining transformers and estimators into a single object that fits all steps together:

python
# pipeline_basics.py
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_val_score

# Create a pipeline that scales features and trains a classifier
pipeline = Pipeline([
    ('scaler', StandardScaler()),  # Step 1: Normalize features
    ('classifier', LogisticRegression(max_iter=1000))  # Step 2: Train model
])

# cross_val_score fits the scaler separately for each fold
# No data leakage: test fold is never seen during scaler.fit()
scores = cross_val_score(pipeline, X, y, cv=5, scoring='accuracy')
print(f"Mean accuracy: {scores.mean():.3f} (+/- {scores.std():.3f})")

The Pipeline object implements fit, predict, and score, making it interchangeable with any scikit-learn estimator. This means GridSearchCV, RandomizedSearchCV, and all cross-validation utilities work without modification.

ColumnTransformer for Mixed Data Types

Real datasets contain numeric columns (age, salary) and categorical columns (country, product_type). ColumnTransformer applies different preprocessing to different column subsets, then concatenates the results:

python
# column_transformer_example.py
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.impute import SimpleImputer
from sklearn.pipeline import Pipeline
import pandas as pd

# Sample dataset with mixed types
df = pd.DataFrame({
    'age': [25, 30, None, 45],
    'salary': [50000, 60000, 75000, None],
    'country': ['US', 'UK', 'DE', 'US'],
    'education': ['Bachelor', 'Master', 'PhD', 'Bachelor']
})

# Define numeric preprocessing: impute missing values, then scale
numeric_transformer = Pipeline([
    ('imputer', SimpleImputer(strategy='median')),
    ('scaler', StandardScaler())
])

# Define categorical preprocessing: impute with mode, then one-hot encode
categorical_transformer = Pipeline([
    ('imputer', SimpleImputer(strategy='most_frequent')),
    ('encoder', OneHotEncoder(handle_unknown='ignore', sparse_output=False))
])

# Combine transformers with ColumnTransformer
preprocessor = ColumnTransformer(
    transformers=[
        ('num', numeric_transformer, ['age', 'salary']),
        ('cat', categorical_transformer, ['country', 'education'])
    ],
    verbose_feature_names_out=True  # Prefix output names with transformer name
)

# Fit and inspect output shape
X_transformed = preprocessor.fit_transform(df)
print(f"Transformed shape: {X_transformed.shape}")
print(f"Feature names: {preprocessor.get_feature_names_out()}")

The verbose_feature_names_out parameter (enhanced in 1.6 to accept strings and callables) controls output feature naming. Setting it to True prefixes each feature with the transformer name, preventing name collisions when multiple transformers generate similar column names.

Automating Column Selection with make_column_selector

Hardcoding column names breaks when datasets change. make_column_selector selects columns by dtype automatically:

python
# automatic_column_selection.py
from sklearn.compose import ColumnTransformer, make_column_selector
from sklearn.preprocessing import StandardScaler, OneHotEncoder

# Select columns by dtype: no hardcoded column names
preprocessor = ColumnTransformer(
    transformers=[
        ('num', StandardScaler(), make_column_selector(dtype_include='number')),
        ('cat', OneHotEncoder(handle_unknown='ignore'),
         make_column_selector(dtype_include='object'))
    ],
    remainder='passthrough'  # Keep unprocessed columns as-is
)

# Works with any DataFrame that has numeric and object columns
X_processed = preprocessor.fit_transform(df)

The remainder parameter controls what happens to columns not matched by any transformer. Options include 'drop' (default), 'passthrough' (keep unchanged), or a transformer to apply to remaining columns.

Ready to ace your Data Science & ML interviews?

Practice with our interactive simulators, flashcards, and technical tests.

Feature Engineering Inside Pipelines

Pipelines extend beyond preprocessing to include feature engineering steps. Custom transformers inherit from BaseEstimator and TransformerMixin:

python
# custom_transformer.py
from sklearn.base import BaseEstimator, TransformerMixin
import numpy as np

class DateFeatureExtractor(BaseEstimator, TransformerMixin):
    """Extract year, month, day, and weekday from datetime columns."""
    
    def __init__(self, date_column='date'):
        self.date_column = date_column
    
    def fit(self, X, y=None):
        # No fitting required for date extraction
        return self
    
    def transform(self, X):
        X = X.copy()
        dates = pd.to_datetime(X[self.date_column])
        
        # Extract temporal features
        X['year'] = dates.dt.year
        X['month'] = dates.dt.month
        X['day'] = dates.dt.day
        X['weekday'] = dates.dt.weekday
        X['is_weekend'] = (dates.dt.weekday >= 5).astype(int)
        
        # Drop original date column
        return X.drop(columns=[self.date_column])
    
    def get_feature_names_out(self, input_features=None):
        # Required for Pipeline.get_feature_names_out() to work
        return ['year', 'month', 'day', 'weekday', 'is_weekend']

# Integrate into a full pipeline
full_pipeline = Pipeline([
    ('date_features', DateFeatureExtractor(date_column='signup_date')),
    ('preprocessor', preprocessor),
    ('model', LogisticRegression())
])

Implementing get_feature_names_out enables the new HTML representation in scikit-learn 1.9 to display output feature names, making debugging and documentation easier.

Hyperparameter Tuning Across Pipeline Steps

GridSearchCV tunes parameters across all pipeline steps using the step__parameter naming convention:

python
# pipeline_gridsearch.py
from sklearn.model_selection import GridSearchCV
from sklearn.ensemble import RandomForestClassifier

# Build pipeline with tunable components
pipeline = Pipeline([
    ('preprocessor', preprocessor),
    ('classifier', RandomForestClassifier(random_state=42))
])

# Define parameter grid: note the double underscore syntax
param_grid = {
    'preprocessor__num__imputer__strategy': ['mean', 'median'],
    'classifier__n_estimators': [100, 200],
    'classifier__max_depth': [10, 20, None]
}

# GridSearchCV handles all combinations automatically
grid_search = GridSearchCV(
    pipeline,
    param_grid,
    cv=5,
    scoring='f1_weighted',
    n_jobs=-1
)

grid_search.fit(X_train, y_train)
print(f"Best params: {grid_search.best_params_}")
print(f"Best score: {grid_search.best_score_:.3f}")

The double underscore (__) accesses nested parameters. For ColumnTransformer, the path includes the transformer name: preprocessor__num__imputer__strategy reaches the imputer's strategy parameter inside the numeric transformer.

Caching Transformers with memory Parameter

Complex preprocessing on large datasets can be slow. The memory parameter caches fitted transformers to disk:

python
# cached_pipeline.py
from sklearn.pipeline import Pipeline
from tempfile import mkdtemp
import joblib

# Create a cache directory
cachedir = mkdtemp()

# Pipeline caches intermediate steps
cached_pipeline = Pipeline(
    [
        ('preprocessor', preprocessor),  # Cached after first fit
        ('classifier', RandomForestClassifier())
    ],
    memory=cachedir  # Or use joblib.Memory for more control
)

# First fit: slow (computes all transformations)
cached_pipeline.fit(X_train, y_train)

# Second fit with same X_train: fast (loads cached transformers)
cached_pipeline.fit(X_train, y_train)  # Skips preprocessor fitting

Caching is particularly useful during hyperparameter search, where the same preprocessing applies to multiple model configurations. The final estimator (classifier or regressor) is never cached, only intermediate transformers.

Monitoring Training with Callbacks (scikit-learn 1.9)

The callback API in version 1.9 adds progress monitoring to pipelines:

python
# callback_monitoring.py
from sklearn.callback import ProgressBar, ScoringMonitor
from sklearn.linear_model import LogisticRegression

# Display progress during GridSearchCV
with ProgressBar():
    grid_search = GridSearchCV(
        pipeline,
        param_grid,
        cv=5,
        n_jobs=1  # Progress bars require single-threaded execution
    )
    grid_search.fit(X_train, y_train)

# Monitor scoring metrics per iteration
with ScoringMonitor(X_val, y_val, scoring='accuracy') as monitor:
    logreg = LogisticRegression(solver='lbfgs', max_iter=500)
    logreg.fit(X_train, y_train)
    print(f"Scores per iteration: {monitor.scores_}")

Callbacks work with Pipeline, StandardScaler, LogisticRegression (LBFGS solver), and all search CV classes. This experimental feature provides visibility into long-running fits without custom logging code.

FeatureUnion for Parallel Feature Extraction

FeatureUnion runs multiple transformers in parallel and concatenates their output horizontally:

python
# feature_union_example.py
from sklearn.pipeline import FeatureUnion, Pipeline
from sklearn.decomposition import PCA
from sklearn.feature_selection import SelectKBest, f_classif

# Combine PCA and univariate selection
feature_union = FeatureUnion([
    ('pca', PCA(n_components=5)),
    ('select', SelectKBest(f_classif, k=10))
])

# Pipeline: preprocess, combine features, classify
pipeline = Pipeline([
    ('preprocessor', preprocessor),
    ('features', feature_union),
    ('classifier', LogisticRegression())
])

# Output contains 5 PCA components + 10 selected features = 15 features
pipeline.fit(X_train, y_train)

Version 1.7.2 added validation ensuring all transformers return 2D outputs. This catches errors early when a custom transformer accidentally returns a 1D array.

Serialization and Deployment

Pipelines serialize with joblib, preserving all fitted parameters:

python
# model_deployment.py
import joblib

# Save the entire pipeline (preprocessing + model)
joblib.dump(pipeline, 'churn_predictor.pkl')

# Load in production
loaded_pipeline = joblib.load('churn_predictor.pkl')

# Predict on new data (same preprocessing automatically applied)
new_data = pd.DataFrame({'age': [32], 'salary': [65000], ...})
predictions = loaded_pipeline.predict(new_data)

The serialized file contains everything: column selector logic, fitted scaler means and variances, encoder categories, and model weights. Deployment requires only scikit-learn and joblib, no custom preprocessing code.

Version Compatibility

Pipelines serialized with scikit-learn 1.8 may not load correctly on 1.9 if they use deprecated parameters. Always pin your scikit-learn version in production and test upgrades before deploying.

Interview Questions on Scikit-Learn Pipelines

Q: What is data leakage and how do pipelines prevent it?

Data leakage occurs when information from the validation or test set influences model training. The classic example: fitting a StandardScaler on the entire dataset before splitting. The scaler's mean and standard deviation include test samples, making cross-validation scores artificially high.

Pipelines prevent this by encapsulating preprocessing inside the cross-validation loop. When cross_val_score calls pipeline.fit(), each fold fits the scaler only on training data. Test fold statistics never leak into the preprocessing.

Q: How do you access and modify parameters of nested pipeline steps?

Use the double underscore syntax: pipeline.set_params(classifier__n_estimators=200). For ColumnTransformer, include the transformer name: pipeline.set_params(preprocessor__num__scaler__with_mean=False). The get_params() method returns all nested parameters as a flat dictionary, useful for inspection and logging.

Q: When should you use FeatureUnion vs ColumnTransformer?

ColumnTransformer applies different transformers to different columns of the same input. FeatureUnion applies different transformers to the entire input and concatenates horizontally. Use ColumnTransformer for heterogeneous column preprocessing (numeric vs categorical). Use FeatureUnion for feature augmentation, where multiple extraction methods (PCA, polynomial features, domain-specific extractors) produce complementary features from the same data.

Q: How do you debug a failing pipeline?

Set verbose=True on the pipeline to see step timings. Access intermediate results with pipeline.named_steps['step_name'].transform(X) for a fitted pipeline. In scikit-learn 1.9, the HTML representation displays fitted attributes and output feature names, simplifying debugging in Jupyter notebooks. For deeper inspection, use pipeline[:-1].fit_transform(X, y) to get the output just before the final estimator.

Production Checklist for ML Pipelines

  • Pin scikit-learn version in requirements to avoid serialization issues
  • Use make_column_selector instead of hardcoded column names when datasets may change
  • Set handle_unknown='ignore' on OneHotEncoder to handle new categories in production
  • Cache expensive preprocessing with the memory parameter during development
  • Validate pipeline output shape and feature names in tests before deployment
  • Store get_feature_names_out() alongside the model for feature importance analysis
  • Use Pipeline inside GridSearchCV, never the reverse, to ensure correct CV behavior

Start practicing!

Test your knowledge with our interview simulators and technical tests.

Daily challenge

Can you spot the bug in Data Science & ML?

One real snippet, one hidden bug, one attempt a day. No account needed to try.

Anthony Fillion-Maillet

Written by

Anthony Fillion-Maillet

Founder of SharpSkill

Full-stack developer for over 10 years. Runs SharpSkill and answers for everything published here.

Updated on September 2, 2026

Tags

#scikit-learn
#pipeline
#feature-engineering
#machine-learning
#python
#interview

Share

Related articles