【极客时间】零基础实战机器学习 - RFM 模型在用户消费行为分析中的应用
作者:黄佳
极客时间专栏链接:https://time.geekbang.org/column/intro/438
特征工程:
一、用户消费行为分析与RFM模型:从数据到洞察的桥梁
在数字化时代,理解用户行为是企业制定精准运营策略的关键。然而,用户的消费行为本身往往是模糊且难以量化的,直接用于数据分析存在较大局限性。因此,将行为转化为可量化的指标,成为企业洞察用户特征、优化运营决策的第一步。
这些指标不仅能够帮助我们直观地理解用户行为模式,还能广泛应用于广告精准投放、产品推荐系统、用户生命周期管理等多个场景,从而显著提升产品和服务的精准度与转化率。
在众多分析方法中,RFM模型(Recency、Frequency、Monetary)是被广泛采用的经典工具。它通过三个核心维度,将复杂的用户行为简化为清晰的量化指标,为企业提供直观的用户价值评估框架。
(1)RFM模型的核心指标
R(Recency,最近消费时间)
定义:用户自上次消费以来的间隔天数,衡量用户的活跃度。 示例:若用户上次消费是30天前,则R值为30;若今天再次消费,R值更新为1。 意义:R值越小,用户越活跃;R值过大可能意味着用户正在流失。
F(Frequency,消费频率)
定义:用户在一定时间内的消费次数,反映用户的黏性和忠诚度。 示例:一个用户在过去3个月内消费了10次,F值为10。 意义:高频率用户通常是企业的核心用户群体,值得重点维护。
M(Monetary,消费金额)
定义:用户在某个时间段内的消费总金额,直接体现用户的价值贡献。 示例:某用户在过去一年内消费了5000元,M值为5000。 意义:M值越高,用户对企业的经济贡献越大,是高价值用户的显著特征。
(2)RFM 模型的应用场景
通过这三个指标的组合,企业可以对用户进行分类或聚类,构建精准的用户画像。例如:
高价值用户:R值小(最近消费频繁)、F值高(消费稳定)、M值高(贡献大)。 中等价值用户:R值中等、F值较低但M值较高(偶尔大额消费)。 低价值用户:R值大(很久未消费)、F值低(消费稀少)、M值低(贡献小)。
基于这些分类,企业可以制定差异化的运营策略:
针对高价值用户,提供专属优惠或会员权益,进一步提升忠诚度; 针对中等价值用户,设计促销活动或个性化推荐,激发消费潜力; 针对低价值用户,通过召回活动或重新激活策略,尝试挽回流失用户。
(3)项目实施步骤
本项目将分为两个关键阶段,逐步实现从数据到洞察的转化:
数据提取与RFM值计算
提取用户的历史消费数据(时间、频率、金额)。 计算每位用户的R、F、M值,形成结构化的用户行为数据集。
用户分组与画像构建
根据RFM值对用户进行分组(如高、中、低价值群体)。 绘制用户分布图,直观呈现不同价值群体的比例与特征。 结合业务场景,制定针对性的营销策略(如推送优惠券、个性化推荐等)。
通过RFM模型,企业不仅能精准识别用户价值,还能动态调整运营策略,实现用户生命周期的精细化管理。这一方法已被众多互联网企业验证为高效、实用的用户分析工具。
二、导入数据
import pandas as pd #导入Pandasdf_sales = pd.read_csv('易速鲜花订单记录.csv') #载入数据df_sales.head() #显示头几行数据
| 订单号 | 产品码 | 消费日期 | 产品说明 | 数量 | 单价 | 用户码 | 城市 | |
|---|---|---|---|---|---|---|---|---|
| 0 | ||||||||
| 1 | ||||||||
| 2 | ||||||||
| 3 | ||||||||
| 4 |
数据解析:
可以看到这个数据集主要包含了订单号、产品码、消费日期,产品说明、数量、单价、用户码和城市等字段。因为我们求的是用户消费行为的新进度(R)、消费的总体频率(F),还有消费总金额(M),所以在这些信息中,我们重点关注的是以下几个数据:
用户码 单价 (订单中产品的)数量 消费日期
三、数据可视化
import matplotlib.pyplot as plt #导入Matplotlib的pyplot模块from matplotlib import rcParams# 配置 matplotlib 使用字体rcParams['font.sans-serif'] = ['Heiti TC']rcParams['axes.unicode_minus'] = False # 解决负号显示问题#构建月度的订单数的DataFramedf_sales['消费日期'] = pd.to_datetime(df_sales['消费日期']) #转化日期格式df_orders_monthly = df_sales.set_index('消费日期')['订单号'].resample('ME').nunique()#设定绘图的画布ax = pd.DataFrame(df_orders_monthly.values).plot(grid=True,figsize=(12,6),legend=False)ax.set_xlabel('月份') # X轴labelax.set_ylabel('订单数') # Y轴Labelax.set_title('月度订单数') # 图题#设定X轴月份显示格式plt.xticks(range(len(df_orders_monthly.index)),[x.strftime('%m.%Y') for x in df_orders_monthly.index],rotation=45)plt.show() # 绘图
数据解析:
我们看到,这个数据集收集了“易速鲜花”公司一整年的订单量。所以啊,我们要求的消费额 M,实际上是每个用户一整年的总消费额。
你可能已经注意到,在最后一个月,也就是 2021 年 6 月,订单量突然大幅下降。其实这是因为运营人员拉这个表的时候,正是 6 月的第一个礼拜。所以,6 月的数据虽然不全,但并不会影响我们对用户 RFM 值的分析。
四、数据清洗
df_sales = df_sales.drop_duplicates() #删除重复的数据行df_sales.isna().sum() # NaN出现的次数订单号 0产品码 0消费日期 0产品说明 0数量 0单价 0用户码 0城市 0dtype: int64
df_sales.describe() #df_sales的统计信息| 消费日期 | 数量 | 单价 | 用户码 | |
|---|---|---|---|---|
| count | ||||
| mean | ||||
| min | ||||
| 25% | ||||
| 50% | ||||
| 75% | ||||
| max | ||||
| std |
数据解析:
在表中你可以看到这个数据集中,共有 9 万多行的数据(count 统计数据条目的数量),每条数据的平均采购数量是 10(mean 统计均值),商品平均单价是 3.575 元左右。在概览中我们发现,(订单中产品的)数量的最小值(min)是一个负数(-9360),这显然是不符合逻辑的,所以我们要把这种脏数据清洗掉。
具体的处理方式是,用 loc 属性通过字段名(也就是列名)访问数据集,同时只保留“数量”字段大于 0 的数据行:
df_sales = df_sales.loc[df_sales['数量'] > 0] #清洗掉数量小于等于0的数据在 DataFrame 对象中,loc 属性是通过行、列的名称来访问数据的,我们做数据预处理时会经常用到;还有一个常被用到的属性是 iloc,它是通过行列的位置(也就是序号)来访问数据的。由于数据行的名称往往也是数值,这两者非常容易被弄混。
所以,在这里我特地给你做了一张图,来说明什么是行列名、什么是行列序号,帮你弄清楚它们的区别。
df_sales.describe() #df_sales的统计信息| 消费日期 | 数量 | 单价 | 用户码 | |
|---|---|---|---|---|
| count | ||||
| mean | ||||
| min | ||||
| 25% | ||||
| 50% | ||||
| 75% | ||||
| max | ||||
| std |
再用一次 describe 方法,就会看到新的最小数量为 1,这就对了。
五、特征工程
df_sales['总价'] = df_sales['数量'] * df_sales['单价'] #计算每单的总价df_sales.head() #显示头几行数据
| 订单号 | 产品码 | 消费日期 | 产品说明 | 数量 | 单价 | 用户码 | 城市 | 总价 | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | |||||||||
| 1 | |||||||||
| 2 | |||||||||
| 3 | |||||||||
| 4 |
数据解析:
现在,在这个数据集中,用户码、总价和消费日期这三个字段,给我们带来了每一个用户的 R、F、M 信息。其中:
一个用户上一次购物的日期,也就是最新的消费日期,就可以转化成这个用户的 R值;一个用户下的所有订单次数之和,就是消费频率值,也就是该用户的 F值;把一个用户所有订单的总价加起来,就是消费金额值,也就是该用户的 M值。
不过,我们目前的这个数据集是一个订单的历史记录,并不是以用户码为主键的数据表。而 R、F、M 信息是和用户相关的,每一个用户都拥有一个独立的 R 值、F 值和 M 值,所以,在计算 RFM 值之前,我们需要先构建一个用户层级表。
六、构建User用户表
df_user = pd.DataFrame(df_sales['用户码'].unique()) #生成以用户码为主键的结构df_userdf_user.columns = ['用户码'] #设定字段名df_user = df_user.sort_values(by='用户码',ascending=True).reset_index(drop=True) #按用户码排序df_user #显示df_user
| 用户码 | |
|---|---|
| 0 | |
| 1 | |
| 2 | |
| 3 | |
| 4 | |
| ... | |
| 975 | |
| 976 | |
| 977 | |
| 978 | |
| 979 |
980 rows × 1 columns
数据解析:
构建用户层级表,简单来说就是生成一个以用户码为关键字段的 Dataframe 对象 df_user,然后在这个 Dataframe 对象中,逐步加入每一个用户的新近度(R)、消费频率(F)、消费金额(M),以及最终总的分组信息。
七、求 R 值
df_sales['消费日期'] = pd.to_datetime(df_sales['消费日期']) #转化日期格式df_recent_buy = df_sales.groupby('用户码').消费日期.max().reset_index() #构建消费日期信息df_recent_buy.columns = ['用户码','最近日期'] #设定字段名df_recent_buy['R值'] = (df_recent_buy['最近日期'].max() - df_recent_buy['最近日期']).dt.days #计算最新日期与上次消费日期的天数df_user = pd.merge(df_user, df_recent_buy[['用户码','R值']], on='用户码') #把上次消费距最新日期的天数(R值)合并至df_user结构df_user.head() #显示df_user头几行数据
| 用户码 | R值 | |
|---|---|---|
| 0 | ||
| 1 | 187 | |
| 2 | ||
| 3 | ||
| 4 |
数据解析:
R 值越大,说明该用户最近一次购物日距离当前日期越久,那么这样的用户就越是处于休眠状态。从表中可以看出来,编号为 14682 的用户已经有 187 天没有购物了。所以我们就可以判断这个用户呈现休眠态,很可能已经被别的购物平台所吸引了,也就是流失了。
八、求F值
df_frequency = df_sales.groupby('用户码').消费日期.count().reset_index() #计算每个用户消费次数,构建df_frequency对象df_frequency.columns = ['用户码','F值'] #设定字段名称df_user = pd.merge(df_user, df_frequency, on='用户码') #把消费频率整合至df_user结构df_user.head() #显示头几行数据
| 用户码 | R值 | F值 | |
|---|---|---|---|
| 0 | |||
| 1 | |||
| 2 | |||
| 3 | |||
| 4 |
九、求 M 值
df_revenue = df_sales.groupby('用户码').总价.sum().reset_index() #根据消费总额,构建df_revenue对象df_revenue.columns = ['用户码','M值'] #设定字段名称df_user = pd.merge(df_user, df_revenue, on='用户码') #把消费金额整合至df_user结构df_user.head() #显示头几行数据
| 用户码 | R值 | F值 | M值 | |
|---|---|---|---|---|
| 0 | ||||
| 1 | ||||
| 2 | ||||
| 3 | ||||
| 4 |
十、小结
要做分组画像,RFM 分析是一个不错的方法,它也是目前很多互联网厂商普遍采用的分析方式。我们今天的重点是,根据用户码、总价和消费日期这三个字段,从消费历史数据中求出每位用户的 R、F、M 的值,这就好像给用户贴上了一堆数字化的标签。
因篇幅限制,我们把分类这块放到下一篇文章。