有的朋友可能遇到过这种情况:市场部的同事拿着一堆会员数据找过来,说“咱们商场有几十万会员,能不能帮我分分类,看看哪些是高价值客户,哪些快流失了,营销预算好往重点人群上投”。这活儿听起来不复杂,但真做起来,最合适的工具反而不是那些需要标注标签的分类模型,而是无监督学习里的聚类。今天我就用“商场客户聚类”这个项目,把从数据清洗、特征构造、算法选型到业务解读的完整链路捋一遍,顺便把实操中那些文档里不会写的坑也抖出来。这套思路适合刚接触机器学习的数据分析师、想用数据驱动精细化运营的运营同学,也适合自己练手完整项目的初学者。
1. 为什么商场要搞客户聚类
1.1 业务问题:从“一刀切”到“千人千面”
传统商场的会员运营方式,坦白说非常粗放。节假日发优惠券,全员一个折扣;新品上市做推广,又是一条短信群发。这种“一刀切”模式的问题在于,高价值客户可能根本不在乎这点折扣,而价格敏感型客户看到满减就想跑,偏偏你又把活动规则设得太复杂。结果两头不讨好。
我当时接到的需求很简单:某连锁商场想在会员里划分出几个清晰的人群,每个群体要有明确的行为特征,比如谁在贡献毛利、谁在凑单买低价、谁已经很久没来了。只有把人群拆开,营销活动才能“千人千面”——给忠诚客户做积分权益,给沉睡客户做召回,给价格敏感客户做爆款促销。聚类就是干这个的:在没有任何标签的情况下,让数据自己把相似的人归到同一堆里。
1.2 为什么偏偏选聚类,不选分类
很多新手会问:既然要给客户分类,为什么不用逻辑回归或者决策树做分类任务?区别很关键:分类是有监督学习,必须提前准备好“类别标签”,比如把客户标成“高价值”“中价值”“低价值”。但实际业务里,这些标签的划分标准是拍脑袋定的,什么叫高价值?消费满一万还是满五万?不同业态都不一样。强行定标签,等于把业务偏见塞进模型。
聚类是无监督学习,核心思路是“让数据自己说话”。我不需要提前告诉模型谁是高价值客户,只需要给它一组特征,比如最近一次消费时间、消费频次、消费金额,K-Means这类算法就会自动找出数据的自然结构。用聚类还有一个隐性好处:它让我发现了三个此前业务经验完全没想到的人群,比如有一类“高频低客单”客户,团队一直以为是羊毛党,实际聚类后发现他们是周边写字楼的上班族,中午来买简餐,复购率极高。这种洞察不是靠拍脑袋能拍出来的。
1.3 这个项目的边界与预期产出
做项目前,一定要先划清楚边界,不然会被业务无限追问。我当时的预期产出是三样东西:第一,一个可解释的客户群体划分表,每个群体有特征均值;第二,每个群体的业务标签命名和营销建议;第三,一个可以定期重跑的数据脚本。边界是:不做预测、不做实时打分,只做“当前会员存量结构”的分析。项目周期大概三周,其中数据清洗占了一半时间,这部分常被新手低估。
2. 数据准备:决定聚类上限的环节
2.1 数据从哪来、拿什么字段
商场会员数据一般存在CRM系统或数据仓库里,取数口径要想清楚。我当时拿到的是某区域门店的会员交易明细,数据量大约25万会员、300多万条消费记录,时间跨度12个月。字段包括会员ID、性别、年龄、入会时间、每笔消费的时间戳、金额、商品品类、优惠券使用情况、支付方式等。
原始字段不能直接丢进聚类模型。我的做法是汇总成客户维度特征,这里最经典的框架是RFM:R(Recency,最近一次消费距离今天多久)、F(Frequency,统计周期内的消费频次)、M(Monetary,统计周期内的消费总金额)。这三个特征组合起来,恰好能覆盖“活跃度、忠诚度、贡献度”三个维度。除此之外,我还加了两个衍生特征:平均客单价(总金额除以总次数)和连带率(总购买件数除以总次数),用来反映客户在购物篮深度上的差异。
这里有个经验:特征不是越多越好,而是越“正交”越好。RFM三个维度之间相关性不能太高,否则模型会重复加权,导致聚类结果偏向某一个维度。我当时把商家补贴金额也放进去了,结果发现它和消费金额相关系数接近0.8,果断删掉。
2.2 特征清洗与异常值处理
商场的脏数据比想象中多。我列几个典型的坑:
- 年龄字段有0值和负数,有人录入了800岁,直接按缺失处理或做截断。
- 消费金额有负数,通常是退款单没对冲干净,这类记录要单独处理,不能直接求和。
- 同一会员出现多个ID,是因为线下办卡和线上注册没有打通,需要按手机号去重。
- 统计周期内只有一笔消费的会员占比很高,他们和真正的低频忠诚用户是两回事,需要在解读时特别标注。
清洗逻辑上,我保留了交易金额大于0且小于某个业务阈值的记录(比如单笔超过5万元的极值客户,先拉出来人工核查,防止是批发走账),把年龄限制在16到90岁区间。对缺失值没有一律删行,而是看缺失比例:性别缺失小于3%,用众数填充;消费特征缺失超过10%的会员直接剔除,因为聚类对这类样本的归属非常不稳定。
2.3 标准化:聚类前的关键一步
聚类算法里最常用的是距离度量,比如欧氏距离。但消费金额的量级和消费频次完全不一样:金额动辄几千,频次可能只有个位数。如果不做标准化,计算距离时金额会彻底主导簇的划分,频次和最近消费时间就变成了摆设。我见过有人在这个环节偷懒,聚类出来全是“钱多钱少”两个极端簇,业务完全没法用。
我用的是Z-score标准化,让每个特征均值变成0、标准差变成1。另外提一句,有些场景也可以用Min-Max缩放,但Z-score对异常值的容忍度更低,所以在前面清洗环节已经把极端值处理掉的情况下,Z-score是更稳的选择。标准化的另一个好处是,后面做轮廓系数评估时,数值之间不会因为尺度原因产生误导。
from sklearn.preprocessing import StandardScaler features = ["Recency", "Frequency", "Monetary", "Avg_Order_Value", "Cross_Sell_Ratio"] X = df[features] scaler = StandardScaler() X_scaled = scaler.fit_transform(X)标准化之后,我还做了相关性检查,确保五个特征之间相关系数绝对值都低于0.6。这个动作不是形式主义,是为了让聚类出的每个簇都有多维度的业务解释权,否则解释的时候会发现高价值和高频是完全绑定的,人群拆不出来。
3. 算法选型:K-Means到底香在哪
3.1 几种聚类算法的适用场景对比
商场客户聚类可以选的算法不少,我拿当时对比的几种来说:
- K-Means:速度快、可解释性强,适合数据量大、维度适中、簇形状近似球形的场景。客户聚类恰好是典型应用,因为RFM特征空间里簇形状通常还算规则。
- 层次聚类(Agglomerative):不需要预先指定K值,可以输出树状图。但样本量一大,距离矩阵就爆炸,25万会员根本跑不动,更适合几千条数据的探索。
- DBSCAN:能识别任意形状的簇,还能自动发现离群点。但它的密度参数(eps)很难调,会员数据密度分布不均匀时,容易出现“一个大簇吞掉所有点”或者“全变成噪声点”的极端情况。
- 高斯混合模型(GMM):可以输出属于每个簇的概率,比硬分配更柔性。可它假设每个簇服从高斯分布,而实际消费数据通常是长尾的,拟合效果不稳定。
说实话,这些算法我在练手项目里都试过一遍,最后在“业务可解释性”和“落地成本”之间反复权衡,还是选了K-Means。不是说其他算法不好,而是这个场景下,K-Means的简单性反而是优点。
3.2 为什么最终选了K-Means
选K-Means有三个非常现实的原因。第一,业务方要的是能马上执行的客户分组,K-Means给出的硬分类结果可以直接打标签写进营销系统,不用处理概率阈值。第二,K-Means的时间复杂度接近线性,25万会员、5个特征,几秒钟就能跑完一轮,后面调参和重训的成本极低。第三,也是最重要的,它的簇中心可以直接解读。每个簇中心就是一组特征均值,业务团队一眼就能看出“哦,这个群体最近一次消费是30天前,平均消费1200元”,不需要懂算法也能听明白。
但我也得承认它的短板:K-Means对初始质心敏感,对异常值敏感,而且只擅长找球形簇。这就需要在实操里配合正确的初始化方法(比如K-Means++)和多轮随机重启来规避,而不是换一个复杂算法。商业分析场景里,能让人看懂的模型,往往比精度高但解释不清的模型更有价值。
3.3 K-Means的原理和迭代过程
用大白话讲,K-Means就是干这么一件事:把数据分成K堆,让每堆内部的点尽量紧凑,堆与堆之间尽量疏远。具体迭代就四步:
- 随机选K个点当初始质心。
- 每个样本计算到K个质心的距离,归到最近的质心那一簇。
- 重新计算每个簇的质心(该簇所有样本的均值)。
- 重复第2、3步,直到质心变化很小。
目标函数是簇内误差平方和(SSE),也叫惯性(inertia)。算法在反复寻找能让这个值最小的分组方式。由于这个优化不是全局收敛的,所以一般要设置n_init=10,即随机跑10次取最优,这能大幅减少撞上局部最优的概率。我习惯把所有随机数种子固定,方便复现,也方便和同事对结果。
4. 实操全过程:从K值选择到结果可视化
4.1 用肘部法则初选K值
聚类和分类最大的差别是:没有正确答案告诉你K该取几。我常用的第一招是肘部法则。思路很简单:随着K增大,SSE一定下降,因为簇越多,组内越紧凑。但下降的幅度会越来越小,在某个临界点出现“拐弯”,像人的手肘一样,这个拐点对应的K就是结构上比较自然的簇数量。
from sklearn.cluster import KMeans import matplotlib.pyplot as plt sse = [] k_range = range(2, 11) for k in k_range: model = KMeans(n_clusters=k, random_state=42, n_init=10) model.fit(X_scaled) sse.append(model.inertia_) plt.plot(k_range, sse, marker="o") plt.xlabel("K") plt.ylabel("SSE") plt.show()跑出来的肘部图里,K从2到5时SSE下降非常陡峭,到6之后曲线明显变平。这个拐点指向K=5左右,但不是特别锐利。于是我没有急着定死,又跑了第二重验证:轮廓系数。
4.2 结合轮廓系数做最终确认
轮廓系数是目前最常用的聚类内部评估指标之一。对于每个样本,它计算两个距离:样本与自己簇内其他样本的平均距离a,以及样本与最近的那个相邻簇所有样本的平均距离b。轮廓系数 = (b - a) / max(a, b)。数值范围在-1到1之间,越接近1说明样本离自己簇近、离邻近簇远,分组效果越好。把所有样本的轮廓系数取平均,就是当前K值的整体轮廓系数。
from sklearn.metrics import silhouette_score sil_scores = [] for k in k_range: model = KMeans(n_clusters=k, random_state=42, n_init=10) labels = model.fit_predict(X_scaled) sil_scores.append(silhouette_score(X_scaled, labels)) plt.plot(k_range, sil_scores, marker="o") plt.show()当时的结果是K=5时轮廓系数约0.42,K=6时掉到0.35,K=4时是0.38。单看数值都不算特别高,但这在真实业务数据里很正常——人的行为本身就有重叠,不可能像合成数据那样跑出0.7以上。综合肘部法的拐点和业务上“五个人群包”的运营规模,我最终选了K=5。
关于K值选择,我个人的原则是:先看肘部法,再用轮廓系数验证,最后一定要问业务“给你5个人群和6个人群,运营上有没有区别”。如果某个K跑出来的结果无法落地命名,数值再漂亮也别用。
4.3 跑通K-Means:Python代码实操
选好K值后就是正式建模。代码本身非常简短,但有几处细节值得说清楚。
import pandas as pd from sklearn.cluster import KMeans best_k = 5 final_model = KMeans(n_clusters=best_k, random_state=42, n_init=10) df["cluster"] = final_model.fit_predict(X_scaled) # 把标准化前的原始特征拉出来做群体画像 profile = df.groupby("cluster")[features].mean().round(2) print(profile)输出结果类似这样(具体数值要按数据分析,不能凭空写死):
| 簇 | Recency(天) | Frequency(次) | Monetary(元) | 平均客单价 | 连带率 |
|---|---|---|---|---|---|
| 0 | 12 | 9 | 4800 | 533 | 3.2 |
| 1 | 45 | 3 | 860 | 287 | 2.1 |
| ... | ... | ... | ... | ... | ... |
这里要注意一个坑:fit_predict是在训练数据上直接给出预测标签,聚类本身没有过拟合问题,但如果你后续要用这些标签做训练特征,必须小心“标签泄露”。另外,KMeans的随机种子一定要固定,不然每次跑出来的簇编号顺序会乱,业务人员第一次看可能每次结果都不一样,会直接对模型失去信任。
4.4 降维可视化:PCA和TSNE怎么看
聚类结果最好还是要可视化,不然业务方没法“眼见为实”。高维空间我一般先用PCA(主成分分析)把五个特征压缩成两个主成分,再画散点图,用不同颜色标出不同簇。PCA降维后的两个轴是有解释意义的,通常是贡献度最大的两个线性组合方向。
from sklearn.decomposition import PCA pca = PCA(n_components=2) X_pca = pca.fit_transform(X_scaled) plt.scatter(X_pca[:, 0], X_pca[:, 1], c=df["cluster"], cmap="viridis", s=1, alpha=0.6) plt.xlabel("PC1") plt.ylabel("PC2") plt.show()如果PCA投影后簇之间重叠严重,我还会用TSNE再跑一次,因为TSNE擅长把局部结构拉开,视觉上更“分得开”。但记住TSNE的距离没有全局含义,不能拿TSNE图去解释“这两个簇有多远”,只能当作辅助确认。实际我发现,PCA图里K=5的簇重叠确实存在,但每个簇的中心区域都很清晰,这和轮廓系数0.42的水平是一致的。
5. 聚类结果解读:每个簇是一个“人群包”
5.1 核心特征画像
聚类模型跑完,真正的重头戏才开始:解读。我先把每个簇的特征均值做成表格,再算每个簇的人数占比。从业务视角把簇编号换成可理解的名字:
| 簇编号 | 人数占比 | Recency | Frequency | Monetary | 业务标签 |
|---|---|---|---|---|---|
| 0 | 18% | 5天 | 14次 | 9600元 | 高价值忠诚客户 |
| 1 | 24% | 50天 | 4次 | 1200元 | 沉睡唤醒客户 |
| 2 | 31% | 15天 | 6次 | 2200元 | 中间潜力人群 |
| 3 | 15% | 3天 | 11次 | 800元 | 高频低客单 |
| 4 | 12% | 90天 | 1次 | 350元 | 临近流失客户 |
这个表一出来,业务同事基本秒懂。但我想特别提醒:给簇命名要克制,不要硬编故事。我是先把特征画成雷达图,再结合访谈数据给标签的。比如簇3,起初团队以为是羊毛党,但看连带率(3.8)和平均客单价(72元),更像每天下班来买熟食和日用品的上班族。后来调了订单详情,验证了这一点。
5.2 从统计显著到业务命名
命名不是随口取的。我当时做了两个额外的分析:第一,计算每个簇在各个特征上的分布和全局均值的差异,用t检验或直接看置信区间,确认这个簇确实在某个维度上显著偏离整体;第二,按簇交叉分析性别、年龄段、入会年限等画像字段,看能不能找到外部可触达的标识。
比如“高价值忠诚客户”这个簇,统计上显著特征是消费金额高、频次高、最近一次消费距离今天小于7天,然后在画像字段里,他们集中在35到50岁、入会超过两年、持有商场联名信用卡。所有这些信息汇总后,我建议加上一句“定向触达时优先使用会员APP推送”,因为这群人的APP活跃度也是最高的。
5.3 营销策略的落地映射
聚类不是终点,营销动作才是。我给每个簇设计了适配的策略:
- 高价值忠诚客户:维系为主,提供专属会员日、双倍积分、新品优先试用。这群人不需要被过度促销刺激,反而在意被认可。
- 沉睡唤醒客户:重点发召回券,但金额不要太大。用数据分析来看,这批人唤醒后复购率显著高于新客,说明是“知道商场好,但最近没来”,而不是质量差。
- 中间潜力人群:他们有一定频次和金额,最值得做交叉销售,推荐关联品类。
- 高频低客单:给满减券,但门槛要低。他们吃的是便利和刚需,你高门槛满减等于没发。
- 临近流失客户:先用问卷或短信了解流失原因,再结合品类偏好做最后一波挽回。
各簇的营销策略写清楚后,业务团队就能直接抄作业。聚类项目真正交付的不是PPT,而是这套“标签+动作”的组合拳。
6. 踩坑记录与经验技巧
6.1 特征选择比调参重要得多
这是我最想强调的一点。很多新人在聚类上花大量时间调n_init、max_iter,效果却不理想。实际上,当聚类结果解释不通时,90%的原因是特征没构造好。特征如果只是原始字段堆砌,比如只放年龄和性别,聚类出来基本是一团浆糊;放RFM三个特征,马上有业务意义。我的习惯是:至少做两轮特征迭代。第一轮跑一个baseline,看出来的簇合不合理;然后根据解释过程中的困惑加特征或删特征,比如我第一轮发现“高价值”和“高频”绑在一起,于是拆出平均客单价,才把“高价值贡献”和“高频低额消费”区分开。这种迭代是聚类的灵魂,不是说算法跑完就结束了。
6.2 做聚类前一定要看数据的分布形态
有次我拿一版新数据直接标准化后丢进K-Means,结果轮廓系数只有0.2,效果惨不忍睹。后来画了分布图才发现,消费金额的偏态非常严重,大部分客户一年只消费几百块,少数大客户消费几十万,Z-score标准化后依然有长尾影响。解决方法是做log1p变换,把金额压缩到近正态分布再做标准化。类似的问题还有:频次特征经常是泊松分布,也可以考虑做平方根变换。
这里教大家一个快速检查方法:在跑K-Means前,对每个特征画直方图,如果看到严重的长尾或双峰,先做变换;聚类结束后看一眼每个簇在原始尺度上的分布,而不是只看标准化后的均值,防止解读时被“还原退火”坑了。
6.3 聚类不是一锤子买卖:上线后要定期重训
有些团队把聚类结果做成一个静态名单,用半年都不换,这是不对的。客户的消费行为会漂移,比如某个节假日大促会让高频客户频次骤增,活动结束恢复;商场的业态调整也会让客户结构变化。我后来把这套聚类脚本封装成月度任务,每个月用最近12个月滚动窗口重新训练。同时做一件事:记录每个客户簇归属的变化轨迹。如果某个客户从“高价值忠诚”漂移到“临近流失”,系统要及时触发预警,这个动态视角比静态分组价值大多了。
重训时还有个细节:不要每次都用完全不同的K值,否则簇的历史对比没法做。我的做法是K值固定,只在必要时重新验证一次,平时的月度重训只看簇的人数占比和特征均值漂移情况。
6.4 冷启动和特殊人群要单独处理
聚类对所有客户一视同仁,但业务上有些特殊情况得单独切出来。比如员工内部采购账号、企业团购客户,他们的消费模式和普通个人客户完全不同,一旦混进去,整个聚类会被带偏。我当时按照“是否绑定企业账户”做了两层处理,先排除企业客户,再对个人会员聚类。还有处于“生命周期初期”的新会员,入会不满一个月,RFM天然偏低,硬塞进聚类会被分到“流失客户”,但这显然不公平。我的处理是给新会员单独建一个“观察期”分组,满了三个月再进入聚类模型。
6.5 解释模型时,别硬造因果
最后一条提醒偏方法论。聚类输出的只是相关性结构,不自动代表因果关系。比如“高价值忠诚客户”频次高、金额高,你不能直接说“因为频次高所以价值高”,只能说“在这批数据里,这两个特征在同一人群中共同出现”。再比如聚类发现“临近流失客户”平均折扣使用率高,可能是他们只在大促销时才回来,而不是因为折扣才流失。一定要避免在业务汇报里说出“打折导致流失”这种话,数据不支持因果结论,真的要说也应该用测试验证。
最后再分享一句实在话
这个商场客户聚类项目做完,我最深的体会是:聚类算法的代码半小时就能写完,但真正难的是在业务场景里定义“什么是相似的人”。同样的RFM特征,在超市、百货、购物中心里,跑出来的簇结构天差地别,因为消费逻辑不一样。所以一定要和业务方反复对齐:你们想区分的到底是什么样的人群?哪个维度对营销决策影响最大?把这个问题聊透,聚类才不是“机器自己玩”,而是帮我们把常识变成了可执行的数据策略。如果你也想练这个项目,我建议从公开的商场客户数据集开始,先跑通流程,然后一定找一个真实业务问题对一下,你会发现比调参有意思得多。