聚類分析在機器學(xué)習(xí)領(lǐng)域?qū)儆跓o監(jiān)督學(xué)習(xí)的一種,能夠根據(jù)一些特征對樣本數(shù)據(jù)進(jìn)行分類。使用聚類分析分完的類具有“類中相似,類間區(qū)別”的特點。RFM模型是非常常見的分析用戶價值的方法,其核心思想是根據(jù)用戶的最近購買時間、購買頻次、花費金額3個特征來對用戶進(jìn)行分群,針對每個群體采取不同的營銷手段。k-means是常用的聚類分析算法之一,基于歐氏距離對樣本進(jìn)行分類。k-means算法運行速度快,能夠處理的數(shù)據(jù)量大,且易于理解。但缺點也很明顯,就是算法性能有限,在高維上可能不是最佳選項。在當(dāng)前動輒上億的數(shù)據(jù)量來看,k-means算法是比較好的選擇了。還有需要提醒的一點是,一定要結(jié)合業(yè)務(wù)使用算法,任何特征都可能拿來聚類,但是聚類的結(jié)果呢,能不能很好的解釋和指導(dǎo)業(yè)務(wù)?如果不能,那么這個算法就沒有什么意義。本次使用的數(shù)據(jù)來源于數(shù)據(jù)不吹牛公眾號,這里也是小打一波廣告。
本案例將從一個簡單的k-means機器學(xué)習(xí)模型入手,在介紹聚類算法的同時也簡單介紹機器學(xué)習(xí)的常規(guī)步驟。
首先,我們導(dǎo)入數(shù)據(jù),并查看前5行。
import pandas as pd
import numpy as np
df = pd.read_excel(r'F:\數(shù)據(jù)分析項目\電商數(shù)據(jù)的RFM模型\RFM\PYTHON-RFM實戰(zhàn)數(shù)據(jù).xlsx')
df.head(5)
df.info()
選取交易成功的用戶:
df['訂單狀態(tài)'].value_counts()
df = df[df['訂單狀態(tài)'] == '交易成功']
data = df[['買家昵稱', '付款日期', '實付金額']]
data.head(5)
有個細(xì)節(jié)需要注意,訂單每一行代表著單個用戶的單次購買行為,什么意思呢?如果一個用戶在一天內(nèi)購買了4次,訂單表對應(yīng)記錄著4行,而在實際的業(yè)務(wù)場景中,一個用戶在一天內(nèi)的多次消費行為,應(yīng)該從整體上看作一次。 ----- 數(shù)據(jù)不吹牛 小Z
根據(jù)我們的RFM模型,我們需要構(gòu)建3列數(shù)據(jù),這個原始表中是沒有直接提供的,需要我們根據(jù)原始表的數(shù)據(jù)來提取。
所以這里需要先提取付款日期數(shù)據(jù):
data['paytime'] = pd.to_datetime(data['付款日期'].apply(lambda x:x.date()))
data.head()
# 提取每個用戶最近(最大)的購買日期
data_r = data.groupby('買家昵稱')['paytime'].max().reset_index()
# 與當(dāng)前日期相減,取得最近一次購買距當(dāng)前的天數(shù)。
data_r['recency'] = data_r['paytime'].apply(lambda x:(pd.to_datetime('2019-07-01')-x).days)
# 兩個日期相減,得到的數(shù)據(jù)類型是timedelta類型,要進(jìn)行數(shù)值計算,需要提取出天數(shù)數(shù)字。
data_r.drop('paytime',axis = 1,inplace = True)
data_r.head()
# 分組聚合,得到每個用戶發(fā)生于不同日期的購買次數(shù)
data_f = data.groupby(['買家昵稱','paytime'])['付款日期'].count().reset_index()
data_f = data_f.groupby('買家昵稱')['paytime'].count().reset_index()
# 修改列名
data_f.rename({'paytime':'frequence'},axis = 1,inplace = True)
data_f.head()
data_m = data.groupby('買家昵稱')['實付金額'].sum().reset_index()
data_m['money'] = data_m['實付金額']/data_f['frequence']
data_m.drop('實付金額',axis = 1,inplace = True)
data_m.head()
data_rf = pd.merge(data_r,data_f,on = '買家昵稱',how = 'inner')
data_rfm = pd.merge(data_rf,data_m, on = '買家昵稱',how = 'inner')
data_rfm.head()
數(shù)據(jù)的分布特征會影響算法結(jié)果,所以有必要先了解數(shù)據(jù)的大致分布。
import matplotlib.pyplot as plt
import seaborn as sns
plt.figure(figsize = (6,4))
sns.set(style = 'darkgrid')
sns.countplot(data_rfm['frequence'])
sns.distplot(data_rfm['recency'])
plt.title('recency的分布直方圖',fontsize = 15)
sns.distplot(data_rfm['money'],color = 'g')
plt.title('money的分布直方圖',fontsize = 15)
首先,對數(shù)據(jù)進(jìn)行標(biāo)準(zhǔn)化處理,這是為了消除量綱的影響。
from sklearn import preprocessing
from sklearn.cluster import KMeans
from sklearn import metrics
data_rfm_s = data_rfm.copy()
min_max_scaler = preprocessing.MinMaxScaler()
data_rfm_s = min_max_scaler.fit_transform(data_rfm[['recency','frequence','money']])
K-Means方法有個經(jīng)常被人詬病的地方,如何選擇K值?也就是要把樣本分成多少類呢?如果貿(mào)然定一個,會太主觀,所以還是要用數(shù)據(jù)說話。一般采用三個指標(biāo):
inertia = []
ch_score = []
ss_score = []
for k in range(2,9):
model = KMeans(n_clusters = k, init = 'k-means++',max_iter = 500)
model.fit(data_rfm_s)
pre = model.predict(data_rfm_s)
ch = metrics.calinski_harabaz_score(data_rfm_s,pre)
ss = metrics.silhouette_score(data_rfm_s,pre)
inertia.append(model.inertia_)
ch_score.append(ch)
ss_score.append(ss)
print(ch_score,ss_score,inertia)
畫圖可以更加直觀看出三個指標(biāo)的變化:
score = pd.Series([ch_score,ss_score,inertia],index = ['ch_score','ss_score','inertia'])
aa = score.index.tolist()
plt.figure(figsize = (15,6))
j = 1
for i in aa:
plt.subplot(1,3,j)
plt.plot(list(range(2,9)),score[i])
plt.xlabel('k的數(shù)目',fontsize = 13)
plt.ylabel(f'{i}值',fontsize = 13)
plt.title(f'{i}值變化趨勢',fontsize = 15)
j+=1
plt.subplots_adjust(wspace = 0.3)
model = KMeans(n_clusters = 4, init = 'k-means++',max_iter = 500)
model.fit(data_rfm_s)
ppre = model.predict(data_rfm_s)
ppre = pd.DataFrame(ppre)
data = pd.concat([data_rfm,ppre],axis = 1)
data.rename({0:u'cluster'},axis = 1,inplace = True)
data.head()
labels = model.labels_ # 取得各樣本的類別數(shù)據(jù)
labels = pd.DataFrame(labels,columns = ['類別'])
result = pd.concat([pd.DataFrame(model.cluster_centers_),labels['類別'].value_counts().sort_index()],axis = 1)
result.columns = ['recency','frequence','money','cluster']
result