Python用K-means聚類演算法進行客戶分群的實現
一、背景
1.專案描述
- 你擁有一個超市(Supermarket Mall)。通過會員卡,你用有一些關於你的客戶的基本資料,如客戶ID,年齡,性別,年收入和消費分數。
- 消費分數是根據客戶行為和購買資料等定義的引數分配給客戶的。
- 問題陳述:你擁有這個商場。想要了解怎麼樣的顧客可以很容易地聚集在一起(目標顧客),以便可以給營銷團隊以靈感並相應地計劃策略。
2.資料描述
欄位名 | 描述 |
---|---|
CustomerID | 客戶編號 |
Gender | 性別 |
Age | 年齡 |
Annual Income (k$) | 年收入,單位為千美元 |
Spending Score (1-100) | 消費分數,範圍在1~100 |
二、相關模組
import numpy as np import pandas as pd
from pandas import plotting import matplotlib.pyplot as plt import seaborn as sns import plotly.graph_objs as go import plotly.offline as py
from sklearn.cluster import KMeans
import warnings warnings.filterwarnings('ignore')
三、資料視覺化
1.資料讀取
io = '.../Mall_Customers.csv' df = pd.DataFrame(pd.read_csv(io)) # 修改列名 df.rename(columns={'Annual Income (k$)': 'Annual Income','Spending Score (1-100)': 'Spending Score'},inplace=True) print(df.head()) print(df.describe()) print(df.shape) print(df.count()) print(df.dtypes)
輸出如下。
CustomerID Gender Age Annual Income Spending Score
0 1 Male 19 15 39
1 2 Male 21 15 81
2 3 Female 20 16 6
3 4 Female 23 16 77
4 5 Female 31 17 40
-----------------------------------------------------------------
CustomerID Age Annual Income Spending Score
count 200.000000 200.000000 200.000000 200.000000
mean 100.500000 38.850000 60.560000 50.200000
std 57.879185 13.969007 26.264721 25.823522
min 1.000000 18.000000 15.000000 1.000000
25% 50.750000 28.750000 41.500000 34.750000
50% 100.500000 36.000000 61.500000 50.000000
75% 150.250000 49.000000 78.000000 73.000000
max 200.000000 70.000000 137.000000 99.000000
-----------------------------------------------------------------
(200,5)
CustomerID 200
Gender 200
Age 200
Annual Income 200
Spending Score 200
dtype: int64
-----------------------------------------------------------------
CustomerID int64
Gender object
Age int64
Annual Income int64
Spending Score int64
dtype: object
2.資料視覺化
2.1 平行座標圖
- 平行座標圖(Parallel coordinates plot)用於多元資料的視覺化,將高維資料的各個屬性(變數)用一系列相互平行的座標軸表示, 縱向是屬性值,橫向是屬性類別。
- 若在某個屬性上相同顏色折線較為集中,不同顏色有一定的間距,則說明該屬性對於預標籤類別判定有較大的幫助。
- 若某個屬性上線條混亂,顏色混雜,則可能該屬性對於標籤類別判定沒有價值。
plotting.parallel_coordinates(df.drop('CustomerID',axis=1),'Gender') plt.title('平行座標圖',fontsize=12) plt.grid(linestyle='-.') plt.show()
2.2 年齡/年收入/消費分數的分佈
這裡用了直方圖和核密度圖。(注:核密度圖看的是(x<X)的面積,而不是高度)
sns.set(palette="muted",color_codes=True) # seaborn樣式 # 配置 plt.rcParams['axes.unicode_minus'] = False # 解決無法顯示符號的問題 sns.set(font='SimHei',font_scale=0.8) # 解決Seaborn中文顯示問題 # 繪圖 plt.figure(1,figsize=(13,6)) n = 0 for x in ['Age','Annual Income','Spending Score']: n += 1 plt.subplot(1,3,n) plt.subplots_adjust(hspace=0.5,wspace=0.5) sns.distplot(df[x],bins=16,kde=True) # kde 密度曲線 plt.title('{}分佈情況'.format(x)) plt.tight_layout() plt.show()
如下圖。從左到右分別是年齡、年收入和消費能力的分佈情況。發現:
- 年齡方面:[30,36]範圍的客戶是最多的另外,在[20,21]也不少,但是60歲以上的老年人是最不常來消費的。
- 年收入方面:大部分的客戶集中在[53,83]範圍裡,在15以下和105以上的很少。
- 消費分數方面:消費分數在[40,55]的佔了大多數,在[70,80]範圍的次之。
2.3年齡/年收入/消費分數的柱狀圖
這裡使用的是柱狀圖,和直方圖不同的是:
plt.figure(1,6)) k = 0 for x in ['Age','Spending Score']: k += 1 plt.subplot(3,1,k) plt.subplots_adjust(hspace=0.5,wspace=0.5) sns.countplot(df[x],palette='rainbow',alpha=0.8) plt.title('{}分佈情況'.format(x)) plt.tight_layout() plt.show()
如下圖。從上到下分別是年齡、年收入和消費能力的柱狀圖。發現:
- 年齡方面:[27,40]範圍的客戶居多。其中,32歲的客戶是商城的常客,55,、56、64、69歲的使用者卻很少。總的來說,年齡較大的人群較少,年齡較少的人群較多。
- 年收入方面:年收入在54和78的頻數是最多的。其他在各個收入的客戶頻數看起來相差不太大。
- 消費分數方面:消費分數在42的客戶數是最多的,56次之。有的客戶的分數甚至達到了99,而分數為1的客戶也存在,沒有分數為0的客戶。
2.4不同性別使用者佔比
df_gender_c = df['Gender'].value_counts() p_lables = ['Female','Male'] p_color = ['lightcoral','lightskyblue'] p_explode = [0,0.05] # 繪圖 plt.pie(df_gender_c,labels=p_lables,colors=p_color,explode=p_explode,shadow=True,autopct='%.2f%%') plt.axis('off') plt.legend() plt.show()
如下餅圖。女性以56%的份額居於領先地位,而男性則佔整體的44%。特別是當男性人口相對高於女性時,這是一個比較大的差距。
2.5 兩兩特徵之間的關係
# df_a_a_s = df.drop(['CustomerID'],axis=1) sns.pairplot(df,vars=['Age','Spending Score'],hue='Gender',aspect=1.5,kind='reg') plt.show()
pairplot主要展現的是屬性(變數)兩兩之間的關係(線性或非線性,有無較為明顯的相關關係)。注意,我對男、女性的資料點進行了區分(但是感覺資料在性別上的差異不大呀?)。如下組圖所示:
- 對角線上的圖是各個屬性的核密度分佈圖。
- 非對角線的圖是兩個不同屬性之間的相關圖。看得出年收入和消費能力之間有較為明顯的相關關係。
- 將
kind
引數設定為reg
會為非對角線上的散點圖擬合出一條迴歸直線,更直觀地顯示變數之間的關係。
2.6 兩兩特徵之間的分佈
# 根據分類變數分組繪製一個縱向的增強箱型圖 plt.rcParams['axes.unicode_minus'] = False # 解決無法顯示符號的問題 sns.set(font='SimHei',font_scale=0.8) # 解決Seaborn中文顯示問題 sns.boxenplot(df['Gender'],df['Spending Score'],palette='Blues') # x:設定分組統計欄位,y:資料分佈統計欄位 sns.swarmplot(x=df['Gender'],y=df['Spending Score'],data=df,palette='dark',alpha=0.5,size=6) plt.title('男女性的消費能力比較',fontsize=12) plt.show()
- 如下圖使用了增強箱圖,可以通過繪製更多的分位數來提供資料分佈的資訊,適用於大資料。
- 男性的消費得分集中在[25,70],而女性的消費得分集中在[35,75],一定程度上說明了女性在購物方面表現得比男性好。
# 根據分類變數分組繪製一個縱向的增強箱型圖 plt.rcParams['axes.unicode_minus'] = False # 解決無法顯示符號的問題 sns.set(font='SimHei',fontsize=12) plt.show()
其實,下面這一部分也包含了上面的資訊。
- 年齡方面:男性分佈較為均勻,20多歲的比較多;女性的年齡大部分集中在20+~30+這個範圍,整體上較為年輕?
- 收入方面:男性略勝一籌
四、K-means聚類分析
0.手肘法簡介
核心指標
誤差平方和(sum of the squared errors,SSE)是所有樣本的聚類誤差反映了聚類效果的好壞,公式如下:
核心思想
- 隨著聚類數k 的增大,樣本劃分會更加精細,每個簇的聚合程度會逐漸提高,那麼SSE會逐漸變小。
- 當k 小於真實聚類數時,由於k 的增大會大幅增加每個簇的聚合程度,故SSE的下降幅度會很大。
- 當k到達真實聚類數時,再增加k所得到的聚合程度回報會迅速變小,所以SSE的下降幅度會驟減。然後隨著k值的繼續增大而趨於平緩,也就是說SSE和k的關係圖是一個手肘的形狀,而這個肘部對應的k值就是資料的真實聚類數。
1.基於年齡和消費分數的聚類
所需要的資料有‘Age'和‘Spending Score'。
df_a_sc = df[['Age','Spending Score']].values # 存放每次聚類結果的誤差平方和 inertia1 = []
使用手肘法確定最合適的
for n in range(1,11): # 構造聚類器 km1 = (KMeans(n_clusters=n,# 要分成的簇數,int型別,預設值為8 init='k-means++',# 初始化質心,k-means++是一種生成初始質心的演算法 n_init=10,# 設定選擇質心種子次數,預設為10次。返回質心最好的一次結果(好是指計算時長短) max_iter=300,# 每次迭代的最大次數 tol=0.0001,# 容忍的最小誤差,當誤差小於tol就會退出迭代 random_state=111,# 隨機生成器的種子 ,和初始化中心有關 algorithm='elkan')) # 'full'是傳統的K-Means演算法,'elkan'是採用elkan K-Means演算法 # 用訓練資料擬合聚類器模型 km1.fit(df_a_sc) # 獲取聚類標籤 inertia1.append(km1.inertia_)
繪圖確定
plt.figure(1,figsize=(15,6)) plt.plot(np.arange(1,11),inertia1,'o') plt.plot(np.arange(1,'-',alpha=0.7) plt.title('手肘法圖',fontsize=12) plt.xlabel('聚類數'),plt.ylabel('SSE') plt.grid(linestyle='-.') plt.show()
通過如下圖,確定
確定
km1_result = (KMeans(n_clusters=4,init='k-means++',n_init=10,max_iter=300,tol=0.0001,random_state=111,algorithm='elkan')) # 先fit()再predict(),一次性得到聚類預測之後的標籤 y1_means = km1_result.fit_predict(df_a_sc) # 繪製結果圖 plt.scatter(df_a_sc[y1_means == 0][:,0],df_a_sc[y1_means == 0][:,1],s=70,c='blue',label='1',alpha=0.6) plt.scatter(df_a_sc[y1_means == 1][:,df_a_sc[y1_means == 1][:,c='orange',label='2',alpha=0.6) plt.scatter(df_a_sc[y1_means == 2][:,df_a_sc[y1_means == 2][:,c='pink',label='3',alpha=0.6) plt.scatter(df_a_sc[y1_means == 3][:,df_a_sc[y1_means == 3][:,c='purple',label='4',alpha=0.6) plt.scatter(km1_result.cluster_centers_[:,km1_result.cluster_centers_[:,s=260,c='gold',label='質心') plt.title('聚類圖(K=4)',fontsize=12) plt.xlabel('年收入(k$)') plt.ylabel('消費分數(1-100)') plt.legend() plt.grid(linestyle='-.') plt.show()
效果如下,基於年齡和消費能力這兩個引數,可以將使用者劃分成4類。
2.基於年收入和消費分數的聚類
所需要的資料
df_ai_sc = df[['Annual Income','Spending Score']].values # 存放每次聚類結果的誤差平方和 inertia2 = []
同理,使用手肘法確定合適的
for n in range(1,11): # 構造聚類器 km2 = (KMeans(n_clusters=n,algorithm='elkan')) # 用訓練資料擬合聚類器模型 km2.fit(df_ai_sc) # 獲取聚類標籤 inertia2.append(km2.inertia_) # 繪製手肘圖確定K值 plt.figure(1,plt.ylabel('SSE') plt.grid(linestyle='-.') plt.show()
通過如下圖,確定
確定
km2_result = (KMeans(n_clusters=5,algorithm='elkan')) # 先fit()再predict(),一次性得到聚類預測之後的標籤 y2_means = km2_result.fit_predict(df_ai_sc) # 繪製結果圖 plt.scatter(df_ai_sc[y2_means == 0][:,df_ai_sc[y2_means == 0][:,alpha=0.6) plt.scatter(df_ai_sc[y2_means == 1][:,df_ai_sc[y2_means == 1][:,alpha=0.6) plt.scatter(df_ai_sc[y2_means == 2][:,df_ai_sc[y2_means == 2][:,alpha=0.6) plt.scatter(df_ai_sc[y2_means == 3][:,df_ai_sc[y2_means == 3][:,alpha=0.6) plt.scatter(df_ai_sc[y2_means == 4][:,df_ai_sc[y2_means == 4][:,c='green',label='5',alpha=0.6) plt.scatter(km2_result.cluster_centers_[:,km2_result.cluster_centers_[:,label='質心') plt.title('聚類圖(K=5)',fontsize=12) plt.xlabel('年收入(k$)') plt.ylabel('消費分數(1-100)') plt.legend() plt.grid(linestyle='-.') plt.show()
效果如下,基於年收入和消費能力這兩個引數,可以將使用者劃分成如下5類:
- 群體1
⇒\Rightarrow⇒目標使用者:這類客戶年收入高,而且高消費。 - 群體2
⇒\Rightarrow⇒普通使用者:年收入與消費得分中等水平。 - 群體3
⇒\Rightarrow⇒高消費使用者:年收入水平較低,但是卻有較強烈的消費意願,捨得花錢。 - 群體4
⇒\Rightarrow⇒節儉使用者:年收入高但是消費意願不強烈。群體5 ⇒\Rightarrow⇒謹慎使用者:年收入和消費意願都較低。
3.基於年齡、收入和消費分數的聚類所需要的資料
df_a_ai_sc = df[['Age','Spending Score']].values
聚類,
km3 = KMeans(n_clusters=5,random_state=0) km3.fit(df_a_ai_sc)
繪圖。
df['labels'] = km3.labels_ # 繪製3D圖 trace1 = go.Scatter3d( x=df['Age'],z=df['Annual Income'],mode='markers',marker=dict( color=df['labels'],size=10,line=dict( color=df['labels'],width=12 ),opacity=0.8 ) ) df_3dfid = [trace1] layout = go.Layout( margin=dict( l=0,r=0,b=0,t=0 ),scene=dict( xaxis=dict(title='年齡'),yaxis=dict(title='消費分數(1-100)'),zaxis=dict(title='年收入(k$)') ) ) fig = go.Figure(data=df_3dfid,layout=layout) py.offline.plot(fig)
效果如下。
五、小結
- 主要是為了記錄下K-means學習過程,而且之前也參與了一個專案用到了K-means演算法。
- 如何進行特徵旋是一個需要考慮的問題,我這裡嘗試了三種不同的方案。然後,確定k 值是另一個重要的問題。我這個用了“手肘法”,但是可以配合“輪廓係數”綜合判斷。
- 還有許多地方不夠詳細。另外,如果有考慮不嚴謹的地方,歡迎批評指正!
到此這篇關於Python用K-means聚類演算法進行客戶分群的實現的文章就介紹到這了,更多相關Python K-means客戶分群內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!