基于RFMT模型与K-Means聚类的客户画像构建实战

📅 2026/8/27 22:41:15
基于RFMT模型与K-Means聚类的客户画像构建实战
1. 项目概述从一道赛题到一套可复用的数据分析方法论2018年高教社杯全国大学生数学建模竞赛的C题题目是“大型百货商场会员画像描绘”。这道题当年让不少队伍挠头因为它看起来不像传统的物理建模或优化问题更像是一个商业数据分析项目。题目给了你一大堆会员的消费记录要求你“描绘”出会员的画像。这“描绘”二字听起来很艺术但落到数学建模上就是要求你用定量的、可解释的模型把一群人的消费行为特征给抽象出来并给出商业建议。我当时带学生备赛这道题我们花了大力气研究。核心就在于题目暗示的“RFMT模型”。RFMT不是什么新发明它是客户关系管理CRM和零售分析领域一个非常经典的分析框架是RFM模型的扩展。RRecency代表最近一次消费FFrequency代表消费频率MMonetary代表消费金额这是老三样。而TTime在这个语境下通常指客户生命周期或消费时间跨度它弥补了RFM模型对客户“忠诚度”或“活跃周期”刻画不足的问题。所以这道题的本质是考察学生如何将经典的商业分析模型应用于具体的数据集通过数据清洗、特征工程、聚类分析等一系列操作最终实现客户分群并为商场运营提供决策支持。这不仅仅是一篇获奖论文和一堆代码它背后是一套完整的数据分析流水线思维。从拿到杂乱无章的Excel表格开始到得出“高价值活跃会员”、“沉睡会员”、“高消费新会员”等清晰的群体标签每一步都充满了抉择和技巧。今天我就以这道赛题为例拆解如何用Python当年很多优秀论文也用Matlab或R完整实现基于RFMT模型的会员画像描绘。你会发现这套方法论不仅适用于数模竞赛对于任何想要入门用户行为分析、精准营销的数据分析师来说都是一个极佳的练手项目。我们不仅会还原获奖论文的核心思路更会补充大量在论文中因篇幅限制未能详述的“坑”和“技巧”让你拿到数据就能跑通并且知道为什么这么做。2. 核心思路与模型选型为什么是RFMT以及如何超越它拿到题目数据通常是包含会员卡号、消费时间、消费金额、商品类型等字段的流水记录。第一步不是急着写代码而是理解RFMT模型在此场景下的具体定义。这是建模的基石定义不同后续所有结果都会天差地别。2.1 RFMT指标的定义与计算逻辑在经典零售分析中RFM通常是对单个客户计算的。但在竞赛中数据是时间切片比如过去两年我们需要为每个会员计算其在观察期内的RFMT值。R最近消费时间 - Recency 指在观察期结束时比如2018年7月31日该会员最近一次消费距离截止日期的天数。这个值越小代表客户最近越活跃。计算时要注意如果会员在观察期内只有一次消费那么R值就是那次消费到截止日的天数。这里第一个坑就来了数据里是否有异常日期比如未来的日期或极早的日期必须进行清洗。F消费频率 - Frequency 指在观察期内该会员总的消费次数。注意是消费“次数”不是“天数”。同一天多次消费算多次。这里容易混淆的是有些同学会用消费天数。但题目通常强调“次数”因为它更能反映客户的互动频率。M消费金额 - Monetary 指在观察期内该会员的总消费金额。这是最直接的“价值”指标。但要注意是否包含退货题目数据通常指净消费额。T消费时间跨度 - Time 这是RFM的扩展。通常有两种定义1) 该会员第一次消费与最后一次消费之间的时间差天数。这反映了客户的活跃周期。2) 该会员成为会员的时长观察期长度 - 入会天数。在本题上下文中结合“会员”这个身份采用第二种定义即会员生命周期可能更贴合“画像”的意图因为它包含了沉默期。我们需要根据数据字段的完整性来选择。如果数据有注册日期就用注册日期如果没有或许只能用第一种定义首次到最后一次消费来近似表征其活跃时间窗口。计算完这四个指标每个会员就从一堆交易记录抽象成了一个四维向量[R, F, M, T]。这就是我们后续分析的“原料”。注意指标定义的业务对齐。在真实业务中这些定义需要和业务方反复确认。例如对于奢侈品商场可能更关注“客单价”M/F而非总金额M对于快消品频率F的权重可能更高。竞赛中虽无业务方但你在论文中清晰阐述定义及其商业意义是加分项。2.2 数据标准化为什么以及怎么做计算出的R、F、M、T四个指标量纲和数量级完全不同。R可能是几十到几百天F可能是1到几十次M可能是几十到几万元T可能是几天到几年。如果直接把这些原始数据扔进聚类算法比如K-Means那么数量级最大的M万元级会完全主导距离计算使得R、F、T的影响被淹没聚类结果基本就等于按消费金额分群了这显然不是我们想要的“综合画像”。因此标准化Normalization是必须的。常用方法有Z-score标准化(x - mean) / std。将数据转换为均值为0标准差为1的分布。适用于数据分布没有明显边界的情况。Min-Max归一化(x - min) / (max - min)。将数据缩放到[0, 1]区间。对异常值比较敏感。对数变换 对于M消费金额这种通常呈长尾分布少数人消费额极高的指标先取对数如log(1x)再标准化效果更好能削弱极端值的影响。在本次项目中我推荐先对M进行对数变换然后对所有指标采用Z-score标准化。因为RFMT的分布不一定均匀Z-score相比Min-Max对异常值的鲁棒性稍好一些。import pandas as pd import numpy as np from sklearn.preprocessing import StandardScaler # 假设 df_rfmt 是包含每个会员 R, F, M, T 原始值的DataFrame # 对M进行对数变换以缓解长尾分布 df_rfmt[‘M_log‘] np.log1p(df_rfmt[‘M‘]) # log1p log(1x)避免x0的情况 # 选择需要标准化的特征列 features_to_scale [‘R‘, ‘F‘, ‘M_log‘, ‘T‘] # 使用变换后的M_log scaler StandardScaler() rfmt_scaled scaler.fit_transform(df_rfmt[features_to_scale]) # 将标准化后的数据存回DataFrame并命名 df_rfmt_scaled pd.DataFrame(rfmt_scaled, columns[‘R_scaled‘, ‘F_scaled‘, ‘M_scaled‘, ‘T_scaled‘])2.3 聚类算法选型K-Means还是DBSCAN标准化后我们得到了一个干净的特征矩阵接下来就是对会员进行分群。聚类算法选择是关键。K-Means 最常用需要预先指定簇的数量K。优点是速度快结果易于解释。缺点是需要指定K且对初始质心敏感对非球形分布的数据效果不好。DBSCAN 基于密度不需要指定簇数能识别噪声点异常会员。适合处理任意形状的簇。缺点是对参数邻域半径eps最小样本数min_samples敏感高维数据下效果可能下降。对于RFMT模型我们的目标是得到几个具有明确商业意义的客户群体如高价值活跃客户、一般保持客户、流失风险客户等群体数量不会太多通常4-8个。K-Means的“指定K”缺点在这里反而成了优点——我们可以通过业务解读来确定一个合理的K范围。因此K-Means通常是首选。但问题来了K到底选几这就是下一个核心环节。3. 核心实现步骤从数据到画像的完整流水线3.1 数据预处理与RFMT特征计算实战假设我们拿到的原始数据是一个交易记录表transaction.csv包含字段member_id会员IDtransaction_date交易日期amount交易金额。import pandas as pd import numpy as np from datetime import datetime # 1. 加载数据 df pd.read_csv(‘transaction.csv‘, parse_dates[‘transaction_date‘]) print(df.head()) print(df.info()) # 2. 数据清洗 # 检查缺失值 print(df.isnull().sum()) # 假设我们删除会员ID或交易时间为空的记录根据实际情况处理 df_clean df.dropna(subset[‘member_id‘, ‘transaction_date‘]) # 检查并处理异常日期如未来日期 current_date pd.to_datetime(‘2018-07-31‘) # 假设观察期截止日 df_clean df_clean[df_clean[‘transaction_date‘] current_date] # 检查异常金额如负数或极大值需根据业务判断 df_clean df_clean[df_clean[‘amount‘] 0] # 假设只考虑正消费 # 3. 计算RFMT原始特征 # 设定观察期截止日 observation_end current_date # 按会员分组计算 rfmt_raw df_clean.groupby(‘member_id‘).agg( R(transaction_date, lambda x: (observation_end - x.max()).days), # Recency: 最近一次距今天数 F(transaction_date, count), # Frequency: 交易次数 M(amount, sum), # Monetary: 总金额 T_first(transaction_date, min), # 首次消费时间用于计算T T_last(transaction_date, max‘) # 末次消费时间 ).reset_index() # 计算T会员生命周期从首次消费到观察期末的天数 rfmt_raw[‘T‘] (observation_end - rfmt_raw[‘T_first‘]).dt.days # 或者如果你想用活跃周期 rfmt_raw[‘T_active‘] (rfmt_raw[‘T_last‘] - rfmt_raw[‘T_first‘]).dt.days 1 # 这里我们采用生命周期T rfmt_raw rfmt_raw[[‘member_id‘, ‘R‘, ‘F‘, ‘M‘, ‘T‘]] print(rfmt_raw.head())3.2 确定最佳聚类数K肘部法则与轮廓系数的博弈使用K-Means我们需要找到最佳的K值。常用方法是“肘部法则”和“轮廓系数”。肘部法则 计算不同K值下聚类结果的误差平方和SSE。随着K增大SSE会下降。当K增加到真实簇数附近时SSE的下降幅度会突然变缓形成一个“肘部”。这个点对应的K就是建议值。轮廓系数 衡量一个样本与其自身簇的相似度内聚度和与其他簇的差异度分离度。轮廓系数越接近1说明聚类效果越好。我们可以计算不同K值下所有样本的平均轮廓系数取最大值对应的K。在实际操作中两者结合看。from sklearn.cluster import KMeans from sklearn.metrics import silhouette_score import matplotlib.pyplot as plt # 假设 X_scaled 是标准化后的RFMT特征矩阵 X_scaled df_rfmt_scaled.values # 肘部法则 sse [] k_range range(2, 11) # 探索K从2到10 for k in k_range: kmeans KMeans(n_clustersk, random_state42, n_init‘auto‘) # n_init‘auto‘ 是较新版本sklearn的用法 kmeans.fit(X_scaled) sse.append(kmeans.inertia_) # inertia_ 即 SSE plt.figure(figsize(12, 4)) plt.subplot(1, 2, 1) plt.plot(k_range, sse, ‘bo-‘) plt.xlabel(‘Number of clusters K‘) plt.ylabel(‘SSE‘) plt.title(‘Elbow Method For Optimal K‘) # 轮廓系数 silhouette_avg [] for k in k_range: kmeans KMeans(n_clustersk, random_state42, n_init‘auto‘) cluster_labels kmeans.fit_predict(X_scaled) silhouette_avg.append(silhouette_score(X_scaled, cluster_labels)) plt.subplot(1, 2, 2) plt.plot(k_range, silhouette_avg, ‘ro-‘) plt.xlabel(‘Number of clusters K‘) plt.ylabel(‘Silhouette Score‘) plt.title(‘Silhouette Analysis For Optimal K‘) plt.tight_layout() plt.show()通过看图你可能会发现肘部在K4或5处而轮廓系数在K3或4时最高。这时就需要结合业务解释性做决策。K3可能过于粗糙只能分出高、中、低价值K5或6可能能细分出“新锐高消费客户”、“稳定忠实客户”、“即将流失客户”等更有价值的群体。我通常会在肘部附近SSE下降放缓的转折点和轮廓系数较高的几个K值中都尝试聚类然后对比每个簇的RFMT均值特征看哪个分群结果在商业上最容易理解和应用。3.3 K-Means聚类与簇中心分析假设我们综合判断后选择K5。# 进行K-Means聚类 optimal_k 5 kmeans KMeans(n_clustersoptimal_k, random_state42, n_init‘auto‘) cluster_labels kmeans.fit_predict(X_scaled) # 将聚类标签加回原始数据 df_rfmt[‘cluster‘] cluster_labels df_rfmt_scaled[‘cluster‘] cluster_labels # 分析每个簇的RFMT均值使用原始值便于业务解释 cluster_profile df_rfmt.groupby(‘cluster‘)[[‘R‘, ‘F‘, ‘M‘, ‘T‘]].mean().reset_index() print(cluster_profile) # 也可以看标准化后的中心了解在“相对尺度”上各簇的特征 cluster_centers_scaled kmeans.cluster_centers_ print(“Scaled Cluster Centers:“) print(cluster_centers_scaled)接下来是最关键的一步解读簇中心。我们需要把每个簇在R、F、M、T四个维度上的平均值或标准化中心翻译成业务语言。例如假设我们得到这样一个簇以下为示意数据簇0: R平均15天最近消费 F平均25次频率很高 M平均58000元金额很高 T平均650天生命周期长。解读这是典型的“高价值活跃忠实会员”。他们最近刚消费过消费频繁花钱多且是商场的老顾客。是商场的核心资产需要重点维护和提供VIP服务。簇1: R平均180天很久没来 F平均3次频率低 M平均1200元金额低 T平均700天注册很久。解读这是“沉睡会员”或“流失会员”。他们曾是会员但很久不活跃消费贡献低。商场可能需要设计唤醒活动如大额优惠券、专属活动邀请或分析其流失原因。簇2: R平均30天较近 F平均5次中等 M平均8000元中等 T平均100天新会员。解读这是“有潜力的新会员”。他们刚加入不久已经产生了几次消费客单价不错。是重点培养对象可以通过个性化推荐和积分激励促进其向高价值客户转化。通过这种方式为每个簇打上业务标签会员画像就初步形成了。3.4 可视化与画像呈现文字描述不够直观我们需要可视化。雷达图非常适合展示每个簇在R、F、M、T四个维度上的相对表现。但注意由于我们做过标准化雷达图展示的是标准化后的中心或者需要将原始值进行归一化到同一尺度。import matplotlib.pyplot as plt import seaborn as sns from math import pi # 为雷达图准备数据使用原始值的归一化版本或标准化中心。 # 这里使用标准化后的中心并取其负值因为R越小越好但雷达图上我们希望指标越大越好这里需要统一 # 一种常见处理对于R用 1/R 或 负的标准化值 来转换使其与其他指标同向。这里为简单我们直接使用标准化中心并理解其含义。 centers cluster_centers_scaled labels [‘R‘, ‘F‘, ‘M‘, ‘T‘] num_vars len(labels) angles np.linspace(0, 2 * pi, num_vars, endpointFalse).tolist() angles angles[:1] # 闭合 fig, ax plt.subplots(figsize(8, 8), subplot_kwdict(projection‘polar‘)) for i in range(optimal_k): values centers[i].tolist() values values[:1] # 闭合 ax.plot(angles, values, linewidth2, labelf‘Cluster {i}‘) ax.fill(angles, values, alpha0.1) ax.set_xticks(angles[:-1]) ax.set_xticklabels(labels) ax.set_yticklabels([]) ax.set_title(‘RFMT Profile of Clusters (Scaled)‘, size16, y1.1) ax.legend(loc‘upper right‘, bbox_to_anchor(1.3, 1.0)) plt.show()二维散点图 由于我们有四个维度可以两两组合进行散点图观察并用颜色区分簇。# 使用原始值或标准化值的两两散点图 sns.pairplot(df_rfmt_scaled, vars[‘R_scaled‘, ‘F_scaled‘, ‘M_scaled‘, ‘T_scaled‘], hue‘cluster‘, palette‘Set2‘, diag_kind‘kde‘) plt.suptitle(‘Pairwise Relationships of RFMT Features by Cluster‘, y1.02) plt.show()条形图 展示各簇的人数占比、消费总额占比等直观显示不同客户群的价值贡献。cluster_summary df_rfmt.groupby(‘cluster‘).agg( member_count(‘member_id‘, ‘count‘), total_M(‘M‘, ‘sum‘), avg_R(‘R‘, ‘mean‘), avg_F(‘F‘, ‘mean‘), avg_M(‘M‘, ‘mean‘), avg_T(‘T‘, ‘mean‘) ).reset_index() cluster_summary[‘member_pct‘] cluster_summary[‘member_count‘] / cluster_summary[‘member_count‘].sum() * 100 cluster_summary[‘M_pct‘] cluster_summary[‘total_M‘] / cluster_summary[‘total_M‘].sum() * 100 fig, axes plt.subplots(1, 2, figsize(12, 4)) axes[0].bar(cluster_summary[‘cluster‘].astype(str), cluster_summary[‘member_pct‘]) axes[0].set_title(‘Member Distribution by Cluster (%)‘) axes[0].set_ylabel(‘Percentage‘) for i, v in enumerate(cluster_summary[‘member_pct‘]): axes[0].text(i, v0.5, f‘{v:.1f}%‘, ha‘center‘) axes[1].bar(cluster_summary[‘cluster‘].astype(str), cluster_summary[‘M_pct‘]) axes[1].set_title(‘Total Consumption Amount Distribution by Cluster (%)‘) axes[1].set_ylabel(‘Percentage‘) for i, v in enumerate(cluster_summary[‘M_pct‘]): axes[1].text(i, v0.5, f‘{v:.1f}%‘, ha‘center‘) plt.tight_layout() plt.show()通过以上图表一份清晰的会员画像报告就有了数据支撑。你可以看到可能只占会员总数20%的“高价值活跃簇”贡献了超过60%的消费额这就是经典的“二八定律”在客户价值上的体现。4. 进阶分析与策略建议从画像到行动聚类完成并贴上标签只是第一步。数模竞赛和实际业务都要求我们基于画像提出“策略建议”。这部分是体现思考深度的关键。4.1 基于画像的精细化运营策略针对不同的客户群策略应该差异化高价值活跃会员簇0目标 提升忠诚度刺激跨品类消费提升客单价。策略 提供顶级VIP服务专属顾问、优先购买权、线下活动邀请推出高门槛、高回报的会员升级计划进行个性化、高品质的商品推荐。沉睡/流失会员簇1目标 唤醒或识别其流失原因。策略 发送“我们想念您”为主题的调查问卷或优惠券分析其最后一次消费的商品和品类推送相关新品或促销信息对于生命周期T很长但已沉睡的会员可考虑电话回访了解不满。有潜力的新会员簇2目标 加速成长培养忠诚度。策略 设置“新手任务”或“成长礼包”引导完成首次复购、首次跨品类消费等加强社群运营将其引入会员社群提供精准的、基于其首次消费偏好的推荐。高频低客单价会员假设有这样一个簇F高M低目标 提升客单价。策略 推送关联的高价值商品推出“满额赠礼”或“套装优惠”活动。低频高客单价会员F低M高目标 提升消费频率。策略 推送其购买过品类的新品或周边产品提供“预约到店体验”等增值服务增加互动触点。4.2 模型评估与迭代思考在论文中还需要体现对模型本身的思考模型稳定性 你可以使用不同的随机种子运行K-Means观察聚类结果特别是各簇人数和中心是否发生剧烈变化。如果变化大说明结果不稳定可能需要更多数据或考虑其他算法如层次聚类。特征权重 RFMT四个指标是否同等重要在商场场景下也许消费金额M和最近消费时间R比频率F和时间跨度T更重要。你可以尝试给特征加权后再聚类例如在标准化前将M乘以一个大于1的系数观察分群变化并讨论其业务合理性。动态画像 竞赛数据是静态快照。在现实中会员画像是动态的。你可以提出一个简单的“状态转移”构想比如如何通过定期如每季度运行此模型来追踪会员从一个簇转移到另一个簇的情况这能帮助预测流失和发现成长机会。5. 常见问题与避坑指南在实际操作和备赛过程中我们踩过不少坑这里总结一下数据清洗不彻底 这是最大的坑。一定要仔细检查日期格式、异常值如负金额、未来日期、重复记录。特别是会员ID是否有空格、格式不一致清洗不干净后续所有分析都是空中楼阁。R值计算错误 Recency是截止日期减最后一次消费日期。很多新手会错误地减第一次消费日期或平均消费日期。忽略数据分布直接标准化 对于消费金额M如果不做对数变换直接标准化那些“超级VIP”的极端高消费会扭曲整个分布导致聚类结果失真。务必先可视化如直方图、箱线图检查每个特征的分布。盲目相信肘部法则 肘部有时不明显轮廓系数也可能出现多个峰值。一定要结合业务常识。分成10个簇可能轮廓系数更高但业务上无法解释和管理没有意义。通常4-7个簇是比较合理的选择。聚类后不做深入分析 跑出聚类结果就结束了这是大忌。必须计算每个簇的RFMT均值、人数占比、消费占比并赋予业务含义。可视化是让结果一目了然的重要手段。策略建议空洞 不要只说“针对高价值客户进行精准营销”。要具体比如“针对簇0高价值活跃会员在每月10号会员日通过APP推送高端化妆品新品试用邀请并附赠双倍积分券”。策略要与画像特征紧密挂钩。代码与论文脱节 论文中出现的图表必须是由你提供的代码生成的。确保代码模块化、注释清晰数据处理、特征工程、建模、可视化每一步都能对应上。评委可能会运行你的代码。实操心得T指标的处理。在2018年这道题中T的定义是个小难点。如果数据有“注册日期”那么T观察期末-注册日期这包含了会员的整个生命周期包括沉默期。如果没有只能用“首次消费日期”代替此时T反映的是“活跃窗口期”。两种定义下的画像会略有不同。在论文中你需要明确说明自己的选择并给出理由。我倾向于使用“注册日期”来计算T因为它更能反映会员的整体状态而不仅仅是活跃状态。最后记住数学建模竞赛的核心是“解决实际问题”。RFMT模型和K-Means聚类只是工具你的思考过程、对业务的理解、对结果的分析和运用才是真正打动评委的地方。这篇特辑论文之所以能获奖绝不仅仅是因为代码跑通了更是因为它展现了一套从数据到洞察再到行动的完整逻辑链条。希望这份超详细的拆解能帮你不仅复现一个项目更能掌握这种数据驱动的分析思维。