项目描述
数据源是来自Kaggle的一个跨国数据集,其中包含2010年12月12日至2011年12月9日期间发生的所有在英国注册的非商店在线零售业务的交易。该公司主要销售独特的全场礼品,并且大部分客户是批发商。分析目的是按照RFM模型对客户进行分级,以用户的实际购买行为数据作为基础,进行用户群体的划分,再基于不同分类信息,分解成不同群体针对运营,从而使企业能更有效的获取客户、使客户更加满意、留住客户成为高价值客户、避免客户流失。
数据一览
数据形状为:542k 行x 8列,8个字段分别为发票号,发票日期,商品码,商品描述,数量,单价,顾客ID,国家。
分析思路
R(Recency): 表示客户最近一次购买的时间距离现在有多远
F(Frequency): 表示用户在定义时间段内购买产品或服务的次数
M(Monetary): 表示用户在定义时间段内购买产品或服务的金额
按照每个指标取值不同分为八类客户,包括重要价值客户、重要发展客户、重要保持客户、重要挽留客户、一般价值客户、一般发展客户、一般保持客户、一般挽留客户等八类用户
数据清洗
0.计算每单的总价,添加Amount列。查看整体数据情况,发现描述和顾客ID列有缺失值,顾客ID类型为浮点型不符合业务逻辑
1.把顾客ID类型转为字符串
df['CustomerID']=df['CustomerID'].astype(str)
2.已知订单号中有“c”的是取消订单,需要删除这部分
df=df.drop(df[df['InvoiceNo'].str.contains('C')].index)
3.给顾客ID缺失的行加上NULL值
df['CustomerID'] = df['CustomerID'].fillna('NULL')
4.将订单时间数据类型转为日期
df['InvoiceDate']=pd.to_datetime(df['InvoiceDate'])
5.计算订单日期距离所有订单日期最大值的天数
df['Diff']=max(df['InvoiceDate'])-df['InvoiceDate']
df['Diff']=df['Diff'].dt.days
6.删除不需要的商品描述特征
df.drop(['Description'],axis=1,inplace=True)
7.以客户ID分组,求最近消费时间距离时间基线的最小值,得到R值
df_r=df.groupby('CustomerID')['Diff'].min()
df_r=df_r.reset_index()
df_r=df_r.drop(df_r[df_r['CustomerID']=='NULL'].index)
8.求出每位顾客在时间周期内消费次数,得到F值,(数据源中一个订单会包含多种产品,但是每种产品订单都会产生一条记录,注意去重)
#选出求消费金额需要的列
df_f_1=df.loc[:,['InvoiceNo','CustomerID','Amount']]
#删除客户ID为空的记录
df_f_1=df_f_1.drop(df_f_1[df_f_1['CustomerID']=='NULL'].index)
#以订单号分组,求每笔订单总金额
df_f_2=df_f_1.groupby('InvoiceNo')['Amount'].sum()
df_f_2=df_f_2.reset_index()
#删除Amount小于0的
df_f_2=df_f_2.drop(df_f_2[(df_f_2['Amount']<=0)].index)
# InvoiceNo相同的算一次购买频次,去除InvoiceNo相同的行
df_f_3=df_f_1.loc[:,['InvoiceNo','CustomerID']]
df_f_3=df_f_3.drop_duplicates()
#在整理的时候看到unique可以进行分组后去重统计,这里还可以简化一下
df_f=df_f_3.groupby("CustomerID")['InvoiceNo'].count()
df_f=df_f.reset_index()
9.表合并
df_data=pd.merge(df_f_2,df_f_3,on='InvoiceNo',how='left')
10.计算顾客消费金额,得到M值,最后合并得到RFM值表
df_m=df_data.groupby('CustomerID')['Amount'].sum()
df_m=df_m.reset_index()
#合并三张表
df_rf=pd.merge(df_r,df_f,on='CustomerID')
df_rfm=pd.merge(df_rf,df_m,on='CustomerID')
df_rfm.columns = ['CustomerID','R','F','M']
df_rfm.tail()
11.对得到三个指标进行分区,映射级别
# 分级,cut函数把数据离散化,按照指定区间划分数据组,打上标签
R_bins = [0,30,90,180,360,720]
F_bins = [0,10,20,50,100,250]
M_bins = [0,500,5000,10000,30000,300000]
R_score=pd.cut(df_rfm['R'],R_bins,right='False',labels=[5,4,3,2,1],include_lowest=True)
F_score=pd.cut(df_rfm['F'],F_bins,right='False',labels=[1,2,3,4,5])
M_score=pd.cut(df_rfm['M'],M_bins,right='False',labels=[1,2,3,4,5])
#连接数据源和标签数据
df_rfm_1=pd.concat([df_rfm,R_score,F_score,M_score],axis=1)
df_rfm_1.columns=['CustomerID','R','F','M','R_score','F_score','M_score']
#求每个指标的均值,作为评判标准
R_mean=df_rfm_1['R_score'].astype(float).mean()
F_mean=df_rfm_1['F_score'].astype(float).mean()
M_mean=df_rfm_1['M_score'].astype(float).mean()
#分类函数,映射
def rank_R(x):
if x>R_mean:
return '高'
else:
return '低'
df_rfm_1['R_rank']=df_rfm_1['R_score'].apply(rank_R)
#得到最后的客户分级
df_rfm['value']=df_rfm['R_rank'].str[:]+df_rfm['F_rank'].str[:]+df_rfm['M_rank'].str[:]
df_rfm.head()
按照RFM分值对顾客分类
def trans_value(x):
if x=='高高高':
return '高价值客户'
elif x=='高低高':
return '重点深耕客户'
elif x=='低高高':
return '重点唤回客户'
elif x=='低低高':
return '重点挽留客户'
elif x=='高高低':
return '潜力客户'
elif x=='高低低':
return '新客户'
elif x=='低高低':
return '一般保持客户'
else:
return '流失客户'
- 客户类型占比
C_type = df_rfm['Rank'].value_counts()
c_num=C_type.tolist()
c_type = C_type.index.tolist()
pie = (
charts.Pie()
.add("",[list(z) for z in zip(c_type, c_num)],
rosetype="radius",
radius=["30%","55%"],
label_opts=opts.LabelOpts(formatter='占比{d}%'),
center=["50%","40%"])
# label_opts=opts.LabelOpts(is_show=False))
.set_global_opts(
title_opts=opts.TitleOpts(title="客户类型",pos_left="80%",pos_top="5%"),
legend_opts=opts.LegendOpts(type_="scroll", pos_left="80%", orient="vertical",pos_top="15%"),
)
)
pie.render_notebook()
2.客户消费情况
bar = (
charts.Bar()
.add_xaxis(c_sales['分级'].tolist())
.add_yaxis("",c_sales['M'].round(2).tolist())
.set_global_opts(
title_opts = opts.TitleOpts(title = '客户消费金额分布',pos_left = '50%',pos_top='5%'),
xaxis_opts=opts.AxisOpts(axislabel_opts=opts.LabelOpts(rotate=-20))
)
.set_series_opts(label_opts = opts.LabelOpts(formatter = '{c}'))
)
bar.render_notebook()
pie2_type = c_sales['分级'].tolist()
pie2_value = c_sales['M'].round(2).tolist()
pie2_datapair = [list(z) for z in zip(pie2_type,pie2_value)]
pie2 = (
charts.Pie()
.add("",pie2_datapair,radius=["30%","55%"],center=["50%","40%"],rosetype="radius",
label_opts = opts.LabelOpts(formatter='占比{d}%')
)
.set_global_opts(
title_opts = opts.TitleOpts(title = "客户消费金额分布",pos_left='80%',pos_top='5%'),
legend_opts=opts.LegendOpts(type_="scroll", pos_left="80%", orient="vertical",pos_top="15%")
)
)
pie2.render_notebook()
3.每个指标的分布情况
#删除R,F,M每列的异常值
q1 = df_rfm['R'].quantile(0.05)
q3 = df_rfm['R'].quantile(0.95)
iqr = q3-q1
df_cluster = df_cluster[(df_cluster["R"]>=q1-1.5*iqr) & (df_cluster["R"]<=q3+1.5*iqr)]
df_cluster.head()
#看指标的箱型图
boxplot4_r = [df_cluster['R'].tolist()]
boxplot4 = (
charts.Boxplot()
.add_xaxis(["R"])
.add_yaxis("R",boxplot4.prepare_data(boxplot4_r))
)
boxplot4.render_notebook()
RFM分析结论
1.客户类型分析
经过第一步的数据清理,最终有4000+条记录,以R,F,M三个指标的不同取值对客户分群,其中重点深耕客户(最近有消费记录且消费金额大,但消费次数不多)占比近四成,流失客户(最近一年无消费,历史消费金额低,消费次数少)占比两成,重点关注这两类人数较多的客户。
在客户总数中占比为7.61%的高价值客户和占比39.83%的重点深耕客户贡献了85.8%的销售额,距离各行业都适用的二八法则差距较大,接下来要提高高价值客户和重点深耕客户的消费金额,或是把重点深耕客户转化为高价值客户,即提升该客户群(总计1728个)的消费频次。
重要挽留客户(消费金额高,但消费频次不高,最近无消费记录),人数511,贡献销售额占比8.75%,首先考虑是否在计算周期内有大型促销,降价活动,如果有,那么要转化这部分客户的难度会大一些,如果没有,则需要分析这类顾客的购买产品特性(是否是日常消费品,利润空间,季节性),首先考虑提高消费频次,培养客户在本平台的消费习惯。
新客户(最近有消费,但消费金额不大,消费次数少),人数835,贡献销售额2.6%,因为需要先把新客户转化为留存用户,在实际运营过程中,可以考虑把这部分客户和重要挽留客户放在一起转化,提升消费次数。
流失客户(最近一年无消费,历史消费金额低,消费次数少),人数927,贡献销售金额占比低,需要继续分析这类客户群体的消费商品和消费时间,但人数较多,分析过程比较耗时可作为提升销量的次要考虑因素 。
重要唤回客户(最近无消费,但过往消费频次和消费金额都很高),人数7,这类客户人数较少,但消费意愿和消费价值较高,可作为短期提升销量的重点关注对象,通过运营活动push,优惠活动刺激消费。
2.R,F,M三个指标数值分布分析
- F指标:有一半的数据集中在0-5区间,有25%的数据分布在4-30区间;M指标:一半的数据集中在0-2500区间,25%的数据在2500-14000区间,两个指标的中位数都偏低,极差较大;R指标分布相对较均匀,一半的数分布在0-150内;总体的消费数据可以看出:客户消费频次整体不高,金额不大,但最近消费情况还不错,即消费意愿高,重点是拉新和提升消费次数,在提升客户消费金额方面应首先考虑消费金额大,但最近无消费的客户。
K-Means聚类
最优k值的确定方法-手肘法和轮廓系数法
核心指标:SSE(sum of the squared errors,误差平方和)
Ci是第i个簇,p是Ci中的样本点,mi是Ci的质心(Ci中所有样本的均值),SSE是所有样本的聚类误差,代表了聚类效果的好坏。
- 随着聚类数k的增大,样本划分会更加精细,每个簇的聚合程度会逐渐提高,那么误差平方和SSE自然会逐渐变小。
- 当k小于真实聚类数时,由于k的增大会大幅增加每个簇的聚合程度,故SSE的下降幅度会很大,而当k到达真实聚类数时,再增加k所得到的聚合程度回报会迅速变小,所以SSE的下降幅度会骤减,然后随着k值的继续增大而趋于平缓,也就是说SSE和k的关系图是一个手肘的形状,而这个肘部对应的k值就是数据的真实聚类数
#数据标准化,这里用的是均值-标准差归一化,尽量将数据转化为均值为零,方差为一的数据,形如标准正态分布(高斯分布)
df_cluster_scaled = df_cluster[['R','F','M']]
scaler = StandardScaler()
df_cluster_scaled = scaler.fit_transform(df_cluster_scaled)
df_cluster_scaled = pd.DataFrame(df_cluster_scaled)
df_cluster_scaled.columns = ['R','F','M']
df_cluster_scaled.head()
#测试应该分为几簇,肘部对于的k值为3(曲率最高),故对于这个数据集的聚类而言,最佳聚类数应该选3
ssd = []
range_n_clusters = [2,3,4,5,6,7,8]
for num_clusters in range_n_clusters:
kmeans = KMeans(n_clusters = num_clusters,max_iter = 50)
kmeans.fit(df_cluster_scaled)
ssd.append(kmeans.inertia_)
plt.plot(ssd)
也使用轮廓系数(silhouette coefficient)来确定K最优值,选择使系数较大所对应的k值
- 轮廓系数范围在[-1,1]之间。该值越大,越合理。
si接近1,则说明样本i聚类合理;
si接近-1,则说明样本i更应该分类到另外的簇;
若si 近似为0,则说明样本i在两个簇的边界上。- 所有样本的s i 的均值称为聚类结果的轮廓系数,是该聚类是否合理、有效的度量。
- 使用轮廓系数(silhouette coefficient)来确定,选择使系数较大所对应的k值
for num_clusters in range_n_clusters:
kmeans = KMeans(n_clusters = num_clusters,max_iter = 50)
kmeans.fit(df_cluster_scaled)
cluster_labels = kmeans.labels_
silhouette_avg = silhouette_score(df_cluster_scaled,cluster_labels)
print('For n_clusters = {0},the silhouette score is {1}'.format(num_clusters,silhouette_avg))
综上确定聚类K值为3
Kmeans = KMeans(n_clusters=3,max_iter=50)
Kmeans.fit(df_cluster_scaled)
df_cluster['Cluster_ID'] = Kmeans.labels_
cluster = df_cluster[['CustomerID','R','F','M','Rank','Cluster_ID']]
cluster.head()
用K-Means进行无监督聚类后,可以看出整体分为三类,与RFM模型分类结果较为相似,可以重点关注偏离集群的几个点,以及蓝色类别中出现的几个红色类别数据,这部分与RFM模型的差异可能是由于RFM模型判断时间的主观性造成的,在实际建模的过程中需要再考虑一下RFM的分级条件。