数据来源于一个公众号:https://mp.weixin.qq.com/s/8KSRiOiY75i9ocxWm7-50g
R:最近一次下单距离现在的时间
F:分析时间段内的下单次数(本例按天汇总,统计下单的天数)
M:分析时间段内的下单总金额
步骤:
首先,根据RFM值打分得到r_score,f_score,m_score。打分方式有多种选择,这里用了三种:
第一种是直接使用rfm作为分数,但需要注意后面对r值的处理,越大得分越低;
第二种是根据其分布或经验自定义离散化,作为各自的分数;
第三种是使用KMeans方法聚类,分别对每个特征进行聚类,根据聚类结果中各类均值大小作为各类得分的依据。
得到分数后,可做两种处理方式:
一种是直接将各自的分数r_score,f_score,m_score加和得到总分,根据总分划定客户档次,如会员等级划分;
一种是根据分数是否大于各自的均值,将各特征分为大、小两类,记为0-1,得到r_c,f_c,m_c,并根据0、1的组合形式划定不同类型的客户,客户标签自定义。针对不同类型客户制定相应的策略。
这里选择第二种处理方式。客户标签如下所示:
R F M 客户类型
大 大 大 重要价值用户
小 大 大 重要价值流失预警客户
大 小 大 重要发展用户
小 小 大 高消费唤回客户
大 大 小 消费潜力客户
小 大 小 一般保持客户
大 小 小 一般发展客户
小 小 小 流失客户
其它指标:
忠诚度:最近一次消费时间、消费频率
购买能力:消费总金额、最大单笔订单消费金额
价格接受度:特价商品消费数量占比、最高单件商品消费金额
import pandas as pd
import numpy as np
from pyecharts.globals import CurrentConfig, OnlineHostType
CurrentConfig.ONLINE_HOST = OnlineHostType.NOTEBOOK_HOST
from pyecharts.charts import Radar,Bar,Heatmap
from pyecharts import options as opts
d = pd.read_excel('数据.xlsx')
d.head()
d = d.set_index('付款日期')
d['month'] = d.index.to_period('M')
d['付款日期'] = d.index
data = d[d['订单状态'] == '交易成功']
数据已经清洗完毕
rfm = data.groupby('买家昵称')[['付款日期','实付金额']].agg({'付款日期':'max','实付金额':'sum'})
rfm['r'] = (pd.to_datetime('2019-07-01') -rfm['付款日期'] ).dt.days
# F:这里统计有多少天下了单,而不是下了几单
f = data.groupby('买家昵称').apply(lambda x: len(pd.unique(x.index))).reset_index()
rfm = pd.merge(rfm,f,right_on='买家昵称',left_index=True,how='inner')
rfm.columns = ['付款日期','m','r','买家昵称','f']
rfm = rfm[['r','f','m']]
分类
方式一:
直接根据RFM值是否大于均值,将各特征分为两类,最后得到八类消费者,打标签。
rfm['r_s1'] = rfm['r'].map(lambda x: '0' if x>= rfm['r'].mean() else '1') # 大于均值记为“0”
rfm[['f_s1','m_s1']] = rfm[['f','m']].apply(lambda x: x-x.mean()).applymap(lambda x: '1' if x>= 0 else '0') # 大于均值记为“1”
label = rfm.r_s1 + rfm.f_s1 + rfm.m_s1
label = rfm.r_s1 + rfm.f_s1 + rfm.m_s1
d = {'111': '重要价值客户', '101': '重要发展客户', '011': '重要价值流失预警客户',
'001': '高消费唤回客户', '110': '消费潜力客户', '010': '一般保持客户',
'100': '一般发展用户', '000': '流失客户'}
rfm['label1'] = label.map(d)
p1 = rfm['label1'].value_counts()
p1
def rfm_radar(p):
r = Radar(init_opts=opts.InitOpts(height='350px', width='550px'))
schema = [{'name':'R','max':1},{'name':'F','max':1},{'name':'M','max':1}]
r.add_schema(
schema=schema,shape='circle',
angleaxis_opts=opts.AngleAxisOpts(axisline_opts=opts.AxisLineOpts(is_show=False)),
radiusaxis_opts=opts.RadiusAxisOpts(max_=1.07,min_=0),
polar_opts=opts.PolarOpts(),
splitline_opt=opts.SplitLineOpts(is_show=False) # 有两层线,将原来不带刻度的关闭
)
for i,c in zip(p.index,['#006699','#3399CC','#0099CC','#66CCFF','#99CCFF','#CCCCFF','#FF6600','#990000']):
r.add(i, data=[p.loc[i].tolist()],label_opts=opts.LabelOpts(is_show=False),color=c)
r.set_global_opts(title_opts={'text':'RFM客户特征'},legend_opts=opts.LegendOpts(orient='vertical',pos_right=10))
return r.render_notebook()
r1 = rfm.groupby('label1')[['r','f','m']].mean()
r1 = (r1 - r1.min()) / (r1.max() - r1.min())
rfm_radar(r1)
方式二:
根据rfm值的分布情况,对其离散化形成得分,再根据得分是否大于均值将各特征分为两类,最后得到八类消费者,打标签。离散化过程使得结果具有很强主观性。
rfm[['r','f','m']].describe()
rfm['r_s2'] = pd.cut(rfm['r'],bins = [0,30,60,90,120,rfm.r.max()+1],labels = [5,4,3,2,1],right = False).astype(float)
rfm['f_s2'] = pd.cut(rfm['f'],bins = [1,2,3,4,5,rfm.f.max()+1],labels = [1,2,3,4,5],right = False).astype(float)
rfm['m_s2'] = pd.cut(rfm['m'],bins = [0,50,100,150,200,rfm.m.max()+1],labels = [1,2,3,4,5],right = False).astype(float)
level2 = rfm[['r_s2','f_s2','m_s2']].apply(lambda x: x-x.mean()).applymap(lambda x:1 if x>=0 else 0)
label2 = (level2['r_s2'] * 100) + (level2['f_s2'] * 10) + (level2['m_s2'] * 1)
d2 = {111: '重要价值客户', 101: '重要发展客户', 11: '重要价值流失预警客户',
1: '高消费唤回客户', 110: '消费潜力客户' ,10: '一般保持客户',100: '一般发展用户',0: '流失客户'}
rfm['label2'] = label2.map(d2)
p2 = rfm['label2'].value_counts()
p2
r2 = rfm.groupby('label2').mean()
r2 = (r2 - r2.min()) / (r2.max() - r2.min())
rfm_radar(r2)
方式三:
通过KMeans聚类将rfm特征值离散化作为其得分,并据此划分0-1
from sklearn.cluster import KMeans
import matplotlib.pyplot as plt
error = []
for col in ['r','f','m']:
e = []
for i in range(1, 10):
model = KMeans(n_clusters=i)
model.fit(rfm[col].values.reshape(-1, 1))
e.append(model.inertia_)
error.append(e)
pd.DataFrame(error,columns=range(1, 10), index=['r','f','m']).T.plot.line(figsize=(18,6), subplots=True, sharey=False,layout=(1,3))
# n_cluster分别为4,4,6
for col,n_cluster in zip(['r','f','m'], [4, 4, 6]):
model = KMeans(n_clusters=n_cluster)
model.fit(rfm[col].values.reshape(-1, 1))
rfm[col+'_s'] = model.labels_
rfm.head()
def get_score(rfm, col_s, col, ascending):
t = rfm.groupby(col_s)[col].mean().sort_values(ascending=ascending).reset_index()
t[col+'_s3'] = list(range(1, len(t)+1))
rfm = pd.merge(rfm, t[[col_s,col+'_s3']], on=col_s)
rfm.drop(columns=[col_s], inplace=True)
return rfm
rfm = get_score(rfm, 'r_s', 'r', False)
rfm = get_score(rfm, 'f_s', 'f', True)
rfm = get_score(rfm, 'm_s', 'm', True)
rfm.head()
level3 = rfm[['r_s3','f_s3','m_s3']].apply(lambda x: x-x.mean()).applymap(lambda x:1 if x>=0 else 0)
label3 = (level3['r_s3'] * 100) + (level3['f_s3'] * 10) + (level3['m_s3'] * 1)
rfm['label3'] = label3.map(d2)
p3 = rfm['label3'].value_counts()
p3
r3 = rfm.groupby('label3').mean()
r3 = (r3 - r3.min()) / (r3.max() - r3.min())
rfm_radar(r3)
三种结果的比较
p = pd.merge(p1,p2,left_index=True, right_index=True).join(p3)
b = Bar(init_opts=opts.InitOpts(height='350px', width='950px'))
b.add_xaxis(p.index.tolist())
for col in p.columns:
b.add_yaxis(col, p[col].tolist(),gap=0, label_opts=opts.LabelOpts(is_show=False))
b.set_global_opts(title_opts=opts.TitleOpts(title='RFM三种方式用户分类结果'),
xaxis_opts=opts.AxisOpts(axislabel_opts=opts.LabelOpts(rotate=-15)))
b.render_notebook()
# 三种分类结果大致相同,但在流失客户、一般发展客户中方式二偏多,方式三类别差异较小些。
# 总体来看该店铺的客户大部分处于流失阶段,即购买频率低,金额小,较长时间未购买
n = rfm.apply(lambda x: x['label1']==x['label2']==x['label2'], axis=1)
print('共%s个分类相同,占%s%%' % (n.sum(), round(100*n.sum()/len(rfm), 2)))
共22673个分类相同,占89.19%
哪类顾客消费金额高
# 横轴为F,纵轴为R,颜色深浅表示金额大小,
c1 = rfm.groupby(['f_s3','r_s3'])[['m']].mean().unstack(level=0).sort_index(ascending=False)
c1
h = HeatMap(init_opts=opts.InitOpts(height='350px', width='550px'))
h.add_xaxis([1,2,3,4])
h.add_yaxis('', c1.index.tolist(), [[i,j,round(c1.iloc[j,i],2)]for i in range(c1.shape[0]) for j in range(c1.shape[1])])
h.set_global_opts(title_opts={'text':'方法三RF得分下的金额'},visualmap_opts=opts.VisualMapOpts(min_=100,max_=600,pos_right=0,pos_top=50),
xaxis_opts=opts.AxisOpts(name='F得分'),yaxis_opts=opts.AxisOpts(name='R得分'))
h.render_notebook()
c2 = rfm.groupby('label3')[['r','f','m']].agg({'r':'mean','f':'mean','m':['mean','sum','count']})
c2.columns = ['r平均','f平均','m平均','消费总金额','人数']
c2['人数占比'] = round(100*c2['人数']/c2['人数'].sum(),2)
c2 = c2.sort_values(by='消费总金额',ascending=False)
c2