“运营负责人拿着报表问我:‘我们的用户到底怎么分群?哪些人值得砸钱维护,哪些人顺其自然就好?’”——这是不少电商数据分析师都遇到过的场景。单纯看客单价、复购率这些整体指标,根本看不出用户结构;而胡乱按消费金额分群,又容易忽略“最近没买但曾经贡献高的用户”。这篇文章要聊的,就是一套可以直接上手复现的解决方案:用RFM模型提炼用户行为特征,再用K-Means做聚类分群,最终把抽象的用户标签翻译成具体的增长决策。它不仅适合想系统学习用户画像分析的数据新人,也适合已经在做运营分析、需要把结果落地成策略的同学。
1. 项目思路与框架设计
1.1 为什么选RFM做用户画像:三个维度足以描述用户价值
RFM是客户分析领域的经典框架,由三个指标构成:Recency(最近一次消费距今的天数)、Frequency(观察窗口内的消费次数)、Monetary(观察窗口内的累计消费金额)。这套模型之所以几十年下来仍然被大规模使用,不是因为它花哨,而是因为它用最少的维度覆盖了用户价值的关键信息。
可以用一个生活化类比来理解:判断一个朋友是否值得深交,你会自然关注三件事——他最近有没有主动联系你(Recency)、联系的频率高不高(Frequency)、关键时刻愿不愿意为你花时间花资源(Monetary)。放到电商场景里,这三个维度分别对应着用户活跃度、用户粘性和用户购买力。值得注意的是,Recency不仅代表最近是否有消费,更是用户流失风险的直接信号。一个一年前高频消费但现在完全沉默的用户,和一个两个月前买过一次的用户,即使总消费金额相同,运营策略也完全不同,这就是单纯的金额分群永远覆盖不了的信息。
从全流程角度,RFM不只是一个计算任务,它定义了整个用户分群分析的数据口径和业务语义。后续K-Means聚类需要特征输入,而RFM恰恰提供了三个逻辑清晰、可解释性强的特征维度,这为聚类结果解读打下了基础。如果没有RFM这一步,直接拿几十个行为字段丢给聚类算法,模型跑出来往往是无法解释的黑盒子。
1.2 RFM与K-Means的分工逻辑:规则判断+自动聚类
在实际项目中,RFM和K-Means并不是二选一的关系,而是各司其职。RFM负责回答“从业务角度哪些维度衡量用户价值”,K-Means负责回答“在这些维度上用户天然地分成几类”。这里有个常见误区:不少人会把RFM的传统八分群方式直接当成最终答案,即对每个维度按高低分切成2×2×2=8类,形成“重要价值客户、重要保持客户、重要发展客户、一般挽留客户”等名称。
传统RFM八分群有个明显问题——阈值是拍脑袋定的。比如一个电商平台的订单周期是35天,你把Recency阈值设置在30天,就会把大量其实还活跃的用户划入“即将流失”群体。即使按分位数切分(比如前20%算高),也仍然是一种相对固定的规则,无法感知用户真实的自然聚合形态。而K-Means的价值恰在于:它不看人为设定的规则,而是在标准化后的特征空间里,根据欧氏距离自动寻找数据密集的簇,让相似的样本自然聚在一起。
所以这个项目采用的方案是:先用RFM框架完成特征构建,然后对R、F、M三个特征做标准化处理,交给K-Means聚类,最后结合业务语义对每个聚类簇进行解读和命名。这套组合兼顾了业务可解释性和数据驱动性——RFM保证维度有意义,K-Means保证分群真实反映数据内在结构。
1.3 全流程步骤一览
从最底层的订单数据到最终运营动作,整条链路可以拆成下面几个环节:
- 数据抽取:从数据仓库或业务库中获取订单明细,确定用户维度的唯一标识。
- 数据清洗:剔除退款订单、测试订单和异常金额记录,保证RFM计算口径干净。
- 特征计算:按用户聚合,生成Recency、Frequency、Monetary三个原始特征。
- 特征工程:处理长尾分布(对数变换),处理极端值,完成标准化。
- 聚类建模:通过肘部法则和轮廓系数确定K值,运行K-Means得到分群标签。
- 结果解读:分析各簇的中心值和分布特征,给每个簇起业务名称。
- 策略落地:针对不同分群制定差异化运营动作,并建立监控指标追踪效果。
这里我特别想强调第3步,也就是RFM计算口径,它往往决定了整个分析项目成败的一半。很多新手一上来就写代码跑聚类,结果分群出来业务方完全不认可,回头排查才发现是观察窗口选错了——有的是半年,有的是一年,甚至还有人用全量历史数据,这些都会导致R值失真。合理的观察窗口通常需要覆盖用户一个完整的消费周期,比如服装类目建议12个月,快消类目建议6到8个月。具体选多久,可以用所有用户的平均消费间隔作参考:如果平均间隔是45天,窗口至少要覆盖8到10个间隔,才能代表用户的稳定消费状态。
2. RFM特征构建与数据处理的实操细节
2.1 字段口径和观察窗口的设计
用户分群做得好不好,首先取决于原始数据的口径是否清晰。先看一个典型的订单表结构,它至少需要包含四个字段:
- user_id:用户唯一标识
- order_date:下单时间
- order_amount:订单实付金额
- order_status:订单状态(用于排除退款、取消)
这里有个角落里容易被忽略的问题:order_amount的定义。不同企业对这个字段的处理差异很大——有的记录的是商品总额,没有扣减优惠券;有的记录的是实付金额,但包含了运费;还有的会把满减活动补贴单独计费。建议统一采用“用户实际支付金额(含运费、扣减优惠后)”作为Monetary的计算基础。如果事后运营部门反馈“高价值用户怎么全是买大件商品的”,那大概率是金额口径混入了未扣除售后退款的总额。
关于观察窗口,常见的做法是取“当前日期前推365天”作为窗口,也可以取固定的财务周期(比如2023-01-01到2023-12-31)作为分析周期。前者适合滚动监控,后者适合做周期性复盘。观察窗口确定后,需要同步确定一个观测基准日(Reference Date),用于计算Recency。基准日通常设置为窗口结束日期,即“截至该日用户多久没下单”。
2.2 数据清洗要点:退款订单和异常值不能马虎
拿到订单后,第一步不是算RFM,而是清洗。需要检查的数据问题有以下几类:
第一,订单状态过滤。退款订单、已取消订单必须剔除,否则一个用户因为某次大额订单退款,Monetary会严重虚高。实际操作中,我习惯再加一层判断:对同一订单出现部分退款的情况,按实际保留金额计算,而不是直接整单剔除。
第二,用户唯一标识去重。一个手机号注册两个账号、同一用户跨平台下单等场景,都会导致同一自然人的行为被拆散到多个ID上。条件允许的情况下,尽量用手机号、设备ID等做用户统一ID映射,再做特征聚合。
第三,异常金额值处理。用户实付金额为负数(退款超扣)、单笔金额异常高(比如单笔几十万的B端采购混在C端数据里),都会严重拉偏Monetary的分布。对这类问题,比较稳妥的做法是先画分布图看整体形态,然后对单笔金额超过99.5分位数的记录做截断或剔除,而不是直接一刀切删除所有超过某个绝对值的订单。
第四,Frequency口径确认。到底按订单数算,还是按下单天数算?如果一个用户在某天下了5单,他的消费频率到底算5次还是1次?在大多数电商场景下,我建议按“下单天数”来统计。原因在于:同一天连下5单的行为更像一次集中采购,而跨越5天每天下一单才更能体现持续的消费习惯。如果不区分这一点,那些喜欢“凑单”的用户频率会虚高,聚类时容易被误判为高粘性人群。
2.3 RFM特征计算与长尾分布处理
完成清洗后,就可以用pandas做特征聚合了。下面是一段可以直接复用的参考代码:
import pandas as pd import numpy as np # 读取订单明细 orders = pd.read_csv('orders.csv') orders['order_date'] = pd.to_datetime(orders['order_date']) # 定义观测基准日与窗口 obs_date = pd.to_datetime('2024-12-31') window_start = obs_date - pd.DateOffset(days=365) # 过滤窗口内的有效订单 orders = orders[(orders['order_date'] >= window_start) & (orders['order_date'] <= obs_date)] orders = orders[orders['order_status'].isin(['completed', 'partial_refund'])] # 部分退款订单按实际支付金额计算 orders['actual_amount'] = orders.apply( lambda x: x['refunded_amount'] if x['order_status'] == 'partial_refund' else x['order_amount'], axis=1 ) # 按用户聚合RFM特征 rfm = orders.groupby('user_id').agg( recency=('order_date', lambda x: (obs_date - x.max()).days), frequency=('order_date', 'nunique'), # 按消费天数计算 monetary=('actual_amount', 'sum') ).reset_index() # 查看分布情况 print(rfm.describe())这里用了nunique来计算Frequency,也就是用户在不同日期下单的天数。如果你内部业务确认按订单笔数更合理,把nunique换成count即可。
聚合后的数据往往会呈现明显的长尾分布:绝大多数用户消费金额集中在几百元区间,少数大客户贡献了几万甚至几十万。如果不做处理直接送进K-Means,Monetary这一维度的极端值会完全主导距离计算,导致聚类结果只看金额而忽略Recency和Frequency。通常做法是对Monetary和Frequency做对数变换:
# 对数变换,压缩长尾 rfm['recency_log'] = rfm['recency'] rfm['frequency_log'] = np.log1p(rfm['frequency']) rfm['monetary_log'] = np.log1p(rfm['monetary'])注意Recency不需要做对数变换,因为距离观测点的天数通常是右偏但不算极端,而且它越小越活跃,保持原始单位(天)更容易解释。
2.4 RFM打分方案:五分位法和业务阈值法怎么选
在聚类之前,有人习惯先给RFM三个维度打分,然后把分数作为聚类特征。这个思路可行,但需要谨慎。常见的打分方案有两种:
五分位法:三个维度各自按数值排序,按20%、40%、60%、80%分位数切成5档,对应1到5分。优点是分布均匀,不受极端值影响;缺点是完全基于排名,会抹掉相邻档位的真实差距。
业务阈值法:根据业务经验设定阈值,比如“距离上次下单小于30天为5分,30到60天为4分”。优点是阈值可直接解释;缺点是阈值本身需要业务方认可,不同类目阈值差异很大。
就这个项目而言,我更推荐先做对数变换和标准化,然后直接对连续值聚类,而不是对打分后的离散值聚类。理由很简单:打分本质上是把连续信息离散化,一定会丢失一部分区分度。K-Means本身对数值分布并没有严格要求,只要距离度量合理就可以工作。打分留到聚类完成后,做业务解释时再用。
3. K-Means聚类实战:从标准化到人群命名
3.1 为什么要标准化:R、F、M的量纲困境
K-Means的核心是计算样本之间的欧氏距离。看一组典型数据:Recency取值范围大约在0到365天,Frequency在1到100次,Monetary在几十到几万元。如果直接把这些原始值放进模型,Monetary的量级会完全碾压其他两个维度,聚类结果实质上等于只按消费金额分群,Recency和Frequency变成了摆设。
处理方法就是对特征做Z-score标准化,让每个维度均值为0、方差为1。需要注意的是,如果使用了记录对数变换后的frequency_log和monetary_log,标准化的时候要基于这两个变换后的特征,而不是原始值。代码实现很简单:
from sklearn.preprocessing import StandardScaler # 选取建模特征 features = ['recency', 'frequency_log', 'monetary_log'] X = rfm[features].copy() # 标准化 scaler = StandardScaler() X_scaled = scaler.fit_transform(X)标准化之后,模型看到的是每个用户相对于整体平均水平的偏移程度,这才真正做到R、F、M三个维度同等对待。
3.2 K值选择:不要只盯着肘部法则,还要看业务可解释性
K-Means需要提前指定聚类个数K,这是使用过程中最让人纠结的参数。常用的辅助手段有两个:
肘部法则:绘制簇内误差平方和(Inertia/SSE)随K值变化的折线图,选取曲线拐点处的K。随着K增大,SSE必然下降,但下降幅度会逐渐变缓,那个“肘部”位置就是信息量增加最快的折点。
轮廓系数:对每个样本计算其与自身簇内样本相似度和最近其他簇样本相似度的比值,全局取平均,取值范围-1到1,越接近1说明聚类效果越好。
实际项目中,我建议两个指标同时看,而且不要机械地选择指标最优的K。曾在一个鞋服类目项目中,轮廓系数在K=4时表现最好,但结合业务看,4个人群的区分度不足,运营需要更细的动作指导。把K提到6之后,轮廓系数虽然略降0.02,但多出来的人群“高价值低频休眠用户”对运营非常有价值。换句话说,K值选择一半看统计指标,一半看业务诉求,没有绝对正确只有相对合理。
from sklearn.cluster import KMeans # 测试K取2到10 inertia_list = [] silhouette_list = [] k_range = range(2, 11) for k in k_range: model = KMeans(n_clusters=k, n_init=10, random_state=42) model.fit(X_scaled) inertia_list.append(model.inertia_) silhouette_list.append(silhouette_score(X_scaled, model.labels_))轮廓系数计算需要引入from sklearn.metrics import silhouette_score。如果数据量超过几十万行,计算全量轮廓系数会比较慢,可以抽样计算,比如随机抽取1万条记录。
3.3 执行聚类与稳定性处理
确定K之后,就可以正式建模了。这里有一个必须提醒的坑:K-Means的初始簇中心是随机选择的,不固定随机种子的话,每次运行出来的结果可能不一样。尤其在K较大或数据中存在多个接近规模的簇时,随机初始化的不同可能产生完全不同的分群结果。
解决办法是两件事。第一,设置random_state参数,保证本次分析结果可复现;第二,设置n_init=10,让算法在10次不同初始化中选择最优的一次。还有新版sklearn里,n_init的默认值从10改成了auto,不过为了代码在不同版本间行为一致,还是显式写明更好。
# 最终模型训练 final_model = KMeans(n_clusters=6, n_init=10, random_state=42) final_model.fit(X_scaled) # 将聚类标签写回原表 rfm['cluster'] = final_model.labels_输出完成后,建议对簇大小做个检查,看看是否有某个簇只有几百人甚至更少。如果某个簇占比极小(低于2%),要考虑是不是K值设得过大,或者数据中存在明显离群点。后者的话,可以先用DBSCAN或IQR法剔除离群点,再做一次K-Means。
3.4 聚类结果解读:从簇中心到业务命名
聚类的输出只是0到5的标签编号,真正困难的工作是把这些编号翻译成业务语言。最有效的切入点是计算每个簇的RFM平均值和占比:
cluster_profile = rfm.groupby('cluster').agg( user_cnt=('user_id', 'count'), avg_recency=('recency', 'mean'), avg_frequency=('frequency', 'mean'), avg_monetary=('monetary', 'mean') ).reset_index() cluster_profile['user_ratio'] = cluster_profile['user_cnt'] / len(rfm) print(cluster_profile)观察每个簇的中心值,同时对比整体均值,就能判断每个簇在三个维度上是高是低。举个例子,一个簇的avg_recency=15天、avg_frequency=18次、avg_monetary=3200元,显然属于活跃且贡献高的核心人群;另一个簇avg_recency=280天、avg_frequency=2次、avg_monetary=650元,就是典型的沉睡流失人群。
命名时不要用“簇0、簇1”这种代号,运营人员记不住也没有共鸣。我习惯用“干系人标签”风格,比如高价值活跃用户、潜力发展用户、新客待转化、沉默高价值用户、长尾低频用户、流失预警用户。命名时还要注意:每个簇的业务行为特征应该能用一句话说清楚,比如“近3个月有复购、客单价中等、但最近45天没有下单”。如果某个簇无法用一句话描述,多半是K值不合适或者特征还不够,需要回头调整。
4. 从用户分群到增长决策落地
4.1 分群结果如何翻译成运营动作:高价值用户要“养”
聚类完成之后,最容易出现的尴尬是:分群报告做得很漂亮,但运营看完不知道下一步干什么。问题出在缺少翻译环节——数据结果和运营动作之间隔着一道鸿沟。
拿高价值活跃用户来说,这个群体人数可能只占10%,但贡献了40%以上的GMV。对他们最忌讳的是“过度营销”——频繁推送促销短信、狂发优惠券,只会让他们觉得品牌掉价,反而降低忠诚度。适合的动作是偏向服务型的:专属客服通道、新品优先试用权、生日礼品、积分加速权益。目标不是让他们买更多,而是让他们买得更久、推荐更多的人。
而高价值但低频的用户(比如一年只买两次,但每次买很多)需要的是“激活”而非“唤醒”。他们不差钱,但对品牌缺乏高频接触的理由。可以推送轻量级互动内容(如穿搭指南、使用技巧)、老客专享回购礼,甚至是一张“春季焕新清单”。关键信息是让他们在下次需要购买时第一时间想到这个品牌。
4.2 中低价值用户的差异化策略:不同人群不同打法
中价值用户往往是运营ROI提升空间最大的群体。他们有一定消费基础,但不是最核心的忠实客户。常见的分群和对应的策略如下:
- 新客逐渐下沉人群:首单后超过75天未复购,频次低但金额尚可。策略是推送场景化组合推荐(“买了A的用户还会买B”),同时用限时复购券降低决策门槛。
- 活跃但客单价低人群:购买频次高,但每单金额小。这类用户适合做向上销售,通过满减门槛设计、套餐搭配来提升客单价。
- 沉睡流失人群:Recency超过180天,频次和金额逐年走低。不建议直接发大额优惠券,因为唤醒成本高、回报不确定。先做低成本触达,比如邮件或者短信推送品牌内容,观察打开率和回访率,再决定是否投入更高成本的召回资源。
这里有一个典型的反直觉场景:有些用户频次很高但金额极低,聚类时会被识别为“低价值活跃”人群。运营往往想放弃他们。但换个角度想,他们活跃说明对品牌有认知,只是因为消费力限制买不起高客单商品。对他们更适合的策略是推性价比款、临期款或小规格装,维持活跃的同时慢慢引导消费升级。
4.3 持续监控与模型迭代:分群不是一次性的
用户分群模型不是建一次就完事,用户行为时刻在变,模型必然需要迭代。实际落地中,建议把建模做成月度或季度的例行任务,每次滚动窗口更新数据,重跑聚类,然后观察三个关键变化:
第一,人群规模变化。高价值人群在扩大还是缩小?如果三个月内高价值人群占比下滑,说明整体用户结构在恶化,需要从拉新端和留存端同时找原因。
第二,人群迁移路径。上个月是“潜力用户”的人,这个月变成了“活跃用户”还是“流失预警”?可以做一张交叉迁移表,跟踪每个用户的分群标签变化,这是用户生命周期运营的核心依据。
第三,新客的归属趋势。新用户首次下单后第30天落在哪个分群?如果大量新用户快速滑向沉睡,说明首购体验或承接策略有问题。
这里我推荐一个操作性很强的做法:每月输出一张“分群迁移矩阵表”,行是上月分群,列是本月分群,单元格是人数。这张表能直观展示各人群的流转路径,比单独盯着人群占比更有利于运营调整策略。比如发现“高价值活跃用户”大量迁移到“高价值低频用户”,说明推送频次或者商品策略出了问题,要尽快干预。
5. 常见问题排查与避坑实录
5.1 K-Means每次运行结果都不一样,到底信哪个
正如前面提到的,K-Means依赖随机初始化,不固定随机种子时每次运行结果可能不同。但需要区分两种情况:
- 设置了random_state之后每次结果相同,说明模型本身稳定,只需要在文档中记录随机种子即可。
- 固定了random_state但调整n_init后结果差异很大,说明数据中可能存在多个局部最优解,这个模型本身不够稳定。建议改用更稳健的K-Means变体(如K-Means++初始化),或者增加n_init到20甚至50,观察结果是否收敛到同一状态。
还有一种情况:聚类结果每次都不太一样,但每个簇的规模分布和中心值大致接近,只是簇编号顺序不同。这属于正常现象,不影响业务解读,只需要在复现时固定好random_state即可。
5.2 聚类出来的人群边界模糊,看不出明显差异
有时候聚类结果让人很挫败:每个簇的中心值都差不多,画散点图也是一团糊。这种情况大概率不是算法问题,而是特征设计问题。
第一种可能:观察窗口和数据口径导致R、F、M三个维度高度相关。比如平台促销极其频繁,几乎所有用户都是近30天下过单,Recency维度差别很小,那么它提供的信息量就很低。此时可以考虑缩小观察窗口突出差异化,或者换用更细粒度的行为特征,比如加一个“平均消费间隔”之类的辅助维度。
第二种可能:数据量太少。几千个用户的样本做K-Means,分出来的簇天然不稳定。这种情况下不需要强行聚类,用RFM打分后配合业务规则做分群可能更实际。
第三种可能:没有做离群点剔除。几个极端值用户会形成独立的微小簇,拉偏其余样本的聚类边界。用IQR或DBSCAN先揪出离群点再聚类,效果通常好很多。
5.3 业务方不认可分群结果,阈值口径有争议怎么办
数据分析中技术上没有错、但业务上不被认可的情况并不少见。最典型的是Monetary口径:你按实付金额算高价值,运营却认为应该按毛利算,因为有些高客单商品毛利率极低,属于“赔本赚吆喝”。
我的建议是:在做特征计算之前,先把口径问题找业务方掰扯清楚。不要等到聚类完再讨论,否则返工成本极高。实操中可以把口径设计成可配置的,比如在参数配置里加上monetary_field选项,默认情况下同时输出“销售额口径”和“毛利口径”两版分群结果,让业务方自己对比选择。虽然多费一些功夫,但结果落地时会顺畅很多。
5.4 常见问题速查表
| 问题 | 常见原因 | 解决方案 |
|---|---|---|
| 聚类结果每次不一样 | 未固定随机种子 | 设置random_state,尝试增大n_init |
| 某个簇人数极少 | 离群点干扰或K值过大 | 剔除离群点并重新聚类,或降低K值 |
| 分群后看不出特征差异 | 特征信息量不足 | 调整观察窗口、补充行为特征 |
| 运营不认可分群结果 | 口径分歧 | 提前对齐口径,输出多口径对比结果 |
| 聚类轮廓系数很低 | 数据本身类别的确不明显 | 尝试层次聚类或GMM,或换用打分分群 |
落地建议与个人体会
最后聊几句我自己的实操感受。这个项目中,最难的一步往往不是建模,而是把“统计上的分群”翻译成“业务上的行动”。我见过不少分析报告,聚类做得中规中矩,结果汇报时运营问一句“那这个人群我下个月做什么活动”,全场沉默。要规避这个问题,最好在建模阶段就让运营参与进来,一起讨论每个簇的命名和特征,让策略人从分群中看到能抓手的东西。
还有一个非常实用的细节:分群之后,给每个人的标签打上之后,同步输出一份用户名单到CRM系统或CDP平台,这样运营才能做定向触达。只停留在分析报告里的用户分群,价值约等于零。数据和业务之间的距离,往往是靠流程打通来缩短的。
如果你现在正要启动类似项目,建议先从一份小规模样本开始跑通全流程,确认口径和业务认可后再扩展到全量数据。这样试错成本最低,也最容易赢得业务团队的信任。希望这篇基于实际项目经验的分享,能帮你少踩坑。