RFM模型—零售数据实战
【开题】在我从事零售行业的期间,曾拜读过"啤酒与尿布"一书,深知业务发展离不开数据的支持。此外作为一个菜鸟级的数据搬运工,面对着业务系统种种不给力的困境下,决定另谋出路,以大佬@数据不吹牛(案列教程:)教学框架为基础,构建业务上的RFM模型
前言
RFM模型在网上有许多介绍,因此仅简单地介绍一下RFM模型概念。RFM模型是对客户交易数据(行为)进行分类,划分不同的消费群体,为后续的业务提供数据支持。在模型中,R(Recency)表示客户最近一次购买的时间有多远,F(Frequency)表示客户在最近一段时间内购买的次数,M (Monetary)表示客户在最近一段时间内购买的金额。
提示:RFM模型介绍可参考该链接:https://www.zhihu.com/question/49439948?sort=created
一、数据源介绍
首先要声明,本文数据源已做处理,仅用于教学讲解目的,不会泄露或侵犯到任何公司或个人利益,如有侵权行为,请咨询相关法律人事。
字段解释:标题(顾客购买商品明细)、下单时间、下单用户(有ID号的为注册会员、空值为普通顾客)、订单状态(成功交易、退款交易等)、应收金额(顾客付款金额)
时间范围:2020年9月19日~12月19日,共计3个月
其他:由于交易记录中的会员ID会有重复交易数据,因此RFM模型数据清洗有个难点,就是剔除会员顾客单日重复交易的数据(如小A在周六购买了3次东西,产生3次交易订单)
二、RFM模型构建
1.数据导入和清洗
本次讲解使用的是python数据分析最常用的两个库:pandas和numpy,数据导入过程如下
代码如下:
#百香果店RMF模型
import pandas as pd
import numpy as np
#显示输出数据对齐设置,仅为数据输出好看一些
pd.set_option('display.unicode.ambiguous_as_wide', True)
pd.set_option('display.unicode.east_asian_width', True)
#导入数据
df_all = pd.read_excel('C:\\Users\\ASUS\\Desktop\\百香果店0919~1219数据.xlsx')
df_all.info() #查看数据类型
本次原始数据源共5个字段,65230条记录,其中下单用户非空行数14992(注册会员ID交易记录),下单时间为string字符型,下单用户为float浮点型(后期需要作清洗)
数据源输出如下:
>>> df_all.info() #查看数据类型
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 65230 entries, 0 to 65229
Data columns (total 5 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 标题 65230 non-null object
1 下单时间 65230 non-null object
2 下单用户 14992 non-null float64
3 订单状态 65230 non-null object
4 应收金额(元) 65230 non-null float64
dtypes: float64(2), object(3)
memory usage: 2.5+ MB
在查看了原始数据源各字段基本的类型结构后,我们开始对原始数据源作清洗。
首先我们先对下单时间字段作清洗,将该字段从字符型转化为日期类型
代码如下:
#转化str为datetime
df_all['下单时间'] = pd.to_datetime(df_all['下单时间'])
接着,交易订单状态有成功交易和失败的交易记录。为了更好地统计有效交易记录,我们抽取订单状态=“已完成”记录(成功交易记录)
代码如下:
#抽取有效订单状态交易数据
>>> df_all['订单状态'].unique()
array(['已完成', '已取消', '已退款', '已拆分', '待自提'], dtype=object)
>>>df = df_all.loc[(df_all['订单状态'] == '已完成'),:] #抽取已完成订单
接着,将非空的会员ID交易记录提取出来,再将浮点型的数据格式转化为字符型性数据格式。为什么我们建议将会员ID或电话号码设置为字符型呢?因为会员ID和电话号码都具有唯一性特征,此外不参与任何数值型的运行,转化为字符型的形式更有利于数据的处理。
最后,提取处理好的字段作为本次RFM基础的数据源
代码如下:
#抽取有效订单状态交易数据
#提取有会员ID的数据,并转化为float为str
df = df.loc[df['下单用户'].notna()].reset_index()
df['下单用户'] = df['下单用户'].astype(str).str.replace('\.0', '')
df = df[['下单时间','下单用户','应收金额(元)']] #抽取必要的三个字段
>>> df.head()
下单时间 下单用户 应收金额(元)
0 2020-12-19 22:22:11 43241007662 6.60
1 2020-12-19 22:04:27 74872391700 1.03
2 2020-12-19 21:40:45 74132497516 7.72
3 2020-12-19 21:40:06 57445110662 13.30
4 2020-12-19 21:31:06 89167359877 18.61
2.R时段计算
本次数据源统计时段为:2020年9月19日00:00~12月19日23:59,故而以12月20日为节点,以下单用户的最近一次下单时间为基期,计算统计时段内不同会员到12月20日的交易间隔期。
举例:小A在统计时段内,最后一次交易在12月18日,其后无购买记录,故而R值为12月20日减去12月18日=2天
代码如下(示例):
#R时段计算 统计时段0919~1219,截取时间1220
r = df.groupby('下单用户')['下单时间'].max().reset_index() #提取每个会员当天交易时间最大值
r['R'] = (pd.to_datetime('2020-12-20') - r['下单时间']).dt.days
>>> r.head()
下单用户 下单时间 R
0 10039577497 2020-10-05 11:18:56 75
1 10057559905 2020-10-01 09:40:17 79
2 10187774033 2020-09-26 16:15:23 84
3 10221565432 2020-09-20 17:39:56 90
4 10318237514 2020-10-02 09:22:16 78
3.F频次计算
本次代码计算F值,先提取下单日期字段中的日期,如2020-09-10 11:20:31提取为2020-09-10形式。
再以下单用户和下单日期两个字段分组统计单个会员ID单天交易次数(默认为一次),如小A在2020-12-15单天虽然重复交易3次,但视为1次交易记录。
最后,合并不同会员ID在不同时段内交易次数的累计之和,如小A在2020年9月20号交易一次,10月30号交易一次,累计2次交易次数。
代码如下(示例):
#F频次计算
df['日期'] = df['下单时间'].astype(str).str[:10] #提取日期
>>> df['日期'].head()
0 2020-12-19
1 2020-12-19
2 2020-12-19
3 2020-12-19
4 2020-12-19
Name: 日期, dtype: object
f_1 = df.groupby(['下单用户','日期'])['下单时间'].count().reset_index() #计算会员单天消费次数
>>> f_1.head()
下单用户 日期 下单时间
0 10039577497 2020-10-05 1
1 10057559905 2020-10-01 1
2 10187774033 2020-09-26 1
3 10221565432 2020-09-20 1
4 10318237514 2020-10-02 1
f = f_1.groupby('下单用户') ['下单时间'].count().reset_index() #计算出统计时段内会员消费次数
f.columns = ['下单用户','F']
>>> f.head()
下单用户 F
0 10039577497 1
1 10057559905 1
2 10187774033 1
3 10221565432 1
4 10318237514 1
4.M金额计算 平均客单价
本次代码计算M值,先累计不同会员ID在统计时段内应收金额的累计之和。
再合并“f”数据集中的频次,计算出不同会员ID的平均客单价
比如:小A在统计时段交易频次为3,累计应收金额为30元,平均客单价:30/3=10元
代码如下(示例):
#M金额计算 平均客单价
m_1 = df.groupby('下单用户')['应收金额(元)'].sum().reset_index()
>>> m_1.head()
下单用户 应收金额(元)
0 10039577497 33.54
1 10057559905 51.77
2 10187774033 45.00
3 10221565432 60.80
4 10318237514 9.25
m = pd.merge(m_1,f,left_on='下单用户',right_on='下单用户',how='inner')
m['M'] = round((m['应收金额(元)'] / m['F']),2) #保留两位小数
>>> m.head()
下单用户 应收金额(元) F M
0 10039577497 33.54 1 33.54
1 10057559905 51.77 1 51.77
2 10187774033 45.00 1 45.00
3 10221565432 60.80 1 60.80
4 10318237514 9.25 1 9.25
5.合并RFM模型
最后拼接RFM模型必要的字段,但这仅是较基础数据,还未到分析过程
代码如下(示例):
#合并RMF模型
rfm = pd.merge(r,m,left_on='下单用户',right_on='下单用户',how='inner')
rfm = rfm[['下单用户','R','F','M']]
>>> rfm.head()
下单用户 R F M
0 10039577497 75 1 33.54
1 10057559905 79 1 51.77
2 10187774033 84 1 45.00
3 10221565432 90 1 60.80
三、RFM模型分析
接着,我们进入最后一步了,最为关键的一步:分析
1.查看R、F、M数值特征
在网上有很多教程大多数是凭个人行业经验作为评分参照,但这个方法往往脱离了数据集固有的特征,比如公司平均客单价是25元,但是在某个门店统计时段的平均客单价却是19元,因此我们要考虑数据源特征是怎么样的!
分别求得数据源的R、F、M数值的四分数、中位数等参考值。
R:最长间隔期91天,最短间隔期0天,平均间隔期48.5天,中位数54天。
由此可知,在统计时段3个月内,大多数会员顾客1.5个月才发生一次交易
F:最大频次86次,最小频次10次,平均频次4.3次,中位数2次。
由此可知,在统计时段3个月内,大多数会员顾客交易2~4次
M:最大客单价848.52元,最小客单价0.7元,平均客单价39.4元,中位数32.8元。
由此可知,在统计时段3个月内,大多数会员客单价在30~40元内
代码如下(示例):
#合并RMF模型
#分别查看R\M\F数据状况
>>> rfm['R'].describe() #中位数:54,平均数:48
count 2984.000000
mean 48.511729
std 31.804325
min 0.000000
25% 16.000000
50% 54.000000
75% 79.000000
max 91.000000
Name: R, dtype: float64
>>> rfm['F'].describe() #中位数:2,平均数:4.3
count 2984.000000
mean 4.294236
std 6.794914
min 1.000000
25% 1.000000
50% 2.000000
75% 5.000000
max 86.000000
Name: F, dtype: float64
>>> rfm['M'].describe() #中位数:32.8,平均数:39.7
count 2984.000000
mean 39.435214
std 34.166844
min 0.700000
25% 19.585000
50% 32.645000
75% 50.000000
max 848.520000
Name: M, dtype: float64
2.构建评分标准
由上面的数据特征可知,该模型各数值呈明显右偏分布(不同的同学,回去看统计书),可推测大部分消费人群处于低消费阶段(以个人行业经验判断)
因此取上下四分位数(25%~75%)和平均值作为评分标准,构建[0,1,2,3]四个打分档位
代码如下(示例):
分值计算
rfm['R-SCORE'] = pd.cut(rfm['R'],bins=[0,16,54,79,100000],labels=[4,3,2,1],right=False).astype(float)
rfm['F-SCORE'] = pd.cut(rfm['F'],bins=[1,2,4,5,100000],labels=[1,2,3,4],right=False).astype(float)
rfm['M-SCORE'] = pd.cut(rfm['M'],bins=[0,20,33,50,100000],labels=[1,2,3,4],right=False).astype(float)
>>> rfm.head()
下单用户 R F M R-SCORE F-SCORE M-SCORE
0 10039577497 75 1 33.54 2.0 1.0 3.0
1 10057559905 79 1 51.77 1.0 1.0 4.0
2 10187774033 84 1 45.00 1.0 1.0 3.0
3 10221565432 90 1 60.80 1.0 1.0 4.0
4 10318237514 78 1 9.25 2.0 1.0 1.0
构建完评分标准,我们接着判断不同评分标准是否大于平均水平,若大于平均水平则返回TRUE(1),否者是FALSE(0)
代码如下(示例):
#判断R/F/M是否大于均值
fm['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()
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 10039577497 75 1 33.54 2.0 1.0 3.0 0 0 1 1
1 10057559905 79 1 51.77 1.0 1.0 4.0 0 0 1 1
2 10187774033 84 1 45.00 1.0 1.0 3.0 0 0 1 1
3 10221565432 90 1 60.80 1.0 1.0 4.0 0 0 1 1
4 10318237514 78 1 9.25 2.0 1.0 1.0 0 0 0 0
RFM经典的分层会按照R/F/M每一项指标是否高于平均值,把用户划分为8类,我们总结了一下,具体像下面表格这样:
将数据处理完成后,我们要将它们转化为我们能理解的语言
代码如下(示例):
#转化语言
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 10039577497 75 1 33.54 2.0 1.0 3.0 0 0 1 1 高消费唤回客户
1 10057559905 79 1 51.77 1.0 1.0 4.0 0 0 1 1 高消费唤回客户
2 10187774033 84 1 45.00 1.0 1.0 3.0 0 0 1 1 高消费唤回客户
3 10221565432 90 1 60.80 1.0 1.0 4.0 0 0 1 1 高消费唤回客户
4 10318237514 78 1 9.25 2.0 1.0 1.0 0 0 0 0 流失客户
3.分类人群的数值统计
我们作了这么久的RFM模型数据清洗,相信各位对数据概况有基本的了解。
最后,我们进入最核心一个环节,究竟我们会员消费情况是怎么样一个状态?
第一步看不同人群各自的人数占比。
高消费唤醒客户人数占比24.2%,即最近未购买、低频、高消费的人群占比偏大,说明门店有较大一部分顾客仅高客单消费了几次,没有形成复购。
流失客户人数占比22.15%,即最近未购买、低频、低消费的人群占比也偏大,说明门店有较大一部分顾客已流失。
那么我们真正能形成稳定消费的人群比例有多少?以最近购买、高/低频,高/低消费为参考值划分重要价值客户、消费潜力客户、频次深耕客户(排除新客户)三个群体,其人数占比37.7%。
由以上数据可见百香果店会员客流不稳定,会员流失情况严重,还未形成稳定(忠诚)的会员消费人群。
第二步看不同人群各自的消费金额占比。
重要价值客户贡献消费金额占比50.19%,消费潜力客户金额占比20.23%,频次深耕客户金额占比7.37%,三者累计占比77.79%,说明百果园店的会员顾客消费金额大多数依靠这三个群体,即37.7%会员人数贡献77.79%的会员消费金额,也可以适用于二八原则(可见,二八原则确实NB)
代码如下(示例):
#分类人群个数和比例
count = rfm['人群类型'].value_counts().reset_index()
count.columns = ['客户类型','人数']
count['人数占比'] = count['人数'] / count['人数'].sum()
>>> count
客户类型 人数 人数占比
0 高消费唤回客户 722 0.241957
1 流失客户 661 0.221515
2 消费潜力客户 416 0.139410
3 重要价值客户 373 0.125000
4 新客户 358 0.119973
5 频次深耕客户 337 0.112936
6 一般客户 72 0.024129
7 重要价值流失预警客户 45 0.015080
#分类人群金额和比例
rfm['购买总金额'] = rfm['F'] * rfm['M']
mon = rfm.groupby('人群类型')['购买总金额'].sum().reset_index()
mon.columns = ['客户类型','消费金额']
mon['金额占比'] = mon['消费金额'] / mon['消费金额'].sum()
>>> mon.sort_values(by='金额占比',ascending=False)
客户类型 消费金额 金额占比
4 重要价值客户 245288.76 0.501977
3 消费潜力客户 98832.27 0.202257
7 高消费唤回客户 58600.08 0.119923
6 频次深耕客户 36013.62 0.073701
2 流失客户 16520.01 0.033808
5 重要价值流失预警客户 13694.61 0.028026
1 新客户 10586.88 0.021666
0 一般客户 9109.67 0.018643
总结
由于个人时间和精力有限,本文仅简略地介绍了一下RFM模型的构建和分析,还有很多需要补充的细节,待各位同学自行去挖掘。最后感谢互联网上各位大佬的案例和讲解,若没有他们的文章,我们难以站在大佬的肩膀上看到这个世界。