Lesson 31 of 40

Chapter 31 — Handling Imbalanced Classification

Class weighting  •  Resampling •  Cost-sensitive thresholds  • Minority-class evaluation

Building classifiers that detect rare but important events without being misled by headline accuracy

BRIDGE FROM CHAPTER 30  Hyperparameter tuning chooses model configurations. Imbalanced classification adds another design question: what errors matter most, and how should training and decision thresholds reflect the rare class?

Chapter map

Table 31.1. Chapter roadmap

Section

Main question

Key idea

31.1 Understanding imbalanceWhat does class imbalance mean?Rare classes may be operationally more important than frequent classes.
31.2 Accuracy can misleadWhy can a high score be useless?Majority-only predictions can hide zero minority recall.
31.3 Suitable metricsWhich metrics expose useful detection?Use precision, recall, F1, balanced accuracy, ROC AUC, PR evaluation, and MCC.
31.4 Class weightingCan learning penalize minority errors more?Increase the contribution of minority-class mistakes during fitting.
31.5 ResamplingCan the training distribution be changed?Under/oversample only within training data or training folds.
31.6 Threshold optimizationCan the operating point match real costs?Choose a probability threshold using validation data and operational constraints.
Practical labWhich intervention works best?Compare untreated, weighted, SMOTE-resampled, and threshold-adjusted models.

 

Chapter overview

Many classification problems are not naturally balanced. Fraudulent transactions, serious failures, attacks, rare diseases, and customer churn may represent only a small share of observations. The rare class can nevertheless carry the largest operational cost. A classifier that optimizes ordinary accuracy may therefore learn a solution that looks strong numerically while failing at the task that matters.

Handling imbalance is not one technique. It is a coordinated evaluation and modeling workflow: define the positive class, select metrics aligned with the decision, protect the validation and test sets, consider class weights or resampling, and choose a threshold that reflects the consequences of false positives and false negatives.

Learning objectives

  • Recognize when class imbalance is a meaningful modeling problem rather than merely an unusual class ratio.
  • Explain why accuracy can be misleading when one class dominates the dataset.
  • Use precision, recall, F1, balanced accuracy, ROC AUC, precision-recall evaluation, and Matthews correlation coefficient appropriately.
  • Configure automatic or custom class weights and explain how they change the fitting objective.
  • Compare random undersampling, random oversampling, and synthetic minority oversampling.
  • Keep all resampling operations inside training data or cross-validation folds to prevent leakage.
  • Select a decision threshold from validation data using costs, recall/precision targets, or operational capacity.
  • Compare untreated, weighted, resampled, and threshold-adjusted classifiers on the same protected test set.

31.1 Understanding imbalance

A classification dataset is imbalanced when the classes occur at substantially different frequencies. The numerical ratio alone does not determine whether intervention is necessary. The important question is whether the minority class is difficult to detect and whether errors involving that class have different consequences.

Table 31.2. Typical imbalanced-classification applications

Application

Possible positive class

Why the rare class matters

Fraud detectionFraudulent transactionMissed fraud may create direct financial loss.
Disease detectionPatient with the conditionFalse negatives may delay further clinical assessment.
Equipment failureFailure within a defined horizonMissed failures may cause downtime or safety risk.
Cyberattack detectionMalicious eventAn undetected attack may compromise systems or data.
Customer churnCustomer likely to leaveMissed churn can reduce retention opportunities.

 

Imbalance ratio

A simple diagnostic is the class prevalence. If 4% of observations are positive, a 96:4 class split exists. This is descriptive, not a decision rule. Some 60:40 problems still require cost-sensitive treatment, while some 95:5 problems may be easy to separate without special sampling.

Minority prevalence = N_minority / N_total

Always inspect counts as well as percentages.

 

PYTHON  •  Inspect the class distribution

import pandas as pd

counts = pd.Series(y).value_counts().sort_index()
shares = pd.Series(y).value_counts(normalize=True).sort_index()

summary = pd.DataFrame({
    "count": counts,
    "percentage"100 * shares,
})
print(summary)

 

 

IMPORTANT   Do not automatically resample every imbalanced dataset. Start with a strong baseline, appropriate metrics, and a clear definition of the operational error costs.

 

31.2 Why accuracy can be misleading

Accuracy counts all correct predictions equally. When the majority class dominates, a classifier can obtain a high accuracy simply by predicting the majority label for almost every observation. This can hide complete failure on the minority class.

Accuracy = (TP + TN) / (TP + TN + FP + FN)

 

Majority-class baseline example

Suppose 1,000 transactions contain 980 legitimate cases and 20 fraud cases. A classifier that predicts every transaction as legitimate obtains 98% accuracy, yet it detects none of the fraud cases: recall for fraud is 0%.

Table 31.3. Majority-only classifier

Measure

Value

Interpretation

Accuracy98%Looks high because the majority class dominates.
Fraud recall0%No fraud case is detected.
Fraud precisionUndefined / effectively no positive alertsThe model never predicts fraud.
Operational usefulnessVery lowThe rare event of interest is completely missed.

 

PYTHON  •  Demonstrate the misleading baseline

from sklearn.dummy import DummyClassifier
from sklearn.metrics import accuracy_score, recall_score

model = DummyClassifier(strategy="most_frequent")
model.fit(X_train, y_train)
y_pred = model.predict(X_test)

print("Accuracy:", accuracy_score(y_test, y_pred))
print("Minority recall:", recall_score(y_test, y_pred))

 

 

DIAGNOSTIC RULE  Always compare accuracy with minority-class recall and the confusion matrix. If the classifier mainly reproduces class prevalence, accuracy alone is not evidence of useful detection.

 

31.3 Suitable metrics

No single metric is universally best. The correct evaluation depends on whether false positives, false negatives, ranking quality, class balance, or overall correlation between predictions and labels matters most.

Table 31.4. Metrics for imbalanced classification

Metric

What it emphasizes

When it is useful

PrecisionHow many predicted positives are truly positiveFalse positives are costly or review capacity is limited.
Recall / sensitivityHow many true positives are detectedFalse negatives are costly and missing positives is unacceptable.
F1-scoreHarmonic balance of precision and recallOne summary is needed and both error types matter.
Balanced accuracyAverage recall across classesOverall class-balanced performance is desired.
ROC AUCRanking quality across TPR/FPR thresholdsGeneral discrimination, especially when both classes remain operationally relevant.
Precision-recall evaluationPrecision-recall trade-off across thresholdsPositive class is rare and alert quality is central.
MCCBalanced correlation using all confusion-matrix cellsA single robust summary is useful under unequal class sizes.

 

Balanced accuracy

Balanced accuracy gives each class equal influence by averaging class-wise recall. In binary classification it is the average of sensitivity and specificity.

Balanced accuracy = (Sensitivity + Specificity) / 2

 

Precision-recall evaluation

When the positive class is rare, the precision-recall curve often reveals operational behavior more clearly than accuracy. Average precision (AP) is a commonly used summary of the precision-recall curve. It is not identical to trapezoidal PR AUC, so reports should state exactly which summary is used.

Matthews correlation coefficient

MCC uses true positives, true negatives, false positives, and false negatives in one coefficient. Values approach +1 for excellent agreement, 0 for little predictive association, and -1 for systematically opposite predictions.

MCC = (TP×TN − FP×FN) / √[(TP+FP)(TP+FN)(TN+FP)(TN+FN)]

 

PYTHON  •  Compute a balanced metric panel

from sklearn.metrics import (
    precision_score, recall_score, f1_score,
    balanced_accuracy_score, roc_auc_score,
    average_precision_score, matthews_corrcoef,
)

print("Precision:", precision_score(y_test, y_pred))
print("Recall:", recall_score(y_test, y_pred))
print("F1:", f1_score(y_test, y_pred))
print("Balanced accuracy:", balanced_accuracy_score(y_test, y_pred))
print("ROC AUC:", roc_auc_score(y_test, y_prob))
print("Average precision:", average_precision_score(y_test, y_prob))
print("MCC:", matthews_corrcoef(y_test, y_pred))

 

 

METRIC SELECTION  Choose the primary metric before comparing interventions. Changing the metric after seeing results can turn evaluation into another form of overfitting.

 

31.4 Class weighting

Class weighting changes the learning objective without duplicating or removing observations. Errors on selected classes receive larger weights, so the estimator has a stronger incentive to fit those observations correctly.

Balanced class weights

Many scikit-learn estimators accept class_weight="balanced". The weight is inversely related to class frequency, giving rarer classes larger influence. This is a useful baseline because it requires no synthetic data and preserves the original sample.

w_c = N / (K × N_c)

N = number of samples, K = number of classes, N_c = samples in class c.

 

PYTHON  •  Automatic balanced class weights

from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler

weighted_model = Pipeline([
    ("scale", StandardScaler()),
    ("model", LogisticRegression(
        class_weight="balanced",
        max_iter=2000,
        random_state=42,
    )),
])

 

 

Custom class costs

Automatic balancing treats inverse frequency as the weighting rule. Real systems may have a different cost ratio. For example, if a false negative is considered five times more serious than a false positive, a custom class weight can be tested. The chosen weights should be validated against the real metric or cost objective.

PYTHON  •  Custom class weights

custom_model = Pipeline([
    ("scale", StandardScaler()),
    ("model", LogisticRegression(
        class_weight={01.0,  15.0},
        max_iter=2000,
        random_state=42,
    )),
])

 

 

Table 31.5. Class weighting: advantages and cautions

Advantage

Caution

Keeps all original observations.Large weights can reduce precision by producing more positive predictions.
Easy to include inside ordinary estimators.The best weight ratio is not necessarily the inverse class ratio.
Works naturally with cross-validation.Not every algorithm supports class_weight directly.
Avoids synthetic examples.Weights change fitting, not the decision threshold by themselves.

 

31.5 Resampling

Resampling modifies the class composition of the training data. Undersampling removes majority observations; oversampling duplicates or generates minority observations. These methods can improve minority detection, but they must be placed correctly in the workflow.

Random undersampling

  • Removes a random subset of majority-class observations.
  • Reduces training time when the majority class is very large.
  • Can discard useful majority patterns and increase variance.

Random oversampling

  • Duplicates minority observations until a desired class ratio is reached.
  • Preserves majority observations.
  • May encourage overfitting because identical minority examples are repeated.

Synthetic minority oversampling

SMOTE creates synthetic minority samples by interpolating between nearby minority observations. It can make the minority region easier to learn, but the generated points are not new real-world measurements. Synthetic observations can be implausible when features are noisy, mixed-type, highly constrained, or poorly scaled.

Table 31.6. Resampling methods

Method

Training effect

Main risk

Random undersamplingFewer majority casesInformation loss.
Random oversamplingRepeated minority casesOverfitting to duplicates.
SMOTESynthetic minority interpolationUnrealistic or noisy synthetic samples.

 

The leakage rule

Resampling must never be performed before the train/test split or before cross-validation folds are created. Otherwise synthetic or duplicated information derived from validation observations can enter the training data. The correct solution is to put the sampler inside an imbalanced-learn Pipeline so it is fitted only on each training fold.

PYTHON  •  Leakage-safe SMOTE pipeline

from imblearn.over_sampling import SMOTE
from imblearn.pipeline import Pipeline as ImbPipeline
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler

smote_model = ImbPipeline([
    ("scale", StandardScaler()),
    ("smote", SMOTE(random_state=42)),
    ("model", LogisticRegression(max_iter=2000, random_state=42)),
])

 

 

PYTHON  •  Cross-validate without resampling leakage

from sklearn.model_selection import StratifiedKFold, cross_validate

cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
scores = cross_validate(
    smote_model,
    X,
    y,
    cv=cv,
    scoring=["balanced_accuracy""recall""average_precision"],
)

print(scores["test_recall"].mean())

 

 

CRITICAL   Never resample the final test set. The test set should retain the natural class distribution so reported performance represents the deployment population as closely as possible.

 

31.6 Threshold optimization

A probabilistic classifier produces scores or probabilities before labels are assigned. The default threshold is often 0.5, but 0.5 has no universal operational meaning. Moving the threshold changes the balance between false positives and false negatives without retraining the model.

Recall-first systems

Lowering the threshold usually produces more positive predictions. Recall tends to increase because fewer positives are missed, but precision may fall because more negative cases are flagged.

Precision-first systems

Raising the threshold usually makes the positive decision more selective. Precision can increase, but recall may decrease because some true positives no longer cross the threshold.

Cost-sensitive threshold selection

Operational cost(t) = C_FN × FN(t) + C_FP × FP(t)

Choose t on validation data, not on the final test set.

 

PYTHON  •  Search thresholds by validation cost

import numpy as np
from sklearn.metrics import confusion_matrix

thresholds = np.linspace(0.050.9591)
fn_cost, fp_cost = 8,  1
costs = []

for threshold in thresholds:
    pred = (val_prob >= threshold).astype(int)
    tn, fp, fn, tp = confusion_matrix(y_val, pred).ravel()
    costs.append(fn_cost  * fn + fp_cost * fp)

best_threshold = thresholds[np.argmin(costs)]
print("Selected threshold:", best_threshold)

 

 

Capacity-constrained decisions

Some systems can investigate only a fixed number of alerts per day. In that case the threshold may be selected to keep the predicted-positive volume within operational capacity. A model with strong ranking quality can be valuable even when only the highest-risk cases are acted upon.

PYTHON  •  Select the top 5% highest-risk cases

import numpy as np

capacity_fraction = 0.05
threshold = np.quantile(val_prob,  1 - capacity_fraction)
val_pred = (val_prob >= threshold).astype(int)

print("Operational threshold:", threshold)
print("Alert rate:", val_pred.mean())

 

 

WORKFLOW RULE   Threshold selection is model selection. Use validation data or inner cross-validation for threshold design, then evaluate the chosen threshold once on the untouched test set.

 

Practical lab — Comparing four imbalance strategies

Goal: train one imbalanced classification problem and compare an untreated baseline, a class-weighted model, a SMOTE-resampled model, and a threshold-adjusted model. All final comparisons use the same untouched test set.

Table 31.7. Lab experiment design

Variant

Training change

Decision threshold

UntreatedOriginal training distribution0.50
Class-weightedclass_weight="balanced"0.50
ResampledSMOTE only inside training pipeline0.50
Threshold-adjustedOriginal baseline modelChosen on validation cost

 

Step 1 — Create an imbalanced dataset

PYTHON  •  Generate reproducible data

from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split

X, y = make_classification(
    n_samples=6000,
    n_features=20,
    n_informative=8,
    n_redundant=4,
    weights=[0.960.04],
    class_sep=1.0,
    flip_y=0.01,
    random_state=42,
)

X_train, X_temp, y_train, y_temp = train_test_split(
    X, y, test_size=0.30, stratify=y, random_state=42
)
X_val, X_test, y_val, y_test = train_test_split(
    X_temp, y_temp, test_size=0.50, stratify=y_temp, random_state=42
)

 

 

CHECKPOINT   Confirm that train, validation, and test sets have similar class prevalence because both splits are stratified.

 

Step 2 — Define an evaluation function

PYTHON  •  Evaluate labels and probability rankings

from sklearn.metrics import (
    accuracy_score, precision_score, recall_score, f1_score,
    balanced_accuracy_score, roc_auc_score,
    average_precision_score, matthews_corrcoef,
)

def evaluate(name, y_true, y_pred, y_prob):
    return {
        "model": name,
        "accuracy": accuracy_score(y_true, y_pred),
        "precision": precision_score(y_true, y_pred, zero_division=0),
        "recall": recall_score(y_true, y_pred),
        "f1": f1_score(y_true, y_pred),
        "balanced_accuracy": balanced_accuracy_score(y_true, y_pred),
        "roc_auc": roc_auc_score(y_true, y_prob),
        "average_precision": average_precision_score(y_true, y_prob),
        "mcc": matthews_corrcoef(y_true, y_pred),
    }

 

 

Step 3 — Train the untreated baseline

PYTHON  •  Baseline logistic regression

from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler

baseline = Pipeline([
    ("scale", StandardScaler()),
    ("model", LogisticRegression(max_iter=2000, random_state=42)),
])

baseline.fit(X_train, y_train)
baseline_prob = baseline.predict_proba(X_test)[:,  1]
baseline_pred = (baseline_prob >=0.50).astype(int)

 

 

Step 4 — Train the class-weighted model

PYTHON  •  Balanced class weights

weighted = Pipeline([
    ("scale", StandardScaler()),
    ("model", LogisticRegression(
        class_weight="balanced",
        max_iter=2000,
        random_state=42,
    )),
])

weighted.fit(X_train, y_train)
weighted_prob = weighted.predict_proba(X_test)[:,  1]
weighted_pred = (weighted_prob >=0.50).astype(int)

 

 

Step 5 — Train the resampled model

PYTHON  •  SMOTE inside an imbalanced-learn pipeline

from imblearn.over_sampling import SMOTE
from imblearn.pipeline import Pipeline as ImbPipeline

resampled = ImbPipeline([
    ("scale", StandardScaler()),
    ("smote", SMOTE(random_state=42)),
    ("model", LogisticRegression(max_iter=2000, random_state=42)),
])

resampled.fit(X_train, y_train)
resampled_prob = resampled.predict_proba(X_test)[:,  1]
resampled_pred = (resampled_prob >=0.50).astype(int)

 

 

Step 6 — Select a cost-sensitive threshold

Use the baseline model, but choose its threshold from validation data. In this exercise a false negative costs eight units and a false positive costs one unit.

PYTHON  •  Choose the threshold on validation only

import numpy as np
from sklearn.metrics import confusion_matrix

val_prob = baseline.predict_proba(X_val)[:,  1]
thresholds = np.linspace(0.050.9591)

costs = []
for threshold in thresholds:
    pred = (val_prob >= threshold).astype(int)
    tn, fp, fn, tp = confusion_matrix(y_val, pred).ravel()
    costs.append(8 * fn +1 * fp)

best_threshold = thresholds[np.argmin(costs)]
print("Best validation threshold:", best_threshold)

 

 

PYTHON  •  Apply the selected threshold once to the test set

threshold_prob = baseline.predict_proba(X_test)[:, 1]
threshold_pred = (threshold_prob >= best_threshold).astype(int)

print("Test alert rate:", threshold_pred.mean())

 

 

Step 7 — Compare all four strategies

PYTHON  •  Create one comparison table

import pandas as pd

results = [
    evaluate("Untreated", y_test, baseline_pred, baseline_prob),
    evaluate("Class weighted", y_test, weighted_pred, weighted_prob),
    evaluate("SMOTE", y_test, resampled_pred, resampled_prob),
    evaluate("Threshold adjusted", y_test, threshold_pred, threshold_prob),
]

results_df = pd.DataFrame(results).set_index("model")
print(results_df.round(3))

 

 

Step 8 — Inspect confusion matrices

PYTHON  •  Compare false positives and false negatives

from sklearn.metrics import confusion_matrix

predictions = {
    "Untreated": baseline_pred,
    "Class weighted": weighted_pred,
    "SMOTE": resampled_pred,
    "Threshold adjusted": threshold_pred,
}

for name, pred in predictions.items():
    print(name)
    print(confusion_matrix(y_test, pred))

 

 

Step 9 — Write the comparison report

Table 31.8. Questions students must answer

Question

Evidence to report

Which model has the highest minority recall?Recall and FN count.
Which model has the highest precision?Precision and FP count.
Which intervention improves balanced accuracy most?Balanced-accuracy values.
Which model ranks positives best?ROC AUC and average precision.
Which model has the strongest overall binary agreement?MCC.
Did class weighting or SMOTE change probability ranking?Compare ROC AUC/AP, not only thresholded metrics.
Was the cost-sensitive threshold below or above 0.5? Why?Selected threshold, FN cost, FP cost.
Which model should be deployed under the stated costs?Validation-selected rule plus untouched test evidence.

 

Optional extension — Cross-validated comparison

For a more stable estimate, compare the untreated, class-weighted, and SMOTE models using the same StratifiedKFold object. Threshold optimization requires its own validation logic and should not be tuned directly on the outer test fold.

PYTHON  •  Compare training interventions with identical folds

from sklearn.model_selection import StratifiedKFold, cross_validate

cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
scoring = ["balanced_accuracy""recall""f1""average_precision"]

for name, model in {
    "Untreated": baseline,
    "Class weighted": weighted,
    "SMOTE": resampled,
}.items():
    scores = cross_validate(model, X, y, cv=cv, scoring=scoring)
    print(name, scores["test_recall"].mean(), scores["test_average_precision"].mean())

 

 

Common mistakes checklist

Table 31.9. Imbalanced-classification pitfalls

Mistake

Why it is dangerous

Better practice

Reporting only accuracyMajority performance dominates the score.Report minority recall/precision and class-balanced metrics.
Oversampling before splittingValidation/test information contaminates training.Split first; resample only training data/folds.
Balancing the test setReported performance no longer matches deployment prevalence.Keep the natural test distribution.
Optimizing the threshold on test dataThe test set becomes part of model selection.Select threshold on validation or inner CV.
Assuming SMOTE always helpsSynthetic points may add noise or unrealistic combinations.Compare against untreated and weighted baselines.
Choosing class weights from frequency aloneFrequency does not equal business cost.Validate weights against the operational objective.
Using ROC AUC alone for rare positivesStrong global ranking may coexist with poor alert precision.Inspect PR behavior, AP, and threshold metrics.

 

Chapter summary

  • Class imbalance matters when class frequencies and error consequences make ordinary accuracy insufficient.
  • A majority-only classifier can achieve high accuracy while having zero minority recall.
  • Precision, recall, F1, balanced accuracy, ROC AUC, precision-recall evaluation, and MCC answer different evaluation questions.
  • Class weighting changes the loss contribution of classes without changing the sample itself.
  • Undersampling, oversampling, and SMOTE alter only the training distribution and must remain inside training folds.
  • The final test set should preserve the natural class distribution and remain untouched until evaluation.
  • Threshold optimization converts probabilities into operational decisions and should be driven by validation costs or constraints.
  • The best imbalance strategy is the one that performs well under the chosen metric and real decision costs—not the one with the highest headline accuracy.
NEXT STEP   After handling class imbalance, the course can move toward end-to-end model selection, final evaluation, interpretability, deployment preparation, or monitoring depending on the remaining syllabus.
Train a Supervised Machine Learning Model
1 Chapter 1 — Introduction to Machine Learning 2 Chapter 2 — Understanding Supervised Learning 3 Chapter 3 — The Complete Supervised Learning Workflow 4 Chapter 4 — Defining the Machine Learning Problem 5 Chapter 5 — Loading and Inspecting Data 6 Chapter 6 — Exploratory Data Analysis 7 Chapter 7 — Cleaning the Dataset 8 Chapter 8 — Feature and Target Preparation 9 Chapter 9 — Splitting the Dataset Correctly 10 Chapter 10 — Numerical Feature Preprocessing 11 Chapter 11 — Encoding Categorical Features 12 Chapter 12 — Preprocessing Pipelines 13 Chapter 13 — Baseline Models 14 Chapter 14 — Logistic Regression 15 Chapter 15 — K-Nearest Neighbors Classification 16 Chapter 16 — Decision Tree Classification 17 Chapter 17 — Ensemble Classification Models 18 Chapter 18 — Support Vector Machines 19 Chapter 19 — Linear Regression 20 Chapter 20 — Regularized Regression 21 Chapter 21 — Tree-Based Regression 22 Chapter 22 — Confusion Matrix and Basic Metrics 23 Chapter 23 — Probability-Based Classification Evaluation 24 Chapter 24 — Regression Metrics 25 Chapter 25 — Residual Analysis 26 Chapter 26 — Underfitting and Overfitting 27 Chapter 27 — Cross-Validation 28 Chapter 28 — Feature Engineering 29 Chapter 29 — Feature Selection 30 Chapter 30 — Hyperparameter Tuning 31 Chapter 31 — Handling Imbalanced Classification 32 Chapter 32 — Designing a Fair Model Comparison 33 Chapter 33 — Final Test Evaluation 34 Chapter 34 — Global Model Interpretation 35 Chapter 35 — Local Prediction Explanation 36 Chapter 36 — Error Analysis and Robustness 37 Chapter 37 — Fairness and Ethical Considerations 38 Chapter 38 — Model Persistence 39 Chapter 39 — Building a Basic Prediction Application 40 Chapter 40 — Monitoring a Supervised Model