A fraud detection model that handles severe class imbalance with SMOTE, evaluates with precision-recall curves, and flags suspicious transactions.
The dataset has 284,807 transactions — only 492 are fraud (0.17%). This class imbalance is the main challenge.
import pandas as pd
import numpy as np
# Download from: https://www.kaggle.com/datasets/mlg-ulb/creditcardfraud
df = pd.read_csv('creditcard.csv')
print(df.shape) # (284807, 31)
print(df['Class'].value_counts())
# 0 284315 (legitimate)
# 1 492 (fraud)
fraud_pct = df['Class'].mean() * 100
print(f"Fraud rate: {fraud_pct:.2f}%") # 0.17% — extremely rare
print(df[['Time','Amount','Class']].describe())
SMOTE (Synthetic Minority Oversampling Technique) creates synthetic fraud examples to balance classes.
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from imblearn.over_sampling import SMOTE
X = df.drop('Class', axis=1)
y = df['Class']
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
# Scale Amount and Time (other features are already PCA-transformed)
scaler = StandardScaler()
X_train[['Amount','Time']] = scaler.fit_transform(X_train[['Amount','Time']])
X_test[['Amount','Time']] = scaler.transform(X_test[['Amount','Time']])
# Apply SMOTE only to training data
smote = SMOTE(random_state=42)
X_resampled, y_resampled = smote.fit_resample(X_train, y_train)
print(f"Before SMOTE: {dict(pd.Series(y_train).value_counts())}")
print(f"After SMOTE: {dict(pd.Series(y_resampled).value_counts())}")
Before SMOTE: {0: 227451, 1: 394}
After SMOTE: {0: 227451, 1: 227451}For fraud detection, recall (catching real fraud) matters more than precision (avoiding false alarms).
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import classification_report, PrecisionRecallDisplay
import matplotlib.pyplot as plt
clf = RandomForestClassifier(n_estimators=50, n_jobs=-1, random_state=42)
clf.fit(X_resampled, y_resampled)
y_pred = clf.predict(X_test)
y_proba = clf.predict_proba(X_test)[:, 1]
print(classification_report(y_test, y_pred, target_names=['Legit','Fraud']))
# Precision-Recall curve (better than ROC for imbalanced classes)
fig, ax = plt.subplots(figsize=(6,5))
PrecisionRecallDisplay.from_predictions(y_test, y_proba, ax=ax, name='Random Forest')
ax.set_title('Precision-Recall Curve — Fraud Detection')
plt.tight_layout()
plt.savefig('precision_recall.png', dpi=120)
plt.show()
Apply a lower threshold to catch more fraud at the cost of more false alarms.
# Default threshold is 0.5 — lower it to catch more fraud
THRESHOLD = 0.3
y_pred_low = (y_proba >= THRESHOLD).astype(int)
print(f"\nWith threshold={THRESHOLD}:")
print(classification_report(y_test, y_pred_low, target_names=['Legit','Fraud']))
# Show top 5 most suspicious transactions
suspicious = X_test.copy()
suspicious['fraud_prob'] = y_proba
suspicious['actual'] = y_test.values
top5 = suspicious.nlargest(5, 'fraud_prob')[['Amount','fraud_prob','actual']]
print("\nTop 5 most suspicious transactions:")
print(top5)
You handled the hardest problem in real-world ML: class imbalance. SMOTE + a lower decision threshold together make the model practical. The same pipeline — SMOTE, RF, precision-recall evaluation — is used in production fraud systems at banks and payment processors.
Spotted a bug, broken code, or something that doesn't look right? Tell us what's off and we'll fix it.