一只阿木木

90万条数据玩转RFM用户分析模型

90万条数据玩转RFM用户分析模型

Image

RFM,是一种经典的用户分类、价值分析模型:

R,Rencency,即每个客户有多少天没回购了,可以理解为最近一次购买到现在隔了多少天。

F,Frequency,是每个客户购买了多少次。

M,Monetary,代表每个客户平均购买金额,也可以是累计购买金额。

这三个维度,是RFM模型的精髓所在,帮助我们把混杂一体的客户数据分成标准的8类,然后根据每一类用户人数占比、金额贡献等不同的特征,进行人、货、场三重匹配的精细化运营。

用Python建立RFM模型,整体建模思路分为五步,分别是数据概览、数据清洗、维度打分、分值计算和客户分层。

一:数据概览

开发环境:jupyter Notebook, python 3.6

import pandas as pdimport numpy as npimport os
os.chdir('F:\\50mat')
df = pd.read_excel('PYTHON-RFM实战数据.xlsx')df.head()

# 打印结果

品牌名称  买家昵称  付款日期  订单状态  实付金额  邮费  省份  城市  购买数量0  一只阿木木  棒西瓜皮的店  2019-04-18 11:05:26  交易成功  210  0  北京  北京市  11  一只阿木木  8fiona_c8  2019-04-18 11:08:03  交易成功  53  0  上海  上海市  42  一只阿木木  3t_1479778131547_04  2019-04-18 11:13:01  交易成功  169  0  上海  上海市  13  一只阿木木  0kexintiantian20  2019-04-18 11:13:19  付款以后用户退款成功,交易自动关闭  107  0  北京  北京市  14  一只阿木木  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 922657Data 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 objectdtypes: object(9)memory usage: 31.7+ MB

数据类型方面,付款日期是时间格式,实付金额、邮费和购买数量是数值型,其他均为字符串类型。

二:数据清洗

2.1 剔除退款

在观察阶段,我们明确了第一个清洗的目标,就是剔除退款数据:

df = df.loc[df['订单状态'] == '交易成功',:]print('剔除退款后还剩:%d行' % len(df))
打印结果

剔除退款后还剩:889372行

2.2 关键字段提取

剔除之后,觉得我们订单的字段还是有点多,而RFM模型只需要买家昵称,付款时间和实付金额这3个关键字段,所以提取之:

df = df[['买家昵称','付款日期','实付金额']]df.head()
# 打印结果
买家昵称  付款日期  实付金额0  棒西瓜皮的店  2019-04-18 11:05:26  2101  8fiona_c8  2019-04-18 11:08:03  532  3t_1479778131547_04  2019-04-18 11:13:01  1697  2jill27  2019-01-01 10:00:11  1218  yjessieni  2019-01-01 10:00:14  211

2.3 关键字段构造

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.0001  .christiny  2019-01-29 14:17:15.0002  .willn1  2019-01-11 03:46:18.0003  .托托m  2019-01-11 02:26:33.0004  0000妮  2019-06-28 16:53:26.458

用今天减去每位用户最近一次付款时间,就得到R值了,测试样本数据是1月至6月上半年的数据,所以我们把“2019-7-1”当作“今天”:

r['R'] = (pd.to_datetime('2019-7-1') - r['付款日期']).dt.daysr = r[['买家昵称','R']]r.head()
#打印结果
买家昵称  R0  .blue_ram  1461  .christiny  1522  .willn1  1703  .托托m  1704  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()
# 打印结果
买家昵称  F0  .blue_ram  11  .christiny  12  .willn1  13  .托托m  14  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  M0  .blue_ram  1568  1  1568.01  .christiny  5856  1  5856.02  .willn1  1088  1  1088.03  .托托m  1184  1  1184.04  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  M0  .blue_ram  146  1  1568.01  .christiny  152  1  5856.02  .willn1  170  1  1088.03  .托托m  170  1  1184.04  0000妮  2  1  5248.0

三:维度打分

此部分不涉及代码。

维度确认的核心是分值确定,按照设定的标准,我们给每个消费者的R/F/M值打分,分值的大小取决于我们的偏好,即我们越喜欢的行为,打的分数就越高:

  • R值 用户有多少天没来下单,所以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 值
11
22
33
44
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-SCORE0  .blue_ram  146  1  1568.0  1.01  .christiny  152  1  5856.0  1.02  .willn1  170  1  1088.0  1.03  .托托m  170  1  1184.0  1.04  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-SCORE0  .blue_ram  146  1  1568.0  1.0  1.01  .christiny  152  1  5856.0  1.0  4.02  .willn1  170  1  1088.0  1.0  1.03  .托托m  170  1  1184.0  1.0  1.04  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()) * 1rfm['F是否大于均值'] = (rfm['F-SCORE'] > rfm['F-SCORE'].mean()) * 1rfm['M是否大于均值'] = (rfm['M-SCORE'] > rfm['M-SCORE'].mean()) * 1rfm.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  01  .christiny  152  1  5856.0  1.0  1.0  4.0  0  0  12  .willn1  170  1  1088.0  1.0  1.0  1.0  0  0  03  .托托m  170  1  1184.0  1.0  1.0  1.0  0  0  04  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函数:

  • 第一个参数传入要切分的数据列。

  • bins参数代表我们按照什么区间进行分组,上面我们已经确定了R值按照30天的间隔进行分组,输入[0,30,60,90,120,1000000]即可,最后一个数值设置非常大,是为了给分组一个容错空间,允许出现极端大的值。

  • right表示了右侧区间是开还是闭,即包不包括右边的数值,如果设置成False,就代表[0,30),包含左侧的分组数据而不含右侧,若设置为True,则是[0,30],首尾都包含。

  • labels和bins切分的数组前后呼应,什么意思呢?bins设置了6个数值,共切分了5个分组,labels则分别给每个分组打标签,0-30是5分,30-60是4分,依此类推。

五:客户分层

清洗完之后我们确定了打分逻辑,然后分别计算每个用户的R、F、M分值(SCORE),随后,用分值和对应的平均值进行对比,得到了是否大于均值的三列结果。那么客户分层如何处理呢。

R 大于均值F 大于均值M 大于均值一般分类RFM 分类释义
111重要价值客户重要价值客户近购、高频、高消费
110重要潜力客户消费潜力客户近购、高频、低消费
101重要深耕客户频次深耕客户近购、低频、高消费
100新客户新客户近购、低频、低消费
011重要唤回客户重要价值流失预警客户近未购、高频、高消费
010一般客户一般客户近未购、高频、低消费
001重要挽回客户高消费唤回客户近未购、低频、高消费
000流失客户流失客户近未购、低频、低消费

潜力是针对消费(平均支付金额),深耕是为了提升消费频次,以及重要唤回客户其实和重要价值客户非常相似,只是最近没有回购了而已,应该做流失预警等等。

5.1 构建合并指标

先引入一个人群数值的辅助列,把之前判断的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  01  .christiny  152  1  5856.0  1.0  1.0  4.0  0  0  1  12  .willn1  170  1  1088.0  1.0  1.0  1.0  0  0  0  03  .托托m  170  1  1184.0  1.0  1.0  1.0  0  0  0  04  0000妮  2  1  5248.0  5.0  1.0  4.0  1  0  1  101

人群数值是数值类型,所以位于前面的0就自动略过,比如1代表着“001”的高消费唤回客户人群,10对应着“010”的一般客户。

5.2 基于指标给客户打标签

为了得到最终人群标签,再定义一个判断函数,通过判断人群数值的值,来返回对应的分类标签:

#判断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

5.3 标签应用

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标签。

六:RFM 模型结果探索性分析

切模型结果最终都要服务于业务,所以我们基于现有模型结果做一些拓展、探索性分析。

6.1 人数统计

# 6.1 人数分析count = rfm['人群类型'].value_counts().reset_index()count.columns = ['客户类型','人数']count['人数占比'] = count['人数'] / count['人数'].sum()count
# 打印结果
客户类型  人数  人数占比0  高消费唤回客户  7338  0.2886701  流失客户  6680  0.2627852  频次深耕客户  5427  0.2134933  新客户  4224  0.1661684  重要价值客户  756  0.0297405  消费潜力客户  450  0.0177036  重要价值流失预警客户  360  0.0141627  一般客户  185  0.007278

6.2 金额统计

# 6.2 金额分析rfm['购买总金额'] = rfm['F'] * rfm['M']mon = rfm.groupby('人群类型')['购买总金额'].sum().reset_index()mon.columns = ['客户类型','消费金额']mon['金额占比'] = mon['消费金额'] / mon['消费金额'].sum()mon
# 打印结果
客户类型  消费金额  金额占比0  一般客户  825696.0  0.0073491  新客户  8667808.0  0.0771432  流失客户  14227744.0  0.1266253  消费潜力客户  2050506.0  0.0182494  重要价值客户  8615698.0  0.0766795  重要价值流失预警客户  3732550.0  0.0332196  频次深耕客户  31420996.0  0.2796447  高消费唤回客户  42819846.0  0.381092

6.3 客户类型

result = pd.merge(count,mon,left_on = '客户类型',right_on = '客户类型')result
# 打印结果
客户类型  人数  人数占比  消费金额  金额占比0  高消费唤回客户  7338  0.288670  42819846.0  0.3810921  流失客户  6680  0.262785  14227744.0  0.1266252  频次深耕客户  5427  0.213493  31420996.0  0.2796443  新客户  4224  0.166168  8667808.0  0.0771434  重要价值客户  756  0.029740  8615698.0  0.0766795  消费潜力客户  450  0.017703  2050506.0  0.0182496  重要价值流失预警客户  360  0.014162  3732550.0  0.0332197  一般客户  185  0.007278  825696.0  0.007349

七:模型封装

模型封装,一个回车就能返回结果

import pandas as pdimport numpy as npimport 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.3810921 流失客户 6680 0.262785 14227744.0 0.1266252 频次深耕客户 5427 0.213493 31420996.0 0.2796443 新客户 4224 0.166168 8667808.0 0.0771434 重要价值客户 756 0.029740 8615698.0 0.0766795 消费潜力客户 450 0.017703 2050506.0 0.0182496 重要价值流失预警客户 360 0.014162 3732550.0 0.0332197 一般客户 185 0.007278 825696.0 0.007349
Image

见字如晤

一只阿木木

Image