简介:这份资源围绕KMeans聚类算法在客户价值分析中的落地应用展开,面向数据挖掘初学者、数据分析从业者以及需要制定客户运营策略的产品与营销人员。内容涵盖客户细分、消费行为分析、流失风险预测、资源分配与产品优化等典型场景,并涉及数据预处理、K值选择与轮廓系数评估等关键步骤,适合作为无监督学习入门到实战的参考材料。压缩包共27个文件,约949KB,以css样式表、html页面及若干下载类脚本文件为主,另含少量js与页面资源,整体为一份可离线浏览的文章配套页面,便于直接打开阅读与查阅。目前已有430人学习下载。通过这份资料,读者可以理解KMeans如何将客户划分为高、中、低价值群体,并据此设计个性化营销与挽留策略,同时掌握聚类流程中的标准化、降维与效果评估思路,为后续在真实业务数据上复现实验提供参考。
1. 从一份客户价值分析源码说起:KMeans 到底能跑出什么
电商后台导出一份 20 万行的订单明细,字段有客户 ID、最近一次下单距今天数、下单频次、累计消费金额。老板要的是「哪些客户值得重点维护、哪些已经快流失」。这时候用 KMeans 做客户价值分析,是最常见也最容易落地的一条路。它把每个客户映射成三维空间里的一个点,按距离把点聚成若干簇,再结合 RFM 三个维度给每簇打标签,输出高价值、潜力、流失预警这类可执行的分层结果。这份资源就是围绕这个场景的完整实现,包含数据预处理、K 值选择、聚类、簇画像和可视化几个环节。适合做用户运营、增长分析、数据挖掘入门的人,也适合已经会调库但没系统跑过一遍完整流程的工程师。它解决的不是「KMeans 是什么」,而是「拿到一份真实客户表,怎么把它跑成能交给业务的分层名单」。
2. KMeans 客户分层的原理与数据准备:为什么是 RFM 三维
2.1 为什么客户价值分析偏爱 KMeans 而不是分类模型
客户价值分析有个绕不开的前提:绝大多数公司没有现成的「客户价值等级」标签。没有标签,监督学习就无从下手,逻辑回归、XGBoost 这类模型再强也训不起来。KMeans 属于无监督聚类,不需要标签,只靠样本之间的相似度自动分组,这正好匹配「我也不知道客户该分几类,你先帮我分分看」的诉求。
选 KMeans 还有几个现实理由。它的计算复杂度对样本量是线性的,20 万行数据在单机上几分钟能跑完,不需要分布式集群。它的结果可解释性强,每个簇的质心就是一组 RFM 均值,业务方看得懂。它的调参空间小,主要就是 K 值和初始化方式,不像深度学习那样一堆超参要调。
但边界也要说清楚。KMeans 假设簇是球形、大小相近的,如果客户分布是长条形或者密度差异极大,它会切得很难看。它对异常值敏感,一个消费千万的客户能把整个簇的质心拽偏。它只能处理数值特征,性别、城市这类类别字段必须先编码。这些不是劝退理由,而是预处理阶段必须处理掉的东西。
常见做法是先用 KMeans 跑出分层,再拿分层结果当标签去训一个分类模型,对新客户做实时预测。这是后话,第一步还是把聚类跑稳。
2.2 RFM 特征工程:三个维度怎么算、怎么对齐
RFM 是客户价值分析的经典框架,三个字母分别对应 Recency(最近一次消费距今天数)、Frequency(消费频次)、Monetary(消费金额)。这三个维度覆盖了「最近还来不来」「来得勤不勤」「花得多不多」,是业务上最能区分客户价值的三个角度。
计算这三个指标,核心是一张按客户聚合的订单表。下面这段代码用 pandas 从订单明细算出 RFM 三列,逻辑是每个客户一行,R 取最近订单日期到基准日的天数,F 数订单条数,M 求和金额。
import pandas as pd import numpy as np # orders: 订单明细,字段 customer_id, order_date, amount orders = pd.read_csv("orders.csv", parse_dates=["order_date"]) # 基准日取数据里最大日期,避免用今天导致结果随时间漂移 snapshot_date = orders["order_date"].max() + pd.Timedelta(days=1) rfm = orders.groupby("customer_id").agg( recency=("order_date", lambda x: (snapshot_date - x.max()).days), frequency=("order_date", "count"), monetary=("amount", "sum") ).reset_index() print(rfm.describe())逻辑说明:snapshot_date用数据内最大日期加一天,而不是pd.Timestamp.today()。这一点很关键,用今天做基准,同一份数据隔一周跑出来的 R 值全变了,聚类结果不可复现。参数上,recency越小代表越近,frequency和monetary越大代表价值越高,方向不一致,后面标准化和解读时要注意。
RFM 三个维度量纲差得远。R 可能是 0 到 365,F 可能是 1 到 50,M 可能是几十到几十万。KMeans 用欧氏距离算相似度,量纲大的维度会主导距离计算,M 会把 R 和 F 完全压住。所以标准化是必须的,常见做法是 Z-Score 标准化,让每列均值 0、标准差 1。
from sklearn.preprocessing import StandardScaler features = rfm[["recency", "frequency", "monetary"]] scaler = StandardScaler() X = scaler.fit_transform(features) # 保存 scaler,线上预测新客户时要复用同一套均值方差 import joblib joblib.dump(scaler, "rfm_scaler.pkl")参数说明:StandardScaler按列减均值除标准差。这里必须把 scaler 存下来,因为线上来一个新客户,要用训练时的均值和方差去变换,不能重新 fit,否则新旧数据不在同一坐标系里,聚类标签就错位了。这是很多人第一次上线会翻车的地方。
提示:如果 M 的分布极度右偏,比如少数客户消费额是普通客户的几百倍,可以先对 M 取 log 再标准化,能明显改善聚类效果。
2.3 数据清洗:异常值和缺失值在聚类前怎么处理
真实订单表里,退款单、测试账号、金额为负的记录都会混进来。这些点如果不清掉,KMeans 会把它们单独聚成一簇,或者把正常簇的质心拉偏。常见做法是先把金额小于等于 0 的订单过滤掉,再把只下过一单且金额极小的测试账号剔除。
缺失值方面,RFM 三个指标理论上不会有缺失,因为都是从订单聚合出来的。但如果某个客户只有浏览没有下单,他根本不在订单表里,这类客户要么单独作为「未转化」人群,要么在分析时排除。不要用均值填充 R 或 M,填充出来的假数据会污染聚类边界。
异常值处理有个折中方案:用分位数截断。比如把 M 超过 99.5 分位的值压到 99.5 分位,保留这个客户但不让他的金额主导距离计算。这样既不丢样本,又控制了影响。
# 过滤无效订单 orders = orders[orders["amount"] > 0] # 对 monetary 做分位数截断 cap = rfm["monetary"].quantile(0.995) rfm["monetary"] = rfm["monetary"].clip(upper=cap)逻辑说明:clip(upper=cap)把所有超过 cap 的值替换成 cap,低于下限的不动。参数 0.995 是经验值,样本量大可以放到 0.999,样本量小就 0.99。截断后再做标准化,质心不会被极端值带跑。
3. K 值选择与聚类执行:手肘法、轮廓系数和初始化
3.1 K 值到底怎么定:手肘法和轮廓系数配合看
KMeans 最大的超参就是 K,也就是分几类。业务上可能想要 3 类(高、中、低价值),也可能想要 5 类(加上潜力和流失)。但业务想要的类数不一定和数据本身的自然分组一致,所以要用指标辅助判断。
手肘法看的是簇内平方和(inertia)随 K 变化的曲线。K 增大,inertia 一定下降,但下降速度会在某个点明显变缓,那个拐点就是候选 K。轮廓系数看的是每个点到本簇和其他簇的分离程度,取值 -1 到 1,越大越好。两个指标结合看,比只看一个靠谱。
from sklearn.cluster import KMeans from sklearn.metrics import silhouette_score inertias, silhouettes = [], [] K_range = range(2, 11) for k in K_range: km = KMeans(n_clusters=k, init="k-means++", n_init=10, random_state=42) labels = km.fit_predict(X) inertias.append(km.inertia_) silhouettes.append(silhouette_score(X, labels)) for k, i, s in zip(K_range, inertias, silhouettes): print(f"K={k} inertia={i:.1f} silhouette={s:.3f}")参数说明:init="k-means++"是初始化策略,让初始质心尽量分散,比随机初始化稳定得多。n_init=10表示跑 10 次不同初始化取最优,sklearn 新版本默认就是 10,显式写出来更清楚。random_state=42保证每次结果一致,做分析报告必须固定。
看结果时,如果 inertia 在 K=4 之后下降变缓,轮廓系数也在 K=4 附近最高,那 K=4 就是合理选择。如果两个指标指向不同 K,优先看轮廓系数,再结合业务能不能接受这个类数。业务上分 4 类不好命名,那就退一步用 3 类或 5 类,指标差一点但可解释性更强。
3.2 聚类执行与簇画像:把数字翻译成业务标签
K 定下来之后,正式跑一次聚类,把标签写回客户表,然后按簇算 RFM 均值,这一步叫簇画像。画像出来才能给每个簇起名字。
k = 4 km = KMeans(n_clusters=k, init="k-means++", n_init=10, random_state=42) rfm["cluster"] = km.fit_predict(X) # 按簇聚合,看每个簇的 RFM 均值 profile = rfm.groupby("cluster").agg( recency_mean=("recency", "mean"), frequency_mean=("frequency", "mean"), monetary_mean=("monetary", "mean"), customer_count=("customer_id", "count") ).reset_index() print(profile)逻辑说明:fit_predict一步完成训练和打标签。profile里每一行是一个簇,看 R 均值小、F 和 M 均值大的簇,就是高价值客户;R 大、F 小、M 小的簇,就是流失客户。customer_count用来判断每个簇的规模,如果某个簇只有几十人,可能是异常值聚出来的,要回头检查预处理。
簇画像出来后,常见命名逻辑是这样:R 小 F 大 M 大叫「重要价值客户」,R 大 F 大 M 大叫「重要保持客户」,R 小 F 小 M 小叫「一般发展客户」,R 大 F 小 M 小叫「流失预警客户」。具体名字按业务习惯定,关键是每个簇的 RFM 特征要能对应上一句人话。
注意:簇的编号是随机的,这次跑出来 cluster 0 是高价值,下次换台机器可能 cluster 0 变成流失客户。所以标签命名不能写死在代码里,要按画像结果动态映射,或者把映射关系存成配置文件。
3.3 结果可视化:三维散点图和雷达图怎么选
聚类结果要给业务看,图比表直观。三维 RFM 空间可以用散点图,三个轴分别是 R、F、M,颜色区分簇。但三维散点图在二维屏幕上不好读,更常用的是雷达图,每个簇一条线,三个轴是标准化后的 RFM,一眼能看出簇之间的形状差异。
import matplotlib.pyplot as plt fig, axes = plt.subplots(1, k, figsize=(4 * k, 4), subplot_kw=dict(polar=True)) angles = np.linspace(0, 2 * np.pi, 3, endpoint=False).tolist() angles += angles[:1] # 闭合雷达图 for c in range(k): vals = profile.loc[profile["cluster"] == c, ["recency_mean", "frequency_mean", "monetary_mean"]].values[0] vals = list(vals) + [vals[0]] axes[c].plot(angles, vals) axes[c].set_xticks(angles[:-1]) axes[c].set_xticklabels(["R", "F", "M"]) axes[c].set_title(f"Cluster {c}") plt.tight_layout() plt.savefig("cluster_radar.png", dpi=150)逻辑说明:雷达图每个簇单独一个子图,三个轴是 R、F、M 的均值。angles += angles[:1]是为了让折线闭合。参数dpi=150保证导出图片清晰,放进报告不糊。如果簇数多,可以改成一张图叠所有簇,但超过 5 条线就会乱,还是分图清楚。
4. 避坑与排查:KMeans 客户分层最容易翻车的五个地方
4.1 现象:每次跑出来的簇标签都不一样
原因:KMeans 的初始质心是随机的,虽然k-means++缓解了这个问题,但没固定random_state时,不同运行仍可能收敛到不同局部最优。
解决:固定random_state,并且把n_init设成 10 或更高。如果换了数据版本,标签映射关系要重新核对,不能沿用旧的簇编号到业务含义的映射。
4.2 现象:某个簇只有几个人,或者一个簇占了 90% 的客户
原因:极端值没处理干净,或者 K 值选得不对。一个消费千万的客户会单独成簇,K 太大时正常客户也会被切碎。
解决:先做分位数截断,再重新看手肘法和轮廓系数。如果 K=4 时出现一个超大簇,试试 K=5 或 K=6,看能不能把大簇拆开。拆不开说明数据本身同质化高,那就接受这个结果,别硬拆。
4.3 现象:R、F、M 没标准化直接聚类,结果 M 主导一切
原因:欧氏距离对量纲敏感,M 的数值范围远大于 R 和 F,距离计算几乎只由 M 决定,R 和 F 形同虚设。
解决:聚类前必须标准化。标准化之后如果还想调整某个维度的重要性,可以给该维度乘一个权重系数,比如把 F 乘 1.5 表示更看重频次。但权重不能乱给,要有业务依据。
4.4 现象:线上新客户预测时标签全错
原因:线上用了新的 scaler 或者新的 KMeans 模型,和训练时的坐标系不一致。标准化参数变了,同样的 RFM 值变换后位置就变了。
解决:把训练时的 scaler 和 KMeans 模型一起持久化,线上加载同一份文件做transform和predict。绝对不能在线上重新fit。
# 训练时保存 joblib.dump(scaler, "rfm_scaler.pkl") joblib.dump(km, "kmeans_model.pkl") # 线上加载 scaler = joblib.load("rfm_scaler.pkl") km = joblib.load("kmeans_model.pkl") new_X = scaler.transform(new_rfm[["recency", "frequency", "monetary"]]) new_rfm["cluster"] = km.predict(new_X)4.5 现象:轮廓系数很高但业务说分层没意义
原因:轮廓系数衡量的是几何分离度,不是业务价值区分度。数据可能在几何上分得很开,但分出来的簇在业务上没法命名、没法行动。
解决:指标只做参考,最终以簇画像能不能翻译成业务语言为准。如果某个簇的 RFM 特征和另一个簇几乎一样,说明 K 大了,减一试试。如果所有簇的 M 都差不多,说明 M 这个维度没区分度,考虑换特征或者加特征。
5. 进阶:把聚类结果变成可复用的客户分层流水线
跑通一次聚类只是开始,真正有价值的是把它做成一条能定期跑的流水线。我一般会把整个流程拆成四步:拉数、算 RFM、加载模型打标签、输出分层名单。每一步都落成独立函数,中间产物存成 parquet,方便回溯。
def build_rfm(orders, snapshot_date): rfm = orders.groupby("customer_id").agg( recency=("order_date", lambda x: (snapshot_date - x.max()).days), frequency=("order_date", "count"), monetary=("amount", "sum") ).reset_index() cap = rfm["monetary"].quantile(0.995) rfm["monetary"] = rfm["monetary"].clip(upper=cap) return rfm def score_customers(rfm, scaler, km): X = scaler.transform(rfm[["recency", "frequency", "monetary"]]) rfm["cluster"] = km.predict(X) return rfm逻辑说明:build_rfm负责从订单算指标并做截断,score_customers负责用已保存的 scaler 和模型打标签。两个函数分开,是因为拉数和打分可能在不同时间点执行,中间可以人工检查 RFM 分布有没有异常漂移。
验证流水线是否稳定,有个简单办法:拿上个月的订单跑一遍,看各簇的客户数和 RFM 均值,和这个月对比。如果某个簇的规模突然翻倍或腰斩,大概率是数据源出了问题,或者业务上发生了大促这类事件。这时候不要急着重新训模型,先确认数据口径。
模型要不要定期重训,取决于客户行为变化速度。快消品可能每月重训一次,耐用品半年一次都行。重训时不要直接覆盖旧模型,保留版本号,新模型先跑影子模式,对比新旧标签差异,确认合理再切换。这个习惯能避免一次错误的模型更新把整个运营策略带偏。
还有个容易被忽略的点:聚类结果要落到客户 ID 粒度,而不是只给业务看几张图。运营需要的是「这批客户 ID 名单,发什么券、推什么活动」。所以最后一步一定是导出customer_id + cluster + 业务标签的明细表,接进 CRM 或者营销系统。图是给汇报用的,名单才是给执行用的。
从那以后我每次做客户分层,都会先把 scaler 和模型一起存好,再跑一遍线上预测验证标签一致性,最后才导出名单。这个顺序走下来,基本没再出现过线上标签错乱的事。希望帮到你。
本文还有配套的精品资源,点击获取