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.805Silhouette CAH: 0.805Les 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 = 15kmeans_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): TrueCAH respecte la contrainte (>= 15): TrueUn 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)Renuméroter pour un livrable lisible
Section titled “Renuméroter pour un livrable lisible”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).indexremap = {ancien: nouveau + 1 for nouveau, ancien in enumerate(ordre_zones)}points["zone"] = points["zone_brute"].map(remap)zone1 352 403 254 30Name: count, dtype: int64Prochaine étape
Section titled “Prochaine étape”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