Detection-Fraude-PFA / data_prep.py
OthmaneW's picture
Dรฉploiement initial de l'IA (RF + SHAP)
e5ddd67 verified
Raw History Blame Contribute Delete
8.65 kB
"""
data_prep.py โ€” Pipeline de Prรฉparation des Donnรฉes
====================================================
PFA : Systรจme de Dรฉtection d'Anomalies dans les Transactions Financiรจres
Auteurs : Othmane & Ramzi
Dataset : Kaggle Credit Card Fraud Detection (creditcard.csv)
Sortie : Datasets transformรฉs dans ./data/ (format .pkl)
Usage : python data_prep.py
"""
import os
import joblib
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from imblearn.over_sampling import SMOTE
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# Configuration
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
DATA_DIR = os.path.join(os.path.dirname(__file__), "data")
CSV_PATH = os.path.join(os.path.dirname(__file__), "creditcard.csv")
RANDOM_STATE = 42
TEST_SIZE = 0.2
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# 1. Chargement des donnรฉes
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
def load_data(path: str) -> pd.DataFrame:
"""Charge le CSV et affiche les statistiques de base."""
print(f"[1/5] Chargement du dataset : {path}")
df = pd.read_csv(path)
print(f" Shape : {df.shape}")
print(f" Colonnes : {list(df.columns)}")
print(f" Valeurs NaN : {df.isnull().sum().sum()}")
n_fraud = (df["Class"] == 1).sum()
n_legit = (df["Class"] == 0).sum()
pct_fraud = n_fraud / len(df) * 100
print(f"\n Distribution des classes :")
print(f" Lรฉgitimes (0) : {n_legit:,} ({100 - pct_fraud:.2f}%)")
print(f" Fraudes (1) : {n_fraud:,} ({pct_fraud:.4f}%)")
return df
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# 2. Prรฉprocessing
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
def preprocess(df: pd.DataFrame) -> tuple[pd.DataFrame, pd.Series, StandardScaler]:
"""
Normalise les colonnes 'Time' et 'Amount' avec StandardScaler.
Les features V1โ€“V28 sont dรฉjร  normalisรฉes (rรฉsultat de PCA sur le dataset original).
Retourne : X (features), y (labels), scaler (pour rรฉutilisation en infรฉrence)
"""
print("\n[2/5] Prรฉprocessing โ€” Normalisation de Time et Amount")
df = df.copy()
scaler = StandardScaler()
df[["Time", "Amount"]] = scaler.fit_transform(df[["Time", "Amount"]])
X = df.drop(columns=["Class"])
y = df["Class"]
print(f" Features : {X.shape[1]} colonnes")
print(f" Target : 'Class' (0=Lรฉgitime, 1=Fraude)")
return X, y, scaler
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# 3. Split Train / Test
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
def split_data(
X: pd.DataFrame,
y: pd.Series,
test_size: float = TEST_SIZE,
random_state: int = RANDOM_STATE,
) -> tuple:
"""
Split stratifiรฉ 80/20.
La stratification garantit que la proportion de fraudes est prรฉservรฉe
dans les deux splits, malgrรฉ le dรฉsรฉquilibre extrรชme du dataset.
"""
print(f"\n[3/5] Split Train/Test (stratifiรฉ, test_size={test_size})")
X_train, X_test, y_train, y_test = train_test_split(
X, y,
test_size=test_size,
random_state=random_state,
stratify=y,
)
print(f" Train : {X_train.shape[0]:,} samples "
f"(fraudes : {y_train.sum():,} โ€” {y_train.mean()*100:.4f}%)")
print(f" Test : {X_test.shape[0]:,} samples "
f"(fraudes : {y_test.sum():,} โ€” {y_test.mean()*100:.4f}%)")
return X_train, X_test, y_train, y_test
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# 4. SMOTE โ€” Rรฉรฉquilibrage (Train uniquement !)
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
def apply_smote(
X_train: pd.DataFrame,
y_train: pd.Series,
random_state: int = RANDOM_STATE,
) -> tuple[pd.DataFrame, pd.Series]:
"""
Applique SMOTE sur le jeu d'entraรฎnement UNIQUEMENT.
Raison critique : Appliquer SMOTE sur le test set provoquerait du
data leakage et des mรฉtriques artificiellement gonflรฉes. Le test
set doit rester dans sa distribution naturelle (dรฉsรฉquilibrรฉe)
pour simuler des conditions rรฉelles de dรฉtection.
"""
print("\n[4/5] Application de SMOTE (sur X_train/y_train uniquement)")
print(f" Avant SMOTE : {y_train.value_counts().to_dict()}")
smote = SMOTE(random_state=random_state)
X_train_res, y_train_res = smote.fit_resample(X_train, y_train)
print(f" Aprรจs SMOTE : {pd.Series(y_train_res).value_counts().to_dict()}")
print(f" Ratio : 1:1 (parfaitement รฉquilibrรฉ)")
return X_train_res, y_train_res
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# 5. Sauvegarde des Artifacts
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
def save_artifacts(
X_train: pd.DataFrame | np.ndarray,
X_test: pd.DataFrame | np.ndarray,
y_train: pd.Series | np.ndarray,
y_test: pd.Series | np.ndarray,
scaler: StandardScaler,
) -> None:
"""Sauvegarde tous les artifacts dans ./data/ au format .pkl."""
print(f"\n[5/5] Sauvegarde dans : {DATA_DIR}/")
os.makedirs(DATA_DIR, exist_ok=True)
artifacts = {
"X_train_balanced.pkl": X_train,
"X_test.pkl": X_test,
"y_train_balanced.pkl": y_train,
"y_test.pkl": y_test,
"scaler.pkl": scaler,
}
for filename, obj in artifacts.items():
path = os.path.join(DATA_DIR, filename)
joblib.dump(obj, path)
size_mb = os.path.getsize(path) / 1_000_000
print(f" [OK] {filename:<30} ({size_mb:.2f} MB)")
print("\n[DONE] Pipeline de donnees terminee avec succes.")
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# Main
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
def main():
print("=" * 60)
print(" PFA โ€” Pipeline de Prรฉparation des Donnรฉes")
print("=" * 60)
# 1. Chargement
df = load_data(CSV_PATH)
# 2. Prรฉprocessing
X, y, scaler = preprocess(df)
# 3. Split
X_train, X_test, y_train, y_test = split_data(X, y)
# 4. SMOTE (train uniquement)
X_train_balanced, y_train_balanced = apply_smote(X_train, y_train)
# 5. Sauvegarde
save_artifacts(X_train_balanced, X_test, y_train_balanced, y_test, scaler)
print("\n--- Recapitulatif final ---")
print(f" X_train_balanced : {X_train_balanced.shape}")
print(f" X_test : {X_test.shape}")
print(f" y_train_balanced : {pd.Series(y_train_balanced).value_counts().to_dict()}")
print(f" y_test : {y_test.value_counts().to_dict()}")
if __name__ == "__main__":
main()