看过来
《pandas 教程》 持续更新中,提供建议、纠错、催更等加作者微信: gairuo123(备注:pandas教程)和关注公众号「盖若」ID: gairuo。跟作者学习,请进入 Python学习课程。欢迎关注作者出版的书籍:《深入浅出Pandas》 和 《Python之光》。
RFM 是典型的用户分层方法, 是评估用户消费能力、 衡量用户贡献价值的重要工具。RFM 代表的是最近一次消费时间间隔(Recency)、消费频率(Frequency)和消费金额(Monetary)。本案例将利用 Pandas 建立用户消费 RFM 模型,实现精细化运营。
首先构造数据,并进行数据类型转换:
import pandas as pd
# 构造数据
import faker # 安装:pip install faker
f = faker.Faker('zh-cn')
df = pd.DataFrame({
'用户': [f.name() for i in range(20000)],
'购买日期': [f.date_between(start_date='-1y',
end_date='today') for i in range(20000)],
'金额': [f.random_int(10, 100) for i in range(20000)]
})
# 数据类型转换
df = df.astype({'购买日期': 'datetime64[ns]'})
# 数据类型
df.dtypes
'''
用户 object
购买日期 datetime64[ns]
金额 int64
dtype: object
'''
df
'''
用户 购买日期 金额
0 张娜 2023-05-11 19
1 傅倩 2022-06-03 64
2 罗秀芳 2022-08-12 63
3 刘刚 2022-07-02 88
4 焦慧 2022-11-14 89
... ... ... ..
19995 李倩 2022-10-26 30
19996 程鑫 2022-10-20 29
19997 聂兰英 2023-05-23 58
19998 汪桂珍 2022-06-04 75
19999 赖海燕 2022-06-27 46
[20000 rows x 3 columns]
'''
以上构造了两万条用户消费记录,有用户名、购买时间和金额,且交易时间在近一年内,交易金额在 10 ~ 100 元之间。
首先来计算 R 值。R 为最后一次购买时间距今的天数,R 值越大代表用户越有可能处于沉睡状态,流失风险越大:
# r 为购买间隔天数
r = (
df.groupby('用户')
.apply(lambda x: (pd.Timestamp('today')-x['购买日期'].max()))
.dt
.days
)
r
'''
用户
丁丹丹 210
丁丽 185
丁亮 225
丁伟 87
丁俊 85
...
龚莹 121
龚阳 225
龚霞 30
龚静 89
龚鹏 290
Length: 9362, dtype: int64
'''
先对用户分组,分组后取每组用户最近购买时间(时间的最大值),然后用今日减去最近购买时间,就得到了最近购买间隔天数。
接下来计算 F 值。F 值是消费频率,消费频次越高代表用户黏性越强。我们将同一天购买多次的情况算作一次。算法也是先对用户分组,然后取购买日期的不重复数量:
# f 为购买次数,一天多次算一次
f = (
df.groupby(['用户'])
.apply(lambda x: x['购买日期'].nunique())
)
f.sort_values()
'''
用户
丁丹丹 1
穆飞 1
穆艳 1
穆秀荣 1
穆秀英 1
..
张莉 19
张明 20
李海燕 20
王文 21
李林 21
Length: 9362, dtype: int64
'''
M 值是金额,我们这里计算用户每次购物的平均金额,即用户总金额 / 用户购买次数。由于前面已经算出购买次数,因此我们在合并数据时再计算 M 值,这里先计算出每个用户的总金额:
# m 为平均每次的购买金额
df.groupby(['用户'])['金额'].sum()
'''
用户
丁丹丹 46
丁丽 164
丁亮 69
丁伟 83
丁俊 152
...
龚莹 59
龚阳 92
龚霞 123
龚静 91
龚鹏 93
Name: 金额, Length: 9362, dtype: int64
'''
接下来将 RFM 数据合并。由于我们之前在计算 R 值和 F 值后都是以用户名称为索引的,因此直接用两个 Series 构造 DataFrame,同时算出 M 值:
# 合并RFM
(
pd.DataFrame({'r': r,'f': f,})
# m为总金额/购买次数
.assign(m=lambda x: df.groupby(['用户'])['金额'].sum()/x.f)
)
'''
r f m
用户
丁丹丹 210 1 46.0
丁丽 185 2 82.0
丁亮 225 2 34.5
丁伟 87 1 83.0
丁俊 85 2 76.0
.. ... .. ...
龚莹 121 1 59.0
龚阳 225 1 92.0
龚霞 30 2 61.5
龚静 89 1 91.0
龚鹏 290 1 93.0
[9362 rows x 3 columns]
'''
这样,每个用户的 RFM 值就计算出来了。接着给 RFM 打分,为了方便演示,采用 3 分制,将 RFM 的值分为三个等级。R 值使用 pd.qcut() 平均分为三段,R 越大代表间隔时间越长,对间隔近的打 3 分,次之打 2 分,最远的打 1 分。F 值和 M 值越大越好,因此我们用 pd.cut() 人工分段,分别打 1、2、3 分。代码如下:
(
pd.DataFrame({'r': r,'f': f,})
# m为总金额/购买次数
.assign(m=lambda x: df.groupby(['用户'])['金额'].sum()/x.f)
.assign(r_s=lambda x: pd.qcut(x.r, q=3, labels=[3,2,1]))
.assign(f_s=lambda x: pd.cut(x.f,bins=[0,2,5,float('inf')], labels=[1,2,3],
right = False))
.assign(m_s=lambda x: pd.cut(x.m,bins=[0,30,60,float('inf')], labels=[1,2,3],
right = False))
)
'''
r f m r_s f_s m_s
用户
丁丹丹 210 1 46.0 1 1 2
丁丽 185 2 82.0 2 2 3
丁亮 225 2 34.5 1 2 2
丁伟 87 1 83.0 2 1 3
丁俊 85 2 76.0 2 2 3
.. ... .. ... .. .. ..
龚莹 121 1 59.0 2 1 2
龚阳 225 1 92.0 1 1 3
龚霞 30 2 61.5 3 2 3
龚静 89 1 91.0 2 1 3
龚鹏 290 1 93.0 1 1 3
[9362 rows x 6 columns]
'''
这样,就给每个用户的 RFM 完成了打分。接下来进行分值归一化,我们把高于平均水平的归为 1,低于平均水平的归为 0:
(
pd.DataFrame({'r': r,'f': f,})
# m为总金额/购买次数
.assign(m=lambda x: df.groupby(['用户'])['金额'].sum()/x.f)
.assign(r_s=lambda x: pd.qcut(x.r, q=3, labels=[3,2,1]))
.assign(f_s=lambda x: pd.cut(x.f,bins=[0,2,5,float('inf')], labels=[1,2,3], right = False))
.assign(m_s=lambda x: pd.cut(x.m,bins=[0,30,60,float('inf')], labels=[1,2,3], right = False))
.assign(r_e=lambda x: (x.r_s.astype(int) > x.r_s.astype(int).mean())*1)
.assign(f_e=lambda x: (x.f_s.astype(int) > x.f_s.astype(int).mean())*1)
.assign(m_e=lambda x: (x.m_s.astype(int) > x.m_s.astype(int).mean())*1)
)
'''
r f m r_s f_s m_s r_e f_e m_e
用户
丁丹丹 210 1 46.0 1 1 2 0 0 0
丁丽 185 2 82.0 2 2 3 0 1 1
丁亮 225 2 34.5 1 2 2 0 1 0
丁伟 87 1 83.0 2 1 3 0 0 1
丁俊 85 2 76.0 2 2 3 0 1 1
.. ... .. ... .. .. .. ... ... ...
龚莹 121 1 59.0 2 1 2 0 0 0
龚阳 225 1 92.0 1 1 3 0 0 1
龚霞 30 2 61.5 3 2 3 1 1 1
龚静 89 1 91.0 2 1 3 0 0 1
龚鹏 290 1 93.0 1 1 3 0 0 1
[9362 rows x 9 columns]
'''
最后将这些打分形成一个统一的标签。在打分设计时我们给正向的方面打了高分,再将分值的重要度 R、F、M 分别转化为数字,放在百位、十位和个位:
(
pd.DataFrame({'r': r,'f': f,})
# m为总金额/购买次数
.assign(m=lambda x: df.groupby(['用户'])['金额'].sum()/x.f)
.assign(r_s=lambda x: pd.qcut(x.r, q=3, labels=[3,2,1]))
.assign(f_s=lambda x: pd.cut(x.f,bins=[0,2,5,float('inf')], labels=[1,2,3], right = False))
.assign(m_s=lambda x: pd.cut(x.m,bins=[0,30,60,float('inf')], labels=[1,2,3], right = False))
.assign(r_e=lambda x: (x.r_s.astype(int) > x.r_s.astype(int).mean())*1)
.assign(f_e=lambda x: (x.f_s.astype(int) > x.f_s.astype(int).mean())*1)
.assign(m_e=lambda x: (x.m_s.astype(int) > x.m_s.astype(int).mean())*1)
.assign(label=lambda x: x.r_e*100+x.f_e*10+x.m_e*1)
)
'''
r f m r_s f_s m_s r_e f_e m_e label
用户
丁丹丹 210 1 46.0 1 1 2 0 0 0 0
丁丽 185 2 82.0 2 2 3 0 1 1 11
丁亮 225 2 34.5 1 2 2 0 1 0 10
丁伟 87 1 83.0 2 1 3 0 0 1 1
丁俊 85 2 76.0 2 2 3 0 1 1 11
.. ... .. ... .. .. .. ... ... ... ...
龚莹 121 1 59.0 2 1 2 0 0 0 0
龚阳 225 1 92.0 1 1 3 0 0 1 1
龚霞 30 2 61.5 3 2 3 1 1 1 111
龚静 89 1 91.0 2 1 3 0 0 1 1
龚鹏 290 1 93.0 1 1 3 0 0 1 1
[9362 rows x 10 columns]
'''
最后可以用 map 方法给数据打上中文标签:
label_names = {111:'重要价值用户',
110:'一般价值用户',
101:'重要发展用户',
100:'一般发展用户',
11:'重要保持用户',
10:'一般保持用户',
1:'重要挽留用户',
0:'一般挽留用户'}
(
pd.DataFrame({'r': r,'f': f,})
# m为总金额/购买次数
.assign(m=lambda x: df.groupby(['用户'])['金额'].sum()/x.f)
.assign(r_s=lambda x: pd.qcut(x.r, q=3, labels=[3,2,1]))
.assign(f_s=lambda x: pd.cut(x.f,bins=[0,2,5,float('inf')], labels=[1,2,3], right = False))
.assign(m_s=lambda x: pd.cut(x.m,bins=[0,30,60,float('inf')], labels=[1,2,3], right = False))
.assign(r_e=lambda x: (x.r_s.astype(int) > x.r_s.astype(int).mean())*1)
.assign(f_e=lambda x: (x.f_s.astype(int) > x.f_s.astype(int).mean())*1)
.assign(m_e=lambda x: (x.m_s.astype(int) > x.m_s.astype(int).mean())*1)
.assign(label=lambda x: x.r_e*100+x.f_e*10+x.m_e*1)
.assign(label_names=lambda x: x.label.map(label_names))
.groupby('label').count().r.plot.bar()
)
# 显示如下图形,各个标签的用户数
这样,将用户按分值由高到低分为 9 类,运营人员可以根据不同的用户类型来制定不同的营销策略。
(完)
注:此案例收录在《深入浅出Pandas:利用Python进行数据处理与分析》17.3.6 小节。
更新时间:Aug. 18, 2024, 4:14 p.m. 标签:pandas python rfm 用户分层