Skip to content

Comparer et choisir entre K-Means et CAH

Évaluer un clustering sans vérité terrain

Section titled “Évaluer un clustering sans vérité terrain”

Sans étiquette de référence, comment juger qu’un clustering est « bon » ? Le score de silhouette compare, pour chaque point, sa distance moyenne aux points de son propre groupe (cohésion) à sa distance moyenne aux points du groupe voisin le plus proche (séparation). Il varie de -1 (mauvais clustering) à +1 (groupes parfaitement compacts et séparés).

from sklearn.metrics import silhouette_score
silhouette_kmeans = silhouette_score(coords_km, labels_kmeans)
silhouette_cah = silhouette_score(coords_km, labels_cah)
print("Silhouette K-Means:", round(silhouette_kmeans, 4))
print("Silhouette CAH:", round(silhouette_cah, 4))
Silhouette K-Means: 0.805
Silhouette CAH: 0.805

Les deux méthodes obtiennent, ici, un score quasi identique et élevé — cohérent avec des données générées à partir de 4 bassins géographiques bien séparés, que les deux algorithmes redécouvrent sans peine.

Une métrique ne suffit jamais : la contrainte métier

Section titled “Une métrique ne suffit jamais : la contrainte métier”
MIN_TAILLE = 15
kmeans_ok = np.all(np.bincount(labels_kmeans) >= MIN_TAILLE)
cah_ok = np.all(np.bincount(labels_cah) >= MIN_TAILLE)
K-Means respecte la contrainte (>= 15): True
CAH respecte la contrainte (>= 15): True

Un score de silhouette élevé ne garantit pas qu’une zone soit exploitable par un camion : une zone de 3 points isolés pourrait obtenir un excellent score de cohésion tout en étant opérationnellement absurde (un camion ne se déplace pas pour 3 points). D’où la contrainte de taille minimale, vérifiée en plus du score.

candidats = [("K-Means", silhouette_kmeans, labels_kmeans), ("CAH", silhouette_cah, labels_cah)]
candidats = [c for c in candidats if np.all(np.bincount(c[2]) >= MIN_TAILLE)]
methode_choisie, score_choisi, labels = max(candidats, key=lambda c: c[1])
Methode retenue: K-Means (silhouette=0.8050)

Les indices bruts (0, 1, 2, 3) attribués par l’algorithme sont arbitraires. Pour un livrable présentable, on les renomme selon un critère métier lisible — ici, du nord au sud :

ordre_zones = points.groupby("zone_brute")["y_km"].mean().sort_values(ascending=False).index
remap = {ancien: nouveau + 1 for nouveau, ancien in enumerate(ordre_zones)}
points["zone"] = points["zone_brute"].map(remap)
zone
1 35
2 40
3 25
4 30
Name: count, dtype: int64

Le découpage en zones est fait. Reste à déterminer, pour chaque zone, l’ordre de collecte le plus court.

👉 Mesurer une distance réelle et l’explosion combinatoire


Junior TSAFACK – 12/09/2026