Resampling changes the training data so that classes become more balanced. Install the library first:
pip install imbalanced-learn
1. Random undersampling
Remove samples from the majority class.
from imblearn.under_sampling import RandomUnderSampler
from collections import Counter
rus = RandomUnderSampler(random_state=42)
X_rus, y_rus = rus.fit_resample(X_train, y_train)
print(Counter(y_train), "->", Counter(y_rus))
| Pros | Cons |
|---|---|
| Fast, smaller training set | Throws away potentially useful data |
2. Random oversampling
Duplicate samples from the minority class.
from imblearn.over_sampling import RandomOverSampler
ros = RandomOverSampler(random_state=42)
X_ros, y_ros = ros.fit_resample(X_train, y_train)
print(Counter(y_train), "->", Counter(y_ros))
| Pros | Cons |
|---|---|
| No information lost | Exact copies encourage overfitting |
3. SMOTE (Synthetic Minority Over-sampling Technique)
Creates new synthetic minority samples by interpolating between a minority point and one of its nearest minority neighbours.
from imblearn.over_sampling import SMOTE, BorderlineSMOTE, ADASYN
smote = SMOTE(sampling_strategy="auto", k_neighbors=5, random_state=42)
X_sm, y_sm = smote.fit_resample(X_train, y_train)
print(Counter(y_train), "->", Counter(y_sm))
Variants:
| Variant | Idea |
|---|---|
| BorderlineSMOTE | Focus on minority points near the decision boundary |
| ADASYN | Generate more samples in harder-to-learn regions |
| SMOTENC | Handles mixed numeric and categorical features |
| SVMSMOTE | Uses an SVM to find border areas |
| Pros | Cons |
|---|---|
| More diverse than duplication | Can create noisy samples where classes overlap |
| Often improves recall | Needs numeric, preferably scaled features |
| Can hurt if the data is very noisy |
4. Cleaning methods (undersampling)
- Tomek links — remove majority points that form a nearest-neighbour pair with a minority point.
- Edited Nearest Neighbours (ENN) — remove points whose neighbours disagree with their label.
- NearMiss — keep majority points closest to the minority class.
from imblearn.under_sampling import TomekLinks, EditedNearestNeighbours
X_tl, y_tl = TomekLinks().fit_resample(X_train, y_train)
X_enn, y_enn = EditedNearestNeighbours().fit_resample(X_train, y_train)
5. Combined resampling
Oversample with SMOTE, then clean noisy points.
from imblearn.combine import SMOTEENN, SMOTETomek
X_comb, y_comb = SMOTETomek(random_state=42).fit_resample(X_train, y_train)
print(Counter(y_comb))
The golden rule: resample only the training set
Never resample the validation or test set — it must reflect the real-world class distribution. Resampling before the split or before cross-validation folds leaks synthetic copies of test samples into training.
Use imblearn.pipeline.Pipeline, which applies resampling only during fit.
from imblearn.pipeline import Pipeline as ImbPipeline
from sklearn.preprocessing import StandardScaler
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import StratifiedKFold, cross_val_score
pipe = ImbPipeline([
("scale", StandardScaler()),
("smote", SMOTE(random_state=42)), # applied only to training folds
("clf", RandomForestClassifier(n_estimators=200, random_state=42)),
])
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
scores = cross_val_score(pipe, X_train, y_train, cv=cv, scoring="average_precision")
print("PR-AUC per fold:", scores.round(3), "| mean:", scores.mean().round(3))
Choosing a sampling ratio
sampling_strategy controls the target balance:
| Value | Meaning |
|---|---|
"auto" | Balance to 1:1 |
0.5 | Minority becomes 50% of the majority size |
{0: 4000, 1: 2000} | Exact counts per class |
You do not always need 1:1; a moderate ratio such as 1:2 often works well.