295 lines
9.1 KiB
Python
295 lines
9.1 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Générateur de données de test pour détecter les outliers UNIVARIÉS et MULTIVARIÉS.
|
|
|
|
Ce script crée un dataset avec :
|
|
1. Des outliers univariés évidents (valeurs extrêmes dans une seule colonne)
|
|
2. Des outliers multivariés (combinaisons de valeurs normales individuellement mais anormales ensemble)
|
|
3. Des données normales pour la majorité
|
|
"""
|
|
|
|
import pandas as pd
|
|
import numpy as np
|
|
from pathlib import Path
|
|
|
|
# Configuration
|
|
np.random.seed(42)
|
|
N_NORMAL = 100 # Nombre de lignes normales
|
|
|
|
def generate_outlier_dataset():
|
|
"""
|
|
Génère un dataset avec des outliers contrôlés pour tester les deux types de détection.
|
|
"""
|
|
data = []
|
|
|
|
# ========================================================================
|
|
# 1. DONNÉES NORMALES (baseline)
|
|
# ========================================================================
|
|
print("📊 Génération des données normales...")
|
|
|
|
for i in range(N_NORMAL):
|
|
data.append({
|
|
"ID": i + 1,
|
|
"Age": np.random.normal(40, 10), # Moyenne 40, écart-type 10
|
|
"Salaire": np.random.normal(35000, 8000), # Moyenne 35k, écart-type 8k
|
|
"Experience": np.random.normal(10, 4), # Moyenne 10 ans, écart-type 4
|
|
"Performance": np.random.normal(75, 10), # Moyenne 75/100, écart-type 10
|
|
"Heures_Sup": np.random.normal(5, 3), # Moyenne 5h/mois, écart-type 3
|
|
"Type": "Normal"
|
|
})
|
|
|
|
# ========================================================================
|
|
# 2. OUTLIERS UNIVARIÉS (évidents dans UNE colonne)
|
|
# ========================================================================
|
|
print("🔴 Génération des outliers univariés...")
|
|
|
|
# Outlier 1: Âge extrême (150 ans - impossible)
|
|
data.append({
|
|
"ID": 101,
|
|
"Age": 150,
|
|
"Salaire": 38000,
|
|
"Experience": 12,
|
|
"Performance": 78,
|
|
"Heures_Sup": 6,
|
|
"Type": "Outlier_Uni_Age"
|
|
})
|
|
|
|
# Outlier 2: Salaire extrêmement élevé (500k - 10x la normale)
|
|
data.append({
|
|
"ID": 102,
|
|
"Age": 45,
|
|
"Salaire": 500000,
|
|
"Experience": 15,
|
|
"Performance": 82,
|
|
"Heures_Sup": 8,
|
|
"Type": "Outlier_Uni_Salaire"
|
|
})
|
|
|
|
# Outlier 3: Salaire négatif (impossible)
|
|
data.append({
|
|
"ID": 103,
|
|
"Age": 35,
|
|
"Salaire": -5000,
|
|
"Experience": 8,
|
|
"Performance": 72,
|
|
"Heures_Sup": 4,
|
|
"Type": "Outlier_Uni_Salaire_Neg"
|
|
})
|
|
|
|
# Outlier 4: Performance > 100 (impossible)
|
|
data.append({
|
|
"ID": 104,
|
|
"Age": 38,
|
|
"Salaire": 42000,
|
|
"Experience": 11,
|
|
"Performance": 150,
|
|
"Heures_Sup": 7,
|
|
"Type": "Outlier_Uni_Perf"
|
|
})
|
|
|
|
# Outlier 5: Heures supplémentaires négatives
|
|
data.append({
|
|
"ID": 105,
|
|
"Age": 42,
|
|
"Salaire": 36000,
|
|
"Experience": 13,
|
|
"Performance": 76,
|
|
"Heures_Sup": -20,
|
|
"Type": "Outlier_Uni_Heures"
|
|
})
|
|
|
|
# ========================================================================
|
|
# 3. OUTLIERS MULTIVARIÉS (normaux individuellement, anormaux ensemble)
|
|
# ========================================================================
|
|
print("🟣 Génération des outliers multivariés...")
|
|
|
|
# Outlier Multivarié 1: Jeune avec BEAUCUP d'expérience (impossible)
|
|
# Age=25 (normal) mais Experience=30 (impossible pour cet âge)
|
|
data.append({
|
|
"ID": 201,
|
|
"Age": 25,
|
|
"Salaire": 32000,
|
|
"Experience": 30,
|
|
"Performance": 70,
|
|
"Heures_Sup": 5,
|
|
"Type": "Outlier_Multi_Age_Exp"
|
|
})
|
|
|
|
# Outlier Multivarié 2: Haut salaire avec basse performance (suspect)
|
|
# Salaire=80k (normal possible) mais Performance=40 (anormalement bas pour ce salaire)
|
|
data.append({
|
|
"ID": 202,
|
|
"Age": 45,
|
|
"Salaire": 80000,
|
|
"Experience": 15,
|
|
"Performance": 40,
|
|
"Heures_Sup": 2,
|
|
"Type": "Outlier_Multi_Salaire_Perf"
|
|
})
|
|
|
|
# Outlier Multivarié 3: Faible expérience avec très haut salaire (suspect)
|
|
data.append({
|
|
"ID": 203,
|
|
"Age": 28,
|
|
"Salaire": 95000,
|
|
"Experience": 1,
|
|
"Performance": 85,
|
|
"Heures_Sup": 15,
|
|
"Type": "Outlier_Multi_Exp_Salaire"
|
|
})
|
|
|
|
# Outlier Multivarié 4: Personne âgée avec junior-level tout
|
|
data.append({
|
|
"ID": 204,
|
|
"Age": 65,
|
|
"Salaire": 25000,
|
|
"Experience": 1,
|
|
"Performance": 60,
|
|
"Heures_Sup": 0,
|
|
"Type": "Outlier_Multi_Senior_Junior"
|
|
})
|
|
|
|
# Outlier Multivarié 5: Performance parfaite avec 0 heures supp (rare)
|
|
data.append({
|
|
"ID": 205,
|
|
"Age": 35,
|
|
"Salaire": 40000,
|
|
"Experience": 10,
|
|
"Performance": 100,
|
|
"Heures_Sup": 0,
|
|
"Type": "Outlier_Multi_Perf_Heures"
|
|
})
|
|
|
|
# Outlier Multivarié 6: Combinaison impossible - Junior avec salaire senior ET perf max
|
|
data.append({
|
|
"ID": 206,
|
|
"Age": 22,
|
|
"Salaire": 85000,
|
|
"Experience": 0,
|
|
"Performance": 95,
|
|
"Heures_Sup": 0,
|
|
"Type": "Outlier_Multi_Impossible"
|
|
})
|
|
|
|
# ========================================================================
|
|
# 4. CAS LIMITES (valeurs frontières)
|
|
# ========================================================================
|
|
print("🎯 Génération des cas limites...")
|
|
|
|
# Cas limite 1: Zéro partout (suspect mais pas impossible)
|
|
data.append({
|
|
"ID": 301,
|
|
"Age": 22,
|
|
"Salaire": 0,
|
|
"Experience": 0,
|
|
"Performance": 0,
|
|
"Heures_Sup": 0,
|
|
"Type": "Cas_Limie_Zeros"
|
|
})
|
|
|
|
# Cas limite 2: Très âgé avec beaucoup d'expérience (normal)
|
|
data.append({
|
|
"ID": 302,
|
|
"Age": 62,
|
|
"Salaire": 70000,
|
|
"Experience": 40,
|
|
"Performance": 88,
|
|
"Heures_Sup": 3,
|
|
"Type": "Normal_Senior"
|
|
})
|
|
|
|
# Cas limite 3: Salaire minimum légal (normal)
|
|
data.append({
|
|
"ID": 303,
|
|
"Age": 25,
|
|
"Salaire": 15000,
|
|
"Experience": 2,
|
|
"Performance": 65,
|
|
"Heures_Sup": 2,
|
|
"Type": "Normal_Salaire_Min"
|
|
})
|
|
|
|
# Création du DataFrame
|
|
df = pd.DataFrame(data)
|
|
|
|
# Arrondir les valeurs numériques pour plus de clarté
|
|
numeric_cols = ["Age", "Salaire", "Experience", "Performance", "Heures_Sup"]
|
|
for col in numeric_cols:
|
|
df[col] = df[col].round(2)
|
|
|
|
# Réordonner les colonnes
|
|
df = df[["ID", "Age", "Experience", "Salaire", "Performance", "Heures_Sup", "Type"]]
|
|
|
|
return df
|
|
|
|
def main():
|
|
print("=" * 70)
|
|
print("🧪 GÉNÉRATEUR DE DONNÉES DE TEST - OUTLIERS UNIVARIÉS & MULTIVARIÉS")
|
|
print("=" * 70)
|
|
print()
|
|
|
|
# Générer le dataset
|
|
df = generate_outlier_dataset()
|
|
|
|
# Sauvegarder en CSV
|
|
output_dir = Path("/home/sepehr/dev/Data_analysis/backend/test_data")
|
|
output_dir.mkdir(exist_ok=True)
|
|
|
|
csv_path = output_dir / "test_outliers_complete.csv"
|
|
df.to_csv(csv_path, index=False)
|
|
|
|
excel_path = output_dir / "test_outliers_complete.xlsx"
|
|
df.to_excel(excel_path, index=False)
|
|
|
|
# Afficher les statistiques
|
|
print()
|
|
print("=" * 70)
|
|
print("📊 STATISTIQUES DU DATASET")
|
|
print("=" * 70)
|
|
print(f"✅ Total lignes : {len(df)}")
|
|
print(f"📈 Colonnes : {len(df.columns)}")
|
|
print()
|
|
print("🔴 Outliers univariés attendus : 5")
|
|
print(" - ID 101: Âge = 150 ans")
|
|
print(" - ID 102: Salaire = 500,000€")
|
|
print(" - ID 103: Salaire = -5,000€ (négatif)")
|
|
print(" - ID 104: Performance = 150 (>100)")
|
|
print(" - ID 105: Heures_Sup = -20 (négatif)")
|
|
print()
|
|
print("🟣 Outliers multivariés attendus : 6")
|
|
print(" - ID 201: Âge=25 avec Exp=30 (impossible)")
|
|
print(" - ID 202: Salaire=80k avec Perf=40 (incohérent)")
|
|
print(" - ID 203: Exp=1 avec Salaire=95k (suspect)")
|
|
print(" - ID 204: Âge=65 avec Exp=1 (incohérent)")
|
|
print(" - ID 205: Perf=100 avec Heures_Sup=0 (rare)")
|
|
print(" - ID 206: Âge=22, Exp=0, Salaire=85k (impossible)")
|
|
print()
|
|
print("=" * 70)
|
|
print("💾 FICHIERS GÉNÉRÉS")
|
|
print("=" * 70)
|
|
print(f"📄 CSV : {csv_path}")
|
|
print(f"📊 Excel : {excel_path}")
|
|
print()
|
|
print("=" * 70)
|
|
print("🎯 COMMENT TESTER")
|
|
print("=" * 70)
|
|
print("1. Importez le fichier 'test_outliers_complete.csv' dans l'application")
|
|
print("2. Vérifiez que les colonnes sont bien détectées comme numériques")
|
|
print("3. Les cercles ROUGES doivent apparaître sur les colonnes avec outliers univariés")
|
|
print("4. Le cercle VIOLET doit apparaître (indicateur global multivarié)")
|
|
print("5. Cliquez sur chaque indicateur pour voir les détails")
|
|
print("6. Vérifiez la cohérence des outliers détectés")
|
|
print()
|
|
print("✨ Bon testing !")
|
|
print("=" * 70)
|
|
|
|
# Afficher un aperçu des outliers
|
|
print()
|
|
print("📋 APERÇU DES OUTLIERS DANS LE DATASET :")
|
|
print("-" * 70)
|
|
outliers_df = df[df["Type"].str.contains("Outlier", case=False, na=False)]
|
|
print(outliers_df[["ID", "Type", "Age", "Experience", "Salaire", "Performance", "Heures_Sup"]].to_string(index=False))
|
|
|
|
if __name__ == "__main__":
|
|
main()
|