ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

K-Means+RFM顾客分群实战:从订单数据到可执行运营策略

2026/9/10 10:49:30 拓冰建站 浏览量
K-Means+RFM顾客分群实战:从订单数据到可执行运营策略 简介本资源是一份面向数据分析初学者与电商运营人员的实战型顾客价值分析方案聚焦于K-Means聚类与RFM模型在用户分群与消费行为挖掘中的联合应用。资源提供从原始订单数据清洗、时序趋势建模含SARIMA预测、消费者画像构建到RFM维度打分的完整Python实现流程覆盖数据逻辑校验、多维特征分析及五类用户群体对比等关键环节。压缩包共13个文件包含9张可视化结果图如销售额时序图、聚类分布图等、1个主程序脚本main.py、1个结构化订单数据集orders_data.csv、1份说明文档readme.md及1个配置文件json整体体积仅906KB轻量易部署。目前已有566人学习下载内容组织清晰代码注释详尽图表与分析结论一一对应可直接复用于课程设计、实习报告或中小电商用户精细化运营实践。1. 为什么用 K-Means RFM 分析顾客消费比直接看销售额更准你手上有 20 万条交易记录导出 Excel 后发现Top 5% 客户贡献了 63% 收入但没人能说清这 5% 是谁、为什么稳定复购、哪类人正在流失。单纯按总金额排序会把刚下单大单的新客和持续小额高频的老客混为一谈按最近一次购买时间筛“活跃用户”又可能漏掉周期性采购的 B 端客户。K-Means 聚类本身不理解业务语义——它只认数字距离直接对原始交易字段聚类结果常是噪声主导的碎片簇。RFM 模型Recency, Frequency, Monetary则把消费行为压缩成三个可解释、可干预的维度R 值越小代表越近刚买过F 值越大说明习惯性回购M 值高反映客单价或总量强。当 K-Means 作用于标准化后的 RFM 三轴坐标时它找的不再是“数值相近的人”而是“行为模式相似的群体”——比如高 R高 F中 M 的“日常高频白领”低 R高 F高 M 的“季度批量采购企业客户”或 R 高但 F/M 双低的“沉默流失预警户”。本方案不是教你怎么调sklearn.cluster.KMeans的n_clusters而是带你从原始订单表出发用pandas构建 RFM 特征、用StandardScaler消除量纲干扰、用肘部法与轮廓系数双验证确定最优簇数并最终输出每个簇的运营标签与行动建议。适合有 Python 基础、手头有交易流水表、急需把“顾客分群”从 PPT 概念落地为 CRM 可执行策略的从业者。2. 从原始订单表到 RFM 三维向量数据清洗与特征工程实操2.1 原始数据结构识别与关键字段提取逻辑典型电商/零售订单表包含order_id,customer_id,order_date,amount四个核心字段。注意order_date必须是datetime64[ns]类型否则无法计算 R 值amount需确认是否含退款——若存在负值订单必须先过滤或取绝对值后再聚合。常见陷阱是customer_id存在空值或匿名化 ID如user_abc123需统一清洗为非空字符串。以下代码段以真实数据结构为基准跳过冗余列并强制类型转换import pandas as pd import numpy as np # 假设原始数据已加载为 df_orders df_orders pd.read_csv(orders.csv, parse_dates[order_date]) # 清洗 customer_id去空格、转字符串、剔除空值 df_orders[customer_id] df_orders[customer_id].astype(str).str.strip() df_orders df_orders.dropna(subset[customer_id, order_date, amount]) # 过滤异常金额如负数、超大值 df_orders df_orders[(df_orders[amount] 0) (df_orders[amount] 1e6)] # 验证关键字段分布 print(f有效订单数: {len(df_orders)}) print(f唯一客户数: {df_orders[customer_id].nunique()}) print(f订单日期范围: {df_orders[order_date].min()} ~ {df_orders[order_date].max()})提示若order_date是字符串格式如2023-05-12parse_dates[order_date]会自动转为 datetime若为时间戳整数毫秒级需用pd.to_datetime(df_orders[order_date], unitms)。未做此转换直接计算max(order_date)会导致 TypeError。2.2 RFM 三指标计算Recency、Frequency、Monetary 的业务定义与实现RFM 中每个字母对应一个聚合指标其计算逻辑必须贴合业务周期。例如R 值应以“分析截止日”为基准而非数据最大日期——因为最后一天可能有未结算订单。我们取df_orders[order_date].max()作为截止日确保所有客户 R 值在同一时间刻度下可比# 设定分析截止日取数据中最新订单日非系统当前日 analysis_date df_orders[order_date].max() # 计算 RFM 三指标 rfm_df df_orders.groupby(customer_id).agg( R(order_date, lambda x: (analysis_date - x.max()).days), # R: 最近一次购买距今天数 F(order_id, count), # F: 总购买次数 M(amount, sum) # M: 总消费金额 ).reset_index() # 强制类型R 必须为整数F/M 为数值 rfm_df[R] rfm_df[R].astype(int) rfm_df[F] rfm_df[F].astype(int) rfm_df[M] rfm_df[M].astype(float) print(fRFM 表行数: {len(rfm_df)}与客户数一致: {rfm_df[customer_id].nunique() len(rfm_df)})2.2.1 R 值陷阱为什么不能直接用max(order_date)差值若某客户最后一次下单是2023-12-31而分析截止日是2024-01-01R1 天但若该客户在2024-01-0100:01 下单R 就变成 0。这种微小时间差会导致 K-Means 将临近截止日的客户强行拉入“高活跃簇”掩盖真实行为差异。解决方案是统一按日截断analysis_date df_orders[order_date].dt.date.max()再计算(analysis_date - x.max().date()).days。这样无论下单是当天几点R 值都相同。2.2.2 F 与 M 的业务校准是否计入退换货F 统计的是订单数不是商品件数——即使一单买 10 件F 仍为 1。M 若需反映“净贡献”应减去退款金额。若原始表无退款字段需额外关联退款表或标记amount 0的订单为退款从 M 中扣除# 若存在退款标识如 amount 为负修正 M 值 refund_mask df_orders[amount] 0 df_orders.loc[refund_mask, amount] 0 # 或取绝对值后加负号视业务而定 # 重新聚合 M rfm_df[M] df_orders.groupby(customer_id)[amount].sum()2.3 RFM 标准化为什么必须用 StandardScaler 而非 MinMaxScalerK-Means 依赖欧氏距离而 R、F、M 的量纲与分布差异极大R 值通常在 0–365 天F 可能是 1–500 次M 可达 0–100000 元。若直接聚类M 的数值范围会主导距离计算导致簇中心几乎只由消费金额决定。StandardScaler将每列转为均值为 0、标准差为 1 的分布使三维度在距离计算中权重相等。MinMaxScaler虽也缩放但会压缩异常值如某客户 M500000导致多数客户挤在 [0,0.1] 区间K-Means 无法区分。代码实现如下from sklearn.preprocessing import StandardScaler # 提取 RFM 数值列 rfm_features rfm_df[[R, F, M]].values # 标准化 scaler StandardScaler() rfm_scaled scaler.fit_transform(rfm_features) # 转回 DataFrame 便于后续操作 rfm_scaled_df pd.DataFrame(rfm_scaled, columns[R_scaled, F_scaled, M_scaled], indexrfm_df.index) rfm_final pd.concat([rfm_df[[customer_id]], rfm_scaled_df], axis1) print(标准化后各维度统计:) print(rfm_scaled_df.describe())注意scaler.fit_transform()必须在训练集上拟合若后续要预测新客户需保存scaler对象并复用scaler.transform()不可重新拟合。3. K-Means 聚类落地肘部法选簇数、轮廓系数验证与模型训练3.1 肘部法Elbow Method确定最优 K 值不止画图更要读懂拐点肘部法通过计算不同 K 值下簇内平方和WCSS来寻找“收益递减”的拐点。但 WCSS 曲线常无明显肘部需结合业务场景判断。例如K3 时 WCSS 下降剧烈K4 开始平缓但业务上需区分“高价值沉睡客户”与“中价值活跃客户”则 K4 更合理。代码实现需遍历 K2 到 10每次训练 K-Means 并记录 WCSSfrom sklearn.cluster import KMeans import matplotlib.pyplot as plt # 计算不同 K 值的 WCSS k_range range(2, 11) wcss [] for k in k_range: kmeans KMeans(n_clustersk, random_state42, n_init10) kmeans.fit(rfm_scaled_df) wcss.append(kmeans.inertia_) # inertia_ 即 WCSS # 绘制肘部图 plt.figure(figsize(8, 5)) plt.plot(k_range, wcss, markero) plt.xlabel(聚类数量 (K)) plt.ylabel(簇内平方和 (WCSS)) plt.title(肘部法确定最优 K 值) plt.grid(True) plt.show() # 打印 WCSS 数值供人工判断 for k, w in zip(k_range, wcss): print(fK{k}: WCSS{w:.2f})3.1.1 如何避免肘部图误判看相对下降率而非绝对值WCSS 绝对值随 K 增大必然下降关键看下降率。计算相邻 K 的下降比例(wcss[i-1] - wcss[i]) / wcss[i-1]。若 K3→4 下降率仅 8%而 K2→3 达 35%则 K3 是更优解。以下代码自动计算并标记拐点# 计算相对下降率 drop_rates [] for i in range(1, len(wcss)): drop_rate (wcss[i-1] - wcss[i]) / wcss[i-1] * 100 drop_rates.append(drop_rate) # 找下降率首次低于 15% 的 K 值业务经验值 optimal_k 2 for i, rate in enumerate(drop_rates): if rate 15 and k_range[i1] 2: optimal_k k_range[i1] break print(f基于下降率阈值 15%推荐 K{optimal_k})3.2 轮廓系数Silhouette Score二次验证量化簇间分离度肘部法易受主观影响轮廓系数提供客观指标取值范围 [-1, 1]越接近 1 表示簇内紧密、簇间分离好。需对每个 K 计算平均轮廓系数选择最高值对应的 Kfrom sklearn.metrics import silhouette_score silhouette_scores [] for k in k_range: kmeans KMeans(n_clustersk, random_state42, n_init10) labels kmeans.fit_predict(rfm_scaled_df) score silhouette_score(rfm_scaled_df, labels) silhouette_scores.append(score) print(fK{k}: 轮廓系数{score:.3f}) # 找最高分对应的 K best_k_silhouette k_range[np.argmax(silhouette_scores)] print(f轮廓系数最高 K{best_k_silhouette}得分为 {max(silhouette_scores):.3f})提示若轮廓系数最高值出现在 K2但业务需至少 4 类客户如 VIP/普通/潜在/流失应优先满足业务需求再检查 K4 时的系数是否 0.4可接受下限。低于 0.25 表示聚类效果差需检查 RFM 特征质量或尝试其他算法如 DBSCAN。3.3 训练最终 K-Means 模型并分配簇标签选定 K 后用n_init20降低局部最优风险max_iter300确保收敛# 采用肘部法与轮廓系数共识的 K 值取两者交集若冲突则选轮廓系数更高者 final_k max(optimal_k, best_k_silhouette) if abs(optimal_k - best_k_silhouette) 1 else best_k_silhouette # 训练最终模型 kmeans_final KMeans(n_clustersfinal_k, random_state42, n_init20, max_iter300) rfm_final[cluster] kmeans_final.fit_predict(rfm_scaled_df) # 查看各簇客户数分布 cluster_dist rfm_final[cluster].value_counts().sort_index() print(各簇客户数量分布:) print(cluster_dist)3.3.1 检查簇中心坐标理解每个簇的行为含义K-Means 输出的cluster_centers_是标准化后的坐标需逆变换回原始 RFM 尺度才能解读业务意义# 获取簇中心标准化尺度 centers_scaled kmeans_final.cluster_centers_ # 逆变换回原始尺度 centers_original scaler.inverse_transform(centers_scaled) # 构建簇中心解读表 centers_df pd.DataFrame(centers_original, columns[R_original, F_original, M_original], indexrange(final_k)) # 添加簇内客户数 centers_df[customer_count] cluster_dist.values print(各簇中心原始 RFM 值R:天数, F:次数, M:元:) print(centers_df.round(2))clusterR_originalF_originalM_originalcustomer_count0120.35.2850.61240018.742.112500.338002290.52.1320.88900解读示例簇 1 的 R8.7 天极近、F42.1 次高频、M12500 元高客单是典型的“高价值活跃客户”簇 2 的 R290 天超半年未购、F2.1 次低频、M320 元低额属“沉默流失风险户”。4. 业务标签生成与运营策略映射让聚类结果真正驱动动作4.1 基于 RFM 原始值定义客户分层标签非标准化值簇标签是数学结果运营需可理解的名称。我们依据 R/F/M 在原始尺度的四分位数Q1/Q3划分高中低档组合生成标签。例如R ≤ Q1 为“近期”R ≥ Q3 为“沉睡”F ≥ Q3 为“高频”F ≤ Q1 为“低频”M ≥ Q3 为“高价值”。代码实现# 计算 RFM 原始值的四分位数 q1_r, q3_r rfm_df[R].quantile(0.25), rfm_df[R].quantile(0.75) q1_f, q3_f rfm_df[F].quantile(0.25), rfm_df[F].quantile(0.75) q1_m, q3_m rfm_df[M].quantile(0.25), rfm_df[M].quantile(0.75) # 为每个客户打标签 def get_rfm_label(row): r_label 近期 if row[R] q1_r else (沉睡 if row[R] q3_r else 一般) f_label 高频 if row[F] q3_f else (低频 if row[F] q1_f else 中频) m_label 高价值 if row[M] q3_m else (低价值 if row[M] q1_m else 中价值) return f{r_label}{f_label}{m_label} rfm_df[rfm_label] rfm_df.apply(get_rfm_label, axis1) print(RFM 标签分布示例:) print(rfm_df[rfm_label].value_counts().head())4.1.1 将 K-Means 簇与 RFM 标签对齐发现算法与业务的偏差将rfm_final[cluster]与rfm_df[rfm_label]关联检查簇内标签一致性。若簇 0 中 80% 客户为“近期高频高价值”则簇 0 可命名为“核心活跃客户”若簇 1 中混杂“沉睡低频高价值”与“近期低频高价值”说明 K-Means 未能区分价值来源需检查 M 值是否受单一大单扭曲如某客户一年只买一次但金额 50 万# 合并簇标签与 RFM 标签 merged_df rfm_df.merge(rfm_final[[customer_id, cluster]], oncustomer_id) # 统计每簇内 RFM 标签占比 label_by_cluster pd.crosstab(merged_df[cluster], merged_df[rfm_label], normalizeindex) * 100 print(各簇内 RFM 标签占比%:) print(label_by_cluster.round(1))4.2 输出可执行的运营策略矩阵每个簇对应具体动作根据簇中心解读与标签一致性制定差异化策略。以下为典型策略映射表直接嵌入 CRM 系统簇ID簇名核心特征短期动作1个月内长期动作季度0核心活跃客户R低、F高、M高发送专属折扣码邀请参与 VIP 社群定制化新品优先体验年度忠诚度计划1潜力成长客户R中、F中、M中但 F增速快推送复购激励满减券推荐互补商品个性化内容推送建立客户成功经理对接2沉默流失预警户R高、F低、M低触发唤醒短信“您有未使用的积分”发放无门槛券分析流失原因问卷优化首单体验3价值波动客户R低但 M极高F不稳定紧急联系确认需求提供定制化服务包建立大客户专线签订年度框架协议注意策略必须可量化。例如“发送专属折扣码”需明确折扣力度如 95 折、有效期7 天、适用品类全店通用“唤醒短信”需设定触发条件R180 天且近 3 月无互动。4.3 导出结果至业务系统生成 main.py 可调度的标准化输出最终交付物需为main.py可一键运行的脚本输出customer_segmentation.csv含customer_id,cluster,rfm_label,strategy_code四列供 BI 工具或营销平台调用# 生成策略编码映射字典 strategy_map { 0: VIP_ACTIVE, 1: POTENTIAL_GROWTH, 2: CHURN_RISK, 3: VOLATILE_HIGH_VALUE } # 添加策略编码列 merged_df[strategy_code] merged_df[cluster].map(strategy_map) # 保存结果 output_cols [customer_id, cluster, rfm_label, strategy_code] merged_df[output_cols].to_csv(customer_segmentation.csv, indexFalse) print(客户分群结果已保存至 customer_segmentation.csv)5. 进阶技巧处理冷启动客户与动态更新机制5.1 新客户无历史订单时的 RFM 初始化策略新注册用户无订单R/F/M 均为空。直接排除会丢失潜在价值需设计默认值。常见做法是R 设为最大值如 365F0M0但这样会全部归入“沉睡”簇。更优方案是引入行为埋点数据若用户完成注册、浏览商品页≥3 次、加入购物车则赋予虚拟 F0.5、M50预估首单金额R0视为即时活跃。代码示例如下# 假设新客户行为数据在 df_new_users 表中 df_new_users pd.read_csv(new_users_behavior.csv) # 初始化 RFM 值 df_new_users[R] 0 # 新用户视为今日活跃 df_new_users[F] df_new_users[cart_adds] * 0.5 df_new_users[page_views] * 0.1 # 权重经验公式 df_new_users[M] 50.0 # 首单预估金额 # 标准化并预测簇 new_rfm_scaled scaler.transform(df_new_users[[R, F, M]]) df_new_users[cluster] kmeans_final.predict(new_rfm_scaled) # 合并到主表 rfm_final_new pd.concat([rfm_final, df_new_users[[customer_id, cluster]]], ignore_indexTrue)5.2 每月自动更新分群用 crontab 调度 main.py将完整流程封装为main.py支持命令行参数指定数据路径与分析日期# Linux 下每日凌晨 2 点执行 0 2 * * * cd /path/to/project python main.py --data_path ./data/orders.csv --analysis_date 2024-06-01main.py内部需解析参数并调用前述函数关键点在于不重训 K-Means 模型只用已有 scaler 和 kmeans_final.predict()确保新客户归属与历史一致import argparse if __name__ __main__: parser argparse.ArgumentParser() parser.add_argument(--data_path, requiredTrue) parser.add_argument(--analysis_date, requiredTrue) args parser.parse_args() # 加载新订单数据 df_new pd.read_csv(args.data_path, parse_dates[order_date]) analysis_date pd.to_datetime(args.analysis_date) # 构建新客户 RFM同前文逻辑 # ...省略中间步骤 # 用已有 scaler 和模型预测 rfm_new_scaled scaler.transform(rfm_new[[R, F, M]]) rfm_new[cluster] kmeans_final.predict(rfm_new_scaled) # 保存增量结果 rfm_new.to_csv(fcustomer_segmentation_{args.analysis_date}.csv, indexFalse)提示生产环境需添加异常处理——若新数据中customer_id为空记录日志并跳过若scaler或kmeans_final文件丢失程序应退出并报警而非用默认值硬编码。5.3 监控分群稳定性检测簇漂移的三个关键指标每月更新后需验证分群是否稳定。计算以下指标任一超标即触发人工复核簇成员变动率某簇本月客户数 vs 上月客户数变动 20%中心偏移距离当前簇中心与上月簇中心的欧氏距离 0.5标准化尺度标签一致性衰减同一客户连续两月被分入不同簇的比例 5%。# 加载上月结果 last_month pd.read_csv(customer_segmentation_2024-05-01.csv) current_month pd.read_csv(customer_segmentation_2024-06-01.csv) # 计算变动率 change_rate abs(current_month[cluster].value_counts() - last_month[cluster].value_counts()) / last_month[cluster].value_counts() print(簇成员变动率:) print(change_rate.round(3)) # 计算中心偏移需保存历史 centers_scaled # centers_last np.load(centers_2024-05-01.npy) # drift np.linalg.norm(centers_current - centers_last, axis1) # print(簇中心偏移距离:, drift.round(3))当发现簇 2 的变动率达 35%且中心偏移 0.72说明“沉默流失预警户”定义可能已失效——需检查近期是否上线了唤醒活动导致大量沉睡客户回流此时应重新评估 RFM 分界点或调整 K 值。本文还有配套的精品资源点击获取