之前我就已经做过B站相关的推荐了这里我就不废话了直接上代码,RFM都不知道是啥东西的多去看看其他博主写的
本次数据集获取方式:
关注:YOLO的学习进阶日常
回复:RFM用户分层实战案例
RFM模型
Recency、Frequency、Monetary Value (RFM) 的概念可以追溯到 Jan Roelf Bult 和 Tom Wansbeek 的文章“直接邮件的最佳选择”,该文章发表在 1995 年的营销科学杂志上。RFM 分析通常支持“80% 的业务来自 20% 的客户”的营销格言
1. RFM是什么?
根据美国数据库营销研究所Arthur Hughes的研究,客户数据库中有三个神奇的要素,最重要的功能就是用来进行用户分群这三个要素构成了数据分析最好的指标:
- 最近一次消费(Recency):客户最近多久进行一次购买,上一次消费离得越近,R值越小,用户价值越高
- 消费频率(Frequency):客户购买的频率,购买频率越高,F值越大,客户价值越高
- 消费金额(Monetary):客户在购买上花了多少钱,消费金额越高,M值越大,用户价值越高
根据这三个指标,我们可以把指标按照价值从低到高排序,并把这三个指标作为XYZ坐标轴,就可以把空间分为8部份
不同用户分类的运营策略
2. RFM有什么用?
金融行业、零售行业、电信行业等经常要对用户进行划分,以标记不同的标签,从而进行个性化的精准化营销行为。RFM模型,是以用户的实际交易或消费或购买或充值等(以下统称“交易”)一系列行为数据作为基础,从而进行用户群体的划分的,简单而又具有实际价值。可以看到客户满意度,实现精准营销,调整战略等。根据企业智库《2017中国会员经济数据报告》数据显示如下
3. RFM如何用?
我们一般使用RFM来计算用户价值度,有两种建模方式:
- 打分制度:根据自己的业务来调整打分制度
- 基于RFM以及K-Means算法的用户价值度分析
案例:
下面通过构建RFM模型对超市订单用户数据集进行分层,以实现精准营销
导入数据
import pandas as pd
import numpy as np
data=pd.read_csv("./supermarket_data.csv",encoding="gbk")
data.head()
客户名称 | 付款日期 | 国家/地区 (Country) | 地区 | 类别 | 销售额 | 数量 | |
---|---|---|---|---|---|---|---|
0 | Aaron Bergman | 2014/11/11 | 美国 | 美国中部 | 技术 | 221.98 | 1 |
1 | Justin Ritter | 2014/2/5 | 澳大利亚 | 大洋洲 | 家具 | 3709.40 | 1 |
2 | Craig Reiter | 2014/10/17 | 澳大利亚 | 大洋洲 | 技术 | 5175.17 | 1 |
3 | Katherine Murray | 2014/1/28 | 德国 | 西欧 | 技术 | 2892.51 | 1 |
4 | Rick Hansen | 2014/11/5 | 塞内加尔 | 西非 | 技术 | 2832.96 | 1 |
数据描述
data.info()
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 客户名称 45693 non-null object
1 付款日期 45693 non-null object
2 国家/地区 (Country) 45693 non-null object
3 地区 45693 non-null object
4 类别 45693 non-null object
5 销售额 45693 non-null float64
6 数量 45693 non-null int64
dtypes: float64(1), int64(1), object(5)
memory usage: 2.4+ MB
R的计算
R=data.groupby("客户名称")["付款日期"].max().reset_index()
# 计算最近一次付款时间截至到2015.12.31日间隔天数,一般就是一个年度来计算一次
R['R']=(pd.to_datetime('2015-12-31')-pd.to_datetime(R["付款日期"])).dt.days
R=R[['客户名称','R']]
R
客户名称 | R | |
---|---|---|
0 | Aaron Bergman | 113 |
1 | Aaron Hawkins | 118 |
2 | Aaron Smayling | 117 |
3 | Adam Bellavance | 105 |
4 | Adam Hart | 96 |
... | ... | ... |
791 | Xylona Preis | 130 |
792 | Yana Sorensen | 125 |
793 | Yoseph Carroll | 99 |
794 | Zuschuss Carroll | 110 |
795 | Zuschuss Donatelli | 211 |
796 rows × 2 columns
F的计算
data['FZ']=data['付款日期']
dup_f=data.groupby(['客户名称','付款日期'])['FZ'].count().reset_index()
f=data.groupby("客户名称")['付款日期'].count().reset_index()
f.columns=['客户名称','F']
f
客户名称 | F | |
---|---|---|
0 | Aaron Bergman | 87 |
1 | Aaron Hawkins | 55 |
2 | Aaron Smayling | 53 |
3 | Adam Bellavance | 55 |
4 | Adam Hart | 64 |
... | ... | ... |
791 | Xylona Preis | 56 |
792 | Yana Sorensen | 61 |
793 | Yoseph Carroll | 51 |
794 | Zuschuss Carroll | 75 |
795 | Zuschuss Donatelli | 48 |
796 rows × 2 columns
计算M值
m=data.groupby('客户名称')['销售额'].sum().reset_index()
m.columns=['客户名称','总支付金额']
fm=pd.merge(m,f,left_on='客户名称',right_on='客户名称',how='inner')
fm['M']=fm['总支付金额']/fm['F']
fm=fm[['客户名称','F','M']]
fm
客户名称 | F | M | |
---|---|---|---|
0 | Aaron Bergman | 87 | 274.515862 |
1 | Aaron Hawkins | 55 | 348.524909 |
2 | Aaron Smayling | 53 | 193.015660 |
3 | Adam Bellavance | 55 | 281.993818 |
4 | Adam Hart | 64 | 259.273594 |
... | ... | ... | ... |
791 | Xylona Preis | 56 | 210.724821 |
792 | Yana Sorensen | 61 | 312.006721 |
793 | Yoseph Carroll | 51 | 363.586275 |
794 | Zuschuss Carroll | 75 | 342.939733 |
795 | Zuschuss Donatelli | 48 | 236.603125 |
796 rows × 3 columns
RFM合并
rfm=pd.merge(R,fm,left_on='客户名称',right_on='客户名称',how='inner')
rfm
客户名称 | R | F | M | |
---|---|---|---|---|
0 | Aaron Bergman | 113 | 87 | 274.515862 |
1 | Aaron Hawkins | 118 | 55 | 348.524909 |
2 | Aaron Smayling | 117 | 53 | 193.015660 |
3 | Adam Bellavance | 105 | 55 | 281.993818 |
4 | Adam Hart | 96 | 64 | 259.273594 |
... | ... | ... | ... | ... |
791 | Xylona Preis | 130 | 56 | 210.724821 |
792 | Yana Sorensen | 125 | 61 | 312.006721 |
793 | Yoseph Carroll | 99 | 51 | 363.586275 |
794 | Zuschuss Carroll | 110 | 75 | 342.939733 |
795 | Zuschuss Donatelli | 211 | 48 | 236.603125 |
796 rows × 4 columns
打分
基于以上的RFM,我们要对每个用户的RFM进行打分,这里的打分用的是五分值(根据自己场景的不同,打分制度也不同)
rfm['R-SCORE'] = pd.cut(rfm['R'],bins = [0,30,60,90,120,float('inf')],labels = [5,4,3,2,1],right = False).astype(float)
rfm['F-SCORE'] = pd.cut(rfm['F'],bins = [0,23,40,57,74,float('inf')],labels = [1,2,3,4,5],right = False).astype(float)
rfm['M-SCORE'] = pd.cut(rfm['M'],bins = [0,180,240,300,360,float('inf')],labels = [1,2,3,4,5],right = False).astype(float)
rfm
客户名称 | R | F | M | R-SCORE | F-SCORE | M-SCORE | |
---|---|---|---|---|---|---|---|
0 | Aaron Bergman | 113 | 87 | 274.515862 | 2.0 | 5.0 | 3.0 |
1 | Aaron Hawkins | 118 | 55 | 348.524909 | 2.0 | 3.0 | 4.0 |
2 | Aaron Smayling | 117 | 53 | 193.015660 | 2.0 | 3.0 | 2.0 |
3 | Adam Bellavance | 105 | 55 | 281.993818 | 2.0 | 3.0 | 3.0 |
4 | Adam Hart | 96 | 64 | 259.273594 | 2.0 | 4.0 | 3.0 |
... | ... | ... | ... | ... | ... | ... | ... |
791 | Xylona Preis | 130 | 56 | 210.724821 | 1.0 | 3.0 | 2.0 |
792 | Yana Sorensen | 125 | 61 | 312.006721 | 1.0 | 4.0 | 4.0 |
793 | Yoseph Carroll | 99 | 51 | 363.586275 | 2.0 | 3.0 | 5.0 |
794 | Zuschuss Carroll | 110 | 75 | 342.939733 | 2.0 | 5.0 | 4.0 |
795 | Zuschuss Donatelli | 211 | 48 | 236.603125 | 1.0 | 3.0 | 2.0 |
796 rows × 7 columns
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 | Aaron Bergman | 113 | 87 | 274.515862 | 2.0 | 5.0 | 3.0 | 1 | 1 | 1 |
1 | Aaron Hawkins | 118 | 55 | 348.524909 | 2.0 | 3.0 | 4.0 | 1 | 0 | 1 |
2 | Aaron Smayling | 117 | 53 | 193.015660 | 2.0 | 3.0 | 2.0 | 1 | 0 | 0 |
3 | Adam Bellavance | 105 | 55 | 281.993818 | 2.0 | 3.0 | 3.0 | 1 | 0 | 1 |
4 | Adam Hart | 96 | 64 | 259.273594 | 2.0 | 4.0 | 3.0 | 1 | 1 | 1 |
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 | Aaron Bergman | 113 | 87 | 274.515862 | 2.0 | 5.0 | 3.0 | 1 | 1 | 1 | 111 |
1 | Aaron Hawkins | 118 | 55 | 348.524909 | 2.0 | 3.0 | 4.0 | 1 | 0 | 1 | 101 |
2 | Aaron Smayling | 117 | 53 | 193.015660 | 2.0 | 3.0 | 2.0 | 1 | 0 | 0 | 100 |
3 | Adam Bellavance | 105 | 55 | 281.993818 | 2.0 | 3.0 | 3.0 | 1 | 0 | 1 | 101 |
4 | Adam Hart | 96 | 64 | 259.273594 | 2.0 | 4.0 | 3.0 | 1 | 1 | 1 | 111 |
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 | Aaron Bergman | 113 | 87 | 274.515862 | 2.0 | 5.0 | 3.0 | 1 | 1 | 1 | 111 | 重要价值客户 |
1 | Aaron Hawkins | 118 | 55 | 348.524909 | 2.0 | 3.0 | 4.0 | 1 | 0 | 1 | 101 | 重要发展客户 |
2 | Aaron Smayling | 117 | 53 | 193.015660 | 2.0 | 3.0 | 2.0 | 1 | 0 | 0 | 100 | 一般发展客户 |
3 | Adam Bellavance | 105 | 55 | 281.993818 | 2.0 | 3.0 | 3.0 | 1 | 0 | 1 | 101 | 重要发展客户 |
4 | Adam Hart | 96 | 64 | 259.273594 | 2.0 | 4.0 | 3.0 | 1 | 1 | 1 | 111 | 重要价值客户 |
count = rfm['客户类型'].value_counts().reset_index()
count.columns = ['客户类型','人数']
count['占比'] = round(count['人数'] / count['人数'].sum(),2)
count
客户类型 | 人数 | 占比 | |
---|---|---|---|
0 | 重要价值客户 | 153 | 0.19 |
1 | 一般发展客户 | 148 | 0.19 |
2 | 一般价值客户 | 133 | 0.17 |
3 | 重要发展客户 | 115 | 0.14 |
4 | 重要挽留客户 | 72 | 0.09 |
5 | 一般挽留客户 | 66 | 0.08 |
6 | 一般保持客户 | 57 | 0.07 |
7 | 重要保持客户 | 52 | 0.07 |
from pyecharts import options as opts
from pyecharts.charts import Bar
from pyecharts.commons.utils import JsCode
from pyecharts.globals import ThemeType
from pyecharts.charts import Line
from pyecharts.charts import *
columns = count['客户类型'].tolist()
data1 = count['人数'].tolist()
data2=count['占比'].tolist()
bar = (
Bar()
.add_xaxis(columns)
.add_yaxis(
"人数",
data1,
label_opts=opts.LabelOpts(is_show=False))
.set_global_opts(
title_opts=opts.TitleOpts(title="RFM用户分类结果"))
.extend_axis(
yaxis=opts.AxisOpts(
type_="value",
min_=0.01,
max_=0.21,
interval=.03,
axislabel_opts=opts.LabelOpts(formatter="{value}"),
)
)
)
line=(
Line()
.add_xaxis(xaxis_data=columns)
.add_yaxis(
series_name="占比",
yaxis_index=1,
y_axis=data2
)
)
all=bar.overlap(line)
aa=Grid(
init_opts=opts.InitOpts(
width="1500px",
height='650px',
# theme=ThemeType.PURPLE_PASSION,
))
aa.add(all,
grid_opts=opts.GridOpts(
pos_top="20%",
pos_left="5%",
pos_right="40%",
),
is_control_axis_index=True
)
aa.render()
我们也直接做了一个脚本只要你输入你的数据直接给你生成图表,需要的话可以关注我!私信我(不过需要收费)大概长这样: