Python实现RFM分析模型:用户分层与价值评估完整实战

📅 发布时间:2026/10/21 17:01:07
Python实现RFM分析模型:用户分层与价值评估完整实战
最近做用户分层复盘的时候有朋友问我你们用那么复杂的模型又是机器学习又是权重算法的最终效果真的比老老实实算一次RFM更好吗这个问题让我想了很多。坦白讲在大多数业务场景里一个清洗干净、逻辑清晰的RFM分析模型往往比花哨的模型更能直接指导运营动作。它不需要训练集不需要调参只需要一张订单表加几十行Python代码就能把用户清晰地分成几个层级告诉你该给谁发优惠券、该对谁做流失召回、该重点关注哪些高价值用户。这篇文章我就从实际项目出发完整拆解一遍用Python实现RFM分析模型的流程。内容包括三个维度的定义、数据预处理、指标计算、评分逻辑、用户分层以及可视化分析最后还会整理一些我实践里经常踩的坑和排查技巧。无论你是刚接触用户分析的新人还是已经在做数据运营的从业者这篇文章都适合用来搭建一套可落地的用户价值评估方案。1. RFM核心概念与项目设计思路1.1 三个维度拆解R、F、M分别度量什么RFM是三个英文单词的缩写分别代表Recency、Frequency、Monetary也就是最近一次消费时间间隔、消费频率和消费金额。这三个指标从三个完全不同的角度刻画一个用户的消费行为特征组合在一起就能形成对用户价值的立体判断。RRecency用户最近一次下单距离统计锚点日期的天数。这个值越小说明用户最近刚买过东西活跃度高越大则说明用户已经很久没有回来了。R反映的是用户的“新鲜度”和活跃状态。FFrequency统计周期内用户完成的有效订单笔数。F越高说明用户对平台的依赖度越高购买习惯越稳定F低则说明用户可能只是偶尔购买。MMonetary统计周期内用户的累计消费金额。这是最直观的价值指标M高说明用户掏钱多贡献大。这三个维度单独拆开来看都不复杂但合在一起就有意思了。比如一个用户R很小、F很小、M也不大说明他是最近刚被拉新进来的未来能不能留下还不一定另一个用户R很大、F很大、M也很大说明他曾经是重度用户但现在已经有一段时间没来了这就是典型的流失预警信号。单纯看消费金额M你不会发现这个用户正在流失单纯看最近购买时间R你也无法判断他过去贡献了多少价值。三者叠加信息量才完整。1.2 常见应用场景与业务价值RFM模型在电商、零售、付费会员、SaaS充值、线下门店等几乎所有有交易数据的场景里都能用。我实际接触过的几个典型场景包括会员精细化运营把用户按月分成不同层级配合不同力度的优惠券策略避免把大额补贴发给本来就会复购的老客。流失预警与召回把R值高但历史F、M很高的那批用户单独圈出来做定向召回活动因为这些用户曾经价值很高沉默后重新激活的难度比拉新低得多。新客价值评估结合R和F看新用户在一个周期内的二次购买率判断渠道质量。商品与活动效果分析对比活动前后用户R、F、M的均值变化评估活动是否真正提升了用户活跃度和客单价。RFM之所以长盛不衰原因在于它足够简单、直观、可解释。运营同事不需要理解算法原理看到“重要保持客户”这个标签就知道这批人应该维稳看到“一般挽留客户”就知道要低价促活。这种业务语言层面的可沟通性是很多复杂模型做不到的。1.3 整体方案设计从原始订单表到RFM评分我从一个几百万行的订单明细表出发走完整条流水线。整体流程可以概括为五步步骤处理内容核心输出1数据清洗干净的订单明细表2用户维度聚合每个用户的 R、F、M 原始数值3分位数评分每个用户的 RFM 三项得分1-4分4分层映射八类用户标签5可视化与解读分布图、矩阵图、策略建议这个设计思路的核心原则是“先算数值再算得分最后做业务映射”。千万不要一开始就想着给用户打标签而是先把三个连续变量算准因为后续不管是换评分规则还是调整分层阈值都是在原始数值基础上重新映射不用回到底层数据重跑。2. 数据准备与预处理2.1 数据字段要求与表结构认识RFM从本质上说只需要三个字段用户标识、订单时间、订单金额。但在实际项目中拿到手的表往往远远不止这三列这时候要先搞清楚哪些字段真正参与计算。我通常要求原始数据至少包含以下字段user_id用户唯一标识可以是会员卡号、手机号脱敏ID、或CookieID。order_id订单唯一标识用于计算F时去重。注意同一笔订单里买了多个商品在明细表里会展开成多行但频率F按订单数算不能按商品行数算。order_time下单时间精确到天即可但如果有时分秒建议保留以免同一天内多笔订单排序错乱。order_amount订单实付金额注意是用户真正付的钱包含优惠券抵扣、满减分摊后的金额而不是商品原价。我接到过一个需求对方给的订单表里有商品原价、商品数量、折扣价、运费、实付款第一版计算M值的人直接用商品单价乘数量结果金额比实际营收高出一大截。后来统一改用订单级别实付金额并且用order_id先聚合再汇总问题才解决。2.2 缺失值与异常值处理订单表是最容易藏脏数据的地方之一。常见的问题包括user_id为空这类记录无法归属到任何用户要么直接剔除要么单独记录一下占比。如果占比超过1%说明埋点或上报链路有问题需要提醒数据团队排查。order_amount为负常见于退款单、售后单。RFM分析的是消费行为退款单不应该算作正向收入。我的处理办法是先把退款单单独标记在计算正向订单时过滤掉金额为负的记录但R值计算时如果用户只有退款行为没有正向购买该用户的R值应该按空值处理而不是把退款时间当成消费时间。order_amount为0有的平台有0元单、试用单、积分兑换单。这类订单是否纳入RFM计算要看业务口径。如果0元单承载的是拉新激活建议单独分析如果混在正常订单里会让F值虚高。我在实践中更倾向剔除0元单因为RFM衡量的是“真实付费意愿”。还有一个很容易忽略的细节是金额单位。有的表金额单位是元有的是分有的是千元在做M值汇总前先统一单位否则后面分位数计算出来的结果差了100倍排查起来非常痛苦。2.3 关键日期计算观察窗口与时间锚点RFM里的R值是个相对概念必须依赖一个“锚点时间”。锚点时间有两种选法用分析执行当天或最新数据日期作为锚点。适合持续监控场景每天更新一次RFM结果。用固定的业务周期截止日作为锚点比如“2024-12-31”。适合月度/季度复盘保证口径统一。我建议做历史回溯分析时锚点日期和观察窗口要一起确定。假如今天是2025年6月我要分析过去一年的RFM那就取订单时间在2024-06-01到2025-05-31之间的订单R值计算锚点为2025-05-31。这样每个用户的R值都表示“距离统计周期结束多久没买东西”口径一致不会出现“有的用户R值才3天有的用户其实已经7个月没来”这种时间基准混乱的问题。对于观察窗口长度我踩过一个坑用30天窗口做RFM结果大量消费周期本来就超过一个月的用户被误判为“已流失”。后来对照不同窗口结果发现这个业务里用户平均复购间隔在45天左右于是把窗口拉长到90天R值的区分度才体现出来。窗口选择没有绝对标准建议先算一下用户平均复购间隔再决定观察周期是30天、90天还是180天。3. RFM数值计算核心实现3.1 R值、F值、M值的计算逻辑数据准备好了接下来就是核心计算环节。我先用pandas把订单表读进来然后做三步聚合。import pandas as pd import numpy as np # 读取订单明细表 df pd.read_csv(orders.csv, parse_dates[order_time]) # 清洗剔除无效记录 df df.dropna(subset[user_id, order_time, order_amount]) df df[df[order_amount] 0] # 剔除0元单和退款单 # 定义锚点日期和观察窗口 anchor_date pd.Timestamp(2025-05-31) start_date anchor_date - pd.Timedelta(days365) # 截取观察窗口内的订单 df_window df[(df[order_time] start_date) (df[order_time] anchor_date)] # 按用户聚合订单级数据 order_df df_window.groupby([user_id, order_id], as_indexFalse).agg( order_time(order_time, max), order_amount(order_amount, sum) ) # 计算R、F、M rfm order_df.groupby(user_id).agg( recency(order_time, lambda x: (anchor_date - x.max()).days), frequency(order_id, count), monetary(order_amount, sum) ).reset_index()这段代码里有一个很关键的细节我先用user_id order_id把订单明细压缩成订单级再在订单级基础上算F和M。如果不做这一步一个买了5件商品的订单在明细表里是5行直接count就会让F值虚增5倍。group内部的order_time取max是因为一笔订单虽然有很多行商品但它们共享同一个下单时间。R值我用了一个lambda表达式计算anchor_date - x.max()的days属性。这里x.max()就是该用户最近一次订单时间减去锚点日期再取天数得到的就是R值。R值越小代表越近消费。3.2 使用pandas groupby完成聚合上面的代码从功能上已经能跑通但实际项目中还有几个细节要处理。第一groupby聚合后索引要reset否则后面合并其他表时会因为索引不一致导致错位。第二如果原始数据量很大比如几千万行groupby之前先做一次order_id去重聚合可以有效降低数据量。我在处理一个接近5000万行的交易明细时先压缩成订单级后只剩800万行后续计算速度提升明显。第三注意数据类型。order_id如果是字符串类型count的时候没问题但如果是数值型且存在NaN先dropna再groupby避免NaN被当作一条记录统计进去。# 补充合并后检查数据形状 print(rfm.shape) print(rfm.isnull().sum()) print(rfm.describe())这三行输出能帮你快速发现计算是否有明显异常。比如recency的最大值如果超过观察窗口天数说明锚点或窗口设置有问题monetary的最小值如果接近0说明过滤条件可能漏掉了异常订单。数据分析最忌讳的就是“算完不看一眼直接下游用”这个习惯一定得养成。3.3 评分方法选型分位数法 vs 均值法拿到RFM原始数值后下一步是打分。常见的打分方式有两种均值法和分位数法。均值法简单粗暴超过均值打高分低于均值打低分。但它的致命弱点是受极端值影响太大。在消费金额分布里往往5%的大客户贡献了50%的收入均值会被拉得很高导致大部分普通用户M值被判为低分区分度很差。均值法还有一个问题它只能区分“高于平均”和“低于平均”两档颗粒度太粗。分位数法是更好的选择。它能保持每个分组内的样本数量大致均衡并且天然把用户按排名切分不受极端值影响。我一般把RFM每个维度分成4档用1到4分表示分数越高代表该维度表现越好。# 四分位数评分 def score_by_quantile(series, reverseFalse): q series.quantile([0.25, 0.5, 0.75]) bins [-np.inf] list(q) [np.inf] labels [1, 2, 3, 4] if reverse: labels labels[::-1] return pd.cut(series, binsbins, labelslabels) rfm[R_score] score_by_quantile(rfm[recency], reverseTrue) rfm[F_score] score_by_quantile(rfm[frequency]) rfm[M_score] score_by_quantile(rfm[monetary])注意这里的reverse参数。F和M都是越大越好所以分数从低到高排R是越小越好所以分数要反过来。pd.cut在边界值处理上默认是“左开右闭”如果有用户恰好落在某个分位数上会被分配到更高的区间这个边界效应会在后面单独说明。至于为什么用四分位而不是五分位我个人的经验是分档太少区分度不足分档太多业务解释成本高。四分位是经典选择也方便对应“低、中、良、优”四档描述。如果你的用户量特别大、分布特别均匀也可以试试五分位但业务侧沟通成本会上升建议先做一版四分位看效果。4. 客户分层与RFM模型应用4.1 RFM八类用户图谱RFM每个维度4档四四四是64种组合直接拿64种标签去运营肯定不现实。经典做法是把三个维度聚合为“高/低”二分得到8类用户。我结合评分制的对应关系如下R_scoreF_scoreM_score用户类型典型特征与运营侧重高高高重要价值用户最近买得多、买得频、花得大核心守护人群高低高重要发展用户最近高额购买但频率低有潜力值得引导复购低高高重要保持用户历史贡献大但近期沉默需要召回唤醒低低高重要挽留用户消费金额高但频率低且近期未购重点挽回高高低一般价值用户活跃但消费低适合提升客单价高低低一般发展用户新客或低频低额用户培养消费习惯低高低一般保持用户频次还行但金额低、近期沉默可以促活低低低一般挽留用户价值低且有流失趋势低优先级或低成本触达实际项目中我只是把RFM得分组合映射成一个字符串然后用字典做翻译。比如R_score4、F_score3、M_score3组合成“433”按高低映射成“高高低”再查表得到“重要发展用户”。4.2 评分合并与分层逻辑# 将得分映射为高低二值 high [3, 4] low [1, 2] def map_score(score): return 高 if score in high else 低 rfm[R_level] rfm[R_score].apply(map_score) rfm[F_level] rfm[F_score].apply(map_score) rfm[M_level] rfm[M_score].apply(map_score) # 组合标签 rfm[rfm_key] rfm[R_level] rfm[F_level] rfm[M_level] tag_dict { 高高高: 重要价值用户, 高低高: 重要发展用户, 低高高: 重要保持用户, 低低高: 重要挽留用户, 高低低: 一般发展用户, 高高低: 一般价值用户, 低高低: 一般保持用户, 低低低: 一般挽留用户, } rfm[user_segment] rfm[rfm_key].map(tag_dict)这里我强调了3、4分代表高1、2分代表低。如果你用的是五分位那3分算中不算高映射逻辑要改建议还是用四分位减少理解成本。这段代码跑完后每个用户身上都有segment标签。接下来我会做一次透视看看8类用户的人数和销售额占比这一步是评估分层质量的关键。4.3 分层结果的业务解读分层完成不等于分析完成。真正的价值在于解读每个层级有多少人、贡献了多少营收、平均客单价是多少、最近一次消费距今多少天。我常用pivot_table做这样一张汇总表result rfm.groupby(user_segment).agg( 用户数(user_id, count), 总消费金额(monetary, sum), 平均消费金额(monetary, mean), 平均消费频次(frequency, mean), 平均最近消费天数(recency, mean) ).reset_index() result[用户占比] result[用户数] / result[用户数].sum() result[金额占比] result[总消费金额] / result[总消费金额].sum() result result.sort_values(总消费金额, ascendingFalse)这张表往往能直接反映出业务问题。比如重要价值用户可能只占5%的人数却贡献了40%的金额重要保持用户人数不少但金额占比下滑说明一大波历史高价值用户正在沉默。这时候运营重点就很清晰了。我在一次分析里发现重要保持用户数量是重要价值用户的两倍但总金额只有后者的六成这说明沉默用户中有一部分是历史高消费人群如果不及时召回损失会持续放大。后来针对这批用户单独做了一次召回活动用大额专享券唤醒月活提升效果非常明显。5. 数据可视化与分析洞察5.1 RFM三维散点图与分布观察数据分析如果只停留在表格层面就很难把结论讲清楚。我习惯先做一张三维散点图用X轴代表R、Y轴代表F、Z轴代表M。虽然三维图在静态报告里不太方便看但我在探索阶段还是喜欢先转一转找分布形态。import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D fig plt.figure(figsize(12, 8)) ax fig.add_subplot(111, projection3d) sc ax.scatter(rfm[recency], rfm[frequency], rfm[monetary], crfm[R_score] rfm[F_score] rfm[M_score], cmapcoolwarm, alpha0.6, s10) ax.set_xlabel(Recency) ax.set_ylabel(Frequency) ax.set_zlabel(Monetary) plt.show()这种图最大的价值是观察整体分布是否呈现长尾特征。绝大多数情况下M值和F值都会高度右偏散点会挤在左下角少数高价值用户像散落的星点分布在右上角。如果看不到明显的长尾反而要怀疑数据质量或者业务人群有问题。5.2 客户价值矩阵热力图三维图适合自己探索但给业务方汇报的时候二维矩阵热力图实用得多。我最常用的两张图是R-F矩阵和F-M矩阵。import seaborn as sns # 交叉表R得分 × F得分 的平均M值 rf_pivot pd.pivot_table(rfm, valuesmonetary, indexR_score, columnsF_score, aggfuncmean) plt.figure(figsize(8, 6)) sns.heatmap(rf_pivot, annotTrue, fmt.0f, cmapYlGnBu) plt.title(R-F矩阵的平均消费金额) plt.xlabel(F_score) plt.ylabel(R_score) plt.show()这类热力图的信息密度很高。比如R_score1、F_score4这一格的平均M值如果极高说明有一批低频刚回流的贵客他们虽然买的次数不多但单次金额很高这类用户值得用高客单价商品去配合营销。我建议至少看三张热力图R-F看活跃与黏性、F-M看频次与钱包份额、R-M看沉默与贡献。三张图一起看用户行为特征就立体了。5.3 分层占比与营销策略建议最后一步我会把8类用户做成饼图或柱状图结合占比形成运营建议。饼图适合展示结构性占比柱状图适合展示指标对比。plt.figure(figsize(8, 6)) sns.barplot(dataresult, x用户占比, yuser_segment) plt.title(各层级用户数量占比) plt.tight_layout() plt.show()不同层级的运营策略我一般这样归纳重要价值用户控制打扰频率提供专属权益和高级会员体验避免价格战式营销。重要发展用户推荐关联商品和会员充值目标是把购买频次拉起来。重要保持用户定向召回活动用大额优惠券或限时福利唤醒同时观察竞品动向。重要挽留用户需要重点人工干预电话回访或一对一服务搞清楚沉默原因。一般价值用户提升客单价做满减、满赠、组合套餐。一般发展用户培养复购习惯推送新人专享、第二件半价等。一般保持用户低成本触达比如短信、Push配合低频促销券唤醒。一般挽留用户暂不投入大量资源以免亏损保持低成本触达即可。不过策略建议一定要结合具体业务验证不同类目的用户生命周期差异很大生搬硬套反而会出错。6. 常见问题与排查技巧实录6.1 pd.qcut报错bin edges must be unique做分位数评分时最常遇到的报错是“Bin edges must be unique”。原因是某档分位数上聚集了大量相同数值特别是F值这种整数变量可能一半用户的消费次数都是1次或2次导致25%分位数等于50%分位数分组边界重复。我的处理办法是改用自定义分箱def score_by_rank(series, reverseFalse, groups4): # 利用rank百分比来分箱避免重复边界问题 rank_pct series.rank(pctTrue) bins [i / groups for i in range(groups)] labels list(range(1, groups 1)) if reverse: labels labels[::-1] return pd.cut(rank_pct, binsbins, labelslabels, include_lowestTrue) rfm[F_score] score_by_rank(rfm[frequency])rank(pctTrue)会把原始值转换成0到1的百分比排名即使原始数值相同也能稳定分箱。这算是用排名替代原始值做分箱能有效规避边界重复问题。6.2 时间字段格式混乱在实际订单表里order_time这一列很可能不是标准datetime格式有的是字符串“2024/06/01 12:30:00”有的是“20240601”还有的是Excel里导出的浮点数日期序列。我在读取数据后一定会先做一次类型探测print(df[order_time].dtype) print(df[order_time].head(20))如果是字符串但格式统一用pd.to_datetime强行转换如果夹杂多种格式可以先让pandas自动推断转换失败的尝试用errorscoerce变成NaT再处理。转换后要检查null数量这一步如果忽略后面R值计算会因为NaT产生大量NaN非常隐蔽。6.3 M值金额量纲不统一我在2.2提过金额单位问题这里再强调一次排查技巧。算出M值后计算一下所有用户的M值中位数和最大值如果中位数是几十、最大值却是几百万且用户量很大先别急着说“我们有大客户”而是回到底表看是不是有单位不一致的脏数据混进来了。一个快速验证方法是看金额字段前几位的长度分布或者抽样打印部分订单金额人工判断。6.4 观察窗口过短导致新用户被误判如果你用30天观察窗口那么一个上月刚注册的用户他的R值必然很小、F值可能只有1、M值也不高会被分到“一般发展用户”。但如果用户本身生命周期就在成长阶段这个标签没太大意义。我通常建议在分群后把用户注册时间也join进来区分“观察窗口内的新客”和“老客”。新客单独看待因为他们还没有足够时间展示真实的F和M水平。6.5 模型结果如何验证RFM没有标准答案分层结果到底准不准我用三种方式验证业务合理性随机抽取每个层级里的用户打开他们的个人交易明细人工判断标签是否合理。比如“重要保持用户”里是不是真的有近期没有交易但历史多次高额消费的人。营销效果A/B测试对同一层级用户分实验组和对照组实验组做定向触达对照组不做对比转化率差异。如果RFM分层准确实验组响应率应该显著高于对照组。指标迁移追踪不同层级用户在一个月后的R/F/M变化看看是否向预期方向移动。比如“重要发展用户”经过运营触达后F值是否上升了。这三种验证方式不一定每次都全做但至少要完成第一种。业务合理性这一关过不了后面做再多统计都是白搭。结尾分享一个实操习惯这套RFM模型我前前后后跑了不下几十次最大的体会是不要急着追求复杂的模型结构先把R、F、M三个数值算得干干净净把分层口径和业务团队达成一致后续运营动作才能踩在点上。RFM模型做得越久我越觉得它不是一个一次性的分析脚本而是一套可以每月重复跑的例行化工具。把它封装成函数输入订单表输出分层结果再配合自动化报表就能持续监控用户结构的变化。最后再分享一个小技巧给用户做分层后一定要把分层结果回写到用户维度的宽表里保存成历史快照。这样过两个月再回看就能知道每个用户从哪个层级转移到哪个层级这种层级流转数据对运营复盘特别有价值。优化不要停模型可以简单但思考的深度不能省。