Build a Full scikit-learn Pipeline

Learn to build a full scikit-learn pipeline end-to-end: preprocessing, feature engineering, modeling, and evaluation. A hands-on, step-by-step Applied AI engineering tutorial.

Focus: build a full scikit-learn pipeline

Sponsored

You've trained models before — a RandomForest here, a regression there — but every time you ship a new dataset or tweak a feature, you end up with duplicated preprocessing code, mismatched transformations, and that sinking feeling when train and test sets drift apart. It's the classic machine learning mess: your notebook works, your pipeline is a house of cards. In this lesson, you'll learn to build a full scikit-learn pipeline — one clean, reproducible object that chains preprocessing, feature selection, and model training into a single call to fit() and predict(). This is the skill that separates hobbyist notebooks from production-ready ML systems, and it's exactly what you need as an applied AI engineer.

The problem this lesson solves

Raw data almost never arrives ready for a model. You have missing values, categorical columns that need encoding, numeric features that live on wildly different scales, and outliers that throw off your algorithm. The beginner approach is to handle each of these in a separate cell or function, then fit your model on the preprocessed array. But that approach breaks down fast:

  • Train/test data leakage: If you fit your scaler on the entire dataset before splitting, you're leaking information from the test set into the training process — your validation scores will lie to you.
  • Inconsistency: You might remember to scale the training data but forget to transform new data before making predictions. Or you swap the order of operations and silently break everything.
  • Reproducibility: A colleague (or future you) tries to run your code, but can't replicate your exact preprocessing steps because they're scattered across notebook cells.

A full pipeline solves all of this head-on. It's a single object that encapsulates every transformation and the final estimator, ensuring the exact same sequence of steps runs on training data, validation data, and any future data point. No more copy-paste, no more forgetfulness, no more subtle bugs.

Core concept / mental model

Think of a pipeline as an assembly line in a factory. Raw materials (your raw DataFrame) enter at one end. Each station (a transformer) performs a specific, reversible-by-design operation: cleaning, scaling, encoding, selecting. At the final station, the model assembles the pieces into a prediction. Crucially, the assembly line is a single, cohesive unit — you either use the whole thing, or you don't.

Here's the key mental model: A pipeline is itself an estimator. It has fit(), predict(), and score() methods, just like a model. When you call pipeline.fit(X_train, y_train), it:

  1. Fits the first transformer on X_train and transforms it.
  2. Feeds the output to the next step, which fits and transforms again.
  3. Continues through every step in order.
  4. Fits the final estimator on the transformed data.

When you later call pipeline.predict(X_new), it applies each transformation using the already-learned parameters (like the mean and scale factor) without refitting. No leakage, no forgotten steps — the assembly line knows exactly what to do.

Pro tip: A pipeline is the perfect vehicle for cross-validation. Because each fold gets its own fit call, you avoid data leakage automatically — a huge win for reliable evaluation.

How it works step by step

Building a full pipeline is about composing building blocks. Let's break down the process:

  1. Collect your data — a feature matrix X and target vector y. For this lesson we'll use a classic tabular dataset so you can see every step clearly.

  2. Design your preprocessing steps — identify which columns are numeric vs. categorical, whether there are missing values, and whether scaling is needed. You'll use ColumnTransformer to apply different transformations to different column groups.

  3. Add feature engineering — optionally create new features (e.g., polynomial features or custom transformations) as part of the pipeline so they are consistent across train and test.

  4. Choose a feature selection step — use variance thresholding or a model-based selector to drop uninformative columns, preventing overfitting and speeding up training.

  5. Select your final estimator — the model that will make predictions, like RandomForestClassifier or LogisticRegression.

  6. Chain everything with Pipeline — define an ordered list of (name, transformer) tuples, ending with the estimator.

  7. Fit, evaluate, and iterate — use the pipeline directly with train_test_split and cross_val_score, or grid-search hyperparameters across the entire chain.

The beauty is that you can now tune hyperparameters of any step (e.g., the scaler, the selector, the model) through a single GridSearchCV call, because the pipeline exposes each step's parameters via stepname__param.

Hands-on walkthrough

Let's put it all together. We'll build a full pipeline for the built-in Breast Cancer dataset, which has numeric features and a binary target — perfect for demonstrating preprocessing and modeling.

Step 1: Import and load

from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split

X, y = load_breast_cancer(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42, stratify=y
)
print(f"Training samples: {X_train.shape[0]}, Test samples: {X_test.shape[0]}")

Expected output:

Training samples: 455, Test samples: 114

Step 2: A minimal pipeline with scaling + logistic regression

from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression

# Create a pipeline: scale features, then train logistic regression
pipe = Pipeline([
    ("scaler", StandardScaler()),
    ("clf", LogisticRegression(max_iter=1000))
])

# Fit and evaluate
pipe.fit(X_train, y_train)
accuracy = pipe.score(X_test, y_test)
print(f"Pipeline test accuracy: {accuracy:.3f}")

Expected output:

Pipeline test accuracy: 0.974

Step 3: Full pipeline with ColumnTransformer and feature engineering

To demonstrate a more realistic setup, let's create a DataFrame with mixed numeric and categorical columns, then build a pipeline that handles them properly.

import pandas as pd
import numpy as np
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.feature_selection import VarianceThreshold
from sklearn.ensemble import RandomForestClassifier

# Create synthetic mixed data
df = pd.DataFrame({
    "age": [25, 34, 45, 52, 61],
    "salary": [50000, 60000, 70000, 80000, 90000],
    "department": ["sales", "engineering", "marketing", "sales", "engineering"],
    "churn": [0, 1, 0, 1, 0]
})
X = df.drop("churn", axis=1)
y = df["churn"]

# Preprocessing: scale numeric, one-hot encode categorical
preprocessor = ColumnTransformer([
    ("num", StandardScaler(), ["age", "salary"]),
    ("cat", OneHotEncoder(handle_unknown="ignore"), ["department"])
])

# Build the full pipeline: preprocessing -> feature selection -> classifier
full_pipe = Pipeline([
    ("preprocess", preprocessor),
    ("variance", VarianceThreshold(threshold=0.1)),
    ("clf", RandomForestClassifier(random_state=42))
])

# Fit on training data and score
full_pipe.fit(X, y)
# Note: using X/y directly for demo; in practice, use train_test_split.
print(f"Pipeline steps: {list(full_pipe.named_steps.keys())}")

Expected output:

Pipeline steps: ['preprocess', 'variance', 'clf']

Step 4: Grid search hyperparameters across the entire pipeline

Here's where pipelines shine: you can search over parameters of the whole chain. The parameter name uses the step name, then two underscores, then the actual parameter.

from sklearn.model_selection import GridSearchCV

param_grid = {
    "preprocess__num__with_mean": [True, False],  # scaler parameter
    "variance__threshold": [0.1, 0.2],
    "clf__n_estimators": [50, 100],
    "clf__max_depth": [None, 10]
}

grid = GridSearchCV(full_pipe, param_grid, cv=3, scoring="accuracy")
grid.fit(X, y)
print(f"Best parameters: {grid.best_params_}")
print(f"Best cross-val accuracy: {grid.best_score_:.3f}")

Expected output: (values may vary slightly)

Best parameters: {'preprocess__num__with_mean': True, 'variance__threshold': 0.1, 'clf__n_estimators': 100, 'clf__max_depth': None}
Best cross-val accuracy: 0.800

Compare options / when to choose what

Approach Pros Cons When to use
Pipeline (sklearn) Full integration with sklearn ecosystem; built-in cross-validation and grid search; no leakage Less flexible for exotic custom transformations Most tabular ML problems — default choice
Manual preprocessing + model (raw code) Full control, transparent debugging Repetitive, error-prone, leaking risk Quick experiments in notebooks; you won't reuse code
Feature-engine transformers (e.g., FeatureUnion) Loads of prebuilt transformations; integrates with pipelines Less common, learning curve When you need out-of-the-box NLP or time-series features

For the Applied AI engineering path, pipelines are the standard. They make your experimentation loop much faster and your results more trustworthy. Use a pipeline unless you have a very specific reason to hand-roll everything.

Troubleshooting & edge cases

  • Data leakage via ColumnTransformer inside a pipeline: If you call preprocessor.fit(X_train) manually, then use it on X_test, you're fine — but if you accidentally fit on X (full dataset) first, leakage happens. The pipeline avoids this by construction. Fix: Always use Pipeline or make_pipeline rather than transforming data yourself before splitting.
  • Getting ValueError from VarianceThreshold when variance is zero: Check if your columns are constant. With threshold=0.0, constant features are removed — but if all features have non-zero variance, you're okay. If you see an error, adjust the threshold.
  • OneHotEncoder sees an unknown category during predict: Set handle_unknown="ignore" to force unseen categories to be all-zero columns. This is crucial for production when new categories appear.
  • Hyperparameter naming mistakes in GridSearchCV: Forgetting the stepname__ prefix (e.g., n_estimators instead of clf__n_estimators) results in a ValueError. Always use the stepname__param format.
  • Same data, different scaling: If you forget to include scaling, some models (like SVM or PCA-based) will perform poorly. Pipeline makes it easy to include StandardScaler consistently.

What you learned & what's next

You now know the core idea behind a full scikit-learn pipeline: a single estimator that chains preprocessing, feature engineering, and modeling, ensuring consistency and eliminating leakage. You've completed a practical exercise on the breast cancer dataset and explored grid search over the entire pipeline. You understand the key mental model of an assembly line, and you can compare pipeline vs. manual approaches.

In the next lesson, you'll build on this by learning how to evaluate pipeline performance rigorously using cross-validation, ROC curves, and learning curves — so you can trust your model's metrics before you deploy it. That's the natural next step in your Applied AI engineering journey.

Practice recap

As a mini exercise, take the breast cancer dataset (or any tabular dataset you like) and build a full pipeline with StandardScaler, VarianceThreshold, and RandomForestClassifier. Then run GridSearchCV over at least five hyperparameter combinations and report the best score on a held-out test set. This solidifies the entire workflow.

Common mistakes

  • Fitting scaler or encoder on the full dataset before splitting — this causes data leakage and inflates validation scores.
  • Forgetting handle_unknown="ignore" in OneHotEncoder, leading to crashes when new categories appear at prediction time.
  • Using the wrong parameter names in GridSearchCV — e.g., n_estimators instead of clf__n_estimators.
  • Not including a feature selection step when there are many irrelevant columns, leading to overfitting and slower training.

Variations

  1. Use make_pipeline for a shorter, name-free version when you don't need step names.
  2. Use FeatureUnion to combine multiple feature extraction branches into one transformer.
  3. Use TransformedTargetRegressor to apply transformations to the target variable inside a pipeline.

Real-world use cases

  • Automating a credit scoring system that preprocesses loan application data and predicts default risk in production.
  • Building a churn prediction service that handles mixed categorical/numeric customer data and retrains daily.
  • Deploying a medical diagnostic model that scales and selects features consistently across hospital data shipments.

Key takeaways

  • A pipeline is an estimator: fit, predict, and score work out of the box.
  • Pipelines prevent data leakage by fitting transformations only on training data.
  • Use ColumnTransformer to apply different preprocessing to numeric and categorical columns.
  • Include feature selection steps like VarianceThreshold to drop uninformative columns.
  • Hyperparameter tuning via GridSearchCV works across the entire pipeline using stepname__param.
  • Always test your pipeline on held-out data to ensure real-world performance.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.