BitWithBite
/
Tier 2 › Module 1 › Lesson 1.1
Next →
Module 1 — The scikit-learn Workflow

Estimators, Transformers
& Pipelines

25 min
📚 Concept + Code
🎯 Lesson 1.1 of 4
scikit-learn has one of the most carefully designed APIs in all of Python. Once you understand its three object types — estimators, transformers, and pipelines — every model you ever use will feel familiar. This lesson builds that mental model from scratch.

The Big Idea

scikit-learn's entire API is built around one consistent pattern. Whether you're using a linear regression, a random forest, a scaler, or a text vectorizer — they all follow the same interface. This is called the Estimator API.

There are three types of objects you need to know:

TypeWhat it doesKey methodsExamples
Estimator Learns from data — fits a model to training examples fit(X, y) LinearRegression, RandomForest, KMeans
Transformer Changes data — scales, encodes, reduces dimensions fit(X) then transform(X) StandardScaler, OneHotEncoder, PCA
Pipeline Chains transformers + an estimator into one object fit(X, y) + predict(X) Pipeline([scaler, model])
Key insight
Most objects in scikit-learn are actually both an estimator and a transformer. StandardScaler learns the mean and std (estimator side), then uses them to scale new data (transformer side). This is called a fit-transform object.

Estimators — Objects That Learn

An estimator is any object with a fit() method. During fit(), it looks at your training data and learns something — the weights of a regression, the split rules of a tree, the cluster centers of K-Means.

Analogy
Think of an estimator like a student taking notes during a lecture (fit()). After the lecture, the student can answer exam questions (predict()). The student doesn't re-read the notes every time they answer — the knowledge is stored internally, just like a fitted estimator stores its learned parameters.
python
from sklearn.linear_model import LinearRegression
import numpy as np

# Training data
X_train = np.array([[1], [2], [3], [4]])
y_train = np.array([2, 4, 6, 8])

# Step 1: create the estimator (nothing learned yet)
model = LinearRegression()

# Step 2: fit() — the model learns from training data
model.fit(X_train, y_train)

# After fitting, parameters are stored on the object
print(model.coef_)       # [2.]  — the slope it learned
print(model.intercept_)  # 0.0   — the intercept it learned

# Step 3: predict() — use learned parameters on new data
X_new = np.array([[5], [10]])
print(model.predict(X_new))  # [10.  20.]
Common mistake
Calling predict() before fit() raises a NotFittedError. The model has nothing to predict with yet. Always fit first.

Transformers — Objects That Change Data

A transformer has two methods: fit() and transform(). The fit() step learns statistics from the training data. The transform() step applies those statistics to any data — train or test.

1
fit(X_train) — learn from training data
For StandardScaler: compute the mean and standard deviation of each feature in the training set. Store these numbers internally.
2
transform(X) — apply to any data
Use the stored mean and std to scale the data. Call this on both your training set and your test set — using the same learned statistics.
3
fit_transform(X_train) — shortcut for training
Equivalent to calling fit() then transform() in one step. Only use this on training data — never on the test set.
python
from sklearn.preprocessing import StandardScaler
import numpy as np

X_train = np.array([[10, 200],
                    [20, 400],
                    [30, 600]])

X_test  = np.array([[15, 300],
                    [25, 500]])

scaler = StandardScaler()

# Fit on TRAIN only — learns mean=[20,400] std=[8.16,163.3]
scaler.fit(X_train)

# Transform TRAIN using learned stats
X_train_scaled = scaler.transform(X_train)

# Transform TEST using the same learned stats (NOT re-fitted)
X_test_scaled  = scaler.transform(X_test)

print(scaler.mean_)  # [20. 400.]  — stored from fit()
print(scaler.scale_) # [8.16 163.3] — stored from fit()
The most common data leakage mistake in ML
Calling scaler.fit(X_test) or scaler.fit_transform(X_test) makes the scaler learn statistics from test data. This means your model indirectly "sees" the test set during training — it's data leakage. Always fit on train, transform on both.

Pipelines — Chaining It All Together

A Pipeline chains one or more transformers followed by a final estimator. When you call pipeline.fit(X, y), it:

1
Fits and transforms each step in order
Step 1 (scaler): fit on X, transform X. Step 2 (model): receives the transformed output and fits.
2
On predict(), transforms first — then predicts
New data flows through every transformer step before reaching the final model. No manual preprocessing required.
3
Makes cross-validation safe
When used inside cross_val_score, the pipeline refits transformers on each fold's training data — preventing leakage automatically.
Pipeline data flow
Raw X
StandardScaler
step 1
Scaled X
LogisticRegression
step 2 (final)
Prediction
python
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split

X, y = load_iris(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)

# Build the pipeline
pipe = Pipeline([
    ('scaler', StandardScaler()),
    ('model',  LogisticRegression(max_iter=200))
])

# fit() triggers: scaler.fit_transform(X_train) → model.fit()
pipe.fit(X_train, y_train)

# predict() triggers: scaler.transform(X_test) → model.predict()
print(pipe.predict(X_test))

# Score the whole pipeline in one call
print(pipe.score(X_test, y_test))  # 0.9667

# Access individual steps
print(pipe.named_steps['scaler'].mean_)
print(pipe.named_steps['model'].coef_.shape)

Why bother with pipelines?

You could do it manually — fit the scaler, transform the data, fit the model. But pipelines give you four things manually chaining doesn't:

4 reasons pipelines matter
1. No leakage in cross-validation. cross_val_score re-fits each step per fold. Without a pipeline, your scaler is fit on all folds including the validation fold — silent data leakage.

2. One object to save. joblib.dump(pipe, 'model.pkl') saves the entire pipeline — scaler parameters and model weights together. Load it anywhere and call predict() directly.

3. Grid search across preprocessing. GridSearchCV can tune pipeline parameters — including which scaler to use, or what n_components for PCA. Not possible when they're separate objects.

4. Cleaner code. A pipeline is a single, named object. You can describe the whole system in one place instead of three separate steps scattered across your notebook.

The Three Methods You Must Know

MethodWhat it doesWhen to callOn what data
fit(X, y) Learn parameters from data. Returns self. Once, during training Training set only
transform(X) Apply learned transformation to data After fit(). Call on train AND test. Any data, using train stats
fit_transform(X) fit() then transform() in one step Shortcut for training data only Training set only
predict(X) Generate predictions from fitted model After fit(). Inference time. Any data
score(X, y) Evaluate model performance After fit(). Evaluation. Test or validation set
What's stored after fit()
sklearn convention: parameters learned during fit() are stored with a trailing underscore. model.coef_, scaler.mean_, pca.components_. Parameters you set before fitting don't have trailing underscores: StandardScaler(with_mean=True). This is how you know what's learned vs configured.

Putting It Together: A Full Working Example

Here is a complete, real-world-style workflow using Pipeline with proper train/test splitting, cross-validation, and scoring:

python — full workflow
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.datasets import load_breast_cancer
import numpy as np

# 1. Load data
X, y = load_breast_cancer(return_X_y=True)

# 2. Split — hold out test set until the very end
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42, stratify=y
)

# 3. Build pipeline
pipe = Pipeline([
    ('scaler', StandardScaler()),
    ('clf',    RandomForestClassifier(n_estimators=100, random_state=42))
])

# 4. Cross-validation on training set
# Pipeline prevents leakage: scaler is re-fit inside each fold
cv_scores = cross_val_score(pipe, X_train, y_train, cv=5, scoring='accuracy')
print(f"CV accuracy: {cv_scores.mean():.3f} ± {cv_scores.std():.3f}")
# CV accuracy: 0.964 ± 0.012

# 5. Final fit on all training data
pipe.fit(X_train, y_train)

# 6. Evaluate on held-out test set (only touch this ONCE)
test_score = pipe.score(X_test, y_test)
print(f"Test accuracy: {test_score:.3f}")
# Test accuracy: 0.965
✓ Check your understanding
1. You call scaler.fit_transform(X_test) instead of scaler.transform(X_test). What problem does this cause?
AIt crashes with a ValueError
BThe scaler re-learns statistics from the test set — data leakage
CNothing — fit_transform and transform are identical
DThe model trains faster but accuracy drops
Correct. fit_transform calls fit() first, which overwrites the statistics learned from training data with test-set statistics. Now your model indirectly knows the test distribution — this is data leakage. The test score will be optimistically biased.
2. Which sklearn convention tells you that a parameter was learned during training (not set by you)?
AThe parameter starts with a capital letter
BThe parameter has a trailing underscore, e.g. coef_
CThe parameter is returned by get_params()
DThe parameter name contains "learned"
Correct. sklearn convention: learned attributes end with an underscore (coef_, mean_, components_). Configuration you set before fitting does not (n_estimators, with_mean). This is how you distinguish what you configured vs what the model discovered.
3. When you call pipe.fit(X_train, y_train) on a Pipeline with two steps, what happens internally?
AOnly the final estimator step is fitted
BAll steps are fitted simultaneously in parallel
CEach transformer is fit_transform'd in order, then the final estimator is fit on the result
DThe pipeline fits on a copy of the data, leaving the original unchanged
Correct. Pipeline calls fit_transform() on each intermediate step (transformers) in order, passing the output of each as input to the next. The final step (the model) only gets fit(), not transform(). This is why the last step must be an estimator, not a transformer.

Summary

What you learned
Estimators learn from data with fit(X, y) and predict with predict(X)
Transformers change data. Fit on train only, transform on train and test
→ Learned parameters end with underscore: coef_, mean_, scale_
Pipelines chain steps safely — no leakage, one object to save, grid-searchable
→ Always fit on training data only. transform on anything. Never fit on test.
← Back to Module 1 Next: Preprocessing — scaling, normalization, encoding →
🗒 Cheat Sheet 📝 Worksheet