"""
Module: Support Vector Machine (SVM)
Categorie: Supervised Classification
Difficulte: Intermediaire

Genere 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('binary_classification.csv')

# Explorer les donnees
# Type: Code executable
print("=" * 70)
print("   EXPLORATION DES DONNEES DE CLASSIFICATION")
print("   Pour l'entrainement du SVM")
print("=" * 70)

# =================================================================
# 1. APERCU DES DONNEES
# =================================================================
print("\n" + "-" * 40)
print("1. APERCU DU DATASET")
print("-" * 40)
print("""
Ce dataset contient des exemples pour un probleme de classification binaire.
Le SVM va trouver l'hyperplan qui separe le mieux les deux classes.
""")
display(df.head(10), title="Dataset de Classification")

# =================================================================
# 2. DISTRIBUTION DES CLASSES
# =================================================================
print("\n" + "-" * 40)
print("2. DISTRIBUTION DES CLASSES")
print("-" * 40)

class_counts = df['label'].value_counts().sort_index()
total = len(df)

print(f"""
Classe 0 : {class_counts[0]:3d} echantillons ({class_counts[0]/total*100:.1f}%)
Classe 1 : {class_counts[1]:3d} echantillons ({class_counts[1]/total*100:.1f}%)

Total    : {total} echantillons
""")

# Verification de l'equilibre
ratio = min(class_counts) / max(class_counts)
if ratio > 0.8:
    print("  [OK] Classes bien equilibrees!")
    print("       Le SVM ne sera pas biaise.")
elif ratio > 0.5:
    print("  [ATTENTION] Leger desequilibre.")
    print("              Considerer class_weight='balanced'.")
else:
    print("  [ATTENTION] Desequilibre significatif!")
    print("              Utiliser class_weight='balanced' recommande.")

# =================================================================
# 3. STATISTIQUES PAR CLASSE
# =================================================================
print("\n" + "-" * 40)
print("3. STATISTIQUES PAR CLASSE")
print("-" * 40)
print("""
Comparons les moyennes des features entre les deux classes.
Si elles sont differentes, le SVM pourra les separer facilement.
""")
display(df.groupby('label').mean().round(3), title="Moyennes par classe")

# Interpreter les differences
mean_class0 = df[df['label'] == 0][['feature1', 'feature2']].mean()
mean_class1 = df[df['label'] == 1][['feature1', 'feature2']].mean()

print(f"""
DIFFERENCES ENTRE CLASSES:

Feature 1:
  - Classe 0: moyenne = {mean_class0['feature1']:.3f}
  - Classe 1: moyenne = {mean_class1['feature1']:.3f}
  - Difference: {abs(mean_class0['feature1'] - mean_class1['feature1']):.3f}

Feature 2:
  - Classe 0: moyenne = {mean_class0['feature2']:.3f}
  - Classe 1: moyenne = {mean_class1['feature2']:.3f}
  - Difference: {abs(mean_class0['feature2'] - mean_class1['feature2']):.3f}

Plus les differences sont grandes, plus la separation sera facile!
""")

# =================================================================
# 4. PLAGE DES VALEURS
# =================================================================
print("\n" + "-" * 40)
print("4. ECHELLE DES FEATURES (IMPORTANT POUR SVM!)")
print("-" * 40)

print(f"""
Feature 1: min = {df['feature1'].min():.3f}, max = {df['feature1'].max():.3f}
Feature 2: min = {df['feature2'].min():.3f}, max = {df['feature2'].max():.3f}

ATTENTION: Le SVM est sensible a l'echelle des features!
Si les echelles sont differentes, la feature avec les plus grandes
valeurs dominera le calcul de distance.

SOLUTION: Toujours normaliser les features avant SVM!
(StandardScaler ou MinMaxScaler)
""")


# Visualiser les donnees
# Type: Code executable
print("=" * 70)
print("   VISUALISATION DES DONNEES")
print("   Comprendre la separabilite des classes")
print("=" * 70)

# =================================================================
# 1. SCATTER PLOT DES CLASSES
# =================================================================
print("\n" + "-" * 40)
print("1. DISTRIBUTION SPATIALE DES CLASSES")
print("-" * 40)
print("""
Ce graphique montre la position de chaque echantillon dans l'espace 2D.
Le SVM va trouver la meilleure frontiere pour separer les couleurs.
""")

plt.figure(figsize=(10, 6))

# Separer par classe
class_0 = df[df['label'] == 0]
class_1 = df[df['label'] == 1]

plt.scatter(class_0['feature1'], class_0['feature2'],
           c='#9B7AC4', label='Classe 0', alpha=0.7, s=60, edgecolors='black')
plt.scatter(class_1['feature1'], class_1['feature2'],
           c='#F7E64D', label='Classe 1', alpha=0.7, s=60, edgecolors='black')

plt.xlabel('Feature 1', fontsize=12)
plt.ylabel('Feature 2', fontsize=12)
plt.title('Donnees de Classification Binaire', fontsize=14, fontweight='bold')
plt.legend(fontsize=11)
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()

# =================================================================
# 2. INTERPRETATION
# =================================================================
print("\n" + "-" * 40)
print("2. INTERPRETATION DU GRAPHIQUE")
print("-" * 40)

# Calculer le chevauchement approximatif
overlap_f1 = max(0, min(class_0['feature1'].max(), class_1['feature1'].max()) -
                   max(class_0['feature1'].min(), class_1['feature1'].min()))
overlap_f2 = max(0, min(class_0['feature2'].max(), class_1['feature2'].max()) -
                   max(class_0['feature2'].min(), class_1['feature2'].min()))

print(f"""
ANALYSE VISUELLE:

Separabilite des classes:
""")

if overlap_f1 < 0.5 and overlap_f2 < 0.5:
    print("  [FACILE] Les classes sont bien separees!")
    print("           Un SVM lineaire devrait suffire.")
elif overlap_f1 < 1 or overlap_f2 < 1:
    print("  [MODERE] Les classes se chevauchent partiellement.")
    print("           Un kernel RBF pourrait etre necessaire.")
else:
    print("  [DIFFICILE] Les classes sont fortement melangees.")
    print("              Essayez differents kernels et regularisation.")

print(f"""
POUR LE SVM:
- Chercher une droite (kernel linear) ou courbe (kernel rbf)
  qui passe entre les deux groupes de couleurs
- Les points les plus proches de la frontiere = Support Vectors
- La marge = espace vide entre la frontiere et les points
""")


# SVM avec kernel lineaire
# Type: Code executable
print("=" * 70)
print("   SVM AVEC KERNEL LINEAIRE")
print("   Trouver l'hyperplan a marge maximale")
print("=" * 70)

from sklearn.svm import SVC
from sklearn.preprocessing import StandardScaler

# =================================================================
# 1. PREPARATION DES DONNEES
# =================================================================
print("\n" + "-" * 40)
print("1. PREPARATION ET NORMALISATION")
print("-" * 40)

X = df[['feature1', 'feature2']].values
y = df['label'].values

print(f"""
Donnees brutes:
  X: {X.shape} (echantillons x features)
  y: {y.shape} (labels)
""")

# NORMALISATION (CRITIQUE POUR SVM!)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

print(f"""
POURQUOI NORMALISER?
Le SVM calcule des distances entre points.
Sans normalisation, une feature avec de grandes valeurs
dominerait le calcul.

Apres normalisation (StandardScaler):
  Feature 1: moyenne = {X_scaled[:,0].mean():.4f}, std = {X_scaled[:,0].std():.4f}
  Feature 2: moyenne = {X_scaled[:,1].mean():.4f}, std = {X_scaled[:,1].std():.4f}
""")

# Division train/test
X_train, X_test, y_train, y_test = train_test_split(
    X_scaled, y, test_size=0.2, random_state=42
)

print(f"""
Division:
  Train: {len(X_train)} echantillons
  Test:  {len(X_test)} echantillons
""")

# =================================================================
# 2. ENTRAINEMENT DU SVM
# =================================================================
print("\n" + "-" * 40)
print("2. ENTRAINEMENT DU SVM LINEAIRE")
print("-" * 40)

svm_linear = SVC(kernel='linear', C=1.0, random_state=42)

print(f"""
CONFIGURATION:
  kernel = 'linear' : Hyperplan (droite en 2D)
  C = 1.0          : Regularisation
                     - C petit = marge large, plus d'erreurs tolerees
                     - C grand = marge etroite, moins d'erreurs

Entrainement en cours...
""")

svm_linear.fit(X_train, y_train)

print("Entrainement termine!")

# =================================================================
# 3. ANALYSE DU MODELE
# =================================================================
print("\n" + "-" * 40)
print("3. ANALYSE DU MODELE ENTRAINE")
print("-" * 40)

n_sv = len(svm_linear.support_vectors_)
n_sv_per_class = svm_linear.n_support_

print(f"""
SUPPORT VECTORS:
  Nombre total: {n_sv} (sur {len(X_train)} echantillons d'entrainement)
  Par classe: Classe 0 = {n_sv_per_class[0]}, Classe 1 = {n_sv_per_class[1]}

INTERPRETATION:
Les Support Vectors sont les points qui "definissent" l'hyperplan.
- {n_sv/len(X_train)*100:.1f}% des points sont des support vectors
""")

if n_sv / len(X_train) < 0.3:
    print("  --> Peu de SV = frontiere bien definie, bonne generalisation")
elif n_sv / len(X_train) < 0.6:
    print("  --> Nombre modere de SV = frontiere raisonnable")
else:
    print("  --> Beaucoup de SV = donnees difficiles a separer")

# Coefficients de l'hyperplan
w = svm_linear.coef_[0]
b = svm_linear.intercept_[0]

print(f"""
EQUATION DE L'HYPERPLAN:
  {w[0]:.4f} * x1 + {w[1]:.4f} * x2 + ({b:.4f}) = 0

Pour classifier un nouveau point:
  Si {w[0]:.4f} * x1 + {w[1]:.4f} * x2 + ({b:.4f}) > 0 → Classe 1
  Sinon → Classe 0
""")

# =================================================================
# 4. EVALUATION
# =================================================================
print("\n" + "-" * 40)
print("4. EVALUATION SUR LE TEST SET")
print("-" * 40)

y_pred = svm_linear.predict(X_test)
acc = accuracy_score(y_test, y_pred)

# Matrice de confusion
from sklearn.metrics import confusion_matrix
cm = confusion_matrix(y_test, y_pred)

print(f"""
RESULTATS:
  Accuracy: {acc:.2%} ({int(acc * len(y_test))}/{len(y_test)} correct)

MATRICE DE CONFUSION:
                    Predit 0    Predit 1
  Vrai 0 (negatif)    {cm[0,0]:3d}         {cm[0,1]:3d}
  Vrai 1 (positif)    {cm[1,0]:3d}         {cm[1,1]:3d}

INTERPRETATION:
  - {cm[0,0]} vrais negatifs (classe 0 correcte)
  - {cm[1,1]} vrais positifs (classe 1 correcte)
  - {cm[0,1]} faux positifs (erreur: predit 1 au lieu de 0)
  - {cm[1,0]} faux negatifs (erreur: predit 0 au lieu de 1)
""")


# Visualiser la frontiere de decision
# Type: Code executable
print("=" * 70)
print("   VISUALISATION DE LA FRONTIERE DE DECISION")
print("   Voir l'hyperplan et les Support Vectors")
print("=" * 70)

from sklearn.svm import SVC
from sklearn.preprocessing import StandardScaler

# =================================================================
# PREPARATION
# =================================================================
X = df[['feature1', 'feature2']].values
y = df['label'].values
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

# Entrainer
svm = SVC(kernel='linear', C=1.0, random_state=42)
svm.fit(X_scaled, y)

# =================================================================
# 1. CREATION DE LA GRILLE DE DECISION
# =================================================================
print("\n" + "-" * 40)
print("1. CALCUL DE LA FRONTIERE DE DECISION")
print("-" * 40)
print("""
Pour visualiser la frontiere, on cree une grille de points
et on predit la classe de chaque point.
""")

xx, yy = np.meshgrid(
    np.linspace(X_scaled[:, 0].min() - 1, X_scaled[:, 0].max() + 1, 200),
    np.linspace(X_scaled[:, 1].min() - 1, X_scaled[:, 1].max() + 1, 200)
)
Z = svm.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)

print(f"Grille: {xx.shape[0]} x {xx.shape[1]} = {xx.size} points evalues")

# =================================================================
# 2. VISUALISATION
# =================================================================
print("\n" + "-" * 40)
print("2. VISUALISATION")
print("-" * 40)

plt.figure(figsize=(12, 8))

# Regions de decision
plt.contourf(xx, yy, Z, alpha=0.3, cmap='coolwarm', levels=1)

# Points de donnees
plt.scatter(X_scaled[y == 0, 0], X_scaled[y == 0, 1],
           c='#9B7AC4', label='Classe 0', alpha=0.7, s=60, edgecolors='black')
plt.scatter(X_scaled[y == 1, 0], X_scaled[y == 1, 1],
           c='#F7E64D', label='Classe 1', alpha=0.7, s=60, edgecolors='black')

# Support vectors (entoures en rouge)
plt.scatter(svm.support_vectors_[:, 0], svm.support_vectors_[:, 1],
           s=200, facecolors='none', edgecolors='red', linewidths=2,
           label=f'Support Vectors ({len(svm.support_vectors_)})')

# Ligne de l'hyperplan
w = svm.coef_[0]
b = svm.intercept_[0]
x_line = np.linspace(X_scaled[:, 0].min() - 1, X_scaled[:, 0].max() + 1, 100)
y_line = -(w[0] * x_line + b) / w[1]
plt.plot(x_line, y_line, 'k-', linewidth=2, label='Hyperplan')

# Marges
margin = 1 / np.sqrt(np.sum(w ** 2))
y_margin_up = -(w[0] * x_line + b + 1) / w[1]
y_margin_down = -(w[0] * x_line + b - 1) / w[1]
plt.plot(x_line, y_margin_up, 'k--', linewidth=1, alpha=0.5)
plt.plot(x_line, y_margin_down, 'k--', linewidth=1, alpha=0.5)

plt.xlabel('Feature 1 (normalise)', fontsize=12)
plt.ylabel('Feature 2 (normalise)', fontsize=12)
plt.title('SVM Lineaire - Frontiere de Decision et Support Vectors',
          fontsize=14, fontweight='bold')
plt.legend(loc='upper right', fontsize=10)
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()

# =================================================================
# 3. INTERPRETATION
# =================================================================
print("\n" + "-" * 40)
print("3. INTERPRETATION DU GRAPHIQUE")
print("-" * 40)

print(f"""
ELEMENTS DU GRAPHIQUE:

1. REGIONS COLOREES:
   - Zone bleue/rouge = regions de decision
   - Un nouveau point dans le bleu --> Classe 0
   - Un nouveau point dans le rouge --> Classe 1

2. LIGNE NOIRE = HYPERPLAN:
   - Frontiere de decision
   - Equation: {w[0]:.2f} * x1 + {w[1]:.2f} * x2 + {b:.2f} = 0

3. LIGNES POINTILLEES = MARGES:
   - Espace vide entre l'hyperplan et les points
   - Largeur de la marge: {margin:.3f}

4. CERCLES ROUGES = SUPPORT VECTORS:
   - {len(svm.support_vectors_)} points definissent l'hyperplan
   - Ce sont les points les plus proches de la frontiere
   - Si on enleve un SV, l'hyperplan change!

IMPORTANT:
Les autres points (sans cercle rouge) ne contribuent PAS
a la definition de l'hyperplan. On pourrait les enlever
sans changer la frontiere!
""")


# Comparer les kernels
# Type: Code executable
print("=" * 70)
print("   COMPARAISON DES KERNELS SVM")
print("   Linear vs RBF vs Polynomial")
print("=" * 70)

from sklearn.svm import SVC
from sklearn.preprocessing import StandardScaler

# =================================================================
# PREPARATION
# =================================================================
X = df[['feature1', 'feature2']].values
y = df['label'].values
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
X_train, X_test, y_train, y_test = train_test_split(X_scaled, y, test_size=0.2, random_state=42)

# =================================================================
# 1. ENTRAINEMENT DES DIFFERENTS KERNELS
# =================================================================
print("\n" + "-" * 40)
print("1. ENTRAINEMENT DES 3 KERNELS")
print("-" * 40)

kernels = {
    'linear': {'kernel': 'linear'},
    'rbf': {'kernel': 'rbf', 'gamma': 'scale'},
    'poly': {'kernel': 'poly', 'degree': 3}
}

results = {}
models = {}

print(f"\n{'Kernel':<12} | {'Accuracy':>10} | {'Support Vectors':>16} | {'Description'}")
print("-" * 70)

for name, params in kernels.items():
    svm = SVC(**params, random_state=42)
    svm.fit(X_train, y_train)
    acc = accuracy_score(y_test, svm.predict(X_test))
    n_sv = len(svm.support_vectors_)
    results[name] = acc
    models[name] = svm

    descriptions = {
        'linear': 'Frontiere lineaire (droite)',
        'rbf': 'Frontiere flexible (courbe)',
        'poly': 'Frontiere polynomiale (deg 3)'
    }

    print(f"{name:<12} | {acc:>9.2%} | {n_sv:>16} | {descriptions[name]}")

# =================================================================
# 2. ANALYSE
# =================================================================
print("\n" + "-" * 40)
print("2. ANALYSE DES RESULTATS")
print("-" * 40)

best_kernel = max(results, key=results.get)
worst_kernel = min(results, key=results.get)

print(f"""
MEILLEUR KERNEL: {best_kernel.upper()}
  - Accuracy: {results[best_kernel]:.2%}

COMPARAISON:
""")

for name, acc in results.items():
    diff = (acc - results['linear']) * 100
    sign = "+" if diff >= 0 else ""
    print(f"  {name}: {acc:.2%} ({sign}{diff:.1f}% vs linear)")

print(f"""
QUAND UTILISER CHAQUE KERNEL?

LINEAR:
  - Donnees lineairement separables
  - Rapidite et interpretabilite prioritaires
  - Grands datasets (>10K samples)

RBF (Radial Basis Function):
  - Choix par defaut si vous ne savez pas
  - Frontieres complexes et flexibles
  - Parametre gamma a tuner

POLYNOMIAL:
  - Relations polynomiales connues
  - Degre a choisir (2, 3, 4...)
  - Plus lent que RBF
""")

# =================================================================
# 3. VISUALISATION DES FRONTIERES
# =================================================================
print("\n" + "-" * 40)
print("3. COMPARAISON VISUELLE DES FRONTIERES")
print("-" * 40)

fig, axes = plt.subplots(1, 3, figsize=(15, 5))

xx, yy = np.meshgrid(
    np.linspace(X_scaled[:, 0].min() - 1, X_scaled[:, 0].max() + 1, 100),
    np.linspace(X_scaled[:, 1].min() - 1, X_scaled[:, 1].max() + 1, 100)
)

for ax, (name, model) in zip(axes, models.items()):
    Z = model.predict(np.c_[xx.ravel(), yy.ravel()])
    Z = Z.reshape(xx.shape)

    ax.contourf(xx, yy, Z, alpha=0.3, cmap='coolwarm', levels=1)
    ax.scatter(X_scaled[y == 0, 0], X_scaled[y == 0, 1],
               c='#9B7AC4', alpha=0.7, s=40, edgecolors='black')
    ax.scatter(X_scaled[y == 1, 0], X_scaled[y == 1, 1],
               c='#F7E64D', alpha=0.7, s=40, edgecolors='black')
    ax.scatter(model.support_vectors_[:, 0], model.support_vectors_[:, 1],
               s=100, facecolors='none', edgecolors='red', linewidths=1.5)

    ax.set_title(f'{name.upper()}\nAcc: {results[name]:.1%} | SV: {len(model.support_vectors_)}',
                 fontsize=12, fontweight='bold')
    ax.set_xlabel('Feature 1')
    ax.set_ylabel('Feature 2')
    ax.grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

print("""
Observez les differences:
- Linear: frontiere droite
- RBF: frontiere qui peut suivre les contours des donnees
- Poly: frontiere avec courbures polynomiales
""")


# Exercice: Tuner C et gamma
# Type: Exercice
# Exercice: Trouvez les meilleurs hyperparametres C et gamma

from sklearn.svm import SVC
from sklearn.preprocessing import StandardScaler

# Preparez les donnees
X = df[['feature1', 'feature2']].values
y = df['label'].values
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
X_train, X_test, y_train, y_test = train_test_split(X_scaled, y, test_size=0.2, random_state=42)

# TODO: Testez differentes combinaisons de C et gamma
C_values = [0.1, 1, 10, 100]
gamma_values = [0.01, 0.1, 1, 10]

# TODO: Utilisez une double boucle pour tester toutes les combinaisons
# TODO: Gardez la meilleure combinaison


# SHAP pour SVM
# Type: Code executable
print("=" * 70)
print("   EXPLICABILITE AVEC SHAP")
print("   Comprendre les predictions du SVM")
print("=" * 70)

from sklearn.svm import SVC
from sklearn.preprocessing import StandardScaler

# =================================================================
# PREPARATION
# =================================================================
X = df[['feature1', 'feature2']].values
y = df['label'].values
feature_names = ['feature1', 'feature2']
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
X_train, X_test, y_train, y_test = train_test_split(X_scaled, y, test_size=0.2, random_state=42)

# =================================================================
# 1. ENTRAINEMENT AVEC PROBABILITES
# =================================================================
print("\n" + "-" * 40)
print("1. ENTRAINEMENT DU SVM")
print("-" * 40)
print("""
Pour utiliser SHAP avec SVM, nous devons activer probability=True.
Cela permet d'obtenir des probabilites au lieu de predictions binaires.
""")

svm = SVC(kernel='rbf', probability=True, random_state=42)
svm.fit(X_train, y_train)

acc = accuracy_score(y_test, svm.predict(X_test))
print(f"SVM RBF entraine avec accuracy = {acc:.2%}")

# =================================================================
# 2. CALCUL SHAP AVEC KERNELEXPLAINER
# =================================================================
print("\n" + "-" * 40)
print("2. CALCUL DES VALEURS SHAP")
print("-" * 40)
print("""
Pour les SVM, on utilise KernelExplainer (modele-agnostique).
C'est plus lent que TreeExplainer mais fonctionne avec tout modele.
""")

# Echantillon de reference pour SHAP
background = shap.sample(X_train, 50)
explainer = shap.KernelExplainer(svm.predict_proba, background)

# Calculer SHAP pour un sous-ensemble (vitesse)
n_explain = min(20, len(X_test))
print(f"Calcul SHAP pour {n_explain} echantillons...")
shap_values = explainer.shap_values(X_test[:n_explain])

print("Calcul termine!")

# Extraire les valeurs SHAP pour la classe 1 (gere differentes structures SHAP)
if isinstance(shap_values, list):
    shap_values_class1 = np.array(shap_values[1])
elif len(shap_values.shape) == 3:
    shap_values_class1 = shap_values[:, :, 1]
else:
    shap_values_class1 = shap_values

# =================================================================
# 3. VISUALISATION GLOBALE
# =================================================================
print("\n" + "-" * 40)
print("3. IMPACT GLOBAL DES FEATURES")
print("-" * 40)

plt.figure(figsize=(10, 5))
shap.summary_plot(shap_values_class1, X_test[:n_explain], feature_names=feature_names, show=False)
plt.title('SHAP - SVM (probabilite de Classe 1)', fontsize=14, fontweight='bold')
plt.tight_layout()
plt.show()

print("""
INTERPRETATION:
- Chaque point = un echantillon du test set
- Position X = impact sur la probabilite de classe 1
- Couleur = valeur de la feature (rouge = haute)

Les features qui poussent vers la droite augmentent P(classe 1).
""")

# =================================================================
# 4. EXPLICATION D'UNE PREDICTION
# =================================================================
print("\n" + "-" * 40)
print("4. EXPLICATION D'UNE PREDICTION INDIVIDUELLE")
print("-" * 40)

idx = 0
sample = X_test[idx]
proba = svm.predict_proba([sample])[0]
pred_class = svm.predict([sample])[0]

print(f"""
ECHANTILLON A EXPLIQUER:
  Feature 1 (normalise): {sample[0]:.3f}
  Feature 2 (normalise): {sample[1]:.3f}

PREDICTION DU SVM:
  Probabilite Classe 0: {proba[0]:.2%}
  Probabilite Classe 1: {proba[1]:.2%}
  --> Prediction: Classe {pred_class}

CONTRIBUTIONS SHAP (pour Classe 1):
""")

for i, name in enumerate(feature_names):
    shap_val = shap_values_class1[idx][i]
    direction = "augmente" if shap_val > 0 else "diminue"
    print(f"    {name}: {shap_val:+.4f} ({direction} P(classe 1))")

# =================================================================
# CONCLUSION
# =================================================================
print("\n" + "=" * 70)
print("SYNTHESE - SVM EXPLIQUE")
print("=" * 70)
print(f"""
CE QUE NOUS AVONS APPRIS:

1. Le SVM trouve l'hyperplan a marge maximale
   - Les Support Vectors definissent la frontiere
   - Les autres points n'influencent pas l'hyperplan

2. Le Kernel Trick permet des frontieres non-lineaires
   - linear: donnees lineairement separables
   - rbf: choix par defaut, flexible
   - poly: relations polynomiales

3. Hyperparametres cles:
   - C: regularisation (petit = marge large)
   - gamma: portee de chaque point (RBF)

4. SHAP permet d'expliquer les predictions
   - Meme pour un modele "boite noire" comme SVM RBF
   - KernelExplainer fonctionne avec tout modele

NOTE: SHAP avec KernelExplainer est lent.
      Pour la production, preferer des modeles avec TreeExplainer.
""")

