"""
Module: K-Means Clustering
Categorie: Unsupervised
Difficulte: Debutant

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

# Explorer les donnees
# Type: Code executable
# =============================================================================
# ETAPE 1 : EXPLORATION DES DONNEES POUR LE CLUSTERING
# =============================================================================
# En clustering non supervise, nous n'avons PAS de labels !
# L'objectif est de decouvrir des structures cachees dans les donnees.

print("=" * 70)
print("EXPLORATION DU DATASET POUR CLUSTERING")
print("=" * 70)
print()
print("RAPPEL : En clustering, nous n'avons PAS de labels predefinies.")
print("L'algorithme doit trouver les groupes par lui-meme !")
print()

# --- 1.1 Apercu des donnees ---
print("1. APERCU DES DONNEES")
print("-" * 40)
print("Chaque ligne represente un point dans un espace 2D (x, y).")
print("Nous ne savons pas a quel groupe appartient chaque point.")
print()
display(df.head(10), title="Apercu du dataset (pas de label !)")

# --- 1.2 Dimensions ---
n_points = len(df)
n_features = len(df.columns)
print()
print("2. DIMENSIONS DU DATASET")
print("-" * 40)
print(f"   Nombre de points   : {n_points}")
print(f"   Nombre de features : {n_features}")
print()
print(f"   Interpretation : Nous avons {n_points} points a regrouper")
print(f"   dans un espace a {n_features} dimensions.")

# --- 1.3 Statistiques descriptives ---
print()
print("3. STATISTIQUES DESCRIPTIVES")
print("-" * 40)
print("   Ces statistiques nous aident a comprendre l'etendue des donnees :")
print()
display(df.describe().round(3), title="Statistiques")

# --- 1.4 Analyse de l'etendue ---
print()
print("4. ANALYSE DE L'ETENDUE DES DONNEES")
print("-" * 40)

x_min, x_max = df['x'].min(), df['x'].max()
y_min, y_max = df['y'].min(), df['y'].max()
x_range = x_max - x_min
y_range = y_max - y_min

print(f"   Coordonnee X : de {x_min:.2f} a {x_max:.2f} (etendue : {x_range:.2f})")
print(f"   Coordonnee Y : de {y_min:.2f} a {y_max:.2f} (etendue : {y_range:.2f})")
print()

# Verification de l'echelle
if abs(x_range - y_range) / max(x_range, y_range) < 0.3:
    print("   ✓ Les echelles X et Y sont similaires.")
    print("     → Pas besoin de normalisation pour K-Means.")
else:
    print("   ⚠ Les echelles X et Y sont differentes.")
    print("     → Envisagez une normalisation avant K-Means.")

# --- 1.5 Detecter des patterns potentiels ---
print()
print("5. INDICES DE STRUCTURE DANS LES DONNEES")
print("-" * 40)

# Verifier si les donnees ont une distribution uniforme ou structuree
x_std = df['x'].std()
y_std = df['y'].std()

print(f"   Ecart-type X : {x_std:.3f}")
print(f"   Ecart-type Y : {y_std:.3f}")
print()
print("   Si l'ecart-type est grand par rapport a l'etendue,")
print("   les donnees sont probablement dispersees (plusieurs clusters).")
print()

# Correlation entre X et Y
correlation = df['x'].corr(df['y'])
print(f"   Correlation X-Y : {correlation:.3f}")
if abs(correlation) < 0.3:
    print("   → Faible correlation : les clusters peuvent etre dans toutes les directions.")
else:
    print("   → Correlation notable : les clusters pourraient suivre une diagonale.")

print()
print("=" * 70)
print("CONCLUSION : Visualisons les donnees pour voir les groupes naturels !")
print("=" * 70)


# Visualiser les donnees brutes
# Type: Code executable
# =============================================================================
# ETAPE 2 : VISUALISATION DES DONNEES BRUTES
# =============================================================================
# La visualisation est CRUCIALE en clustering pour :
# 1) Observer les groupes naturels a l'oeil nu
# 2) Estimer le nombre de clusters (K) a utiliser
# 3) Detecter des outliers potentiels

print("=" * 70)
print("VISUALISATION DES DONNEES BRUTES")
print("=" * 70)
print()
print("Question cle : Combien de groupes distincts voyez-vous ?")
print("Cette observation guidera notre choix de K pour K-Means.")
print()

# Creation du graphique
plt.figure(figsize=(10, 8))
plt.scatter(df['x'], df['y'], alpha=0.6, color='#9B7AC4', s=50,
            edgecolors='white', linewidth=0.5)
plt.xlabel('X', fontsize=12)
plt.ylabel('Y', fontsize=12)
plt.title('Donnees brutes - Pouvez-vous identifier des groupes naturels ?', fontsize=14)
plt.grid(True, alpha=0.3)

# Ajouter une annotation
plt.annotate('Chaque point = un echantillon a clusteriser',
             xy=(0.02, 0.98), xycoords='axes fraction',
             fontsize=10, color='gray', ha='left', va='top')

plt.tight_layout()
plt.show()

# --- Analyse visuelle guidee ---
print()
print("GUIDE D'OBSERVATION")
print("-" * 40)
print()
print("   Posez-vous ces questions en regardant le graphique :")
print()
print("   1. GROUPES DISTINCTS :")
print("      → Combien de 'nuages' de points distincts voyez-vous ?")
print("      → Les groupes sont-ils bien separes ou se chevauchent-ils ?")
print()
print("   2. FORME DES GROUPES :")
print("      → Les groupes sont-ils plutot ronds (bon pour K-Means)")
print("        ou allonges/irreguliers (mieux adapte a DBSCAN) ?")
print()
print("   3. TAILLE DES GROUPES :")
print("      → Les groupes semblent-ils de taille similaire ?")
print("      → Y a-t-il des groupes beaucoup plus petits que d'autres ?")
print()
print("   4. POINTS ISOLES :")
print("      → Y a-t-il des points tres eloignes des autres (outliers) ?")
print("      → Ces points pourraient perturber K-Means.")
print()

# Estimation visuelle
print("   ESTIMATION VISUELLE :")
print("   Sur ce dataset, on peut observer environ 5 groupes distincts.")
print("   Nous allons utiliser K=5 pour commencer, puis valider avec")
print("   la methode du coude.")


# Appliquer K-Means
# Type: Code executable
# =============================================================================
# ETAPE 3 : APPLICATION DE L'ALGORITHME K-MEANS
# =============================================================================
# K-Means est l'algorithme de clustering le plus populaire.
# Il partitionne les donnees en K groupes en minimisant la distance
# intra-cluster (les points d'un cluster sont proches de leur centroide).

# Fonction pour formater les nombres au style francais (espace comme separateur)
def fmt(n, decimals=0):
    return f"{n:,.{decimals}f}".replace(",", " ")

print("=" * 70)
print("APPLICATION DE K-MEANS CLUSTERING")
print("=" * 70)
print()

# --- 3.1 Preparation des donnees ---
print("1. PREPARATION DES DONNEES")
print("-" * 40)

X = df[['x', 'y']].values

print(f"   Matrice de features : {X.shape[0]} points x {X.shape[1]} dimensions")
print(f"   Format attendu par K-Means : tableau numpy 2D")
print()

# --- 3.2 Configuration de K-Means ---
print("2. CONFIGURATION DE K-MEANS")
print("-" * 40)
print()
print("   Parametres choisis :")
print("   → n_clusters = 5 (nombre de groupes souhaites)")
print("   → n_init = 10 (nombre d'initialisations differentes)")
print("   → random_state = 42 (pour reproductibilite)")
print()
print("   Note : n_init=10 signifie que l'algorithme sera execute")
print("          10 fois avec des centroides initiaux differents,")
print("          et le meilleur resultat sera conserve.")
print()

# --- 3.3 Entrainement ---
print("3. EXECUTION DE L'ALGORITHME")
print("-" * 40)
print()
print("   K-Means va iterer jusqu'a convergence :")
print("   1. Placer 5 centroides aleatoirement")
print("   2. Assigner chaque point au centroide le plus proche")
print("   3. Recalculer les centroides (moyenne des points)")
print("   4. Repeter jusqu'a stabilisation")
print()

kmeans = KMeans(n_clusters=5, random_state=42, n_init=10)
kmeans.fit(X)

print("   ✓ Clustering termine !")
print(f"   → Nombre d'iterations : {kmeans.n_iter_}")
print()

# --- 3.4 Resultats ---
print("4. RESULTATS DU CLUSTERING")
print("-" * 40)

labels = kmeans.labels_
centroids = kmeans.cluster_centers_
inertia = kmeans.inertia_

print()
print(f"   Nombre de clusters formes : {len(np.unique(labels))}")
print()

# Afficher les centroïdes
print("   CENTROIDES (centres des clusters) :")
print("   " + "-" * 30)
for i, c in enumerate(centroids):
    n_points_cluster = np.sum(labels == i)
    print(f"   Cluster {i} : centre = ({c[0]:6.2f}, {c[1]:6.2f}) | {n_points_cluster:3d} points")

print()

# --- 3.5 Inertie ---
print("5. QUALITE DU CLUSTERING : INERTIE")
print("-" * 40)
print()
print(f"   Inertie totale : {fmt(inertia, 2)}")
print()
print("   QU'EST-CE QUE L'INERTIE ?")
print("   → C'est la somme des distances au carre entre chaque point")
print("     et le centroide de son cluster.")
print("   → Plus l'inertie est BASSE, plus les clusters sont compacts.")
print()
print("   INTERPRETATION :")
print(f"   → Inertie = {fmt(inertia, 2)}")
print(f"   → Inertie moyenne par point = {inertia/len(X):.2f}")
print()
print("   L'inertie seule ne suffit pas a juger la qualite.")
print("   Il faut la comparer avec d'autres valeurs de K (methode du coude).")

print()
print("=" * 70)
print("K-MEANS APPLIQUE ! Visualisons les clusters obtenus.")
print("=" * 70)


# Visualiser les clusters
# Type: Code executable
# =============================================================================
# ETAPE 4 : VISUALISATION DES CLUSTERS
# =============================================================================
# La visualisation permet de valider que le clustering fait sens
# et que les groupes correspondent a notre intuition.

print("=" * 70)
print("VISUALISATION DES CLUSTERS")
print("=" * 70)
print()

# Preparation et clustering
X = df[['x', 'y']].values
kmeans = KMeans(n_clusters=5, random_state=42, n_init=10)
kmeans.fit(X)
labels = kmeans.labels_
centroids = kmeans.cluster_centers_

print("Ce graphique montre :")
print("→ Les points colores selon leur cluster assigne")
print("→ Les croix rouges : les centroides (centres) de chaque cluster")
print()

# Visualisation
plt.figure(figsize=(12, 8))
colors = ['#9B7AC4', '#F7E64D', '#4CAF50', '#2196F3', '#FF5722']

for i in range(5):
    mask = labels == i
    n_points = np.sum(mask)
    plt.scatter(X[mask, 0], X[mask, 1],
                c=colors[i], label=f'Cluster {i} ({n_points} pts)',
                s=60, alpha=0.6, edgecolors='white', linewidth=0.5)

# Centroides avec style distinctif
plt.scatter(centroids[:, 0], centroids[:, 1],
            c='red', marker='X', s=300, edgecolors='black',
            linewidths=2, label='Centroides', zorder=10)

# Annoter les centroides
for i, c in enumerate(centroids):
    plt.annotate(f'C{i}', (c[0], c[1]), xytext=(5, 5),
                 textcoords='offset points', fontsize=10, fontweight='bold')

plt.xlabel('X', fontsize=12)
plt.ylabel('Y', fontsize=12)
plt.title('K-Means Clustering (K=5) - Resultat final', fontsize=14, fontweight='bold')
plt.legend(loc='best', fontsize=10)
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()

# --- Analyse des resultats ---
print()
print("ANALYSE DES RESULTATS")
print("-" * 40)
print()

print("   TAILLE DES CLUSTERS :")
for i in range(5):
    n = np.sum(labels == i)
    pct = n / len(labels) * 100
    bar = "█" * int(pct / 2)
    print(f"   Cluster {i} : {n:3d} points ({pct:5.1f}%) {bar}")

print()
print("   OBSERVATION :")

sizes = [np.sum(labels == i) for i in range(5)]
min_size, max_size = min(sizes), max(sizes)
ratio = min_size / max_size

if ratio > 0.7:
    print("   → Les clusters sont de tailles similaires (equilibres).")
elif ratio > 0.4:
    print("   → Les clusters ont des tailles moderement differentes.")
else:
    print("   → Les clusters ont des tailles tres differentes (desequilibre).")

print()
print("   SEPARATION DES CLUSTERS :")
print("   → Les clusters semblent bien separes visuellement.")
print("   → Les points de chaque cluster sont proches de leur centroide.")
print("   → K=5 semble etre un bon choix pour ces donnees.")


# Methode du coude (Elbow Method)
# Type: Code executable
# =============================================================================
# ETAPE 5 : METHODE DU COUDE - CHOISIR K OPTIMAL
# =============================================================================
# Comment savoir si K=5 est le bon choix ?
# La methode du coude (Elbow Method) nous aide a trouver K optimal.

# Fonction pour formater les nombres au style francais (espace comme separateur)
def fmt(n, decimals=0):
    return f"{n:,.{decimals}f}".replace(",", " ")

print("=" * 70)
print("METHODE DU COUDE : TROUVER LE K OPTIMAL")
print("=" * 70)
print()

print("PRINCIPE DE LA METHODE :")
print("-" * 40)
print("   1. Tester K-Means avec differentes valeurs de K (1, 2, 3, ...)")
print("   2. Pour chaque K, noter l'inertie (somme des distances²)")
print("   3. Tracer la courbe inertie vs K")
print("   4. Chercher le 'coude' : le point ou ajouter des clusters")
print("      n'ameliore plus significativement l'inertie")
print()

# Preparation des donnees
X = df[['x', 'y']].values

# --- Test de differentes valeurs de K ---
print("CALCUL POUR K = 1 a 10")
print("-" * 40)

inertias = []
K_range = range(1, 11)

for k in K_range:
    km = KMeans(n_clusters=k, random_state=42, n_init=10)
    km.fit(X)
    inertias.append(km.inertia_)
    print(f"   K = {k:2d} → Inertie = {fmt(km.inertia_, 2):10}")

print()

# --- Visualisation ---
print("GRAPHIQUE DE LA METHODE DU COUDE")
print("-" * 40)
print()

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

# Courbe d'inertie
plt.plot(K_range, inertias, 'o-', linewidth=2, markersize=10,
         color='#9B7AC4', markerfacecolor='#F7E64D', markeredgecolor='#9B7AC4',
         markeredgewidth=2)

plt.xlabel('Nombre de clusters (K)', fontsize=12)
plt.ylabel('Inertie (somme des distances²)', fontsize=12)
plt.title('Methode du Coude - Choisir le K optimal', fontsize=14, fontweight='bold')
plt.xticks(K_range)
plt.grid(True, alpha=0.3)

# Annoter le coude
plt.annotate('← COUDE\nK optimal ≈ 5',
             xy=(5, inertias[4]),
             xytext=(6.5, inertias[2]),
             arrowprops=dict(arrowstyle='->', color='#e74c3c', lw=2),
             fontsize=12, color='#e74c3c', fontweight='bold')

# Zone de sur-segmentation
plt.axvspan(6, 10, alpha=0.1, color='red', label='Sur-segmentation')
plt.axvspan(1, 4, alpha=0.1, color='blue', label='Sous-segmentation')

plt.legend(loc='upper right')
plt.tight_layout()
plt.show()

# --- Interpretation ---
print()
print("INTERPRETATION DE LA COURBE")
print("-" * 40)
print()
print("   FORME ATTENDUE :")
print("   → L'inertie diminue toujours quand K augmente")
print("   → La diminution est FORTE au debut (K petit)")
print("   → La diminution devient FAIBLE apres le coude")
print()

print("   LE 'COUDE' :")
print("   → C'est le point d'inflexion de la courbe")
print("   → Avant : chaque cluster en plus ameliore beaucoup")
print("   → Apres : ajouter des clusters n'aide plus vraiment")
print()

# Calcul de la reduction d'inertie
print("   REDUCTION D'INERTIE PAR CLUSTER AJOUTE :")
print("   " + "-" * 35)
for k in range(1, 10):
    reduction = inertias[k-1] - inertias[k]
    pct_reduction = reduction / inertias[k-1] * 100
    marker = " ← COUDE (forte reduction avant, faible apres)" if k == 5 else ""
    print(f"   K={k} → K={k+1} : -{fmt(reduction):8} ({pct_reduction:5.1f}%){marker}")

print()
print("   CONCLUSION :")
print("   → Le coude est a K = 5")
print("   → C'est le nombre optimal de clusters pour ces donnees")
print("   → Notre choix initial de K=5 etait correct !")


# Exercice: Tester d'autres valeurs de K
# Type: Exercice
# =============================================================================
# EXERCICE : IMPACT DU CHOIX DE K
# =============================================================================
# Objectif : Comprendre visuellement ce qui se passe quand on choisit
# un K trop petit ou trop grand.

# Fonction pour formater les nombres au style francais (espace comme separateur)
def fmt(n, decimals=0):
    return f"{n:,.{decimals}f}".replace(",", " ")

print("=" * 70)
print("EXERCICE : TESTER K=3 AU LIEU DE K=5")
print("=" * 70)
print()
print("Question : Que se passe-t-il si on utilise K=3 au lieu de K=5 ?")
print()

# TODO: Creez un modele K-Means avec K=3
# kmeans_3 = KMeans(n_clusters=3, random_state=42, n_init=10)
# kmeans_3.fit(X)

# TODO: Visualisez les clusters obtenus
# Observez quels groupes naturels ont ete fusionnes

# TODO: Comparez l'inertie avec K=5
# Que remarquez-vous ?

print("-" * 40)
print("INDICE : Avec K=3, certains groupes naturels seront fusionnes.")
print("         Observez lesquels !")


# Expliquer l'assignation d'un point
# Type: Code executable
# =============================================================================
# EXPLICABILITE : POURQUOI CE POINT EST DANS CE CLUSTER ?
# =============================================================================
# En clustering, l'explicabilite est simple : le point est assigne
# au centroide le plus proche (distance minimale).

print("=" * 70)
print("EXPLICABILITE : POURQUOI CE CLUSTER ?")
print("=" * 70)
print()

# Preparation et clustering
X = df[['x', 'y']].values
feature_names = ['x', 'y']
kmeans = KMeans(n_clusters=5, random_state=42, n_init=10)
kmeans.fit(X)
centroids = kmeans.cluster_centers_
labels = kmeans.labels_

# Prendre un point a expliquer
idx = 10
point = X[idx]
cluster_assigne = labels[idx]

# --- Presentation du point ---
print("1. POINT ANALYSE")
print("-" * 40)
print()
print(f"   Coordonnees du point #{idx} :")
print(f"   → x = {point[0]:.3f}")
print(f"   → y = {point[1]:.3f}")
print()
print(f"   Cluster assigne par K-Means : Cluster {cluster_assigne}")
print()

# --- Calcul des distances a tous les centroides ---
print("2. DISTANCES A TOUS LES CENTROIDES")
print("-" * 40)
print()
print("   K-Means assigne chaque point au centroide le PLUS PROCHE.")
print("   Verifions :")
print()

distances = []
for i, centroid in enumerate(centroids):
    dist = np.sqrt(np.sum((point - centroid)**2))
    distances.append(dist)

# Afficher les distances triees
sorted_clusters = np.argsort(distances)

print(f"   {'Cluster':<10} {'Centre':<20} {'Distance':<12} {'Verdict'}")
print("   " + "-" * 55)

for rank, i in enumerate(sorted_clusters):
    centroid = centroids[i]
    dist = distances[i]
    if i == cluster_assigne:
        verdict = "← PLUS PROCHE (assigne)"
        style = "*"
    else:
        verdict = ""
        style = " "

    print(f"   {style}Cluster {i:<3} ({centroid[0]:6.2f}, {centroid[1]:6.2f})    {dist:8.3f}    {verdict}")

print()

# --- Explication en langage naturel ---
print("3. EXPLICATION EN LANGAGE NATUREL")
print("-" * 40)
print()

dist_assigne = distances[cluster_assigne]
second_closest = sorted_clusters[1]
dist_second = distances[second_closest]
diff_pct = (dist_second - dist_assigne) / dist_assigne * 100

print(f"   'Le point ({point[0]:.2f}, {point[1]:.2f}) est assigne au Cluster {cluster_assigne} car :'")
print()
print(f"   → Sa distance au centre du Cluster {cluster_assigne} est de {dist_assigne:.3f}")
print(f"   → C'est la distance LA PLUS COURTE parmi tous les clusters")
print()
print(f"   → Le 2eme cluster le plus proche est le Cluster {second_closest}")
print(f"     (distance : {dist_second:.3f}, soit {diff_pct:.1f}% plus loin)")
print()

if diff_pct > 50:
    certitude = "ELEVEE"
    commentaire = "Le point est clairement dans ce cluster."
elif diff_pct > 20:
    certitude = "MODEREE"
    commentaire = "Le point est raisonnablement bien assigne."
else:
    certitude = "FAIBLE"
    commentaire = "Le point est proche de la frontiere entre clusters."

print(f"   CERTITUDE DE L'ASSIGNATION : {certitude}")
print(f"   → {commentaire}")

print()
print("=" * 70)
print("L'explicabilite en clustering = distance au centroide !")
print("=" * 70)


# Profil des clusters
# Type: Code executable
# =============================================================================
# PROFIL DES CLUSTERS : CARACTERISER CHAQUE GROUPE
# =============================================================================
# Apres le clustering, il est essentiel de comprendre ce qui caracterise
# chaque cluster. C'est crucial pour l'interpretation metier.

print("=" * 70)
print("PROFIL DES CLUSTERS : QU'EST-CE QUI CARACTERISE CHAQUE GROUPE ?")
print("=" * 70)
print()

# Preparation et clustering
X = df[['x', 'y']].values
kmeans = KMeans(n_clusters=5, random_state=42, n_init=10)
kmeans.fit(X)
labels = kmeans.labels_
centroids = kmeans.cluster_centers_

# --- Statistiques par cluster ---
print("1. STATISTIQUES DETAILLEES PAR CLUSTER")
print("-" * 40)
print()

cluster_stats = []

for i in range(5):
    mask = labels == i
    cluster_points = X[mask]
    n_points = np.sum(mask)

    x_mean = cluster_points[:, 0].mean()
    x_std = cluster_points[:, 0].std()
    y_mean = cluster_points[:, 1].mean()
    y_std = cluster_points[:, 1].std()

    # Distance moyenne au centroide (compacite)
    distances_to_centroid = np.sqrt(np.sum((cluster_points - centroids[i])**2, axis=1))
    compacite = distances_to_centroid.mean()

    cluster_stats.append({
        'cluster': i,
        'n_points': n_points,
        'x_mean': x_mean,
        'y_mean': y_mean,
        'compacite': compacite
    })

    print(f"   CLUSTER {i}")
    print(f"   {'─' * 35}")
    print(f"   Nombre de points : {n_points} ({n_points/len(labels)*100:.1f}%)")
    print(f"   Position X       : {x_mean:.2f} ± {x_std:.2f}")
    print(f"   Position Y       : {y_mean:.2f} ± {y_std:.2f}")
    print(f"   Compacite        : {compacite:.2f} (distance moyenne au centre)")
    print()

# --- Comparaison des clusters ---
print("2. COMPARAISON DES CLUSTERS")
print("-" * 40)
print()

# Trouver le plus compact et le plus disperse
compacites = [s['compacite'] for s in cluster_stats]
plus_compact = np.argmin(compacites)
plus_disperse = np.argmax(compacites)

print(f"   Cluster le plus COMPACT  : Cluster {plus_compact} (compacite = {compacites[plus_compact]:.2f})")
print(f"   → Les points sont tres proches de leur centre.")
print()
print(f"   Cluster le plus DISPERSE : Cluster {plus_disperse} (compacite = {compacites[plus_disperse]:.2f})")
print(f"   → Les points sont plus eloignes de leur centre.")
print()

# --- Visualisation des profils ---
print("3. VISUALISATION DES DISTRIBUTIONS")
print("-" * 40)
print()

fig, axes = plt.subplots(1, 2, figsize=(14, 5))
colors = ['#9B7AC4', '#F7E64D', '#4CAF50', '#2196F3', '#FF5722']

# Distribution X par cluster
for i in range(5):
    mask = labels == i
    axes[0].hist(X[mask, 0], bins=15, alpha=0.5, label=f'Cluster {i}', color=colors[i])
axes[0].set_xlabel('Coordonnee X', fontsize=11)
axes[0].set_ylabel('Frequence', fontsize=11)
axes[0].set_title('Distribution de X par cluster', fontsize=12)
axes[0].legend()
axes[0].grid(True, alpha=0.3)

# Distribution Y par cluster
for i in range(5):
    mask = labels == i
    axes[1].hist(X[mask, 1], bins=15, alpha=0.5, label=f'Cluster {i}', color=colors[i])
axes[1].set_xlabel('Coordonnee Y', fontsize=11)
axes[1].set_ylabel('Frequence', fontsize=11)
axes[1].set_title('Distribution de Y par cluster', fontsize=12)
axes[1].legend()
axes[1].grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

# --- Interpretation metier ---
print()
print("4. EXEMPLE D'INTERPRETATION METIER")
print("-" * 40)
print()
print("   Si ces donnees representaient des clients (X=age, Y=revenu) :")
print()

for i in range(5):
    s = cluster_stats[i]
    print(f"   Cluster {i} : 'Segment {i+1}'")
    print(f"   → {s['n_points']} clients ({s['n_points']/len(labels)*100:.0f}% du total)")
    print(f"   → Caracteristiques moyennes : X={s['x_mean']:.1f}, Y={s['y_mean']:.1f}")
    print()

print("   Ce type de profiling est essentiel pour :")
print("   → Marketing cible (adapter les offres par segment)")
print("   → Personnalisation (recommandations adaptees)")
print("   → Analyse strategique (comprendre sa clientele)")

print()
print("=" * 70)
print("Le profiling transforme le clustering en insights actionnables !")
print("=" * 70)

