PC & Mobile

Digital Tribes: regroupement de clients avec K-Means – Gabriel Signoretti

Digital Tribes: regroupement de clients avec K-Means - Gabriel Signoretti


Cet article fait suite à la Divide and Conquer: segmentez vos clients en utilisant l'analyse RFM histoire, où nous avons commencé à segmenter notre base d'utilisateurs en utilisant l'analyse RFM classique.

Cette fois-ci, nous allons explorer quelques techniques d’apprentissage automatique afin d’obtenir des groupements plus personnalisés et mieux ajustés. Une façon de le faire consiste à utiliser des algorithmes de clustering tels que K-Means.

Les modèles de clustering font partie du groupe d'algorithmes non supervisé Machine Learning. Dans les problèmes d’apprentissage non supervisés, il n’existe pas de variable cible claire, par ex. nous n'essayons pas de classer les clients dans différentes catégories qui, à notre connaissance, existent déjà. Au lieu de cela, l'algorithme utilise des modèles dans les données mêmes pour identifier et regrouper des observations similaires et trouver ces catégories.

le K-Means model fonctionne en recherchant différents groupes (clusters) de points de données partageant des propriétés similaires dans l'ensemble de données. Pour ce faire, il regroupe les points de manière à minimiser la distance entre tous les points et le centre de gravité de la grappe dans laquelle ils se trouvent.

En d'autres termes: l'objectif principal de l'algorithme est de trouver des grappes de sorte que la distance entre les points de données dans la même grappe soit inférieure à la distance entre deux points quelconques dans des grappes différentes. De cette façon, les membres appartenant au même groupe tendent à partager les mêmes caractéristiques et à se distinguer des membres des autres groupes.

Cela fait, revenons à notre analyse de données, allons-nous?

Puisque nous essayons d’en savoir plus sur les comportements de nos clients, nous pouvons utiliser les caractéristiques indicatives de tels comportements pour former le modèle K-Means. Par exemple, nous pouvons commencer par utiliser le récence, la fréquence, valeur monétaire, et mandat que nous avons calculé dans l'article précédent.

Avec cela, nous pouvons nous attendre à regrouper des clients partageant les mêmes idées, puis à analyser ces différents groupes pour obtenir des informations sur les modèles et les tendances afin de contribuer à la formulation de décisions commerciales futures.

Commençons par échantillonner les colonnes nécessaires de la client ensemble de données que nous avons généré dans l'article précédent et en regardant la corrélation entre les caractéristiques:

# ne conserve que les colonnes récence, fréquence, durée et monnaie
rfm = client[['customer_id', 'recency', 'frequency', 'tenure', 'mean_value', 'total_value']]
rfm.set_index ('customer_id', inplace = True)
# construire une matrice de corrélation d'entités
rfm_corr = rfm.corr ()
fig, ax = plt.subplots (1, 2, figsize = (12,6))# créer une carte thermique pour afficher les corrélations
sns.heatmap (rfm_corr, annot = True, ax = ax[0], carré = vrai)
hache[0].set_ylim ([5, 0])
hache[0].set_title ('Carte thermique de corrélation')
hache[0].set_xlabel ('Variables')
# trace la ligne de repousse pour mettre en évidence la forte corrélation
sns.regplot (rfm.frequency, rfm.total_value, ax = ax[1])
hache[1].set_title ('Fréquence x valeur totale')
hache[1].set_xlabel ('Fréquence')
hache[1].set_ylabel ('Valeur totale')
# Désactiver les étiquettes de tick
hache[1].set_yticklabels ([])
hache[1].set_xticklabels ([])
fig.suptitle ('Corrélations de caractéristiques', fontsize = 20)
plt.show ()
Figure 1: Corrélations de caractéristiques

Comme on le voit, le Valeur totale et la fréquence les colonnes sont très fortement corrélées (avec un coefficient de 0,9). Vous pensez peut-être que cela est assez évident avec le recul: les clients qui achètent plus souvent ont tendance à dépenser plus d’argent à long terme. Bien que cela montre à quel point il est important que vous inspectiez soigneusement vos données avant d'essayer de former un modèle d'apprentissage automatique!

Il est recommandé de supprimer les entités hautement corrélées, car elles ajoutent une certaine redondance à vos données et risquent de fausser le résultat final du modèle. Nous allons laisser tomber le Valeur totale et utiliser le valeur moyenne colonne dans notre analyse à partir d'ici.

rfm.drop ('total_value', axis = 1, inplace = True)

Une autre étape à franchir consiste à supprimer les valeurs aberrantes de l'ensemble de données. Depuis le K-Means algorithme repose sur le calcul de la distance entre les points (la plupart du temps une simple distance euclidienne), il est fortement influencé par la présence de valeurs aberrantes. Commençons par examiner l'intrigue de nos quatre caractéristiques.

fig, ax = plt.subplots (2, 2, figsize = (12,6))sns.boxplot (rfm.recency, orient = 'v', ax = ax[0][0])
hache[0][0].set_title ('Récence')
hache[0][0].set_ylabel ('Values')
hache[0][0].set_yticklabels ([])
hache[0][0].set_xticklabels ([])
sns.boxplot (rfm.frequency, orient = 'v', ax = ax[0][1])
hache[0][1].set_title ('Fréquence')
hache[0][1].set_ylabel ('Values')
hache[0][1].set_yticklabels ([])
hache[0][1].set_xticklabels ([])
sns.boxplot (rfm.mean_value, orient = 'v', ax = ax[1][0])
hache[1][0].set_title ('Monétaire')
hache[1][0].set_ylabel ('Values')
hache[1][0].set_yticklabels ([])
hache[1][0].set_xticklabels ([])
sns.boxplot (rfm.tenure, orient = 'v', ax = ax[1][1])
hache[1][1].set_title ('Tenure')
hache[1][1].set_ylabel ('Values')
hache[1][1].set_yticklabels ([])
hache[1][1].set_xticklabels ([])
plt.tight_layout ()
plt.show ()
Figure 2: boîte à moustaches des fonctionnalités avant le retrait des valeurs aberrantes

Nous pouvons voir la présence évidente de valeurs aberrantes dans la distribution des valeur moyenne et la fréquence des colonnes, certaines très éloignées du reste des points. Nous allons supprimer ces valeurs aberrantes sur la base des Z-score: tout point avec un score supérieur à 3 sera supprimé, c'est-à-dire tout point situé à plus de 3 écarts types de la moyenne.

à partir des statistiques d'importation scipy# supprimer les valeurs aberrantes basées sur le Z-score# Pour chaque colonne, il calcule d'abord le score Z de chaque valeur de la colonne, par rapport à la moyenne de la colonne et à l'écart type. 
# Ensuite, prend l'absolu de Z-score, car la direction n'a pas d'importance, uniquement si elle est inférieure au seuil.
# Le .all (axe = 1) garantit que pour chaque ligne, toutes les colonnes satisfont à la contrainte.
# Enfin, le résultat de cette condition est utilisé pour indexer le cadre de données.
is_inliner = (np.abs (stats.zscore (rfm)) <3) .all (axis = 1)
rfm = rfm[is_inliner]

Ensuite, nous pouvons répéter la boîte à moustaches pour vérifier les nouvelles distributions de données.

Figure 3: boîte à moustaches des caractéristiques après la suppression des valeurs aberrantes

Beaucoup mieux! il restera quelques valeurs aberrantes, car elles ne tomberont pas assez loin de la moyenne à éliminer. Cependant, les plus agressifs sont tous partis.

La dernière étape du prétraitement consiste à normaliser les données. le K-Means algorithme fonctionne mieux sur les données qui sont normalement distribuées. Dans cet esprit, nous allons utiliser un bûche fonction pour essayer de supprimer une partie de l'asymétrie des données et ensuite utiliser le StandardScaler de scikit-learn transformer les distributions pour avoir un signifier proche de 0 et a déviation standard près de 1.

# normaliser les variables
depuis sklearn.preprocessing import StandardScaler
# prend le journal pour décompresser les données
log_rfm = np.log (rfm)
# utilise fit_transfome de StandardScaler pour normaliser les données (moyenne = 0, std = 1)
scaler = StandardScaler ()
scaled_rfm_array = scaler.fit_transform (log_rfm)
scaled_rfm = pd.DataFrame (
scaled_rfm_array,
colonnes = rfm.colonnes,
index = rfm.index,
)

Terminons le prétraitement en jetant un dernier regard sur nos distributions de données.

fig, ax = plt.subplots (2, 2, figsize = (12,6))sns.distplot (scaled_rfm.recency, ax = ax[0][0])
hache[0][0].set_title ('Récence')
hache[0][0].set_xlabel ('Values')
sns.distplot (scaled_rfm.frequency, ax = ax[0][1])
hache[0][1].set_title ('Fréquence')
hache[0][1].set_xlabel ('Valeur')
sns.distplot (scaled_rfm.mean_value, ax = ax[1][0])
hache[1][0].set_title ('Monétaire')
hache[1][0].set_xlabel ('Valeur')
sns.distplot (scaled_rfm.tenure, ax = ax[1][1])
hache[1][1].set_title ('Tenure')
hache[1][1].set_xlabel ('Valeur')
plt.tight_layout ()
plt.show ()
Figure 4: distributions de fonctionnalités

Cela semble assez bon! Laissez-nous aller à la bonne partie que vous avez tous attendue.

Nous sommes enfin prêts à K-Means modèle! Cependant, l’un des inconvénients de l’algorithme K-Means est que vous devez fournir le nombre de clusters que vous souhaitez générer au préalable. L'algorithme n'est pas capable d'apprendre ce paramètre à partir des données.

Nous pouvons commencer par fixer arbitrairement le nombre de clusters à 3. C’est le nombre de niveaux que nous avons utilisé pour segmenter davantage le Score RFM sur l'article précédent et cela me semble être une bonne règle de base.

K-Means est un algorithme itératif. Avec le nombre de clusters prédéterminé, le modèle affecte chaque point de données au cluster avec le centroïde le plus proche. Ensuite, les centroïdes sont recalculés et le processus est répété jusqu'à ce qu'il n'y ait aucun changement dans l'attribution des points de données. Cela signifie que nous avons alors minimisé efficacement les distances.

# import kmeans de sklearn
à partir de sklearn.cluster import KMeans
# run kmeans
kmeans = KMeans (n_clusters = 2, random_state = 1)
kmeans.fit (scaled_rfm)
# extraire les étiquettes de la grappe du modèle ajusté
cluster_labels = kmeans.labels_

Terminé! c'est littéralement tout ce que vous avez à faire pour former un K-Means modèle. Mais attendez, nous avons établi de manière empirique le nombre de grappes à 3, mais comment pouvons-nous déterminer si ce nombre est le plus approprié pour nos données?

Nous pouvons jeter un oeil à la Somme d'erreur des carrés (SSE) de divers K-Means modèles équipés de différents nombres de K. le SSE est la somme des différences au carré entre chaque observation et la moyenne de son groupe. Il peut être utilisé comme mesure de la variation au sein d’un cluster.

le SSE la fonction diminuera de façon monotone à mesure que le nombre de grappes augmentera. Cependant, la diminution est d’abord très forte et, après une certaine valeur de K, la diminution commence à ralentir, créant ce que nous appelons un complot du coude. La valeur K ‘coude’ de la parcelle (c’est-à-dire la valeur de K pour laquelle la diminution commence à ralentir) nous donne une indication du nombre optimal de grappes pour l'ensemble de données donné. Cette méthode peut être combinée avec une connaissance du problème spécifique au domaine et des exigences pour décider du nombre de clusters à utiliser.

# trouver le nombre idéal de grappes
# Fit KMeans et calcule l'ESS pour chaque k
sse = {}
pour k dans l'intervalle (1, 11):
kmeans = KMeans (n_clusters = k, random_state = 1)
kmeans.fit (scaled_rfm)
sse[k] = kmeans.inertia_ # somme des distances au centre du centre du cluster le plus proche

# SSE de parcelle pour chaque k
plt.title ('La méthode du coude')
plt.xlabel ('k')
plt.ylabel ('SSE')
sns.pointplot (x = liste (sse.keys ()), y = liste (sse.values ​​()))
plt.show ()

Figure 4: Complot de coude

Il semble que 3 ou 4 grappes seront le nombre de grappes qui représentera le mieux l'ensemble de données. Juste pour comparer, nous allons courir K-Means Encore 3 fois pour l'ajuster avec 2, 3 et 4 grappes.

# ajustement kmeans avec 2 clustes
kmeans = KMeans (n_clusters = 2, random_state = 1)
kmeans.fit (scaled_rfm)
# extraire les lables
cluster_labels_k2 = kmeans.labels_
# assing les étiquettes de cluster à l'ensemble de données
rfm_k2 = rfm.assign (cluster = cluster_labels_k2)
# ajustement kmeans avec 3 clustes
kmeans = KMeans (n_clusters = 3, random_state = 1)
kmeans.fit (scaled_rfm)
# extraire les lables
cluster_labels_k3 = kmeans.labels_
# assing les étiquettes de cluster à l'ensemble de données
rfm_k3 = rfm.assign (cluster = cluster_labels_k3)
# ajustement kmeans avec 4 clustes
kmeans = KMeans (n_clusters = 4, random_state = 1)
kmeans.fit (scaled_rfm)
# extraire les lables
cluster_labels_k4 = kmeans.labels_
# assing les étiquettes de cluster à l'ensemble de données
rfm_k4 = rfm.assign (cluster = cluster_labels_k4)

Nous nous retrouvons avec 3 jeux de données distincts: rfm_k2, rfm_k3, et rfm_k4, chacun d’entre eux contenant les informations de cluster pour chaque valeur différente o K.

La première chose à faire est de créer des tableaux récapitulatifs pour vérifier les informations de base sur les exemples inclus dans chaque cluster. Comme ce que nous avons fait dans le précédent article, nous allons inclure des statistiques sommaires, comme le signifier valeur pour chaque fonctionnalité et le nombre d'échantillons assignés à chaque cluster.

# grouper le jeu de données rfm_k2 par les clusters
rfm_k2_summary = rfm_k2.groupby ('cluster'). agg (
récence = ('récence', 'moyenne'),
fréquence = ('fréquence', 'moyenne'),
titularisation = ('titularisation', 'moyenne'),
monétaire = ('valeur_moyenne', 'moyenne'),
samples = ('mean_value', 'count')
) .round (0)
# grouper le jeu de données rfm_k3 par les clusters
rfm_k3_summary = rfm_k3.groupby ('cluster'). agg (
récence = ('récence', 'moyenne'),
fréquence = ('fréquence', 'moyenne'),
titularisation = ('titularisation', 'moyenne'),
monétaire = ('valeur_moyenne', 'moyenne'),
samples = ('mean_value', 'count')
) .round (0)
# grouper le jeu de données rfm_k4 par les clusters
rfm_k4_summary = rfm_k4.groupby ('cluster'). agg (
récence = ('récence', 'moyenne'),
fréquence = ('fréquence', 'moyenne'),
titularisation = ('titularisation', 'moyenne'),
monétaire = ('valeur_moyenne', 'moyenne'),
samples = ('mean_value', 'count')
) .round (0)

Une autre façon de mieux comprendre et comparer les segments est de créer le soi-disant Snakeplot. Ce graphique est issu de techniques d’étude de marché permettant de comparer différents segments et fournit une représentation visuelle des attributs de chaque segment.

Pour créer ce tracé, nous devons normaliser les données (centre et échelle), puis tracer les valeurs normalisées moyennes de chaque cluster de chaque attribut.

# assigner les labos de cluster pour le rfm mis à l'échelle
scaled_rfm_k2 = scaled_rfm.copy ()
scaled_rfm_k2['cluster'] = rfm_k2.cluster
scaled_rfm_k3 = scaled_rfm.copy ()
scaled_rfm_k3['cluster'] = rfm_k3.cluster
scaled_rfm_k4 = scaled_rfm.copy ()
scaled_rfm_k4['cluster'] = rfm_k4.cluster
# fondre les images pour obtenir le format requis
rfm_k2_melt = pd.melt (
scaled_rfm_k2.reset_index (),
id_vars =['customer_id', 'cluster'],
valeur_vars =['recency', 'frequency', 'mean_value', 'tenure'],
nom_var = 'attribut',
valeur_nom = 'valeur'
)
rfm_k3_melt = pd.melt (
scaled_rfm_k3.reset_index (),
id_vars =['customer_id', 'cluster'],
valeur_vars =['recency', 'frequency', 'mean_value', 'tenure'],
nom_var = 'attribut',
valeur_nom = 'valeur'
)
rfm_k4_melt = pd.melt (
scaled_rfm_k4.reset_index (),
id_vars =['customer_id', 'cluster'],
valeur_vars =['recency', 'frequency', 'mean_value', 'tenure'],
nom_var = 'attribut',
valeur_nom = 'valeur'
)
# tracez le diagramme de serpent pour chaque jeu de données
fig, ax = plt.subplots (1, 3, figsize = (15,5))
sns.lineplot (
x = "attribut",
y = "valeur",
hue = 'cluster',
data = rfm_k2_melt,
ax = ax[0],
)
hache[0].set_title ('2 clusters')
hache[0].set_xlabel ('Variables')
sns.lineplot (
x = "attribut",
y = "valeur",
hue = 'cluster',
data = rfm_k3_melt,
ax = ax[1],
)
hache[1].set_title ('3 Clusters')
hache[1].set_xlabel ('Variables')
sns.lineplot (
x = "attribut",
y = "valeur",
hue = 'cluster',
data = rfm_k4_melt,
ax = ax[2],
)
hache[2].set_title ('4 clusters')
hache[2].set_xlabel ('Variables')
fig.suptitle ('Tracé de Snake des variables standardisées', taille de la police = 20)
plt.show ()
Figure 6: Snakeplot

Avec ça Snakeplot il devient plus facile de voir les différences entre les clusters générés.

Enfin, une dernière chose que nous pouvons voir les différences entre les groupes est de vérifier l’importance relative des attributs des groupes par rapport à la population. Avec cela, nous pouvons voir quelle caractéristique a joué un rôle plus important dans la formation des clusters. Nous devons d’abord calculer les valeurs moyennes pour chaque grappe, puis les diviser par la moyenne de la population soustraite de 1.

# importance relative des attributs de segment
cluster_avg_k2 = rfm_k2.groupby (['cluster']).signifier()
cluster_avg_k3 = rfm_k3.groupby (['cluster']).signifier()
cluster_avg_k4 = rfm_k4.groupby (['cluster']).signifier()
population_avg = rfm.mean ()
relative_imp_k2 = cluster_avg_k2 / population_avg - 1
relative_imp_k3 = cluster_avg_k3 / population_avg - 1
relative_imp_k4 = cluster_avg_k4 / population_avg - 1
fig, ax = plt.subplots (1, 3, figsize = (11,5))sns.heatmap (
data = relative_imp_k2,
annot = True,
fmt = '. 2f',
cmap = 'RdYlGn',
largeurs de trait = 2,
square = True,
ax = ax[0],
)
hache[0].set_ylim ([0, 2])
hache[0].set_title ('2 clusters')
hache[0].set_xlabel ('Variables')
sns.heatmap (
data = relative_imp_k3,
annot = True,
fmt = '. 2f',
cmap = 'RdYlGn',
largeurs de trait = 2,
square = True,
ax = ax[1],
)
hache[1].set_ylim ([0, 3])
hache[1].set_title ('3 Clusters')
hache[1].set_xlabel ('Variables')
sns.heatmap (
data = relative_imp_k4,
annot = True,
fmt = '. 2f',
cmap = 'RdYlGn',
largeurs de trait = 2,
square = True,
ax = ax[2],
)
hache[2].set_ylim ([0, 4])
hache[2].set_title ('4 clusters')
hache[2].set_xlabel ('Variables')
fig.suptitle ('Importance relative des attributs', fontsize = 20)
# plt.tight_layout ()
plt.show ()
Figure 7: Importance relative des attributs

Ici, plus on s'éloigne de 0, plus cette caractéristique est importante pour le segment.

Tout cela est cool, mais comment pouvons-nous vérifier nos résultats en plus de regarder les métriques pour chaque cluster? Et si nous pouvions avoir un moyen de visualiser ces grappes spatialement? Mais notre matrice de fonctionnalités a 4 fonctionnalités, nous ne pouvons pas tracer quelque chose dans un espace à 4 dimensions… pouvons-nous?

Oui et non! Eh bien, vous ne pouvez pas réellement tracer sur 4 dimensions, mais vous pouvez utiliser quelques astuces pour réduire la dimensionnalité du jeu de données à 2 puis le tracer sur un plan régulier!

Une de ces techniques est le bon vieux Analyse en composantes principales (ACP). Avec elle, nous pouvons faire pivoter et transformer les données autour des axes de la variance la plus élevée, puis choisir de ne conserver que les valeurs. k principaux composants dont nous avons besoin. Dans notre cas, nous allons l'utiliser pour faire pivoter et transformer notre jeu de données multidimensionnel en un jeu de données bidimensionnel.

depuis sklearn.decomposition import PCApca = PCA (n_components = 2)pca_array = pca.fit_transform (scaled_rfm)
pca_data = pd.DataFrame (pca_array, columns =['x', 'y'], index = scaled_rfm.index)
pca_data['k2'] = rfm_k2.cluster
pca_data['k3'] = rfm_k3.cluster
pca_data['k4'] = rfm_k4.cluster
fig, ax = plt.subplots (1, 3, figsize = (15,5))sns.scatterplot (x = 'x', y = 'y', hue = 'k2', data = pca_data, ax = ax[0])
hache[0].set_title ('2 clusters')
hache[0].set_xlabel ('X')
hache[0].set_ylabel ('Y')
sns.scatterplot (x = 'x', y = 'y', hue = 'k3', data = pca_data, ax = ax[1])
hache[1].set_title ('3 Clusters')
hache[1].set_xlabel ('X')
hache[1].set_ylabel ('Y')
sns.scatterplot (x = 'x', y = 'y', hue = 'k4', data = pca_data, ax = ax[2])
hache[2].set_title ('4 clusters')
hache[2].set_xlabel ('X')
hache[2].set_ylabel ('Y')
fig.suptitle ('Parcelle PCA des clusters', fontsize = 20)
plt.show ()
Figure 8: Réduction de la dimensionnalité avec PCA

Comme nous pouvons le voir dans les graphiques ci-dessus, il semble que 3 soit le nombre idéal de grappes après tout.

Une autre de ces techniques pour visualiser des données de grande dimension est la Incorporation de voisins stochastiques t-distribués (t-SNE). C'est une technique beaucoup plus complexe, non linéaire, qui implique des probabilités communes. L'explication détaillée du fonctionnement interne du processus sort du cadre de cet article. Cependant, puisqu'il est également inclus dans le scikit-learn bibliothèque, son utilisation est aussi simple que les autres modèles que nous avons utilisés jusqu’à présent.

à partir de sklearn.manifold import TSNEtsne = TSNE (learning_rate = 300, perplexity = 80, early_exaggeration = 20)tsne_array = tsne.fit_transform (scaled_rfm)
tsne_data = pd.DataFrame (tsne_array, columns =['x', 'y'], index = scaled_rfm.index)
tsne_data['k2'] = rfm_k2.cluster
tsne_data['k3'] = rfm_k3.cluster
tsne_data['k4'] = rfm_k4.cluster
fig, ax = plt.subplots (1, 3, figsize = (15,5))sns.scatterplot (x = 'x', y = 'y', hue = 'k2', data = tsne_data, ax = ax[0])
hache[0].set_title ('2 clusters')
hache[0].set_xlabel ('X')
hache[0].set_ylabel ('Y')
sns.scatterplot (x = 'x', y = 'y', hue = 'k3', data = tsne_data, ax = ax[1])
hache[1].set_title ('3 Clusters')
hache[1].set_xlabel ('X')
hache[1].set_ylabel ('Y')
sns.scatterplot (x = 'x', y = 'y', hue = 'k4', data = tsne_data, ax = ax[2])
hache[2].set_title ('4 clusters')
hache[2].set_xlabel ('X')
hache[2].set_ylabel ('Y')
fig.suptitle ('parcelle de grappes t-SNE', taille de police = 20)
# plt.tight_layout ()
plt.show ()
Figure 9: Réduction de la dimensionnalité avec PCA

Voilà pour notre parcours de segmentation client! J'espère que cela vous a plu et, espérons-le, appris quelque chose en cours de route. Si c'est le cas, n'oubliez pas de laisser des applaudissements et de rester à l'écoute pour la prochaine série.

Afficher plus

SupportIvy

SupportIvy.com : Un lieu pour partager le savoir et mieux comprendre le monde. Meilleure plate-forme de support gratuit pour vous, Documentation &Tutoriels par les experts.

Articles similaires

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Bouton retour en haut de la page
Fermer