datarekha

The scikit-learn API

Every scikit-learn estimator follows the same three-method contract. Once you see it, the whole library makes sense.

6 min read Beginner Machine Learning Lesson 2 of 33

What you'll learn

  • The fit / predict / transform pattern that every estimator follows
  • Why Pipelines are the single most underrated tool in sklearn
  • How ColumnTransformer handles mixed numeric and categorical features
  • The shape of a real end-to-end workflow — load, split, preprocess, fit, predict

Before you start

The first time you read scikit-learn code, every model looks suspiciously similar. LinearRegression, RandomForestClassifier, KMeans, StandardScaler — they all have the same handful of methods. That’s not a coincidence. It’s the estimator pattern: an estimator is any sklearn object that learns from data — models, scalers, encoders, all of it. This shared interface is the reason sklearn is the lingua franca of tabular ML.

The contract

Throughout sklearn, X is the feature matrix (rows = samples, columns = input variables) and y is the label vector (the target you’re trying to predict). Every estimator implements at least one of these methods:

MethodWhat it doesWho has it
fit(X, y)Learn parameters from training dataEverything
predict(X)Produce predictionsSupervised models
transform(X)Modify features (scale, encode, reduce dims)Preprocessors, dim reducers
predict_proba(X)Class probabilitiesClassifiers
fit_transform(X)fit then transform in one callPreprocessors

That’s it. Logistic regression, a random forest, and a standard scaler all expose this same interface, which is why you can swap them in and out of a pipeline without changing the surrounding code.

A minimal end-to-end run

Predicting whether a credit card transaction will be flagged as risky — a mix of numeric and categorical features, the bread and butter of any fraud team:

import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler

# Toy dataset — 6 transactions, 2 numeric features, binary label
df = pd.DataFrame({
    "amount":     [12.50, 850.00, 45.00, 2100.00, 9.99, 320.00],
    "hours_old":  [0.1,    23.0,   3.5,   71.0,   0.2,  12.0],
    "is_risky":   [0,      1,      0,     1,      0,    0],
})
X = df[["amount", "hours_old"]]
y = df["is_risky"]

# Same three lines every time:
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, random_state=0)
scaler = StandardScaler().fit(X_train)        # learn means + stds on TRAIN ONLY
X_train_s = scaler.transform(X_train)
X_test_s  = scaler.transform(X_test)

model = LogisticRegression().fit(X_train_s, y_train)
print("scaler.mean_ (learned on train):", scaler.mean_.round(2).tolist())
print("test predictions:", model.predict(X_test_s).tolist())
scaler.mean_ (learned on train): [743.12, 23.58]
test predictions: [0, 0]

Notice the discipline: the scaler is .fit on the training data only, then .transform is applied to both. If you fit the scaler on the full dataset you’ve leaked test information into preprocessing — a subtle bug that inflates your metrics and falls apart in production.

Pipelines — the underrated superpower

Doing that manual scaler-then-model dance is fine for one model. The moment you cross-validate, tune hyperparameters, or save to disk, it gets tedious and error-prone. Pipeline wraps the whole sequence into a single estimator that itself has .fit / .predict.

import pandas as pd
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split

df = pd.DataFrame({
    "amount":    [12.50, 850.00, 45.00, 2100.00, 9.99, 320.00, 75.00, 1500.0],
    "hours_old": [0.1,   23.0,   3.5,   71.0,    0.2,  12.0,   1.0,   48.0],
    "is_risky":  [0,     1,      0,     1,       0,    0,      0,     1],
})
X, y = df[["amount", "hours_old"]], df["is_risky"]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=0)

pipe = Pipeline([
    ("scale", StandardScaler()),
    ("clf",   LogisticRegression()),
])

pipe.fit(X_train, y_train)
print("score:", pipe.score(X_test, y_test))
print("preds:", pipe.predict(X_test).tolist())
score: 1.0
preds: [0, 0]

The pipeline IS an estimator. You call .fit once, and at predict time the same scaler trained on X_train is applied to whatever you pass in. No leakage, no bookkeeping.

Mixed types — ColumnTransformer

Real data is messy: numeric columns, categorical columns, maybe text. You want different preprocessing per column. ColumnTransformer is how.

import pandas as pd
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.linear_model import LogisticRegression

df = pd.DataFrame({
    "amount":      [12.5, 850, 45, 2100, 9.99, 320, 75, 1500],
    "hours_old":   [0.1, 23, 3.5, 71, 0.2, 12, 1, 48],
    "merchant":    ["food", "elec", "food", "elec", "food", "ride", "food", "elec"],
    "is_risky":    [0, 1, 0, 1, 0, 0, 0, 1],
})
X, y = df.drop(columns=["is_risky"]), df["is_risky"]

pre = ColumnTransformer([
    ("num", StandardScaler(),                ["amount", "hours_old"]),
    ("cat", OneHotEncoder(handle_unknown="ignore"), ["merchant"]),
])

pipe = Pipeline([("pre", pre), ("clf", LogisticRegression())])
pipe.fit(X, y)
print("score on train:", pipe.score(X, y))
print("predict new row:",
      pipe.predict(pd.DataFrame([{"amount": 9.0, "hours_old": 0.5, "merchant": "food"}])).tolist())
score on train: 1.0
predict new row: [0]

ColumnTransformer slices columns out by name, applies the transformer you specified, then stacks the results back together. The Pipeline wraps that with a classifier. The whole thing is now ONE object — serialize it, ship it, done.

Why this matters

The estimator pattern means experimentation is cheap. Want to try a random forest instead? Replace LogisticRegression() with RandomForestClassifier(). Want to compare scalers? Swap StandardScaler for RobustScaler. The surrounding code does not change. This composability is what makes sklearn productive — and it’s why every downstream library (XGBoost, LightGBM, scikit-learn-contrib packages) mimics the same interface.

In one breath

  • Every scikit-learn estimator (model, scaler, encoder) follows one contract: fit (learn from data), predict (supervised output), transform (modify features), plus predict_proba and fit_transform.
  • Because the interface is shared, you swap estimators without touching the surrounding code — the source of sklearn’s composability.
  • Fit preprocessing on train only, then transform both; fitting on the full data leaks test info and inflates metrics.
  • A Pipeline bundles preprocessing + model into one estimator with its own .fit/.predict — no leakage, no bookkeeping; treat “wrap it in a Pipeline” as non-negotiable before deploying.
  • ColumnTransformer routes different transforms to different columns (scale numerics, one-hot categoricals) and stacks them — wrap it in a Pipeline and you have one serializable, shippable object.

Quick check

Quick check

0/3
Q1Why fit your scaler on `X_train` only, not on `X_train + X_test`?
Q2What does `Pipeline([('scale', StandardScaler()), ('clf', LogisticRegression())]).predict(X)` actually run?
Q3You have 5 numeric columns and 3 categorical columns. Which tool routes different transformers to different columns?

Sign in to track your progress

Completed lessons, your XP, level, and streak save to your account — it's free and takes a few seconds.

Practice this in an interview

All questions
How does k-means clustering work?

K-means partitions n points into k clusters by alternating between two steps: assigning each point to its nearest centroid, then recomputing each centroid as the mean of its assigned points. It repeats until assignments stop changing, which guarantees convergence but not a globally optimal solution.

What is the difference between standardization and normalization, and which models require feature scaling?

Standardization rescales features to zero mean and unit variance; normalization squashes values into a fixed range, usually [0, 1]. Distance-based and gradient-based models are sensitive to scale and require one of these; tree-based models split on rank order and are scale-invariant.

How does CI/CD for ML differ from standard software CI/CD, and what stages should an ML pipeline include?

ML CI/CD must validate not just code correctness but also model quality — automated retraining triggers, data validation, model evaluation gates, and canary deployment checks that standard software pipelines have no equivalent for. A regression in model AUC is as much a deployment failure as a 500 error.

How do you achieve reproducibility in ML training pipelines — covering seeds, environment, and data versioning?

Full ML reproducibility requires locking three layers: the random seed across all frameworks, the software environment via pinned dependency manifests or container images, and the training data via content-addressed versioning. Missing any one layer means the same code can produce different models on different runs or machines.

Related lessons

Explore further

Glossary terms
Cheat sheets
Skip to content