Data Preparation / Data Preprocessing Slides 📂 Introduction · 12 of 13 36 min read

Handling Imbalanced Data in Machine Learning

A practical, visual guide to modelling rare events like fraud and disease. Learn why accuracy lies, which metrics to trust (precision, recall, F1, AUC-PR), and six treatments — class weights, SMOTE, undersampling, threshold tuning, combined methods and balanced algorithms — wired into a leak-proof imblearn pipeline in the right order.

Handling Imbalanced Data

Fraud, disease and defects are rare by nature — and standard models quietly ignore them. Fix the metric first, then the data, then the pipeline. The model is the last thing you change.
The Problem Right Metrics SMOTE & Weights Threshold Tuning

Press Next → or use ← → arrow keys

Section 01

The Class Imbalance Problem

The fraud model that caught zero frauds
A payment processor trained a fraud model on 99.1% legitimate transactions. It hit 99.1% accuracy — by labelling everything "legit" and catching not a single fraud. Month-one losses: ₹4.2 crore in undetected fraud. The model wasn't broken; the objective was.
Legit 99% Fraud 1%
🚨
Accuracy is a lie on imbalanced data

On a 99:1 split, "always predict the majority" scores 99% while being completely useless. The rarest class is usually the most important one — and it gets the least attention.

Section 01 · Severity

How Bad Is Your Imbalance?

1 : 4
Mild
~20% minority · churn, review ratings — may need no treatment
1 : 10
Moderate
~9% · disease screening, loan default — class weights
1 : 100
Severe
~1% · fraud, defects — SMOTE / resampling
1 : 1000
Extreme
~0.1% · rare disease, intrusion — combine strategies
counts = df['target'].value_counts();  ratio = counts.max() / counts.min()
print(f"Imbalance ratio: {ratio:.0f}:1")   # severity guides the treatment
Counter(y_train)   # Counter({0: 9910, 1: 90})  → 110:1, severe
Section 03

Step 1 — Fix the Metric, Not the Model

❌ Naïve — predicts all "legit"
9910TN legit
0FP
90FN · fraud missed
0TP

99.1% acc · 0% recall · 0/90 caught

✅ Balanced — SMOTE + weights
9640TN legit
270FP · false alarms
18FN missed
72TP · fraud caught

97.1% acc · 80% recall · 72/90 caught

🎯
A 2-point accuracy drop that catches 72 frauds

The "worse" model on accuracy is dramatically better in reality. That's why you must read the confusion matrix and the minority-class metrics — never the headline accuracy alone.

Section 03 · Metrics

Precision, Recall & the Right Curve

Precision–Recall (use this ✓) good model naïve collapses recall → ROC (misleading here ✗) naïve still "looks fine" false-positive rate →
🔍
Precision vs Recall

Recall = of all real frauds, how many caught? Precision = of all flagged, how many were real? F1 balances the two.

📈
AUC-PR > AUC-ROC

ROC is inflated by the huge pile of true negatives, so it flatters weak models. AUC-PR is the honest primary metric on severe imbalance.

Section 04

Six Ways to Rebalance

⚖️
Class Weights
Penalise minority errors more. Zero data change — class_weight='balanced'.
🧬
SMOTE
Synthesise minority points by interpolation. The most widely used oversampler.
✂️
Undersampling
Drop majority rows. Fast, but throws away data.
🎚️
Threshold Tuning
Lower the 0.5 cutoff to trade precision for recall.
🔀
Combined
SMOTE + Tomek/ENN cleanup — oversample, then de-noise the boundary.
🌲
Algorithm Choice
Balanced Random Forest, EasyEnsemble, XGBoost scale_pos_weight.
🪜
Start cheap, escalate as needed

Try class weights first, add SMOTE for severe imbalance, tune the threshold to hit your business target, and reach for combined methods only when the boundary is noisy.

Section 05 · Strategy 1

Class Weights — the Two-Line Fix

An insurance team spent three weeks tuning XGBoost on a 100:1 fraud set — recall stuck at 31%. Adding class_weight='balanced' doubled it to 64%. Two lines, no new architecture.

# auto-computed inverse-frequency weights
LogisticRegression(class_weight='balanced')
RandomForestClassifier(class_weight='balanced')

# manual: minority gets ~100× the weight
w = compute_class_weight('balanced', classes=np.unique(y_train), y=y_train)
print(dict(enumerate(w)))   # {0: 0.505, 1: 50.5}

# XGBoost equivalent
xgb.XGBClassifier(scale_pos_weight=neg/pos, eval_metric='aucpr')
Always try this first

It's two characters of code, costs nothing, changes no data, and often delivers ~80% of the benefit of heavier resampling. Only escalate if it isn't enough.

Section 06 · Strategy 2

SMOTE — Synthesising the Minority

A hospital's rare-condition model (100:1) reached only 42% recall with weights alone. SMOTE generated 9,520 synthetic cases (down to 5:1) and recall leapt to 87% — an estimated 180 extra patients caught per year.

New points are interpolated along the line between two real minority samples real minoritysyntheticmajority
🧬
Interpolate, don't copy

SMOTE doesn't duplicate rows — it places new points between real minority neighbours, forcing the model to learn a denser, more accurate boundary around the rare class.

Section 06 · Code

SMOTE Done Right — Split First

from imblearn.over_sampling import SMOTE
from imblearn.pipeline import Pipeline  # imblearn!

pipe = Pipeline([
  ('scaler', StandardScaler()),
  ('smote', SMOTE(sampling_strategy=0.5,
                 random_state=42)),
  ('model', RandomForestClassifier())])

pipe.fit(X_train, y_train)   # SMOTE on train only
pipe.predict(X_test)         # test untouched ✓
# BorderlineSMOTE / ADASYN = harder-case variants
☠️
The #1 SMOTE mistake

Never SMOTE the full dataset before splitting — synthetic points made from train rows leak into test, giving fake-brilliant scores that collapse in production.

before 100:1 after 2:1
🎚️
Don't over-correct to 1:1

sampling_strategy=0.5 makes the minority 50% of the majority (a 2:1 ratio). Forcing a perfect 1:1 usually over-synthesises and hurts precision — 2:1 or 3:1 is plenty.

Section 07 · Strategy 3

Undersampling & Combined Cleanup

# drop majority rows (fast, lossy)
RandomUnderSampler(sampling_strategy=0.5)

# Tomek: remove borderline majority
TomekLinks()

# SMOTE + cleanup = best of both
from imblearn.combine import \
    SMOTETomek, SMOTEENN
SMOTETomek(random_state=42)  # over + de-noise
SMOTEENN(random_state=42)
✂️ Undersample
Great for very large datasets where speed matters more than every row.
🔗 Tomek Links
Deletes majority points sitting right on the boundary — sharpens the margin.
🔀 SMOTE + Tomek/ENN
Oversample the minority, then scrub noisy borderline pairs. Best boundary clarity.
⚖️
Over vs under

Oversampling keeps all your data but adds synthetic rows; undersampling is faster but discards real majority examples. Combined methods do both, then clean the seam between classes.

Section 08 · Strategy 4

Threshold Tuning — 0.5 Is Almost Always Wrong

Predicted-probability distributions — move the cutoff, trade precision for recall P(fraud) → 0 ............................................. 1 legit fraud 0.5 default 0.3 tuned ↙ lower = catch more fraud
p, r, t = precision_recall_curve(y_val, model.predict_proba(X_val)[:,1])
best = t[np.argmax(2*p*r/(p+r+1e-10))]     # threshold that maximises F1
y_pred = (model.predict_proba(X_test)[:,1] >= best).astype(int)   # not 0.5!
Section 09

Which Strategy Wins? It Depends

Recall on fraud by strategy baseline0.00 class weights0.64 SMOTE0.76 SMOTE + weights0.81 ★ threshold 0.20.89 …but threshold 0.2 drops precision to 0.48 — recall bought with false alarms
🏅
No universal winner

SMOTE + class weights gives the best F1 (0.77) — the balanced all-rounder. Need maximum recall and can tolerate false alarms? Drop the threshold. The choice is a business decision about which error costs more.

Section 10

The Full Imbalanced-Data Pipeline

from imblearn.pipeline import Pipeline  # NOT sklearn

pipe = Pipeline([
  ('prep',  preprocessor),
  ('smote', SMOTE(sampling_strategy=0.5)),
  ('model', RandomForestClassifier(
       class_weight='balanced'))])

cv = StratifiedKFold(5, shuffle=True)
cross_val_score(pipe, X_train, y_train,
    cv=cv, scoring='average_precision')  # AUC-PR

pipe.fit(X_train, y_train)
joblib.dump({'pipeline': pipe,
             'threshold': best}, 'model.pkl')
FOUR NON-NEGOTIABLE RULES
1imblearn Pipeline — SMOTE on fit() only
2StratifiedKFold — keep class ratio per fold
3tune & save the threshold with the model
4score with AUC-PR, never accuracy
🧷
imblearn Pipeline vs sklearn Pipeline

sklearn's Pipeline would resample the test set too — catastrophic leakage. imblearn's applies SMOTE only inside fit() and skips it at predict time. This one import is the difference between honest and fake metrics.

Pro · Debug

Common Pitfalls & Their Fixes

SymptomThe trapThe fix
99% accuracy, 0 frauds caughtOptimising accuracyPrecision, recall, F1, AUC-PR
Perfect CV, collapses in prodSMOTE before the splitSplit first · imblearn Pipeline
Test set secretly resampledsklearn Pipeline + SMOTEUse imblearn Pipeline
A fold has zero fraudsPlain KFoldStratifiedKFold
Great model, poor resultsDefault 0.5 thresholdTune on validation, save it
SMOTE makes near-duplicates<10 minority samplesClass weights instead
Recall high, everyone complainsIgnoring false-alarm costSet threshold to the business cost
Section 11 · Part 1

The Golden Rules — 1 to 4

⚖️ IMBALANCED DATA · RULES 1–4
1
Never use accuracy as your primary metric. "Always predict majority" scores 99% and catches nothing. Use precision, recall, F1, AUC-PR.
2
Try class weights first. Two characters, zero cost, and often ~80% of the benefit of heavier resampling.
3
Never SMOTE before the split. Synthetic rows bleed into test and inflate metrics that collapse in production.
4
Use imblearn's Pipeline, not sklearn's — it resamples only during fit() and skips it at prediction.
Section 11 · Part 2

The Golden Rules — 5 to 8 & Takeaway

⚖️ IMBALANCED DATA · RULES 5–8
5
StratifiedKFold, always. Plain KFold can hand a fold zero minority rows, making CV scores meaningless.
6
Tune the threshold on validation, not test — maximise F1 (or recall) and save it beside the model.
7
SMOTE needs samples. Below ~10 minority rows it just clones near-duplicates — use class weights instead.
8
Know the cost of each error. A missed fraud usually costs far more than a false alarm — that's a business call, not a stats one.
🎯
The model is the last thing you change

The zero-fraud model wasn't a modelling failure — it was a problem-formulation failure. Choose the right metric first, then the right treatment, then build the pipeline in the right order. Everything before the model decides whether it can even learn what matters.

⚖️ End of tutorial · Press to review, or click Restart