"""
Module: Autoencoders
Catégorie : Apprentissage non supervisé, Deep learning
Difficulté : Intermédiaire

Généré depuis la plateforme ML Formation
"""

# Imports
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, mean_squared_error, r2_score

# Charger le dataset
df = pd.read_csv('anomaly_data.csv')

# Explorer les données (transactions)
# Type: Code exécutable
print("=" * 70)
print("       EXPLORATION DES DONNEES DE TRANSACTIONS FINANCIERES")
print("=" * 70)
print("""
Les autoencoders excellent dans la DETECTION D'ANOMALIES car ils
apprennent ce qui est "normal" et signalent ce qui est "inhabituel".

Notre dataset simule des transactions bancaires avec:
- La grande majorite: transactions normales
- Une petite minorite: transactions frauduleuses (anomalies)

C'est un probleme de DESEQUILIBRE DE CLASSES tres realiste!
""")

# ========== 1. APERCU DES DONNEES ==========
print("=" * 70)
print("1. APERCU DU DATASET DE TRANSACTIONS")
print("=" * 70)
display(df.head(10), title="Echantillon de Transactions")

print("""
Chaque ligne represente une transaction avec:
- transaction_amount : Montant de la transaction (euros)
- transaction_time   : Heure de la transaction (0-24h)
- account_age        : Anciennete du compte (jours)
- num_transactions   : Nombre de transactions recentes
- is_anomaly         : 0 = normal, 1 = fraude potentielle
""")

# ========== 2. DIMENSIONS ==========
print("-" * 50)
print("2. DIMENSIONS DU DATASET")
print("-" * 50)
n_samples, n_features = df.shape
feature_cols = ['transaction_amount', 'transaction_time', 'account_age', 'num_transactions']
print(f"   Nombre de transactions : {n_samples}")
print(f"   Nombre de features     : {len(feature_cols)}")
print(f"   Features utilisees     : {', '.join(feature_cols)}")

# ========== 3. DISTRIBUTION DES CLASSES ==========
print("-" * 50)
print("3. DISTRIBUTION NORMALES vs ANOMALIES")
print("-" * 50)
counts = df['is_anomaly'].value_counts()
n_normal = counts.get(0, 0)
n_anomaly = counts.get(1, 0)
normal_pct = n_normal / len(df) * 100
anomaly_pct = n_anomaly / len(df) * 100

print(f"""
┌─────────────────────────────────────────────────┐
│  Transactions normales : {n_normal:5} ({normal_pct:5.1f}%)           │
│  Anomalies (fraudes)   : {n_anomaly:5} ({anomaly_pct:5.1f}%)            │
└─────────────────────────────────────────────────┘
""")

# ========== 4. INTERPRETATION DU DESEQUILIBRE ==========
print("-" * 50)
print("4. POURQUOI CE DESEQUILIBRE EST REALISTE?")
print("-" * 50)
ratio = n_normal / max(n_anomaly, 1)
print(f"""
Ratio normal/anomalie: {ratio:.1f}:1

Dans la vraie vie, les fraudes representent typiquement:
- Cartes bancaires : 0.1% des transactions
- Assurance        : 1-5% des reclamations
- Comptabilite     : <1% des ecritures

Notre dataset avec {anomaly_pct:.1f}% d'anomalies est deja
"genereux" en anomalies pour l'apprentissage!

DEFI: Detecter ces rares anomalies sans trop de faux positifs.
""")

# ========== 5. STATISTIQUES PAR GROUPE ==========
print("=" * 70)
print("5. COMPARAISON STATISTIQUE: NORMALES vs ANOMALIES")
print("=" * 70)
display(df.groupby('is_anomaly')[feature_cols].mean().round(2), title="Moyennes par groupe")

print("""
INTERPRETATION DES DIFFERENCES:
""")

# Calcul des differences
normal_stats = df[df['is_anomaly'] == 0][feature_cols].mean()
anomaly_stats = df[df['is_anomaly'] == 1][feature_cols].mean()

for col in feature_cols:
    diff_pct = (anomaly_stats[col] - normal_stats[col]) / max(normal_stats[col], 0.01) * 100
    direction = "↑ plus eleve" if diff_pct > 0 else "↓ plus bas"
    print(f"   {col:25}: {direction} de {abs(diff_pct):.0f}% pour les anomalies")

print("""
Ces differences sont les "signatures" que l'autoencoder apprendra
a reconnaitre comme inhabituelles!
""")


# Visualiser les données
# Type: Code exécutable
print("=" * 70)
print("       VISUALISATION DES DONNEES: NORMALES vs ANOMALIES")
print("=" * 70)
print("""
Avant de construire un autoencoder, visualisons les donnees pour
comprendre COMMENT les anomalies different des transactions normales.

L'autoencoder apprendra ces differences implicitement, mais nous
pouvons les observer directement avec des graphiques.
""")

# ========== SEPARATION DES DONNEES ==========
normal_df = df[df['is_anomaly'] == 0]
anomaly_df = df[df['is_anomaly'] == 1]

print(f"   Transactions normales a visualiser : {len(normal_df)}")
print(f"   Anomalies a visualiser             : {len(anomaly_df)}")

# ========== CREATION DES GRAPHIQUES ==========
print("\n" + "-" * 50)
print("GRAPHIQUES: Distribution et Separation")
print("-" * 50)

fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# --- Graphique 1: Distribution des montants ---
normal = df[df['is_anomaly'] == 0]['transaction_amount']
anomaly = df[df['is_anomaly'] == 1]['transaction_amount']

axes[0].hist(normal, bins=20, alpha=0.7, label=f'Normal (n={len(normal)})', color='#9B7AC4')
axes[0].hist(anomaly, bins=20, alpha=0.7, label=f'Anomalie (n={len(anomaly)})', color='#F7E64D')
axes[0].set_xlabel('Montant de transaction (euros)', fontsize=11)
axes[0].set_ylabel('Frequence', fontsize=11)
axes[0].set_title('Distribution des Montants', fontsize=12, fontweight='bold')
axes[0].legend(loc='upper right')
axes[0].grid(True, alpha=0.3)

# Ajouter lignes de moyenne
axes[0].axvline(normal.mean(), color='#9B7AC4', linestyle='--', linewidth=2, label='Moy. Normal')
axes[0].axvline(anomaly.mean(), color='#F7E64D', linestyle='--', linewidth=2, label='Moy. Anomalie')

# --- Graphique 2: Scatter 2D ---
axes[1].scatter(normal_df['transaction_amount'], normal_df['account_age'],
               alpha=0.6, label=f'Normal', color='#9B7AC4', s=40)
axes[1].scatter(anomaly_df['transaction_amount'], anomaly_df['account_age'],
               alpha=0.9, label=f'Anomalie', color='#F7E64D', s=120, marker='*', edgecolors='#e74c3c')
axes[1].set_xlabel('Montant (euros)', fontsize=11)
axes[1].set_ylabel('Age du compte (jours)', fontsize=11)
axes[1].set_title('Montant vs Age du Compte', fontsize=12, fontweight='bold')
axes[1].legend(loc='upper right')
axes[1].grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

# ========== INTERPRETATION ==========
print("""
INTERPRETATION DES GRAPHIQUES:

Graphique 1 - Distribution des montants:
─────────────────────────────────────────
• Les transactions normales suivent une distribution "typique"
• Les anomalies ont souvent des montants ATYPIQUES (tres hauts ou tres bas)
• Les lignes en pointilles montrent la difference des moyennes

Graphique 2 - Espace 2D:
─────────────────────────────────────────
• Les anomalies (etoiles jaunes) apparaissent souvent dans des zones
  peu peuplees par les transactions normales
• Un autoencoder apprendra cette "zone normale" et signalera
  les points qui en sortent
""")

# ========== STATISTIQUES COMPLEMENTAIRES ==========
print("-" * 50)
print("STATISTIQUES DE SEPARATION")
print("-" * 50)
print(f"""
Montant moyen:
  - Normal  : {normal.mean():.2f} euros
  - Anomalie: {anomaly.mean():.2f} euros
  - Ecart   : {abs(anomaly.mean() - normal.mean()):.2f} euros

Age compte moyen:
  - Normal  : {normal_df['account_age'].mean():.1f} jours
  - Anomalie: {anomaly_df['account_age'].mean():.1f} jours

Observation: Les anomalies ont des patterns differents que
l'autoencoder apprendra a distinguer!
""")


# ----------------------------------------------------------------------
# La suite de ce module demande un compte.
# Les cellules de code restantes ne sont pas dans ce fichier.
# ----------------------------------------------------------------------
