"""
Module: Naive Bayes
Catégorie : Apprentissage supervisé - Classification, Statistiques bayésiennes
Difficulté : Débutant

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

from scipy import stats

# Pour le module MCMC uniquement (pip install pymc arviz)
try:
    import arviz as az
    import pymc as pm
except ImportError:
    pass

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

# Explorer les données
# Type: Code exécutable
print("=" * 70)
print("       EXPLORATION DES DONNEES POUR NAIVE BAYES")
print("=" * 70)

print("\n" + "=" * 70)
print("1. APERCU DU DATASET")
print("=" * 70)
print("""
Nous avons un dataset de classification binaire.
Naive Bayes va apprendre les distributions de probabilite de chaque
feature pour chaque classe, puis utiliser le theoreme de Bayes
pour classifier de nouvelles observations.
""")
display(df.head(10), title="Dataset de Classification")

print("\n" + "=" * 70)
print("2. DISTRIBUTION DES CLASSES (PROBABILITES A PRIORI)")
print("=" * 70)
print("""
Les probabilites a priori P(classe) representent la frequence
de chaque classe dans les donnees d'entrainement.
""")
class_counts = df['label'].value_counts().sort_index()
total = len(df)
print("Distribution des classes:")
print("-" * 40)
for label, count in class_counts.items():
    prior = count / total
    print(f"  Classe {label}: {count:4d} observations ({prior:.1%})")
    print(f"            → P(Classe {label}) = {prior:.3f}")

# Verifier l'equilibre
balance_ratio = class_counts.min() / class_counts.max()
print(f"\n  Ratio d'equilibre: {balance_ratio:.2f}")
if balance_ratio > 0.8:
    print("  → Classes bien equilibrees ✓")
elif balance_ratio > 0.5:
    print("  → Leger desequilibre (acceptable)")
else:
    print("  → Desequilibre important - attention aux metriques!")

print("\n" + "=" * 70)
print("3. STATISTIQUES PAR CLASSE")
print("=" * 70)
print("""
Naive Bayes Gaussien apprend la moyenne et la variance de chaque
feature pour chaque classe. Ces statistiques sont cruciales!
""")
display(df.groupby('label').describe().round(3), title="Statistiques par classe")

print("\n" + "-" * 40)
print("INTERPRETATION DES STATISTIQUES:")
print("-" * 40)
for feature in ['feature1', 'feature2']:
    mean_0 = df[df['label'] == 0][feature].mean()
    mean_1 = df[df['label'] == 1][feature].mean()
    std_0 = df[df['label'] == 0][feature].std()
    std_1 = df[df['label'] == 1][feature].std()
    separation = abs(mean_1 - mean_0) / ((std_0 + std_1) / 2)

    print(f"\n  {feature}:")
    print(f"    Classe 0: moyenne={mean_0:.2f}, ecart-type={std_0:.2f}")
    print(f"    Classe 1: moyenne={mean_1:.2f}, ecart-type={std_1:.2f}")
    print(f"    Separation des classes: {separation:.2f}")
    if separation > 1.5:
        print(f"    → Bonne separation - feature discriminante!")
    elif separation > 0.5:
        print(f"    → Separation moderee - feature utile")
    else:
        print(f"    → Faible separation - feature peu discriminante")

print("\n" + "=" * 70)
print("RESUME")
print("=" * 70)
print(f"""
Naive Bayes va utiliser:
• Les probabilites a priori (frequence des classes)
• La moyenne et variance de chaque feature par classe

Pour predire, il calcule P(classe|features) avec Bayes!
""")


# Visualiser la distribution des features
# Type: Code exécutable
print("=" * 70)
print("    VISUALISATION DES DISTRIBUTIONS PAR CLASSE")
print("=" * 70)
print("""
Naive Bayes Gaussien suppose que chaque feature suit une
distribution normale (gaussienne) pour chaque classe.

Visualisons si cette hypothese est raisonnable!
""")

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

# Feature 1 par classe
for label in [0, 1]:
    data = df[df['label'] == label]['feature1']
    axes[0].hist(data, bins=15, alpha=0.6, label=f'Classe {label}',
                color='#9B7AC4' if label == 0 else '#F7E64D')
axes[0].set_xlabel('Feature 1')
axes[0].set_ylabel('Frequence')
axes[0].set_title('Distribution de Feature 1 par classe')
axes[0].legend()
axes[0].grid(True, alpha=0.3)

# Feature 2 par classe
for label in [0, 1]:
    data = df[df['label'] == label]['feature2']
    axes[1].hist(data, bins=15, alpha=0.6, label=f'Classe {label}',
                color='#9B7AC4' if label == 0 else '#F7E64D')
axes[1].set_xlabel('Feature 2')
axes[1].set_ylabel('Frequence')
axes[1].set_title('Distribution de Feature 2 par classe')
axes[1].legend()
axes[1].grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

print("\n" + "=" * 70)
print("INTERPRETATION DES DISTRIBUTIONS")
print("=" * 70)

print("""
Ce que nous observons:

1. FORME DES DISTRIBUTIONS:
   → Si les histogrammes ressemblent a des cloches, l'hypothese
      gaussienne est raisonnable
   → Des distributions asymetriques peuvent reduire la precision

2. CHEVAUCHEMENT DES CLASSES:
   → Peu de chevauchement = classification plus facile
   → Beaucoup de chevauchement = zone d'incertitude importante

3. CENTRES DES DISTRIBUTIONS:
   → Plus les moyennes sont eloignees, plus les classes sont separables
   → C'est ce que Naive Bayes utilise pour decider!
""")

# Calculer le chevauchement approximatif
for feature in ['feature1', 'feature2']:
    data_0 = df[df['label'] == 0][feature]
    data_1 = df[df['label'] == 1][feature]

    overlap_min = max(data_0.min(), data_1.min())
    overlap_max = min(data_0.max(), data_1.max())

    if overlap_max > overlap_min:
        total_range = max(data_0.max(), data_1.max()) - min(data_0.min(), data_1.min())
        overlap_pct = (overlap_max - overlap_min) / total_range * 100
        print(f"\n  {feature}: {overlap_pct:.0f}% de chevauchement entre classes")
    else:
        print(f"\n  {feature}: Pas de chevauchement - separation parfaite!")

print("\n" + "-" * 40)
print("CONCLUSION")
print("-" * 40)
print("""
Naive Bayes suppose que chaque feature suit une distribution
gaussienne par classe. Meme si cette hypothese n'est pas
parfaitement respectee, l'algorithme fonctionne souvent bien!
""")


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