Study interactive :: Progress tools open in the Study Hub reader.

Imbalanced Data Cheatsheet

Comprehensive quick reference for handling imbalanced datasets in machine learning.

Table of Contents


Understanding Imbalanced Data

Problem Definition

Imbalanced Data: When classes are not equally represented in the dataset.

Example:

Why It's a Problem

# Naive baseline: Always predict majority class
# Accuracy: 99% (but useless!)

# Example
y_true = [0, 0, 0, 0, 0, 0, 0, 0, 0, 1]  # 9:1 ratio
y_pred = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]  # Always predict 0
accuracy_score(y_true, y_pred)  # 0.90 (misleading!)

Resampling Techniques

Oversampling

SMOTE (Synthetic Minority Oversampling Technique)

from imblearn.over_sampling import SMOTE

# Apply SMOTE
smote = SMOTE(random_state=42)
X_resampled, y_resampled = smote.fit_resample(X_train, y_train)

# With custom sampling strategy
smote = SMOTE(
    sampling_strategy=0.5,  # Balance to 50:50
    random_state=42,
    k_neighbors=5
)
X_resampled, y_resampled = smote.fit_resample(X_train, y_train)

ADASYN (Adaptive Synthetic Sampling)

from imblearn.over_sampling import ADASYN

adasyn = ADASYN(random_state=42)
X_resampled, y_resampled = adasyn.fit_resample(X_train, y_train)

Random Oversampling

from imblearn.over_sampling import RandomOverSampler

ros = RandomOverSampler(random_state=42)
X_resampled, y_resampled = ros.fit_resample(X_train, y_train)

Undersampling

Random Undersampling

from imblearn.under_sampling import RandomUnderSampler

rus = RandomUnderSampler(random_state=42)
X_resampled, y_resampled = rus.fit_resample(X_train, y_train)
from imblearn.under_sampling import TomekLinks

tl = TomekLinks()
X_resampled, y_resampled = tl.fit_resample(X_train, y_train)

Edited Nearest Neighbours

from imblearn.under_sampling import EditedNearestNeighbours

enn = EditedNearestNeighbours()
X_resampled, y_resampled = enn.fit_resample(X_train, y_train)

Combined Methods

from imblearn.combine import SMOTETomek

smt = SMOTETomek(random_state=42)
X_resampled, y_resampled = smt.fit_resample(X_train, y_train)

SMOTE + ENN

from imblearn.combine import SMOTEENN

sme = SMOTEENN(random_state=42)
X_resampled, y_resampled = sme.fit_resample(X_train, y_train)

Algorithm-Level Solutions

Class Weights

Scikit-learn

from sklearn.ensemble import RandomForestClassifier

# Automatic balancing
model = RandomForestClassifier(class_weight='balanced')

# Custom weights
model = RandomForestClassifier(
    class_weight={0: 1, 1: 10}  # Give class 1 10x weight
)

# Calculate weights
from sklearn.utils.class_weight import compute_class_weight

class_weights = compute_class_weight(
    'balanced',
    classes=np.unique(y_train),
    y=y_train
)
class_weight_dict = dict(zip(np.unique(y_train), class_weights))

XGBoost

import xgboost as xgb

# Scale positive weight
model = xgb.XGBClassifier(
    scale_pos_weight=len(y_train[y_train==0]) / len(y_train[y_train==1])
)

Threshold Tuning

from sklearn.metrics import precision_recall_curve

# Get probabilities
y_proba = model.predict_proba(X_test)[:, 1]

# Find optimal threshold
precision, recall, thresholds = precision_recall_curve(y_test, y_proba)
f1_scores = 2 * (precision * recall) / (precision + recall)
optimal_threshold = thresholds[np.argmax(f1_scores)]

# Use optimal threshold
y_pred = (y_proba >= optimal_threshold).astype(int)

Cost-Sensitive Learning

from sklearn.ensemble import RandomForestClassifier

# Define cost matrix
cost_matrix = {
    (0, 0): 0,    # True negative: no cost
    (0, 1): 10,   # False positive: low cost
    (1, 0): 100,  # False negative: high cost
    (1, 1): 0     # True positive: no cost
}

# Use class weights based on cost
model = RandomForestClassifier(
    class_weight={0: 1, 1: 10}  # Penalize missing class 1 more
)

Evaluation Metrics

Appropriate Metrics

from sklearn.metrics import (
    precision_score, recall_score, f1_score,
    roc_auc_score, average_precision_score,
    confusion_matrix, classification_report
)

# Don't use accuracy!
# accuracy = accuracy_score(y_test, y_pred)  # 

# Use these instead:
precision = precision_score(y_test, y_pred)  # 
recall = recall_score(y_test, y_pred)        # 
f1 = f1_score(y_test, y_pred)               # 
roc_auc = roc_auc_score(y_test, y_proba)     # 
pr_auc = average_precision_score(y_test, y_proba)  # 

# Confusion matrix
cm = confusion_matrix(y_test, y_pred)
# [[TN, FP],
#  [FN, TP]]

# Classification report
print(classification_report(y_test, y_pred))

Precision-Recall Curve

from sklearn.metrics import precision_recall_curve
import matplotlib.pyplot as plt

precision, recall, thresholds = precision_recall_curve(y_test, y_proba)

plt.plot(recall, precision)
plt.xlabel('Recall')
plt.ylabel('Precision')
plt.title('Precision-Recall Curve')
plt.show()

ROC Curve

from sklearn.metrics import roc_curve, roc_auc_score

fpr, tpr, thresholds = roc_curve(y_test, y_proba)
auc = roc_auc_score(y_test, y_proba)

plt.plot(fpr, tpr, label=f'AUC = {auc:.2f}')
plt.plot([0, 1], [0, 1], 'k--')
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('ROC Curve')
plt.legend()
plt.show()

Complete Workflow

Step-by-Step Pipeline

from imblearn.pipeline import Pipeline
from imblearn.over_sampling import SMOTE
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
from sklearn.metrics import make_scorer, f1_score

# Create pipeline
pipeline = Pipeline([
    ('smote', SMOTE(random_state=42)),
    ('classifier', RandomForestClassifier(
        class_weight='balanced',
        random_state=42
    ))
])

# Use F1-score for cross-validation
f1_scorer = make_scorer(f1_score)

# Cross-validate
scores = cross_val_score(
    pipeline, X_train, y_train,
    cv=5, scoring=f1_scorer
)

print(f"F1-score: {scores.mean():.2f} (+/- {scores.std() * 2:.2f})")
from sklearn.model_selection import GridSearchCV

# Define pipeline
pipeline = Pipeline([
    ('smote', SMOTE(random_state=42)),
    ('classifier', RandomForestClassifier(random_state=42))
])

# Define parameter grid
param_grid = {
    'smote__k_neighbors': [3, 5, 7],
    'classifier__n_estimators': [100, 200],
    'classifier__max_depth': [10, 20, None]
}

# Grid search with F1-score
grid_search = GridSearchCV(
    pipeline, param_grid,
    cv=5, scoring='f1',
    n_jobs=-1
)

grid_search.fit(X_train, y_train)
best_model = grid_search.best_estimator_

Best Practices

Do's

Don'ts

Decision Tree

YesNoIs data imbalanced?Use F1 / PR-AUCTry resamplingSMOTE / ADASYNUse class weightsTune thresholdCost-sensitive learningStandard metricsaccuracy OK as a start

Quick Reference

Resampling Methods

Method Type Best For
SMOTE Oversampling General use, creates synthetic samples
ADASYN Oversampling Adaptive, focuses on hard examples
Random Oversampling Oversampling Simple, duplicates samples
Random Undersampling Undersampling Large datasets, removes samples
Tomek Links Undersampling Clean boundary, removes noisy samples
SMOTE + Tomek Combined Best of both worlds

Metrics Comparison

Metric Use When Formula
F1-Score Balance precision/recall $2 \cdot \frac{Precision \cdot Recall}{Precision + Recall}$
Precision Minimize false positives $\frac{TP}{TP + FP}$
Recall Minimize false negatives $\frac{TP}{TP + FN}$
PR-AUC Imbalanced data Area under PR curve
ROC-AUC General performance Area under ROC curve

Class Weight Calculation

# Automatic
class_weight='balanced'

# Manual
n_samples / (n_classes * np.bincount(y))

# Custom
class_weight={0: 1, 1: 10}

Common Pitfalls

Pitfall 1: Resampling Test Set

# WRONG
X_resampled, y_resampled = smote.fit_resample(X, y)
X_train, X_test, y_train, y_test = train_test_split(X_resampled, y_resampled)

# CORRECT
X_train, X_test, y_train, y_test = train_test_split(X, y)
X_train_resampled, y_train_resampled = smote.fit_resample(X_train, y_train)

Pitfall 2: Using Accuracy

# WRONG
accuracy = accuracy_score(y_test, y_pred)

# CORRECT
f1 = f1_score(y_test, y_pred)
pr_auc = average_precision_score(y_test, y_proba)

Pitfall 3, not Validating on Original Data

# Always evaluate on original test distribution
# Don't resample test set!

Try next: Replace accuracy with PR-AUC on your next imbalanced notebook.