The Silent Bug That Makes Your Model Wrong, Not Broken
The Silent Bug That Makes Your Model Wrong, Not Broken
Two customers. One is 25, the other is 30 — five years apart. Another pair: 25 and 70 — forty-five years apart.
Ask a K-Nearest Neighbors model which pair is more similar, using age and income, and it tells you the 45-year age gap pair is the closer match.
No error. No crash. Just a wrong answer, delivered with total confidence.
Why It Happens
Income ranges from roughly zero to a hundred thousand. Age ranges from zero to about a hundred. When a model measures "distance" between two people, it just does math on the raw numbers — and a $1,000 income difference completely swamps a 45-year age difference, because the numbers are so much bigger to begin with.
The model isn't wrong about the arithmetic. It's just never been told these two numbers live on completely different scales.
The Fix, in Real Numbers
Scale those same two customers properly, and the distances flip to the right answer: the pair five years apart comes out at a distance of 0.28. The pair forty-five years apart comes out at 2.49. The model now sees what a human already knows — five years apart is far more similar than forty-five.
This fix is called feature scaling — putting every feature on the same numeric footing before a model ever sees it. There are two standard ways to do it: MinMaxScaler and StandardScaler.
MinMaxScaler: Squeeze Into 0–1
X′ = (X − min) / (max − min)
Age 30, in a range of 18 to 80, becomes 0.19.
from sklearn.preprocessing import MinMaxScaler scaler = MinMaxScaler() X_train_scaled = scaler.fit_transform(X_train)
fit_transform learns the range from training data and rescales it in one step — both calculated together, in a single call.
StandardScaler: Center Around Zero
Instead of squeezing into a fixed range, StandardScaler centers each feature around zero and scales it by how spread out the values are:
Z = (X − mean) / standard deviation
Data: 10, 20, 30, 40, 50 → mean 30, spread ≈ 14. A value of 40 becomes 0.71.
from sklearn.preprocessing import StandardScaler scaler = StandardScaler() X_train_scaled = scaler.fit_transform(X_train)
Same method call, same pattern — the difference is entirely in what the scaler calculates internally, not in how you use it.
Where It Actually Breaks
A mistake that shows up constantly with any scikit-learn transformer, scalers included: fit the scaler on training data, then fit it again, separately, on test data.
# Wrong: fitting separately on train and test train_scaled = scaler.fit_transform(X_train) test_scaled = scaler.fit_transform(X_test) # refits! different scale!
Training data gets scaled one way, test data gets scaled another way, and the model receives numbers that don't mean the same thing on both sides. This one doesn't throw an error at all. It just quietly gives you a worse model — arguably more dangerous than a crash, because nothing tells you it happened.
# Right: fit once, transform everywhere train_scaled = scaler.fit_transform(X_train) test_scaled = scaler.transform(X_test) # reuses training scale
The rule holds for any scikit-learn transformer: fit once, on training data, and reuse that exact same fitted object on everything after.
Which One to Use
MinMaxScaler when values need to land in a strict 0-to-1 range — some algorithms expect that. StandardScaler when the data has outliers, since it's less thrown off by extreme values than squeezing everything into a fixed range. When unsure, StandardScaler is the more common default.
Get the Code
The full breakdown, including the KNN distance walkthrough, lives in the video script: FeatureScaling.ipynb.
What's Next
Fitting once and reusing the same transformer is the same rule that showed up with encoders. The pattern isn't a coincidence — it's the one rule underneath most "works in testing, breaks in production" preprocessing bugs.
This post is the companion to Video 2 in the preprocessing series: Feature Scaling. For the previous part, check out: The One-Hot Encoding Bug That Only Shows Up in Production.
Comments
Post a Comment