ai_ml / feature engineering / 02_scaling_and_transforms.md

Scaling and numeric transforms

6 interview angles 5 min read source

Scaling and numeric transforms

Which models need scaling, which don’t, and what to do when a feature is skewed or full of outliers.

Who needs scaling

Needs it Doesn’t
Linear/logistic with regularisation Decision trees
SVM (distance-based kernels) Random Forest
kNN, k-means, PCA Gradient boosting
Neural networks Naive Bayes
Anything using gradient descent

The rule underneath: if the algorithm computes distances or penalises coefficient magnitude, scale. Trees only compare one feature against a threshold at a time, so units are irrelevant to them.

Getting this wrong on an SVM or kNN doesn’t degrade the model slightly — it lets the largest-magnitude feature dominate the distance metric and effectively deletes the others.

The scalers

from sklearn.preprocessing import (
    StandardScaler, MinMaxScaler, RobustScaler, QuantileTransformer, PowerTransformer
)
Scaler Maps to Use when
StandardScaler mean 0, std 1 default; roughly symmetric data
MinMaxScaler [0, 1] bounded inputs needed; very sensitive to outliers
RobustScaler median 0, IQR 1 outliers present
QuantileTransformer uniform or normal badly non-normal; destroys the original shape
PowerTransformer approx. normal skewed positive data (Yeo-Johnson handles negatives)
Normalizer unit norm per row rarely — this scales samples, not features

Normalizer catches people out: it normalises each row to unit length, not each column. That’s what you want for embeddings before cosine similarity, and almost never what you want for tabular features.

Skew

Right-skewed features (prices, counts, durations, income) hurt linear models and distance metrics. The common fixes:

import numpy as np

X["amount_log"] = np.log1p(X["amount"])     # log1p handles zeros; log(0) is -inf

log1p / expm1 rather than log / exp — the 1p variants are both zero-safe and more numerically accurate for small values.

For the target rather than a feature, log-transforming often fixes heteroscedasticity too. Remember that back-transforming a prediction of the log-mean gives you an estimate of the median, not the mean — a subtle bias worth naming.

PowerTransformer with Yeo-Johnson finds the transform for you and handles zeros and negatives, which plain log can’t.

Outliers

Decide what they are before deciding what to do:

  • Data errors (a birth year of 1850, a negative price) — fix or drop.
  • Genuine extremes (a whale customer) — often your most important rows. Do not delete.

Options that don’t throw information away:

# Winsorise - cap at percentiles
lo, hi = X["amount"].quantile([0.01, 0.99])
X["amount_capped"] = X["amount"].clip(lo, hi)

# Or keep the signal explicitly
X["is_extreme"] = (X["amount"] > hi).astype(int)

RobustScaler uses the median and IQR, so extreme values don’t move the scaling parameters at all. It’s the usual choice when you want to keep outliers but not let them distort everything else.

Tree models are naturally robust — an outlier just ends up in its own leaf.

Binning

Turning a continuous feature into buckets. Sometimes helps linear models capture non-linearity; usually pointless for trees, which do their own binning by splitting.

from sklearn.preprocessing import KBinsDiscretizer
KBinsDiscretizer(n_bins=10, encode="ordinal", strategy="quantile")

Costs resolution, gains robustness and interpretability. Worth it mainly when the relationship is genuinely non-monotonic and you’re stuck with a linear model.

Missing values

Missingness is information. Check whether it’s random before erasing it.

from sklearn.impute import SimpleImputer

SimpleImputer(strategy="median", add_indicator=True)    # keep the fact it was missing

add_indicator=True is the underused part: it appends a binary column recording that the value was imputed. If missingness correlates with the target — an unfilled income field on a loan application, say — that indicator can be your strongest feature.

Strategy Note
Drop rows only if missingness is rare and random
Mean/median fast; median is safer with skew
Most frequent categoricals
IterativeImputer models each feature from the others; slow, sometimes much better
kNN imputer uses similar rows; expensive
Leave it LightGBM, XGBoost and CatBoost handle NaN natively and learn a direction for it

That last row is worth stating in an interview: for gradient boosting, imputing is often worse than doing nothing, because the model can learn an optimal default direction per split.

Feature interactions

Trees find interactions implicitly. Linear models need them spelled out.

from sklearn.preprocessing import PolynomialFeatures
PolynomialFeatures(degree=2, interaction_only=True, include_bias=False)

Feature count grows quadratically, so pair it with regularisation. Domain-driven interactions (price_per_sqm, orders_per_active_day) usually beat exhaustive polynomial expansion — they encode knowledge rather than searching blindly.

Doing it correctly in a pipeline

Different columns need different treatment, and everything must fit on training data only:

from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline

numeric = Pipeline([
    ("impute", SimpleImputer(strategy="median", add_indicator=True)),
    ("scale", RobustScaler()),
])
categorical = Pipeline([
    ("impute", SimpleImputer(strategy="most_frequent")),
    ("encode", OneHotEncoder(handle_unknown="infrequent_if_exist")),
])

pre = ColumnTransformer([
    ("num", numeric, numeric_cols),
    ("cat", categorical, categorical_cols),
])

model = Pipeline([("pre", pre), ("clf", LogisticRegression())])

This is the shape to reproduce in an interview whiteboard. It gets the fit-on-train-only property for free inside cross-validation, and it serialises as one artefact so serving can’t drift from training. See 04_data_leakage.md.

Interview angle

  • “Which models need feature scaling?” — anything distance-based or gradient-descent-based, plus anything regularised (the penalty is on coefficient magnitude, which depends on units). Trees and tree ensembles don’t.
  • “StandardScaler or MinMaxScaler?” — StandardScaler by default. MinMax when you need a bounded range, but it’s dominated by outliers since it uses min and max. RobustScaler when outliers are present and real.
  • “Your amount column is heavily right-skewed. What do you do?”log1p (zero-safe), or PowerTransformer with Yeo-Johnson if there are negatives. For trees, do nothing — monotone transforms don’t change a tree.
  • “How do you handle missing values?” — first ask whether missingness is informative. If it is, keep an indicator column. Median-impute numerics, most-frequent categoricals, and for gradient boosting consider leaving NaN alone since those libraries learn a default direction natively.
  • “Where do you put the scaler so cross-validation is honest?” — inside a Pipeline, so it refits on each fold’s training portion. Fitting before splitting leaks test statistics into training.
  • “Why doesn’t a random forest need scaling?” — splits are threshold comparisons on a single feature, so any monotone transform, including rescaling, produces the same tree.