RFM,是一种经典的用户分类、价值分析模型:
R,Rencency,即每个客户有多少天没回购了,可以理解为最近一次购买到现在隔了多少天。
F,Frequency,是每个客户购买了多少次。
M,Monetary,代表每个客户平均购买金额,也可以是累计购买金额。
这三个维度,是RFM模型的精髓所在,帮助我们把混杂一体的客户数据分成标准的8类,然后根据每一类用户人数占比、金额贡献等不同的特征,进行人、货、场三重匹配的精细化运营。
用Python建立RFM模型,整体建模思路分为五步,分别是数据概览、数据清洗、维度打分、分值计算和客户分层。
开发环境:jupyter Notebook, python 3.6
import pandas as pd
import numpy as np
import os
os.chdir('F:\\50mat')
df = pd.read_excel('PYTHON-RFM实战数据.xlsx')
df.head()
品牌名称 买家昵称 付款日期 订单状态 实付金额 邮费 省份 城市 购买数量
0 一只阿木木 棒西瓜皮的店 2019-04-18 11:05:26 交易成功 210 0 北京 北京市 1
1 一只阿木木 8fiona_c8 2019-04-18 11:08:03 交易成功 53 0 上海 上海市 4
2 一只阿木木 3t_1479778131547_04 2019-04-18 11:13:01 交易成功 169 0 上海 上海市 1
3 一只阿木木 0kexintiantian20 2019-04-18 11:13:19 付款以后用户退款成功,交易自动关闭 107 0 北京 北京市 1
4 一只阿木木 ysxxgx 2019-04-18 11:18:07 付款以后用户退款成功,交易自动关闭 254 0 江苏省 苏州市 2
在订单状态中,交易成功、用户退款导致交易关闭的,那还包括其他状态吗?退款订单对于我们模型价值不大,需要在后续清洗中剔除。
df['订单状态'].unique()
array(['交易成功', '付款以后用户退款成功,交易自动关闭', '订单状态'], dtype=object)
再观察数据的类型和缺失情况:
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 922658 entries, 0 to 922657
Data columns (total 9 columns):
品牌名称 922658 non-null object
买家昵称 922658 non-null object
付款日期 922658 non-null object
订单状态 922658 non-null object
实付金额 922658 non-null object
邮费 922658 non-null object
省份 922658 non-null object
城市 922626 non-null object
购买数量 922658 non-null object
dtypes: object(9)
memory usage: 31.7+ MB
数据类型方面,付款日期是时间格式,实付金额、邮费和购买数量是数值型,其他均为字符串类型。
在观察阶段,我们明确了第一个清洗的目标,就是剔除退款数据:
df = df.loc[df['订单状态'] == '交易成功',:]
print('剔除退款后还剩:%d行' % len(df))
剔除退款后还剩:889372行
剔除之后,觉得我们订单的字段还是有点多,而RFM模型只需要买家昵称,付款时间和实付金额这3个关键字段,所以提取之:
df = df[['买家昵称','付款日期','实付金额']]
df.head()
买家昵称 付款日期 实付金额
0 棒西瓜皮的店 2019-04-18 11:05:26 210
1 8fiona_c8 2019-04-18 11:08:03 53
2 3t_1479778131547_04 2019-04-18 11:13:01 169
7 2jill27 2019-01-01 10:00:11 121
8 yjessieni 2019-01-01 10:00:14 211
R 值构造 。
R值,即每个用户最后一次购买时间距今多少天。
距离今天越近,时间也就越“大”,
pd.to_datetime('2019-11-11') > pd.to_datetime('2019-1-1')
# 打印结果
True
用户最近一次付款时间,只需要按买家昵称分组,再选取付款日期的最大值即可:
r = df.groupby('买家昵称')['付款日期'].max().reset_index()
r.head()
买家昵称 付款日期
0 .blue_ram 2019-02-04 17:49:34.000
1 .christiny 2019-01-29 14:17:15.000
2 .willn1 2019-01-11 03:46:18.000
3 .托托m 2019-01-11 02:26:33.000
4 0000妮 2019-06-28 16:53:26.458
用今天减去每位用户最近一次付款时间,就得到R值了,测试样本数据是1月至6月上半年的数据,所以我们把“2019-7-1”当作“今天”:
r['R'] = (pd.to_datetime('2019-7-1') - r['付款日期']).dt.days
r = r[['买家昵称','R']]
r.head()
买家昵称 R
0 .blue_ram 146
1 .christiny 152
2 .willn1 170
3 .托托m 170
4 0000妮 2
F 值构造
F值,即每个用户累计购买频次。
我们明确“单个用户一天内多次下单行为看作整体一次”,引入一个精确到天的日期标签,依照“买家昵称”和“日期标签”进行分组,把每个用户一天内的多次下单行为合并
统计购买次数:
# F 值构造
df['日期标签'] = df['付款日期'].astype(str).str[:10]
#把单个用户一天内订单合并
dup_f = df.groupby(['买家昵称','日期标签'])['付款日期'].count().reset_index()
#对合并后的用户统计频次
f = dup_f.groupby('买家昵称')['付款日期'].count().reset_index()
f.columns = ['买家昵称','F']
f.head()
买家昵称 F
0 .blue_ram 1
1 .christiny 1
2 .willn1 1
3 .托托m 1
4 0000妮 1
M 值构造
客户平均购买金额,需要得到每个用户总金额,再用总金额除以购买频次即可。
# M 值构造
sum_m = df.groupby('买家昵称')['实付金额'].sum().reset_index()
sum_m.columns = ['买家昵称','总支付金额']
com_m = pd.merge(sum_m,f,left_on = '买家昵称',right_on = '买家昵称',how = 'inner')
#计算用户平均支付金额
com_m['M'] = com_m['总支付金额'] / com_m['F']
com_m.head()
买家昵称 总支付金额 F M
0 .blue_ram 1568 1 1568.0
1 .christiny 5856 1 5856.0
2 .willn1 1088 1 1088.0
3 .托托m 1184 1 1184.0
4 0000妮 5248 1 5248.0
R F M 三值合并
# R F M 值构造并合并
rfm = pd.merge(r,com_m,left_on = '买家昵称',right_on = '买家昵称',how = 'inner')
rfm = rfm[['买家昵称','R','F','M']]
rfm.head()
买家昵称 R F M
0 .blue_ram 146 1 1568.0
1 .christiny 152 1 5856.0
2 .willn1 170 1 1088.0
3 .托托m 170 1 1184.0
4 0000妮 2 1 5248.0
此部分不涉及代码。
维度确认的核心是分值确定,按照设定的标准,我们给每个消费者的R/F/M值打分,分值的大小取决于我们的偏好,即我们越喜欢的行为,打的分数就越高:
RFM模型中打分一般采取5分制,有两种比较常见的方式,
这里使用的是第二种,即提前制定好不同数值对应的分值,加深对数据的理解。
R值根据行业经验,设置为30天一个跨度,区间左闭右开:
R 分
R 值
1
[120, +%)
2
[90, 120)
3
[60, 90)
4
[30, 60)
5
[0, 30)
F值 F值和购买频次挂钩,每多一次购买,分值就多加一分:
F 分
F 值
1
1
2
2
3
3
4
4
5
[5, +%)
M值 先对M值做个简单的区间统计,然后分组,这里我们按照1600元的一个区间来进行划分:
M 分
M 值
1
[0, 1600)
2
[1600, 3200)
3
[3200, 4800)
4
[4800, 6400)
5
[6400, +%)
我们确定了一个打分框架,每一位用户的每个指标,都有了与之对应的分值。
R值:
# 4 R 值计算
rfm['R-SCORE'] = pd.cut(rfm['R'],bins = [0,30,60,90,120,1000000],labels = [5,4,3,2,1],right = False).astype(float)
rfm.head()
买家昵称 R F M R-SCORE
0 .blue_ram 146 1 1568.0 1.0
1 .christiny 152 1 5856.0 1.0
2 .willn1 170 1 1088.0 1.0
3 .托托m 170 1 1184.0 1.0
4 0000妮 2 1 5248.0 5.0
F M 值计算
# F M 值计算
rfm['F-SCORE'] = pd.cut(rfm['F'],bins = [1,2,3,4,5,1000000],labels = [1,2,3,4,5],right = False).astype(float)
rfm['M-SCORE'] = pd.cut(rfm['M'],bins = [0,1600,3200,4800,6400,10000000],labels = [1,2,3,4,5],right = False).astype(float)
rfm.head()
买家昵称 R F M F-SCORE M-SCORE
0 .blue_ram 146 1 1568.0 1.0 1.0
1 .christiny 152 1 5856.0 1.0 4.0
2 .willn1 170 1 1088.0 1.0 1.0
3 .托托m 170 1 1184.0 1.0 1.0
4 0000妮 2 1 5248.0 1.0 4.0
过多的分类和不分类本质是一样的。所以,我们通过判断每个客户的R、F、M值是否大于平均值,来简化分类结果。
因为每个客户和平均值对比后的R、F、M,只有0和1(0表示小于平均值,1表示大于平均值)两种结果,整体组合下来共有8个分组,是比较合理的一个情况。我们来判断用户的每个分值是否大于平均值:
# 第二次计算
rfm['R是否大于均值'] = (rfm['R-SCORE'] > rfm['R-SCORE'].mean()) * 1
rfm['F是否大于均值'] = (rfm['F-SCORE'] > rfm['F-SCORE'].mean()) * 1
rfm['M是否大于均值'] = (rfm['M-SCORE'] > rfm['M-SCORE'].mean()) * 1
rfm.head()
买家昵称 R F M R-SCORE F-SCORE M-SCORE R是否大于均值 F是否大于均值 M是否大于均值
0 .blue_ram 146 1 1568.0 1.0 1.0 1.0 0 0 0
1 .christiny 152 1 5856.0 1.0 1.0 4.0 0 0 1
2 .willn1 170 1 1088.0 1.0 1.0 1.0 0 0 0
3 .托托m 170 1 1184.0 1.0 1.0 1.0 0 0 0
4 0000妮 2 1 5248.0 5.0 1.0 4.0 1 0 1
代码为什么 * 1,这是由于Python中判断后返回的结果是True和False,对应着数值1和0,只要把这个布尔结果乘上1,True就变成了1,False变成了0,处理之后更加易读。
Pandas的cut函数:
清洗完之后我们确定了打分逻辑,然后分别计算每个用户的R、F、M分值(SCORE),随后,用分值和对应的平均值进行对比,得到了是否大于均值的三列结果。那么客户分层如何处理呢。
R 大于均值
F 大于均值
M 大于均值
一般分类
RFM 分类
释义
1
1
1
重要价值客户
重要价值客户
近购、高频、高消费
1
1
0
重要潜力客户
消费潜力客户
近购、高频、低消费
1
0
1
重要深耕客户
频次深耕客户
近购、低频、高消费
1
0
0
新客户
新客户
近购、低频、低消费
0
1
1
重要唤回客户
重要价值流失预警客户
近未购、高频、高消费
0
1
0
一般客户
一般客户
近未购、高频、低消费
0
0
1
重要挽回客户
高消费唤回客户
近未购、低频、高消费
0
0
0
流失客户
流失客户
近未购、低频、低消费
潜力是针对消费(平均支付金额),深耕是为了提升消费频次,以及重要唤回客户其实和重要价值客户非常相似,只是最近没有回购了而已,应该做流失预警等等。
先引入一个人群数值的辅助列,把之前判断的R\F\M是否大于均值的三个值给串联起来:
# 5 客户分层,构建合并指标
rfm['人群数值'] = (rfm['R是否大于均值'] * 100) + (rfm['F是否大于均值'] * 10) + (rfm['M是否大于均值'] * 1)
rfm.head()
买家昵称 R F M R-SCORE F-SCORE M-SCORE R是否大于均值 F是否大于均值 M是否大于均值 人群数值
0 .blue_ram 146 1 1568.0 1.0 1.0 1.0 0 0 0 0
1 .christiny 152 1 5856.0 1.0 1.0 4.0 0 0 1 1
2 .willn1 170 1 1088.0 1.0 1.0 1.0 0 0 0 0
3 .托托m 170 1 1184.0 1.0 1.0 1.0 0 0 0 0
4 0000妮 2 1 5248.0 5.0 1.0 4.0 1 0 1 101
人群数值是数值类型,所以位于前面的0就自动略过,比如1代表着“001”的高消费唤回客户人群,10对应着“010”的一般客户。
为了得到最终人群标签,再定义一个判断函数,通过判断人群数值的值,来返回对应的分类标签:
#判断R/F/M是否大于均值
def transform_label(x):
if x == 111:
label = '重要价值客户'
elif x == 110:
label = '消费潜力客户'
elif x == 101:
label = '频次深耕客户'
elif x == 100:
label = '新客户'
elif x == 11:
label = '重要价值流失预警客户'
elif x == 10:
label = '一般客户'
elif x == 1:
label = '高消费唤回客户'
elif x == 0:
label = '流失客户'
return label
rfm['人群类型'] = rfm['人群数值'].apply(transform_label)
rfm.head()
买家昵称 R F M R-SCORE F-SCORE M-SCORE R是否大于均值 F是否大于均值 M是否大于均值 人群数值 人群类型
0 .blue_ram 146 1 1568.0 1.0 1.0 1.0 0 0 0 0 流失客户
1 .christiny 152 1 5856.0 1.0 1.0 4.0 0 0 1 1 高消费唤回客户
2 .willn1 170 1 1088.0 1.0 1.0 1.0 0 0 0 0 流失客户
3 .托托m 170 1 1184.0 1.0 1.0 1.0 0 0 0 0 流失客户
4 0000妮 2 1 5248.0 5.0 1.0 4.0 1 0 1 101 频次深耕客户
RFM 建模,每一位客户都有了属于自己的RFM标签。
切模型结果最终都要服务于业务,所以我们基于现有模型结果做一些拓展、探索性分析。
# 6.1 人数分析
count = rfm['人群类型'].value_counts().reset_index()
count.columns = ['客户类型','人数']
count['人数占比'] = count['人数'] / count['人数'].sum()
count
客户类型 人数 人数占比
0 高消费唤回客户 7338 0.288670
1 流失客户 6680 0.262785
2 频次深耕客户 5427 0.213493
3 新客户 4224 0.166168
4 重要价值客户 756 0.029740
5 消费潜力客户 450 0.017703
6 重要价值流失预警客户 360 0.014162
7 一般客户 185 0.007278
# 6.2 金额分析
rfm['购买总金额'] = rfm['F'] * rfm['M']
mon = rfm.groupby('人群类型')['购买总金额'].sum().reset_index()
mon.columns = ['客户类型','消费金额']
mon['金额占比'] = mon['消费金额'] / mon['消费金额'].sum()
mon
客户类型 消费金额 金额占比
0 一般客户 825696.0 0.007349
1 新客户 8667808.0 0.077143
2 流失客户 14227744.0 0.126625
3 消费潜力客户 2050506.0 0.018249
4 重要价值客户 8615698.0 0.076679
5 重要价值流失预警客户 3732550.0 0.033219
6 频次深耕客户 31420996.0 0.279644
7 高消费唤回客户 42819846.0 0.381092
result = pd.merge(count,mon,left_on = '客户类型',right_on = '客户类型')
result
客户类型 人数 人数占比 消费金额 金额占比
0 高消费唤回客户 7338 0.288670 42819846.0 0.381092
1 流失客户 6680 0.262785 14227744.0 0.126625
2 频次深耕客户 5427 0.213493 31420996.0 0.279644
3 新客户 4224 0.166168 8667808.0 0.077143
4 重要价值客户 756 0.029740 8615698.0 0.076679
5 消费潜力客户 450 0.017703 2050506.0 0.018249
6 重要价值流失预警客户 360 0.014162 3732550.0 0.033219
7 一般客户 185 0.007278 825696.0 0.007349
模型封装,一个回车就能返回结果
import pandas as pd
import numpy as np
import os
os.chdir('F:\\50mat')
#输入源数据文件名
def get_rfm(name = 'PYTHON-RFM实战数据.xlsx'):
# 数据概览
df = pd.read_excel(name)
# 数据清洗
df = df.loc[df['订单状态'] == '交易成功',:]
print('剔除退款后还剩:%d行' % len(df))
df = df[['买家昵称','付款日期','实付金额']]
# 构造 R 值,Recency 即每个用户最后一次购买时间距今多少天。
r = df.groupby('买家昵称')\['付款日期'\].max().reset\_index()
r\['R'\] = (pd.to\_datetime('2019-7-1') - r\['付款日期'\]).dt.days
r = r\[\['买家昵称','R'\]\]
# 构造 F 值,Frequency 即每个用户累计购买频次。
#引入日期标签辅助列
df\['日期标签'\] = df\['付款日期'\].astype(str).str\[:10\]
#把单个用户一天内订单合并
dup\_f = df.groupby(\['买家昵称','日期标签'\])\['付款日期'\].count().reset\_index()
#对合并后的用户统计频次
f = dup\_f.groupby('买家昵称')\['付款日期'\].count().reset\_index()
f.columns = \['买家昵称','F'\]
# M 值构造,Monetary 客户平均购买金额
sum\_m = df.groupby('买家昵称')\['实付金额'\].sum().reset\_index()
sum\_m.columns = \['买家昵称','总支付金额'\]
com\_m = pd.merge(sum\_m,f,left\_on = '买家昵称',right\_on = '买家昵称',how = 'inner')
#计算用户平均支付金额
com\_m\['M'\] = com\_m\['总支付金额'\] / com\_m\['F'\]
rfm = pd.merge(r,com\_m,left\_on = '买家昵称',right\_on = '买家昵称',how = 'inner')
rfm = rfm\[\['买家昵称','R','F','M'\]\]
rfm\['R-SCORE'\] = pd.cut(rfm\['R'\],bins = \[0,30,60,90,120,1000000\],labels = \[5,4,3,2,1\],right = False).astype(float)
rfm\['F-SCORE'\] = pd.cut(rfm\['F'\],bins = \[1,2,3,4,5,1000000\],labels = \[1,2,3,4,5\],right = False).astype(float)
rfm\['M-SCORE'\] = pd.cut(rfm\['M'\],bins = \[0,1600,3200,4800,6400,10000000\],labels = \[1,2,3,4,5\],right = False).astype(float)
rfm\['R是否大于均值'\] = (rfm\['R-SCORE'\] > rfm\['R-SCORE'\].mean()) \* 1
rfm\['F是否大于均值'\] = (rfm\['F-SCORE'\] > rfm\['F-SCORE'\].mean()) \* 1
rfm\['M是否大于均值'\] = (rfm\['M-SCORE'\] > rfm\['M-SCORE'\].mean()) \* 1
rfm\['人群数值'\] = (rfm\['R是否大于均值'\] \* 100) + (rfm\['F是否大于均值'\] \* 10) + (rfm\['M是否大于均值'\] \* 1)
rfm\['人群类型'\] = rfm\['人群数值'\].apply(transform\_label)
count = rfm\['人群类型'\].value\_counts().reset\_index()
count.columns = \['客户类型','人数'\]
count\['人数占比'\] = count\['人数'\] / count\['人数'\].sum()
rfm\['购买总金额'\] = rfm\['F'\] \* rfm\['M'\]
mon = rfm.groupby('人群类型')\['购买总金额'\].sum().reset\_index()
mon.columns = \['客户类型','消费金额'\]
mon\['金额占比'\] = mon\['消费金额'\] / mon\['消费金额'\].sum()
result = pd.merge(count,mon,left\_on = '客户类型',right\_on = '客户类型')
return result
#判断R/F/M是否大于均值
def transform_label(x):
if x == 111:
label = '重要价值客户'
elif x == 110:
label = '消费潜力客户'
elif x == 101:
label = '频次深耕客户'
elif x == 100:
label = '新客户'
elif x == 11:
label = '重要价值流失预警客户'
elif x == 10:
label = '一般客户'
elif x == 1:
label = '高消费唤回客户'
elif x == 0:
label = '流失客户'
return label
res = get_rfm(name = 'PYTHON-RFM实战数据.xlsx')
res
剔除退款后还剩:889372行
客户类型 人数 人数占比 消费金额 金额占比
0 高消费唤回客户 7338 0.288670 42819846.0 0.381092
1 流失客户 6680 0.262785 14227744.0 0.126625
2 频次深耕客户 5427 0.213493 31420996.0 0.279644
3 新客户 4224 0.166168 8667808.0 0.077143
4 重要价值客户 756 0.029740 8615698.0 0.076679
5 消费潜力客户 450 0.017703 2050506.0 0.018249
6 重要价值流失预警客户 360 0.014162 3732550.0 0.033219
7 一般客户 185 0.007278 825696.0 0.007349
by:一只阿木木
手机扫一扫
移动阅读更方便
你可能感兴趣的文章