RFM模型—零售数据实战

开题】在我从事零售行业的期间,曾拜读过"啤酒与尿布"一书,深知业务发展离不开数据的支持。此外作为一个菜鸟级的数据搬运工,面对着业务系统种种不给力的困境下,决定另谋出路,以大佬@数据不吹牛(案列教程:https://blog.csdn.net/SeizeeveryDay/article/details/102735628)教学框架为基础,构建业务上的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  object1   下单时间     65230 non-null  object2   下单用户     14992 non-null  float643   订单状态     65230 non-null  object4   应收金额(元)  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模型的构建和分析,还有很多需要补充的细节,待各位同学自行去挖掘。最后感谢互联网上各位大佬的案例和讲解,若没有他们的文章,我们难以站在大佬的肩膀上看到这个世界。
*本文不提供数据源,你们可用大佬:数据不吹牛的教学案例作实操

RFM模型—零售数据实战相关推荐

  1. 购物篮分析( Apriori算法)—零售数据实战

    购物篮分析( Apriori算法)-零售数据实战 [开题]在我从事零售行业的期间,曾拜读过"啤酒与尿布"一书,对于沃尔玛的购物篮分析模型产生极大的兴趣.由于网上对Aprioro算法 ...

  2. rfm模型python_数据分析实战——用RFM模型分析客户价值

    数据分析实战--用RFM模型分析客户价值 阿雷边学边教python数据分析第4期--数据可视化 一.介绍什么是RFM模型和作用 1.什么是RFM模型 RFM模型是衡量客户价值的一种工具,该模型通过客户 ...

  3. 数据挖掘应用案例:RFM模型分析与客户细分(转)

    正好刚帮某电信行业完成一个数据挖掘工作,其中的RFM模型还是有一定代表性,就再把数据挖掘RFM模型的建模思路细节与大家分享一下吧!手机充值业务是一项主要电信业务形式,客户的充值行为记录正好满足RFM模 ...

  4. 某电信公司客户RFM模型分析

    手机充值业务是一项主要电信业务形式,客户的充值行为记录正好满足RFM模型的交易数据要求. 根据美国数据库营销研究所Arthur Hughes的研究,客户数据库中有三个神奇的要素,这三个要素构成了数据分 ...

  5. 我用加强版RFM模型,轻松扒出B站优质up主!(含数据+实战代码)

    点击上方"Python爬虫与数据挖掘",进行关注 回复"书籍"即可获赠Python从入门到进阶共10本电子书 今 日 鸡 汤 红军不怕远征难,万水千山只等闲. ...

  6. 零售行业交易数据分析(2)——RFM模型分类及可视化(Python实现)

    内容简介 接上一篇文章<客户终身价值(CLTV)计算和回归预测模型>,本文继续分析一年的零售交易数据,从用户的角度,使用RFM模型对用户进行打分归类,并对结果进行可视化展示. 数据集介绍 ...

  7. Python 数据分析实战案例:基于电商销售数据的 RFM 模型构建

    目录 1.背景 2.分析目标 3.数据准备 4.数据清洗 4.1 查看是否含有缺失值 4.2 查看是否有异常值 4.3 数据整理 5.具体目标分析 5.1 分析每年销售额的增长率 5.2 各个地区分店 ...

  8. SPSS用KMEANS(K均值)、两阶段聚类、RFM模型在P2P网络金融研究借款人、出款人行为数据规律...

    全文下载链接:http://tecdat.cn/?p=27831 随着P2P网络金融平台的交易量的激增,其交易数据不能得到充分有效地利用.将聚类分析引入到P2P网络金融平台的管理之中,利用聚类分析技术 ...

  9. 用户分析与RFM模型实战|一个可以写在简历上的项目(下)

    大家好,我是芒果. 接上文:用户分析与RFM模型实战|一个可以写在简历上的项目(上) 本篇会继续对此数据对产品维度和用户分层维度进行分析. 依然结论先行: 重要结论

最新文章

  1. kindeditor上传图片的大小在哪控制
  2. 对于微分的一些理解更新
  3. Python索引index常用的8种操作
  4. 搭建hypervisor类型为VMWare的cloudstack环境
  5. SAP Spartacus由于导入module路径在服务器上不正确而导致的Travis build错误
  6. Juypter 代码自动补全
  7. SSM框架笔记08:初探Spring——采用配置类与注解方式
  8. ZAO回应被约谈...
  9. 学java有什么技巧?
  10. defer 和 async 区别
  11. selenium 配合多线程_Selenium Webdriver 远程测试和多线程并发测试
  12. linux两个树莓派通信,Arduino与树莓派间的通信实践
  13. 关于Volatile
  14. 处理自己计算机某的端口被占问题
  15. android腾讯微博客户端开发,基于android系统的腾讯微博客户端的开发
  16. 新版白话空间统计(24):中位数中心
  17. react-子传父案例(汇率转换)
  18. 【尚硅谷 Java Web 笔记】表格的跨行跨列
  19. 用户画像无头绪?手把手教你RFM模型
  20. IDA详细使用教程,适合逆向新手的实验报告

热门文章

  1. 关于mysql的timestamp时间范围
  2. excel2016打开空白解决方法
  3. vue动态设置路由重定向
  4. 如何通过API接口,获取商品详情数据
  5. 51单片机,时钟频率,机器周期,与执行指令的时间
  6. <textarea></textarea> placeholder属性不显示
  7. freenom又行了-免费顶级域名白嫖一年,赶紧看看如何申请
  8. 验证码识别PaddleOCR 快速开始
  9. [转载]当猫忧郁的时候
  10. mysql数据库的在线数据备份与数据恢复