Un algorithme qui fait glisser chaque point vers la zone la plus dense de son voisinage, pas à pas, jusqu'à un sommet de densité. Les points arrivés au même sommet forment un groupe. On ne fixe pas le nombre de groupes, seulement la largeur de la fenêtre qui définit le voisinage.
Des randonneurs lâchés dans le brouillard montent toujours vers la pente la plus raide autour d'eux. Ceux qui arrivent au même sommet font partie du même massif.
La fenêtre a une largeur fixe, la bandwidth. Elle définit les voisins pris en compte.
La moyenne des voisins est du côté où ils sont les plus nombreux. En s'y déplaçant, le point monte vers une zone plus dense. On répète jusqu'à ce qu'il ne bouge plus.
Chaque sommet de densité (un mode) devient le centre d'un segment. Le nombre de segments est le nombre de sommets trouvés.
Panier moyen, achats par mois, jours depuis le dernier achat, nombre de catégories, part d'achats en promotion. La direction marketing ne sait pas combien de profils existent vraiment.
Mean Shift trouve 4 sommets de densité : clients occasionnels, clients inactifs portés sur les promos, fidèles à gros paniers, et un petit segment de très gros clients (294 sur 2 000). Le nombre de segments est une conclusion de l'analyse, pas une hypothèse de départ.
On fait varier la bandwidth autour de sa valeur estimée. Si le nombre de segments reste le même sur une plage raisonnable, la structure est solide. S'il change à chaque essai, les segments sont fragiles.
Noms donnés pour R (meanShiftR) et Python (scikit-learn). Les deux paquets n'utilisent pas le même noyau, donc pas les mêmes valeurs.
La largeur de la fenêtre, réglage décisif. Trop petite : un segment par petit amas. Trop grande : tout fusionne. En Python, estimate_bandwidth la déduit des distances entre voisins ; ici, un rayon de 1,32 sur données standardisées. meanShiftR utilise un noyau gaussien dont la bandwidth est un écart-type par variable (0,5 ici).
Obligatoire : la fenêtre a la même largeur sur toutes les variables. Sans standardisation, le panier en euros écrase la part de promotions.
En Python, bin_seeding=True ne lance les ascensions que depuis une grille de points de départ, ce qui accélère beaucoup. En R, iterations fixe le nombre maximum de pas : 10 par défaut, souvent trop peu pour converger.
# Segmentation clients sans fixer k : Mean Shift en R
library(meanShiftR)
clients <- read.csv("clients_segmentation.csv")
variables <- c("panier_moyen", "achats_par_mois", "recence_jours", "nb_categories", "part_promo")
X <- scale(clients[, variables])
# Noyau gaussien d'écart-type 0,5 sur chaque variable ; jusqu'à 100 pas de montée
modele <- meanShift(X, X, bandwidth = rep(0.5, ncol(X)), iterations = 100)
clients$segment <- as.vector(modele$assignment)
# Nombre de sommets de densité trouvés, taille et profil de chaque segment
cat("Nombre de segments trouvés :", length(unique(clients$segment)), "\n")
print(table(clients$segment))
print(round(aggregate(clients[, variables], by = list(segment = clients$segment), FUN = mean), 2))
# Segmentation clients sans fixer k : Mean Shift en Python
import pandas as pd
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import MeanShift, estimate_bandwidth
clients = pd.read_csv("clients_segmentation.csv")
variables = ["panier_moyen", "achats_par_mois", "recence_jours", "nb_categories", "part_promo"]
X = StandardScaler().fit_transform(clients[variables])
# Rayon de la fenêtre : distance moyenne de chaque client à son voisin de rang 20 % (400e sur 2 000)
rayon = estimate_bandwidth(X, quantile=0.2, random_state=42)
print("Rayon :", round(rayon, 2))
# Chaque client glisse vers le sommet de densité le plus proche
modele = MeanShift(bandwidth=rayon, bin_seeding=True).fit(X)
clients["segment"] = modele.labels_
print("Nombre de segments trouvés :", len(modele.cluster_centers_))
# Taille et profil moyen de chaque segment, en unités d'origine
print(clients["segment"].value_counts().sort_index().to_string())
print(clients.groupby("segment")[variables].mean().round(2).to_string())
On part d'une estimation automatique, comme estimate_bandwidth dans scikit-learn, qui s'appuie sur la distance aux plus proches voisins. On teste ensuite des valeurs autour et on retient une plage où le nombre de segments reste stable. La bandwidth dépend de l'échelle des données : standardisez d'abord.
K-means demande le nombre de groupes et place des centres pour minimiser les distances. Mean Shift cherche les sommets de densité et en déduit le nombre de groupes. En contrepartie, Mean Shift est bien plus lent et dépend fortement de la largeur de fenêtre.
Mal au-delà de quelques dizaines de milliers de lignes, car chaque point doit regarder ses voisins à chaque pas. Des astuces aident, comme partir d'une grille de points (bin_seeding) ou d'un échantillon. Pour de gros volumes, HDBSCAN ou Mini-batch K-means sont plus adaptés.
Très rapide et simple, mais le nombre de segments doit être choisi avant. Mean Shift peut servir à le déterminer.
Voir la fiche → densité, avec bruitAutre méthode par densité, qui isole aussi les points isolés au lieu de les rattacher à un sommet.
Voir la fiche → avec probabilitésModélise la densité par des ellipses et donne une probabilité d'appartenance. Le nombre de groupes se choisit par le BIC.
Voir la fiche →Dataistudio forme les équipes au machine learning et à l'IA, sur des cas concrets.
Nous utilisons des cookies de mesure d'audience et de suivi publicitaire pour comprendre la fréquentation du site et l'efficacité de nos annonces. Rien n'est déposé sans votre accord. En savoir plus