基于k-means的某品牌用户分群实战

前言:

聚类目标:根据业务人员要求,对用户进行分类,以提高钱效

指标:点单频次,实付单价,优惠力度,最近一次下单距今的天数;

根据上述目的和指标,从数据库里面取出了该品牌近3个月的历史数据,共计348655个用户数据,下面开始对 数据进行聚类分群;

项目过程:

1.数据标准化处理
数据的标准化处理的主要目的是将数据按照比例进行特征缩放,使之落入一个小的区间范围之内,使得不同的变量经过标准化处理后可以有平等分析和比较的基础。本数据集中上述几个指标不在同一个量纲上,因此需要进行标准化处理;

import numpy as np
import pandas as pd
import datetime,time
import os
import sys

def normalization(data):
    #最小-最大规范化
    df1=(data-data.min())/(data.max()-data.min())
	#零-均值规范化
    df2=(data-data.mean())/data.std()
	#小数定标规范化
	df3=data/10**np.ceil(np.log10(data.abs().max()))
	return df1,df2,,df3

我采用了三种标准化的方式进行尝试。在实际标准化处理中,数据转换的方式多种多样,操作起来简单,灵活,但是,他也有缺点,主要在于在具体的数据挖掘实践中有些非线性转换如log转换,平方根转换,多次放转换等的含义无法用清晰的商业逻辑和商业含义向业务方解释。比如,你无法解释“把消费者在线消费金额取对数”在商业上是什么意思,这在一定程度上影响了业务应用方对模型的接受程度和理解能力。

2.去除异常值
经过对各指标的观察,发现客单价指标有些值不合常理,有些可能是团餐,买了较多分,这种不在我们的考虑范围内,本来是按照箱线图去除异常值,根据和业务部门的商讨,最终确定按照【17,300】的区间范围选择数据集;

#探索,根据客单价去除异常值
# 根据箱型图Q3+1.5IQR/Q1-1.5IQR去除异常点
def filer_outliers1(df):
    summary=df.describe()
    Q1=summary['25%']
    Q3=summary['75%']
    IQR=Q3-Q1
    outerliers1=Q1-1.5*IQR
    outerliers2=Q3+1.5*IQR
    return outerliers1,outerliers2
#其它去除异常值的方法可以参考我的另一篇文章;
df1['客单价']=df1['income']/df1['order_cnt']
df1['客单价'].describe()
r1=54.069000-1.5*24.647889
r2=54.069000+1.5*24.647889
x=df1[df1['客单价']<=300]
y=x[x['客单价']>=17]
print (len(y))#301733

3.跑模型,并利用SSE选择K
手肘法:
核心指标:SSE(sum of the squared errors,误差平方和)
手肘法的核心思想:
随着聚类数k的增大,样本划分会更加精细,每个簇的聚合程度会逐渐提高,那么误差平方和SSE自然会逐渐变小。并且,当k小于真实聚类数时,由于k的增大会大幅增加每个簇的聚合程度,故SSE的下降幅度会很大,而当k到达真实聚类数时,再增加k所得到的聚合程度回报会迅速变小,所以SSE的下降幅度会骤减,然后随着k值的继续增大而趋于平缓,也就是说SSE和k的关系图是一个手肘的形状,而这个肘部对应的k值就是数据的真实聚类数。当然,这也是该方法被称为手肘法的原因。

#读取数据
inputpath=r'D:\用户分群-kmeans\cluster_result.xlsx'
df1=pd.read_excel(inputpath,index_col=0)
a1,a2,a3=normalization(df1) #对数据进行标准化
from sklearn.cluster import KMeans
import matplotlib.pyplot as plt
#sklearn中的kmeans仅支持欧氏距离,其它距离并不一定能够保证算法的收敛性
SSE=[]
#对k从1-9进行取值,最大迭代500次
for k in range(1,9):
    iteration=500
    model=KMeans(n_clusters=k,n_jobs=4,max_iter=iteration) #开始训练模型,具体可以查看KMeans的说明文档,进行参数调整
    model.fit(a2) #这里我们选择了第二种标准化的结果,零-均值规范化
    r1=pd.Series(model.labels_).value_counts()
    r2=pd.DataFrame(model.cluster_centers_)
    SSE.append(model.inertia_)

#打印sse和K的关系对应图
print (SSE)
SSE=[3373843.9999912404, 2057163.7857949256, 1453803.8884930713, 1057351.1372552786, 877168.1346558917, 774419.7604420237, 685031.2726909643, 632071.8233276705]
X=[1,2,3,4,5,6,7,8]
import matplotlib.pyplot as plt
plt.xlabel('k')
plt.ylabel('SSE')
plt.plot(X, SSE, 'o-')
plt.show()

在这里插入图片描述
由手肘法的核心思想,我要计算的是每个点之间的曲率是多少,并且根据曲率的变化来判断手肘点的位置;

#计算离散点曲率
#按上图的话三点顺序是BAC,求A点曲率
#根本不用求几阶导,需要求的是A点的对边BC,和A角角度
SSE=[1315740.0000009362, 798215.806836126, 561478.9901756453, 415383.98152023414, 348845.9645697368,
     309291.44088541594, 273925.8514853136, 249604.4309639081]
X=[1,2,3,4,5,6,7,8]

from scipy.spatial.distance import pdist
import numpy as np

def qulv(x2,y2):
    for i in range(len(x2)-2):
        x=(x2[i]-x2[i-1],y2[i]-y2[i-1])
        y=(x2[i+1]-x2[i],y2[i+1]-y2[i])
        d=1-pdist([x,y],'cosine')
        sin=np.sqrt(1-d**2)
        dis=np.sqrt((x2[i-1]-x2[i+1])**2+(y2[i-1]-y2[i+1])**2)
        k=2*sin/dis
        print (k)
        return k
    
if __name__=='__main__':
    for j in range(8):
        x2=X[j:j+3]
        print (x2)
        y2=SSE[j:j+3]
        k=qulv(x2,y2)
'''
================= RESTART: C:\Program Files\Python36\qulv.py =================
[1, 2, 3]
[6.07923005e-12]
[2, 3, 4]
[1.36903301e-11]
[3, 4, 5]
[7.6981346e-11]
[4, 5, 6]
[1.93274027e-10]
[5, 6, 7]
[7.99448446e-11]
[6, 7, 8]
[4.3024371e-10]
[7, 8]
[8]
'''

根据SSE和k的对应关系图以及对应的曲率,可知K=7,曲率最高,其次是K=5,经过结合实际业务的情况,我们选择了5类作为最终的人群划分;

4.人群画像描述
确定K值后,再次跑一下模型,并对每类人在指标上的数据表现进行计算;

#同上步骤,再跑一次模型;
r=pd.concat([r2,r1],axis=1)
r.columns=list(df1.columns)+[u'类别数目'] #获取数据的表头
print (r)
r_detail=pd.concat([df1,pd.Series(model.labels_,index=df2.index)],axis=1)
r_detail.columns=list(df1.columns)+[u'类别'] #表头增加类别列
writer=pd.ExcelWriter(r'D:\用户分群-kmeans\cluster_result.xlsx')
r_detail.to_excel(writer,sheet_name='明细') #每个用户对应的类别明细表

##具体查看各类用户的各指标分布
r_detail_income_sum = r_detail.groupby('类别')['income'].agg([('income',sum)]).reset_index() #每类人的实收总额
r_detail_act_sum = r_detail.groupby('类别')['activity_total'].agg([('activity_total',sum)]).reset_index()  #每类人的优惠金额
r_detail_order_sum=r_detail.groupby('类别')['order_cnt'].agg([('order_cnt',sum)]).reset_index() #每类人的订单数
r_detail_days_sum=r_detail.groupby('类别')['last_to_cur'].agg([('last_to_cur',sum)]).reset_index() #每类人最后下单距今天数

r_detail_result1=pd.merge(r_detail_income_sum,r_detail_act_sum,on='类别',how='outer')
r_detail_result2=pd.merge(r_detail_result1,r_detail_order_sum,on='类别',how='outer')
r_detail_result3=pd.merge(r_detail_result2,r_detail_days_sum,on='类别',how='outer')
#我这个上面关联的过于繁琐,可以直接使用concat,利用index进行多个数据框连接,可以自行尝试一下
rr=r.reset_index()[['index','类别数目']]
r_detail_result4=pd.merge(r_detail_result3,rr,left_on='类别',right_on='index',how='outer')

outputpath=r'D:\用户分群-kmeans\cluster_result_'
writer2=pd.ExcelWriter(outputpath+str(k)+'.xlsx')
r_detail_result4.to_excel(writer2,sheet_name='类别数目')
writer2.save()

5.用户分析
最后,根据特征判断每类用户是属于羊毛党还是忠实型,这个阶段就是需要业务的加入了,他们承担了主力军的作用;
在这里插入图片描述
第四类人:最近下单距离现在较近,且点单频次非常高,客单价也高,这是绝对的忠实VIP客户,可惜这种客户的比例比较少,,只占0.42%,
以此,可以将用户分为羊毛党,中庸型,土豪型等不同类别,并分别制定营销策略;

补充:概率密度图查看一下最终结果的分布情况

plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['axes.unicode_minus']=False
#查看指标的概率密度分布图
def density_plot(data,title):
    plt.figure()
    plt.title(u'聚类类别%s各属性的密度曲线'%title)
    plt.xlabel(u'人数')
    plt.ylabel(u'密度')
    for j in range(4):
        plt.subplot(4,1,j+1)
        (data.iloc[:,j]).plot(kind='kde',label = data.columns[j],linewidth=2)
        plt.legend()
    return plt	

#可视化
if __name__=='__main__':
    inputpath=r'D:\用户分群-kmeans\cluster_result.xlsx' #上述步骤生成的用户对应的类目明细表
    df1=pd.read_excel(inputpath,index_col=0)
    print ('数据读取完毕')
    k=5
    pic_output=r'D:\shiheng\绿茶\用户分群-kmeans\pd_'
    for i in range(5):
        df2=df1[df1['cluster_type']==i]
        density_plot(df2,i).savefig(u'%s%s.png'%(pic_output,i))
        print ('第%s个密度图生成完毕'%i)
        print ('sucess')

在这里插入图片描述

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值