"""
Module: Clustering hiérarchique
Catégorie : Apprentissage non supervisé
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

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

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

print("""
Le clustering hierarchique cree un ARBRE de clusters (dendrogramme).
Contrairement a K-Means, on n'a pas besoin de specifier k a l'avance!

Le dendrogramme nous montre la structure COMPLETE des donnees:
- En bas: chaque point est son propre cluster
- En haut: tous les points forment un seul cluster
- La hauteur indique la "dissimilarite" entre clusters fusionnes
""")

print("\n" + "=" * 70)
print("1. APERCU DU DATASET")
print("=" * 70)
display(df.head(10), title="Premieres lignes du dataset")

print("""
Ce dataset contient des points 2D qui forment naturellement des groupes.
Notre objectif: decouvrir cette structure de maniere HIERARCHIQUE.
""")

print("\n" + "=" * 70)
print("2. DIMENSIONS ET STATISTIQUES")
print("=" * 70)

n_points, n_features = df.shape
print(f"""
DIMENSIONS:
  • Nombre de points: {n_points}
  • Nombre de features: {n_features} (x, y)

STATISTIQUES DESCRIPTIVES:
""")
display(df.describe().round(2), title="Statistiques")

print(f"""
OBSERVATIONS:
  • X varie de {df['x'].min():.1f} a {df['x'].max():.1f}
  • Y varie de {df['y'].min():.1f} a {df['y'].max():.1f}
  • Les donnees semblent bien reparties dans l'espace
""")

print("\n" + "=" * 70)
print("3. VISUALISATION DES DONNEES")
print("=" * 70)

print("""
Visualisons les donnees pour avoir une intuition des clusters possibles.
""")

plt.figure(figsize=(10, 6))
plt.scatter(df['x'], df['y'], alpha=0.6, color='#9B7AC4', s=40, edgecolors='white', linewidth=0.5)
plt.xlabel('X', fontsize=12)
plt.ylabel('Y', fontsize=12)
plt.title('Donnees de Clustering - Structure a Decouvrir', fontsize=14, fontweight='bold')
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()

print("""
ANALYSE VISUELLE:
  • On distingue visuellement plusieurs groupes de points
  • Certains groupes semblent plus denses que d'autres
  • Le clustering hierarchique va nous reveler la structure COMPLETE

AVANTAGE DU HIERARCHIQUE:
  → Pas besoin de decider combien de clusters maintenant!
  → Le dendrogramme nous montrera TOUTES les possibilites.
""")

print("\n" + "=" * 70)
print("              PRET POUR LA CONSTRUCTION DU DENDROGRAMME")
print("=" * 70)


# Construire le dendrogramme
# Type: Code exécutable
from scipy.cluster.hierarchy import dendrogram, linkage
from sklearn.preprocessing import StandardScaler

print("=" * 70)
print("       CONSTRUCTION DU DENDROGRAMME")
print("=" * 70)

print("""
Le DENDROGRAMME est un arbre qui represente l'historique complet des
fusions de clusters. C'est l'outil principal du clustering hierarchique!

ETAPES DE CONSTRUCTION:
1. Normaliser les donnees (StandardScaler)
2. Calculer la matrice de linkage (distances entre clusters)
3. Construire l'arbre des fusions
""")

print("\n" + "=" * 70)
print("1. PREPARATION DES DONNEES")
print("=" * 70)

X = df[['x', 'y']].values
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

print(f"""
Donnees brutes: {X.shape[0]} points x {X.shape[1]} dimensions

POURQUOI NORMALISER?
  • Les features doivent avoir le meme poids dans les distances
  • Sans normalisation, une feature avec grande variance dominerait
  • StandardScaler: moyenne=0, ecart-type=1

VERIFICATION DE LA NORMALISATION:
  • Avant: X moyen = {X.mean(axis=0).round(2)}, std = {X.std(axis=0).round(2)}
  • Apres: X moyen = {X_scaled.mean(axis=0).round(4)}, std = {X_scaled.std(axis=0).round(4)}
""")

print("\n" + "=" * 70)
print("2. CALCUL DE LA MATRICE DE LINKAGE")
print("=" * 70)

print("""
Le LINKAGE definit comment calculer la distance entre clusters.

METHODE 'WARD' (utilisee ici):
  • Minimise l'augmentation de variance intra-cluster
  • Tend a creer des clusters spheriques et de taille similaire
  • Recommandee pour la plupart des cas
""")

Z = linkage(X_scaled, method='ward')

print(f"""
MATRICE DE LINKAGE CALCULEE:
  • Forme: {Z.shape}
  • Chaque ligne = une fusion: [cluster1, cluster2, distance, nb_points]
  • Nombre de fusions effectuees: {len(Z)}
  • Distance min de fusion: {Z[:, 2].min():.4f}
  • Distance max de fusion: {Z[:, 2].max():.4f}
""")

print("\n" + "=" * 70)
print("3. VISUALISATION DU DENDROGRAMME")
print("=" * 70)

print("""
LECTURE DU DENDROGRAMME:
  • Axe X: echantillons (feuilles de l'arbre)
  • Axe Y: distance (hauteur de fusion)
  • Chaque 'pont' horizontal = une fusion de clusters
  • La hauteur du pont = dissimilarite entre les clusters fusionnes
""")

plt.figure(figsize=(14, 7))
dendrogram(Z, truncate_mode='lastp', p=30, show_leaf_counts=True,
           leaf_rotation=90, leaf_font_size=10)
plt.xlabel('Echantillons (ou nombre de points dans le sous-arbre)', fontsize=12)
plt.ylabel('Distance de fusion', fontsize=12)
plt.title('Dendrogramme - Vue Hierarchique Complete', fontsize=14, fontweight='bold')
plt.grid(True, alpha=0.3, axis='y')
plt.tight_layout()
plt.show()

print("""
INTERPRETATION:
  • Les grands "sauts" verticaux indiquent des separations naturelles
  • Plus on coupe bas, plus on a de clusters
  • Plus on coupe haut, moins on a de clusters

COMMENT CHOISIR LE NOMBRE DE CLUSTERS?
  → Cherchez le plus grand "saut" vertical (gap)
  → Cela correspond a une separation naturelle des donnees
  → On va voir comment faire dans la section suivante!

ANALOGIE BIOLOGIQUE:
  Le dendrogramme ressemble a un arbre genealogique:
  - Les feuilles = individus (points de donnees)
  - Les branches = ancetres communs (clusters)
  - La racine = ancetre commun a tous (cluster unique)
""")

print("\n" + "=" * 70)
print("              DENDROGRAMME CONSTRUIT AVEC SUCCES!")
print("=" * 70)


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