Алгоритмы кластеризации — это мощные инструменты машинного обучения для группировки похожих точек данных. В этом исследовании мы рассмотрим четыре популярных алгоритма кластеризации: K-средних, иерархический, DBSCAN и Affinity Propagation.
- Кластеризация K-средних.
K-средних — это итеративный алгоритм, который разбивает данные на K отдельных кластеров на основе близости точек данных к центроидам кластера. Он направлен на минимизацию суммы квадратов внутри кластера. K-средние вычислительно эффективны и хорошо работают, когда кластеры хорошо разделены и имеют одинаковый размер. Это требует указания количества кластеров заранее.
2. Иерархическая кластеризация.
Иерархическая кластеризация создает иерархию кластеров путем многократного слияния или разделения существующих кластеров на основе их сходства. Она может быть агломерационной (снизу вверх) или разделительной (сверху вниз). Иерархическая кластеризация не требует предварительного указания количества кластеров и предоставляет дендрограмму для визуализации иерархии кластеризации.
3. DBSCAN (пространственная кластеризация приложений с шумом на основе плотности):
DBSCAN — это алгоритм кластеризации на основе плотности, который группирует точки данных на основе их плотности. Он определяет кластеры как плотные области, разделенные более разреженными областями. DBSCAN может обнаруживать кластеры произвольной формы, обрабатывать зашумленные данные и не требует предварительного указания количества кластеров. Он классифицирует точки как ядро, границу или шум на основе плотности и связности.
4. Affinity Propagation:
Affinity Propagation — это итеративный алгоритм, не требующий указания количества кластеров. Он использует подход передачи сообщений для определения экземпляров, которые являются репрезентативными точками в данных. Каждая точка данных сообщает о своем желании быть образцом, и алгоритм обновляет эти сообщения до тех пор, пока не появится стабильный набор образцов. Affinity Propagation может потребовать значительных вычислительных ресурсов, но полезно, когда количество кластеров неизвестно.
Чтобы применить эти алгоритмы кластеризации, мы будем использовать данные сегментации клиентов торгового центра, доступные на Kaggle https://www.kaggle.com/datasets/vjchoudhary7/customer-segmentation-tutorial-in-python.
Набор данных
- CustomerID: идентификатор для каждого клиента.
- Пол: указывает пол клиента (мужской или женский).
- Возраст: представляет возраст клиента в годах.
- Годовой доход (k$): Обозначает годовой доход клиента в тысячах долларов.
- Spending Score (1–100): балл от 1 до 100, который дает количественную оценку потребительских привычек и предпочтений. Более высокий балл указывает на более высокую склонность к тратам.
Импорт связанных библиотек
import pandas as pd %matplotlib inline import matplotlib.pyplot as plt import numpy as np import seaborn as sns import lightgbm as lgb import warnings from itertools import combinations import plotly.express as px import matplotlib.pyplot as plt from matplotlib.pyplot import figure
Предварительная обработка
pd.set_option('max_columns',100) pd.set_option('max_rows',900) pd.set_option('max_colwidth',200) df = pd.read_csv('/kaggle/input/customer-segmentation-tutorial-in-python/Mall_Customers.csv') df.head()
df.info() <class 'pandas.core.frame.DataFrame'> RangeIndex: 200 entries, 0 to 199 Data columns (total 5 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 CustomerID 200 non-null int64 1 Gender 200 non-null object 2 Age 200 non-null int64 3 Annual Income (k$) 200 non-null int64 4 Spending Score (1-100) 200 non-null int64 dtypes: int64(4), object(1) memory usage: 7.9+ KB df.describe()
#Numerical values distribution graphs # get the numerical columns of the DataFrame num_cols = df.select_dtypes(include=['float64','int64']).columns # create a figure with size (10, 10) for each numerical column for col in num_cols: plt.figure(figsize=(10,10)) sns.histplot(data=df, x=col) plt.title(col) plt.show()
#Categorical values graphs # get the categorical columns of the DataFrame cat_cols = df.select_dtypes(include=['object','category']).columns # create a countplot for each categorical column for col in cat_cols: plt.figure(figsize=(10,10)) ax = sns.countplot(data=df, x=col) plt.title(col) # add percentage labels on each bar for p in ax.containers[0].patches: percent = (p.get_height()/len(df))*100 ax.text(p.get_x()+p.get_width()/2, p.get_height()+20, '{:1.2f}%'.format(percent), ha='center', fontsize=12) plt.show()
# create a countplot for each categorical column for col in df.select_dtypes(include=['float64','int64']).columns: plt.figure(figsize=(10,10)) ax = sns.boxplot(data=df, y=col, x="Gender") plt.title(col) plt.show()
АНАЛИЗ ИССЛЕДОВАТЕЛЬСКИХ ДАННЫХ
df_train=df.drop('CustomerID',axis=1) df_train.corr()
plt.figure(figsize=(10,10)) sns.heatmap(df_train.corr(), annot=True, cmap='coolwarm') plt.show()
# create a scatterplot for each numeric column plt.figure(figsize=(10,10)) sns.scatterplot(data=df_train, x="Age",y="Annual Income (k$)", hue="Gender") plt.show()
# create a scatterplot for each numeric column plt.figure(figsize=(10,10)) sns.scatterplot(data=df_train, x="Age",y="Spending Score (1-100)", hue="Gender") plt.show()
# create a scatterplot for each numeric column plt.figure(figsize=(10,10)) sns.scatterplot(data=df_train, x="Annual Income (k$)",y="Spending Score (1-100)", hue="Gender") plt.show()
ПОСТРОЕНИЕ МОДЕЛИ
K означает
df_km=df_train.copy(deep=True) df_train.info() <class 'pandas.core.frame.DataFrame'> RangeIndex: 200 entries, 0 to 199 Data columns (total 4 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 Gender 200 non-null object 1 Age 200 non-null int64 2 Annual Income (k$) 200 non-null int64 3 Spending Score (1-100) 200 non-null int64 dtypes: int64(3), object(1) memory usage: 6.4+ KB from sklearn.preprocessing import LabelEncoder # Create an instance of the LabelEncoder class le = LabelEncoder() # Get a list of categorical columns categorical_cols = df_km.select_dtypes(include='object').columns # Apply the label encoder to each categorical column for col in categorical_cols: df_km[col] = le.fit_transform(df_km[col]) df_km.info() <class 'pandas.core.frame.DataFrame'> RangeIndex: 200 entries, 0 to 199 Data columns (total 4 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 Gender 200 non-null int64 1 Age 200 non-null int64 2 Annual Income (k$) 200 non-null int64 3 Spending Score (1-100) 200 non-null int64 dtypes: int64(4) memory usage: 6.4 KB from sklearn.cluster import KMeans # select the features X = df_km #Scaling Data from sklearn.preprocessing import StandardScaler scaler = StandardScaler() X = scaler.fit_transform(X) from yellowbrick.cluster import KElbowVisualizer model = KMeans(random_state=1) visualizer = KElbowVisualizer(model, k=(2,10)) visualizer.fit(X) visualizer.show() plt.show()
model = KMeans(random_state=1) visualizer = KElbowVisualizer(model, k=(2,10), metric='silhouette') visualizer.fit(X) visualizer.show() plt.show()
оптимальный k = 5 может быть хорошим выбором.
# create a k-means object with the optimal number of clusters optimal_k = 5 # number of clusters where the elbow is kmeans = KMeans(n_clusters=optimal_k, init='k-means++', max_iter=300, n_init=10, random_state=0) # fit the k-means object to the data kmeans.fit(X) # predict the cluster for each data point y_kmeans = kmeans.predict(X) # add the cluster predictions to the dataframe df_km['cluster'] = y_kmeans # display the first 5 rows of the dataframe with the cluster predictions print(df_km.head())
# create a scatter plot of the data with different colors for each cluster sns.scatterplot(x='Annual Income (k$)', y='Spending Score (1-100)', hue='cluster', data=df_km, palette="deep") # add a title and labels to the plot plt.title('Clusters of Customers') plt.xlabel('Annual Income (k$)') plt.ylabel('Spending Score (1-100)') # show the plot plt.show()
# create a scatter plot of the data with different colors for each cluster sns.scatterplot(x='Annual Income (k$)', y='Spending Score (1-100)', hue='cluster', data=df_km, palette="deep") # add a title and labels to the plot plt.title('Clusters of Customers') plt.xlabel('Annual Income (k$)') plt.ylabel('Gender') # show the plot plt.show()
# create a scatter plot of the data with different colors for each cluster sns.scatterplot(x='Annual Income (k$)', y='Spending Score (1-100)', hue='cluster', data=df_km, palette="deep") # add a title and labels to the plot plt.title('Clusters of Customers') plt.xlabel('Age)') plt.ylabel('Gender') # show the plot plt.show()
from mpl_toolkits.mplot3d import Axes3D fig = plt.figure(figsize=(7, 7)) ax = Axes3D(fig, rect=[0, 0, .99, 1], elev=20, azim=210) ax.scatter(df_km['Age'], df_km['Annual Income (k$)'], df_km['Spending Score (1-100)'], c=df_km['cluster'], s=35, edgecolor='k', cmap=plt.cm.Set1) ax.w_xaxis.set_ticklabels([]) ax.w_yaxis.set_ticklabels([]) ax.w_zaxis.set_ticklabels([]) ax.set_xlabel('Age') ax.set_ylabel('Annual Income (k$)') ax.set_zlabel('Spending Score (1-100)') ax.set_title('3D view of K-Means 5 clusters') ax.dist = 12 plt.show()
from mpl_toolkits.mplot3d import Axes3D fig = plt.figure(figsize=(7, 7)) ax = Axes3D(fig, rect=[0, 0, .99, 1], elev=20, azim=210) ax.scatter(df_km['Gender'], df_km['Annual Income (k$)'], df_km['Spending Score (1-100)'], c=df_km['cluster'], s=35, edgecolor='k', cmap=plt.cm.Set1) ax.w_xaxis.set_ticklabels([]) ax.w_yaxis.set_ticklabels([]) ax.w_zaxis.set_ticklabels([]) ax.set_xlabel('Gender') ax.set_ylabel('Annual Income (k$)') ax.set_zlabel('Spending Score (1-100)') ax.set_title('3D view of K-Means 5 clusters') ax.dist = 12 plt.show()
df_km.groupby('cluster').describe()
Иерархическая кластеризация (агломеративная)
df_ag=df_train.copy(deep=True) df_ag.info() <class 'pandas.core.frame.DataFrame'> RangeIndex: 200 entries, 0 to 199 Data columns (total 4 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 Gender 200 non-null object 1 Age 200 non-null int64 2 Annual Income (k$) 200 non-null int64 3 Spending Score (1-100) 200 non-null int64 dtypes: int64(3), object(1) memory usage: 6.4+ KB from sklearn.preprocessing import LabelEncoder # Create an instance of the LabelEncoder class le = LabelEncoder() # Get a list of categorical columns categorical_cols = df_ag.select_dtypes(include='object').columns # Apply the label encoder to each categorical column for col in categorical_cols: df_ag[col] = le.fit_transform(df_ag[col]) df_ag.info() <class 'pandas.core.frame.DataFrame'> RangeIndex: 200 entries, 0 to 199 Data columns (total 4 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 Gender 200 non-null int64 1 Age 200 non-null int64 2 Annual Income (k$) 200 non-null int64 3 Spending Score (1-100) 200 non-null int64 dtypes: int64(4) memory usage: 6.4 KB from sklearn.cluster import AgglomerativeClustering from scipy.cluster.hierarchy import dendrogram from scipy.cluster import hierarchy model = AgglomerativeClustering(n_clusters=None,distance_threshold=0) cluster_labels = model.fit_predict(df_ag) cluster_labels
linkage_matrix = hierarchy.linkage(model.children_) linkage_matrix[:][:5]
plt.figure(figsize=(30,10)) hierarchy.set_link_color_palette(['r','grey', 'b', 'grey', 'm', 'grey', 'g', 'grey', 'orange']) # set colors for the clusters dn = hierarchy.dendrogram(linkage_matrix,truncate_mode='level',p=15, color_threshold=23) # color_threshold=23 sets clusters below y-axis value of 23 to be of the same color
model = AgglomerativeClustering(n_clusters=5) df_ag['cluster']=model.fit_predict(df_ag) # predict the categories for each point. # create a scatter plot of the data with different colors for each cluster sns.scatterplot(x='Annual Income (k$)', y='Spending Score (1-100)', hue='cluster', data=df_ag, palette="deep") # add a title and labels to the plot plt.title('Clusters of Customers') plt.xlabel('Annual Income (k$)') plt.ylabel('Spending Score (1-100)') # show the plot plt.show()
# create a scatter plot of the data with different colors for each cluster sns.scatterplot(x='Annual Income (k$)', y='Spending Score (1-100)', hue='cluster', data=df_ag, palette="deep") # add a title and labels to the plot plt.title('Clusters of Customers') plt.xlabel('Age)') plt.ylabel('Gender') # show the plot plt.show()
from mpl_toolkits.mplot3d import Axes3D fig = plt.figure(figsize=(7, 7)) ax = Axes3D(fig, rect=[0, 0, .99, 1], elev=20, azim=210) ax.scatter(df_ag['Age'], df_ag['Annual Income (k$)'], df_ag['Spending Score (1-100)'], c=df_ag['cluster'], s=35, edgecolor='k', cmap=plt.cm.Set1) ax.w_xaxis.set_ticklabels([]) ax.w_yaxis.set_ticklabels([]) ax.w_zaxis.set_ticklabels([]) ax.set_xlabel('Age') ax.set_ylabel('Annual Income (k$)') ax.set_zlabel('Spending Score (1-100)') ax.set_title('3D view of K-Means 5 clusters') ax.dist = 12 plt.show()
from mpl_toolkits.mplot3d import Axes3D fig = plt.figure(figsize=(7, 7)) ax = Axes3D(fig, rect=[0, 0, .99, 1], elev=20, azim=210) ax.scatter(df_ag['Gender'], df_ag['Annual Income (k$)'], df_ag['Spending Score (1-100)'], c=df_ag['cluster'], s=35, edgecolor='k', cmap=plt.cm.Set1) ax.w_xaxis.set_ticklabels([]) ax.w_yaxis.set_ticklabels([]) ax.w_zaxis.set_ticklabels([]) ax.set_xlabel('Gender') ax.set_ylabel('Annual Income (k$)') ax.set_zlabel('Spending Score (1-100)') ax.set_title('3D view of K-Means 5 clusters') ax.dist = 12 plt.show()
DBScan
df_db=df_train.copy(deep=True) df_db.info() <class 'pandas.core.frame.DataFrame'> RangeIndex: 200 entries, 0 to 199 Data columns (total 4 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 Gender 200 non-null object 1 Age 200 non-null int64 2 Annual Income (k$) 200 non-null int64 3 Spending Score (1-100) 200 non-null int64 dtypes: int64(3), object(1) memory usage: 6.4+ KB from sklearn.preprocessing import LabelEncoder # Create an instance of the LabelEncoder class le = LabelEncoder() # Get a list of categorical columns categorical_cols = df_db.select_dtypes(include='object').columns # Apply the label encoder to each categorical column for col in categorical_cols: df_db[col] = le.fit_transform(df_db[col]) from sklearn.cluster import DBSCAN from itertools import product eps_values = np.arange(8,12.75,0.25) # eps values to be investigated min_samples = np.arange(3,10) # min_samples values to be investigated DBSCAN_params = list(product(eps_values, min_samples)) from sklearn.metrics import silhouette_score no_of_clusters = [] sil_score = [] for p in DBSCAN_params: DBS_clustering = DBSCAN(eps=p[0], min_samples=p[1]).fit(df_db) no_of_clusters.append(len(np.unique(DBS_clustering.labels_))) sil_score.append(silhouette_score(df_db, DBS_clustering.labels_)) tmp = pd.DataFrame.from_records(DBSCAN_params, columns =['Eps', 'Min_samples']) tmp['No_of_clusters'] = no_of_clusters pivot_1 = pd.pivot_table(tmp, values='No_of_clusters', index='Min_samples', columns='Eps') fig, ax = plt.subplots(figsize=(12,6)) sns.heatmap(pivot_1, annot=True,annot_kws={"size": 16}, cmap="YlGnBu", ax=ax) ax.set_title('Number of clusters') plt.show()
tmp = pd.DataFrame.from_records(DBSCAN_params, columns =['Eps', 'Min_samples']) tmp['Sil_score'] = sil_score pivot_1 = pd.pivot_table(tmp, values='Sil_score', index='Min_samples', columns='Eps') fig, ax = plt.subplots(figsize=(18,6)) sns.heatmap(pivot_1, annot=True, annot_kws={"size": 10}, cmap="YlGnBu", ax=ax) plt.show()
DBS_clustering = DBSCAN(eps=12.5, min_samples=4).fit(df_db) DBSCAN_clustered = df_db.copy() DBSCAN_clustered.loc[:,'Cluster'] = DBS_clustering.labels_ # append labels to points DBSCAN_clust_sizes = DBSCAN_clustered.groupby('Cluster').size().to_frame() DBSCAN_clust_sizes.columns = ["DBSCAN_size"] DBSCAN_clust_sizes
outliers = DBSCAN_clustered[DBSCAN_clustered['Cluster']==-1] fig2, (axes) = plt.subplots(1,2,figsize=(12,5)) sns.scatterplot('Annual Income (k$)', 'Spending Score (1-100)', data=DBSCAN_clustered[DBSCAN_clustered['Cluster']!=-1], hue='Cluster', ax=axes[0], palette='Set1', legend='full', s=45) sns.scatterplot('Age', 'Spending Score (1-100)', data=DBSCAN_clustered[DBSCAN_clustered['Cluster']!=-1], hue='Cluster', palette='Set1', ax=axes[1], legend='full', s=45) axes[0].scatter(outliers['Annual Income (k$)'], outliers['Spending Score (1-100)'], s=5, label='outliers', c="k") axes[1].scatter(outliers['Age'], outliers['Spending Score (1-100)'], s=5, label='outliers', c="k") axes[0].legend() axes[1].legend() plt.setp(axes[0].get_legend().get_texts(), fontsize='10') plt.setp(axes[1].get_legend().get_texts(), fontsize='10') plt.show()
Модель точки доступа
df_ap=df_train.copy(deep=True) df_ap.info() <class 'pandas.core.frame.DataFrame'> RangeIndex: 200 entries, 0 to 199 Data columns (total 4 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 Gender 200 non-null object 1 Age 200 non-null int64 2 Annual Income (k$) 200 non-null int64 3 Spending Score (1-100) 200 non-null int64 dtypes: int64(3), object(1) memory usage: 6.4+ KB from sklearn.preprocessing import LabelEncoder # Create an instance of the LabelEncoder class le = LabelEncoder() # Get a list of categorical columns categorical_cols = df_ap.select_dtypes(include='object').columns # Apply the label encoder to each categorical column for col in categorical_cols: df_ap[col] = le.fit_transform(df_ap[col]) df_ap.info() <class 'pandas.core.frame.DataFrame'> RangeIndex: 200 entries, 0 to 199 Data columns (total 4 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 Gender 200 non-null int64 1 Age 200 non-null int64 2 Annual Income (k$) 200 non-null int64 3 Spending Score (1-100) 200 non-null int64 dtypes: int64(4) memory usage: 6.4 KB from sklearn.cluster import AffinityPropagation from sklearn.metrics import silhouette_score no_of_clusters = [] preferences = range(-20000,-5000,100) # arbitraty chosen range af_sil_score = [] # silouette scores for p in preferences: AF = AffinityPropagation(preference=p, max_iter=200).fit(df_ap) no_of_clusters.append((len(np.unique(AF.labels_)))) af_sil_score.append(silhouette_score(df_ap, AF.labels_)) af_results = pd.DataFrame([preferences, no_of_clusters, af_sil_score], index=['preference','clusters', 'sil_score']).T af_results.sort_values(by='sil_score', ascending=False).head() # display only 5 best scores
fig, ax = plt.subplots(figsize=(12,5)) ax = sns.lineplot(preferences, af_sil_score, marker='o', ax=ax) ax.set_title("Silhouette score method") ax.set_xlabel("number of clusters") ax.set_ylabel("Silhouette score") ax.axvline(-11800, ls="--", c="red") plt.grid() plt.show()
AF = AffinityPropagation(preference=-11800).fit(df_ap) AF_clustered = df_ap.copy() AF_clustered.loc[:,'Cluster'] = AF.labels_ # append labels to points AF_clust_sizes = AF_clustered.groupby('Cluster').size().to_frame() AF_clust_sizes.columns = ["AF_size"] AF_clust_sizes
fig3, (ax_af) = plt.subplots(1,2,figsize=(12,5)) scat_1 = sns.scatterplot('Annual Income (k$)', 'Spending Score (1-100)', data=AF_clustered, hue='Cluster', ax=ax_af[0], palette='Set1', legend='full') sns.scatterplot('Age', 'Spending Score (1-100)', data=AF_clustered, hue='Cluster', palette='Set1', ax=ax_af[1], legend='full') plt.setp(ax_af[0].get_legend().get_texts(), fontsize='10') plt.setp(ax_af[1].get_legend().get_texts(), fontsize='10') plt.show()
Заключение
Лично я думаю, что иерархическая кластеризация дает лучшие результаты в этом случае. Однако важно признать, что производительность моделей кластеризации может варьироваться в зависимости от конкретного набора данных и контекста. Поэтому крайне важно учитывать производительность и пригодность различных моделей в каждом конкретном случае.