做数据分析这几年,我越来越觉得,所谓“懂用户”这件事,其实可以拆成两步:一是把用户行为摸清楚,二是把用户群体分明白。摸清楚靠的是埋点、报表、漏斗,分明白靠的就是聚类。而在聚类算法里,K-Means又是最让人又爱又恨的一个——好用,但容易用错。这篇博文我就拿一个电商场景的实战案例,完整走一遍K-Means做市场细分的流程,从数据清洗到特征构建,从K值确定到人群画像解读,把里面的关键细节和踩过的坑都摊开来讲。无论你是刚接触机器学习的数据新人,还是已经在做用户增长但想换个思路的运营同学,应该都能从中拿到点能直接用的东西。1. 市场细分这件事,为什么非要用K-Means1.1 先搞清楚一个根本问题:细分到底是在分什么很多人一提到用户细分,第一反应就是“按性别分”“按年龄分”“按城市分”。但做过业务的都知道,这种分法在实际运营里基本没什么用。你说你是女性用户,25岁,在一线城市,然后呢?你知道她喜欢什么吗?你知道她多久打开一次你的小程序吗?你知道她上一次下单是什么时候吗?不知道。因为这些人口统计属性跟真实消费行为之间,隔着一层很厚的“意愿”和“场景”。真正能指导运营动作的细分,一定是建立在行为数据上的。也就是说,一个用户被你归到“高活跃、高客单价、低价格敏感度”这个群里,那你至少知道可以给他推新品;另一个用户是“高活跃、低客单价、容易被优惠券打动”,那你的策略就得换成促转化、拉复购。K-Means能做的,就是把这种多维度的行为数据压缩成几个“行为特征相似的人群”,让运营同学不用对着几万个用户ID发呆,而是对着三五个有名字、有画像、有策略的人群做决策。我常打一个比方:K-Means就像是在帮一群散落在操场上的学生排队,它不管你的姓名和学号,只看你现在站在哪里,然后按照“离谁最近就跟谁站一起”的原则自然分组。最后每个小组自己推选一个“队长”(质心),组内成员围绕队长聚成一团。你回头看,操场被分成了几块,每一块都有自己的聚集特征,这就是人群画像。1.2 K-Means的原理其实很简单,但简单不等于没用K-Means的核心逻辑就四步:选K个初始中心点,把每个样本分给离它最近的质心,重新计算每个簇的质心,重复直到质心不再移动。听起来像废话,但就是因为这种“局部迭代、逐步收敛”的思路,让它在海量行为数据上的表现极其稳定。这里面有一个很多教材不会细讲的问题:K-Means的“距离”到底是什么?在实际的用户行为分析中,如果你直接用原始字段(比如“最近一次购买距今天数”和 “90天内购买次数”)去算欧氏距离,那么量级大的字段会彻底主导聚类结果。比如“90天购买次数”可能是0到30的范围,而“最近一次购买距今天数”可能是1到365的范围,两个字段直接算距离的话,后者就把前者完全淹没了,聚类出来的群基本等于按一个字段切分。所以数据标准化不是一个“可选的加分项”,而是一个“不做就出错”的必选项。这一点后面实操部分我会再详细展开。可能还有人会问:既然K-Means要事先指定K值、对初始中心点敏感、还只能处理球形簇,为什么不用层次聚类或者DBSCAN?答案是:在真实的用户分群业务里,数据量动辄几十万上百万,层次聚类的时间复杂度撑不住,DBSCAN的密度参数又难以调优。K-Means的可解释性、可并行性、可重复性,让它依然是工业界做用户细分的默认起点。先用K-Means跑一个基线版本,后续再考虑用更复杂的模型去细化,这才是务实的路径。2. 把原始数据变成特征矩阵:这一步决定聚类效果的上限2.1 别急着跑代码,先想清楚“用什么特征来描述一个人”我在实际项目里见过太多人,拿到一张用户订单表就急着导进Python跑K-Means,结果跑出来一堆不知道含义的簇。问题的根源往往不是算法,而是特征没构建好。你要记住一句话:聚类是“输入垃圾,输出垃圾”最严重的算法之一,因为没人告诉你聚类结果错了,你看到的只是“分成了5群,每群人数还算均匀”,但根本没法解释。那么对于用户行为分析,什么特征是最合理的选择?我的经验是参考RFM模型的思路,但不局限于RFM的原始定义。经典的RFM是三个维度:最近一次消费时间(Recency)、消费频率(Frequency)、消费金额(Monetary)。这三个维度能描述“活跃度”和“价值度”,但还缺了“偏好度”和“行为模式”。所以我在实际业务里通常会在基础RFM上再增加几个维度:用户生命周期时长(从首次下单到现在的天数,区分新老客)品类偏好分布(比如某用户购买的商品里,食品类占比多少、美妆类占比多少)价格偏好区间(购买订单的平均折扣率,或者客单价落在哪个区间)活跃时段偏好(用户偏向在白天打开应用还是凌晨打开应用)这些字段加起来,一个用户就变成了一个多维向量。每个维度都在回答一个问题:“这个人在用什么方式跟我们发生交易”。当这些维度一起进入K-Means,它找出来的“相似的人群”,才真正具有业务含义。在处理这个阶段的实战项目时,我当时拿到的数据大概是这样的:某零售小程序过去90天的订单记录,字段包括用户ID、订单号、实付金额、优惠金额、下单时间、商品一级类目、商品二级类目。加上用户维度的注册时间、最近一次登录时间。看起来字段不多,但经过聚合就能生成30多个候选特征。我这里选了8个核心特征进入聚类,一方面是控制维度数量防止过拟合,另一方面是确保每个进入模型的特征都能讲出业务故事。2.2 数据清洗的3个关键动作:去重、填充、滤极值数据清洗这部分,直接影响聚类到底是在“看用户”还是在“看脏数据”。我按重要性排序,提醒三个关键动作。第一个动作是去重。同一用户在一天内下了多笔订单,这在数据里是正常的,但如果同一个订单因为系统重试出现了两行记录,这种脏数据会让“购买频次”和“消费金额”虚高。处理方式是按“订单号+商品ID”去重。此外还有个很容易漏掉的点——退款订单。如果你分析的是“消费行为”而不是“交易流水”,建议把退款率特别高的用户单独标记,或者用“退款后净消费金额”作为特征,避免聚类结果被“假土豪”扰乱。第二个动作是处理缺失值。用户行为表里最常见的缺失是登录时间和注册渠道,这类字段如果不参与聚类,可以直接不填充。但如果使用的是RFM特征,比如“最近一次购买距今天数”,对于注册但从未下单的用户,这个值是空的,我的做法是把这一部分用户单独拆出去,因为他们属于“零成交用户”,跟“有成交用户”混合聚类会制造出大量没有区分度的中间簇。第三个动作是过滤极值。用户行为数据里一定存在这种妖怪用户:90天成交100单,客单价5000块,连续365天每天打开小程序。这类用户的特征向量是典型的离群点,如果不处理,K-Means会被他们拖着走,最终可能产生一个“1个人的簇”和另一个“所有人挤在一起”的簇。处理方式不是粗暴删除,而是做Winsorize截尾处理——比如把“90天购买次数”超过99百分位的值替换成99百分位的值,把“客单价”超过上限的也压缩一下。这样既保留了用户的真实行为倾向,又不会让极端值主导距离计算。2.3 标准化和编码:为什么我推荐Z-score而不是Min-Max特征构建完成后,进入聚类前的最后一个关键步骤:数据标准化。前面提到,不同字段的量级差异会主导距离计算,所以必须把每个特征拉到相近的尺度上。常见的两种做法是Min-Max归一化和Z-score标准化。Min-Max归一化将数据压缩到0到1之间,看起来很简单,但对离群点极其敏感。如果“90天购买次数”最大值为60,而99%的用户在10以下,那归一化之后绝大多数用户的这个特征都集中在0.15以下,等于把特征的分辨率都压没了。Z-score就不一样,它按公式(x-μ)/σ缩放,受极端值影响的方式更温和,而且能保留数据的分布形状,更适合K-Means这种基于距离的聚类算法。实际操作中,我会在构建特征矩阵后用StandardScaler做转换,同时在转换前对明显偏态的字段取对数,比如“消费金额”和“购买次数”通常都服从长尾分布,先取log再标准化,能把大多数用户拉到中间区域,让聚类结果更集中、更稳定。我第一次做的时候跳过取对数这一步,聚出来的簇几乎就是把用户分成“大客户”和“普通客户”两类,特征严重倾斜导致其他维度全部失效。后来加上对数变换,细分效果才真正出来。3. 从零跑通一个K-Means分群项目:完整代码流程3.1 K值选择的两种经典方法:肘部法则和轮廓系数K-Means有一个绕不开的问题:K怎么定?选少了,人群太粗,一个簇里啥人都有;选多了,人群太碎,运营没法为每个簇定制策略。两种最经典的方法配合着用,可以做出比较科学的判断。第一种是肘部法则。原理很简单:把K从1试到10,每次计算所有样本到所属簇质心的距离平方和(SSE,也叫惯性值inertia)。K越大,SSE天然越小,但下降速度会有一个从“快速下降”到“平缓下降”的拐点,这个拐点就是“肘部”。其背后的逻辑是:K还没到真实分组数时,每增加一个簇能显著降低组内误差;K超过真实分组数后,增加的簇只是在把原本就已经很紧凑的组再切碎,误差下降收益很小。画出来就是一根折线,弯折点对应的K就是候选值。第二种是轮廓系数(Silhouette Score),它同时衡量两个指标:一个样本到同簇其他样本的平均距离(a),以及到最近的其他簇样本的平均距离(b),轮廓系数=(b-a)/max(a,b)。这个值的范围是-1到1,越接近1说明样本离自己的簇越近、离其他簇越远,聚类效果越好。这个指标比SSE更能告诉我们“分出来的群是不是真的边界清晰”。在实际代码里,我会做一个综合评估:import numpy as np import pandas as pd from sklearn.cluster import KMeans from sklearn.preprocessing import StandardScaler from sklearn.metrics import silhouette_score import matplotlib.pyplot as plt # 假设 X_std 已经是对数变换 + 标准化后的特征矩阵 inertias = [] silhouettes = [] for k in range(2, 11): km = KMeans(n_clusters=k, n_init=10, random_state=42) labels = km.fit_predict(X_std) inertias.append(km.inertia_) silhouettes.append(silhouette_score(X_std, labels)) # 画肘部图 plt.figure(figsize=(12, 4)) plt.subplot(1, 2, 1) plt.plot(range(2, 11), inertias, marker='o') plt.xlabel('K') plt.ylabel('SSE / Inertia') plt.title('Elbow Method') plt.subplot(1, 2, 2) plt.plot(range(2, 11), silhouettes, marker='o') plt.xlabel('K') plt.ylabel('Silhouette Score') plt.title('Silhouette Score') plt.tight_layout() plt.show()从图中的变化趋势中可以观察到,当K=4或K=5时,SSE的下降幅度已经明显放缓,且轮廓系数在这两个值附近达到相对高点,我就会把K候选锁定在4到5之间。只讲一个最终结果当然是不负责任的,但综合两个指标和业务可解释性,最终采用K=4作为细分人群的类别数,分别是“高价值活跃型”“潜力成长型”“价格敏感型”和“低活跃沉睡型”,这是后面画像分析的重点。3.2 运行聚类训练:初始化方式、随机种子与结果稳定性确定K值之后,正式跑聚类。但这里还有个很多人忽视的细节:K-Means对初始化中心点敏感,不同的起点可能收敛到不同的局部最优解。sklearn里KMeans默认使用K-Means++初始化方法,它会尽量让初始质心之间距离远一些,大大降低落到局部最优的概率。此外n_init=10表示随机初始化10次并选择SSE最小的一次,这个参数在旧版本中默认是10,新版本中略有调整,但显式写上更保险。还要设置random_state。这个参数我每次都会强调,因为它决定了可复现性。你这次跑出来的分组结果,下次再跑如果结果完全变了,那不是数据变了,是你没设随机种子。尤其是在你需要在不同版本或不同同事之间对比结果的时候,固定种子可以让整个分析过程对账清晰。我习惯设定为42,这个数字其实不重要,重要的是在报告里写清楚“本次分析使用K=4、random_state=42”,所有结果都基于这一个版本。训练代码非常简洁,但我建议加一步:把聚类标签回填到原始数据集里,方便后面做画像分析。k = 4 final_model = KMeans(n_clusters=k, init='k-means++', n_init=10, random_state=42) cluster_labels = final_model.fit_predict(X_std) # 回填标签到原始数据 analysis_df['cluster'] = cluster_labels回填之后,第一步看每个簇的样本量和占比。这里要提醒一句:K-Means倾向于产出大小差不多的簇,如果跑出来一个簇只占0.1%,一个簇占了70%,大概率不是算法问题,而是特征构造或者离群点处理出了问题。我在第一个版本的模型里就出现过“1人簇”,排查下来发现是某个用户的退货率异常高,又没有做截尾处理,结果他一个人被单独分了出来。3.3 聚类质量评估:不只是看指标,还要看业务可解释性很多教程到这里就结束了,但我会建议再多走一步:算一下每个簇在各个特征维度上的均值,对比总体均值,做成一张明细表,判断“这个簇到底长什么样”。轮廓系数高只能说明“数学上分得开”,不代表“业务上看得懂”。一个优质的聚类结果,必须每个簇都能讲出一个故事。下面就是我当时跑出来的簇中心对比数据,为了便于阅读,我已经把标准化后的均值还原成了业务口径:用户群人数占比平均购买次数平均客单价平均折扣率最近购买间隔(天)品类集中度簇031.7%3.258.20.2445.8中等簇128.4%12.686.40.1811.3较低簇225.8%2.144.70.4731.6较高簇314.1%1.396.80.0992.4高光看这张表,你就能大致给每个簇起名字:簇0是平均用户,人数最多,各项指标都在中等水平,运营策略应该是“激活与向上转化”。簇1是活跃忠诚用户,买得多、隔得短、折扣率低,对平台有很强的信任度,适合推新品和会员升级。簇2是价格敏感用户,折扣率高到0.47,也就是几乎一半的订单都靠优惠拉动,适合用券和满减活动维护。簇3是低频高价值用户,买得少但客单价极高,这类人可能只是来买特定品类,适合做定向召回和专属服务。走到这一步,一个K-Means聚类项目才算真正跑完。接下来你才能拿着这套标签去做人群画像、做策略分层、做个性化触达。实操过程到这里是闭环的,但很多人都在中间某个环节出了问题导致结果不能落地,后面我专门写一节讲讲最常见的几个坑怎么排查。4. 聚类结果怎么用:三张图看懂人群画像与运营策略4.1 人群画像可视化:从“数字簇”变成“有血有肉的人”聚类结果回填后,模型的工作就算完成了,但分析的工作才刚开始。纯看表格里的均值差异,业务同事很难共情。我会习惯性做三张图,让数据自己说话。第一张图是特征雷达图。把每个簇的标准化值画在同一张图上,每个角是一个特征,能一眼看出簇与簇之间的“形状差异”。比如簇2的价格敏感群,在“折扣率”这个维度上会突出一个大角,而簇1的活跃用户则在“购买频次”和“近度”上占优。雷达图的好处是非常直观,你在复盘会上指一下“这个角翘起来的群,就是优惠券依赖人群”,全场立刻就懂。第二张图是二维散点图。利用主成分分析PCA把多维特征压缩到两个主成分上,再按聚类标签着色。虽然有过信息损失,但能看到簇与簇之间的边界是否重叠严重。如果两个簇交叉很多、边界模糊,说明这两个群在行为特征上差异还不够大,要么重新调K,要么增加新的区分特征。第三张图是簇内典型样本摘录。我的做法是每个簇随机抽取3个真实脱敏用户,把他们的原始行为列出来,例如“最近30天浏览了18个SKU、下了2单、平均折扣0.35、偏好品类是零食”。这种做法在实际工作中最打动人,因为运营同学一看到具体场景,就会自动代入“这类人我应该怎么发消息”。我在项目复盘时发现一个很实用的技巧:不要尝试描述“所有用户”,先锚定两个最特殊的簇,比如“价格敏感群”和“低频高价值群”,分别为它们设计完整的人物故事,再反过来用故事解释数据特征。这就是所谓“由特例到普遍”的表达方式,在现场沟通中非常高效。4.2 从洞察到动作:不同人群的差异化运营策略分群本身不是目的,目的是让运营动作更精准。我见过很多项目死在“分完群就解散了”——因为没有把标签转化成运营策略。下面是我在“某商城”项目里针对4个簇实际落地过的运营动作,列出来供参考:用户群核心洞察运营动作预期指标活跃忠诚用户高频、近度好、信任度高新品首发体验官邀请、会员等级加速复购率提升、客单价提升平均用户行为中规中矩、没有明显偏好个性化推荐优化、每周精选推送次月转化率、活跃度价格敏感用户折扣驱动、品类集中限时秒杀、满减券包、低价试用装转化率提升、过敏率回访低频高价值用户买的少、买得贵、需求明确私人导购、专属客服、稀缺品定向推送年度复购次数增加这里面有一个重要原则:同一个用户群,不要同时推太多类型的活动。比如价格敏感用户,你一边给他发满减券,一边给他推高客单价的新品,他大概率只点开优惠券那个消息。一次只围绕一个核心动机去刺激,效果最好。从数据反馈上,我当时跟进了一期活动,针对价格敏感用户发了五元无门槛券,整体核销率比对照组提升了1.6倍,但这个群的客单价没有明显提升,反而略有下降,因为他们在券的诱惑下选择了更多低价商品。这说明策略是有效的,但要非常清楚地知道你设定的北极星指标是什么——“唤醒沉睡用户”和“提升客单价”本质上是两件事,不要被中间指标带偏。4.3 分群结果的迭代更新:模型不是一次性的用户行为是会变化的,上个月还是活跃忠诚用户,这个月可能就变成了平均用户。所以聚类模型不能“一次建模,终身使用”。我建议以月度或季度为周期,重新跑一次K-Means分群,对比相同人群的占比变化和流动性。有一个比较简单的方法可以追踪群体的迁移:上一次的聚类结果作为标签,本月的用户行为作为特征,然后用同样的K和随机种子重新建模,再统计“上个月的簇X用户在本月落入了哪些簇”。比如上个月的“高价值活跃型”有26%本月落到了“平均用户”,那说明这部分用户正在流失,需要提前介入。这种迁移分析比单纯看活跃率变化更有预警意义。做这部分工作时,保持特征口径一致性特别重要。如果你的特征从“近90天购买次数”改成了“近30天购买次数”,人群画像的变化就没法区分是“用户变了”还是“口径变了”。提供稳定的口径,记录每次迭代的特征版本号,是长期可持续运行的基础。5. 常见问题与排错实录:K-Means实战中那些坑5.1 聚类结果全是“均匀大饼”,找不到业务解释怎么办跑一遍K-Means,出来4个簇,每个簇的均值跟总体均值差不多,差异小到没法讲故事。这个问题出现的频率远超想象。而且它在数据上不会报任何错误,你只能靠“解读不了”来发现问题。从我的排错经验看,最常见的起因是特征选择太宽泛,混进了大量不相关的字段。比如你把“注册设备型号编码”也塞进特征矩阵,这种高基数的类别特征在标准化后会带来大量噪声,把所有用户都“拉平”了。另一个常见原因是K值选得太小或太大,在错误的粒度下看人群,差异当然不明显。排错思路是:先减少特征维度,只保留最核心的5到7个行为指标,再跑一次聚类看轮廓系数是否有上升。如果依然没有业务解释力,就把问题的层次降到“单特征分布对比”——直接看每个簇在各个特征上的标准差,找出差异最大的两三个维度,围绕这几个维度重新构建故事。我在一次跨境电商项目的经验是,砍掉8个冗余特征后,聚类结果从“看不了”变成了“能精准定位三类人群”,这说明冗余特征对聚类的影响非常实在。5.2 离群点干扰严重:一个用户撑起一个簇我前面已经提过这个坑,但这里值得再深入展开一下。K-Means的目标函数是SSE最小化,离群点因为距离大,会在计算质心时产生不成比例的影响力。当某一个用户的“90天购买金额”是5000元,而其余用户的中位数只有200元时,这个用户在距离计算中几乎等于“独立王国”。解决办法除了Winsorize截尾,还可以在进入聚类前先用IQR(四分位距)检测离群值。IQR等于上四分位数减去下四分位数,经验法则是,高于(上四分位数+1.5倍IQR)或低于(下四分位数-1.5倍IQR)的值视为离群点。对于真实业务数据,直接删除极端值并不是好选择,因为那些极端值可能是你最重要的“ VIP用户”。更稳的做法是单独把离群点抽出来,作为“特殊人群”单独分析,不参与主体聚类,让常规用户的分群更清晰,然后为离群点单独制定服务策略。我在“某商城”项目里就是先把“近90天消费超过5000元”的用户单独取出,大概占总人数的1.2%,直接归为“高净值人群”,剩下的98.8%进入K-Means分群,最终4个簇的业务解释都很干净。这其实就是“从算法驱动到业务规则驱动”的折中方案。5.3 K值怎么选都有理,陷入选择困难症肘部法则的“肘部”有时候并不明显,轮廓系数在你关注的K范围也相差不大,这时你就会陷入K值选择的纠结。我的经验是:多选两个K跑业务验证,而不是在数学指标里死磕。具体方法是:分别在K=4和K=6的模型下抽出任意两个簇,列出它们的画像数据和典型用户路径,给业务同事看,问一个很简单的问题:“这两个分群下的运营策略你能各写出三条吗?”。如果K=6时你依然能轻松写出差异化策略,那说明6个簇的粒度是可接受的;如果写到第四个簇时已经开始胡编乱造,那说明K=4更符合业务承载能力。数据指标帮你缩小范围,业务解释力帮你做最终决定。还有一个小技巧:把K=4和K=6的聚类结果做个交叉表,看K=6的哪些簇是从K=4的某个簇里分裂出来的。如果分裂出来的两个子簇又能讲出不同的故事,那K=6确实更好;如果只是随机切碎,那说明K=4最合适。5.4 标准化后还是被单一维度主导有些伙伴跑来问我:“我明明做了标准化,为什么跑出来还是只按某一个字段分类?”这种情况通常是因为这个字段本身携带的信息量太大,比如“最近一次购买距今超过XX天”这种特征,可能直接等于“是否流失”,它天然就把人群分成两堆。处理方式有两种。第一种是对特征做“业务约束”:如果不想让聚类过度关注这个维度,可以降低它的权重,最直接的做法是标准化后乘一个小于1的系数。第二种是增加相似维度进行“稀释”:比如你担心“购买频次”独大,可以同时加入“访问频率”“加购频率”等多个相关维度,让刻画“活跃度”的信息分散到多个特征中,而不是押注在一个指标上。我自己更推荐第二种,因为它保留的信息更完整。踩过这个坑之后,我也养成了一个习惯:聚类跑完后,顺手跑一个各特征与聚类标签之间的相关性分析。如果某一个特征与标签的相关系数接近1,就说明模型基本上是在“靠这一道题做分组”,这往往不是好事,需要重新调整特征组合。最后分享一点个人经验如果你正在学K-Means做用户分群,我的建议是:从数据清洗和特征工程入手,而不是急于调参和换算法。一个模型能不能用,七分在特征,三分在算法。先把你手头的数据吃透,想清楚每个字段能回答什么业务问题,再去考虑K值、初始化、评估指标。在项目里,我见过最多的情况不是模型跑不出来,而是模型跑出来了但没人看得懂、更没人敢用。如果你能避免这一点,就已经超过大多数人的水平。另一个经验是,别追求一次做到完美。先快速跑一个能解释的版本,跟业务对齐,再迭代特征和参数,比在后台纠结一周有效得多。市场细分不是一锤子买卖,它更像是你每月对用户的一次重新认识,迭代本身就是工作的常态。
做数据分析这几年,我越来越觉得,所谓“懂用户”这件事,其实可以拆成两步:一是把用户行为摸清楚,二是把用户群体分明白。摸清楚靠的是埋点、报表、漏斗,分明白靠的就是聚类。而在聚类算法里,K-Means又是最让人又爱又恨的一个——好用,但容易用错。这篇博文我就拿一个电商场景的实战案例,完整走一遍K-Means做市场细分的流程,从数据清洗到特征构建,从K值确定到人群画像解读,把里面的关键细节和踩过的坑都摊开来讲。无论你是刚接触机器学习的数据新人,还是已经在做用户增长但想换个思路的运营同学,应该都能从中拿到点能直接用的东西。1. 市场细分这件事,为什么非要用K-Means1.1 先搞清楚一个根本问题:细分到底是在分什么很多人一提到用户细分,第一反应就是“按性别分”“按年龄分”“按城市分”。但做过业务的都知道,这种分法在实际运营里基本没什么用。你说你是女性用户,25岁,在一线城市,然后呢?你知道她喜欢什么吗?你知道她多久打开一次你的小程序吗?你知道她上一次下单是什么时候吗?不知道。因为这些人口统计属性跟真实消费行为之间,隔着一层很厚的“意愿”和“场景”。真正能指导运营动作的细分,一定是建立在行为数据上的。也就是说,一个用户被你归到“高活跃、高客单价、低价格敏感度”这个群里,那你至少知道可以给他推新品;另一个用户是“高活跃、低客单价、容易被优惠券打动”,那你的策略就得换成促转化、拉复购。K-Means能做的,就是把这种多维度的行为数据压缩成几个“行为特征相似的人群”,让运营同学不用对着几万个用户ID发呆,而是对着三五个有名字、有画像、有策略的人群做决策。我常打一个比方:K-Means就像是在帮一群散落在操场上的学生排队,它不管你的姓名和学号,只看你现在站在哪里,然后按照“离谁最近就跟谁站一起”的原则自然分组。最后每个小组自己推选一个“队长”(质心),组内成员围绕队长聚成一团。你回头看,操场被分成了几块,每一块都有自己的聚集特征,这就是人群画像。1.2 K-Means的原理其实很简单,但简单不等于没用K-Means的核心逻辑就四步:选K个初始中心点,把每个样本分给离它最近的质心,重新计算每个簇的质心,重复直到质心不再移动。听起来像废话,但就是因为这种“局部迭代、逐步收敛”的思路,让它在海量行为数据上的表现极其稳定。这里面有一个很多教材不会细讲的问题:K-Means的“距离”到底是什么?在实际的用户行为分析中,如果你直接用原始字段(比如“最近一次购买距今天数”和 “90天内购买次数”)去算欧氏距离,那么量级大的字段会彻底主导聚类结果。比如“90天购买次数”可能是0到30的范围,而“最近一次购买距今天数”可能是1到365的范围,两个字段直接算距离的话,后者就把前者完全淹没了,聚类出来的群基本等于按一个字段切分。所以数据标准化不是一个“可选的加分项”,而是一个“不做就出错”的必选项。这一点后面实操部分我会再详细展开。可能还有人会问:既然K-Means要事先指定K值、对初始中心点敏感、还只能处理球形簇,为什么不用层次聚类或者DBSCAN?答案是:在真实的用户分群业务里,数据量动辄几十万上百万,层次聚类的时间复杂度撑不住,DBSCAN的密度参数又难以调优。K-Means的可解释性、可并行性、可重复性,让它依然是工业界做用户细分的默认起点。先用K-Means跑一个基线版本,后续再考虑用更复杂的模型去细化,这才是务实的路径。2. 把原始数据变成特征矩阵:这一步决定聚类效果的上限2.1 别急着跑代码,先想清楚“用什么特征来描述一个人”我在实际项目里见过太多人,拿到一张用户订单表就急着导进Python跑K-Means,结果跑出来一堆不知道含义的簇。问题的根源往往不是算法,而是特征没构建好。你要记住一句话:聚类是“输入垃圾,输出垃圾”最严重的算法之一,因为没人告诉你聚类结果错了,你看到的只是“分成了5群,每群人数还算均匀”,但根本没法解释。那么对于用户行为分析,什么特征是最合理的选择?我的经验是参考RFM模型的思路,但不局限于RFM的原始定义。经典的RFM是三个维度:最近一次消费时间(Recency)、消费频率(Frequency)、消费金额(Monetary)。这三个维度能描述“活跃度”和“价值度”,但还缺了“偏好度”和“行为模式”。所以我在实际业务里通常会在基础RFM上再增加几个维度:用户生命周期时长(从首次下单到现在的天数,区分新老客)品类偏好分布(比如某用户购买的商品里,食品类占比多少、美妆类占比多少)价格偏好区间(购买订单的平均折扣率,或者客单价落在哪个区间)活跃时段偏好(用户偏向在白天打开应用还是凌晨打开应用)这些字段加起来,一个用户就变成了一个多维向量。每个维度都在回答一个问题:“这个人在用什么方式跟我们发生交易”。当这些维度一起进入K-Means,它找出来的“相似的人群”,才真正具有业务含义。在处理这个阶段的实战项目时,我当时拿到的数据大概是这样的:某零售小程序过去90天的订单记录,字段包括用户ID、订单号、实付金额、优惠金额、下单时间、商品一级类目、商品二级类目。加上用户维度的注册时间、最近一次登录时间。看起来字段不多,但经过聚合就能生成30多个候选特征。我这里选了8个核心特征进入聚类,一方面是控制维度数量防止过拟合,另一方面是确保每个进入模型的特征都能讲出业务故事。2.2 数据清洗的3个关键动作:去重、填充、滤极值数据清洗这部分,直接影响聚类到底是在“看用户”还是在“看脏数据”。我按重要性排序,提醒三个关键动作。第一个动作是去重。同一用户在一天内下了多笔订单,这在数据里是正常的,但如果同一个订单因为系统重试出现了两行记录,这种脏数据会让“购买频次”和“消费金额”虚高。处理方式是按“订单号+商品ID”去重。此外还有个很容易漏掉的点——退款订单。如果你分析的是“消费行为”而不是“交易流水”,建议把退款率特别高的用户单独标记,或者用“退款后净消费金额”作为特征,避免聚类结果被“假土豪”扰乱。第二个动作是处理缺失值。用户行为表里最常见的缺失是登录时间和注册渠道,这类字段如果不参与聚类,可以直接不填充。但如果使用的是RFM特征,比如“最近一次购买距今天数”,对于注册但从未下单的用户,这个值是空的,我的做法是把这一部分用户单独拆出去,因为他们属于“零成交用户”,跟“有成交用户”混合聚类会制造出大量没有区分度的中间簇。第三个动作是过滤极值。用户行为数据里一定存在这种妖怪用户:90天成交100单,客单价5000块,连续365天每天打开小程序。这类用户的特征向量是典型的离群点,如果不处理,K-Means会被他们拖着走,最终可能产生一个“1个人的簇”和另一个“所有人挤在一起”的簇。处理方式不是粗暴删除,而是做Winsorize截尾处理——比如把“90天购买次数”超过99百分位的值替换成99百分位的值,把“客单价”超过上限的也压缩一下。这样既保留了用户的真实行为倾向,又不会让极端值主导距离计算。2.3 标准化和编码:为什么我推荐Z-score而不是Min-Max特征构建完成后,进入聚类前的最后一个关键步骤:数据标准化。前面提到,不同字段的量级差异会主导距离计算,所以必须把每个特征拉到相近的尺度上。常见的两种做法是Min-Max归一化和Z-score标准化。Min-Max归一化将数据压缩到0到1之间,看起来很简单,但对离群点极其敏感。如果“90天购买次数”最大值为60,而99%的用户在10以下,那归一化之后绝大多数用户的这个特征都集中在0.15以下,等于把特征的分辨率都压没了。Z-score就不一样,它按公式(x-μ)/σ缩放,受极端值影响的方式更温和,而且能保留数据的分布形状,更适合K-Means这种基于距离的聚类算法。实际操作中,我会在构建特征矩阵后用StandardScaler做转换,同时在转换前对明显偏态的字段取对数,比如“消费金额”和“购买次数”通常都服从长尾分布,先取log再标准化,能把大多数用户拉到中间区域,让聚类结果更集中、更稳定。我第一次做的时候跳过取对数这一步,聚出来的簇几乎就是把用户分成“大客户”和“普通客户”两类,特征严重倾斜导致其他维度全部失效。后来加上对数变换,细分效果才真正出来。3. 从零跑通一个K-Means分群项目:完整代码流程3.1 K值选择的两种经典方法:肘部法则和轮廓系数K-Means有一个绕不开的问题:K怎么定?选少了,人群太粗,一个簇里啥人都有;选多了,人群太碎,运营没法为每个簇定制策略。两种最经典的方法配合着用,可以做出比较科学的判断。第一种是肘部法则。原理很简单:把K从1试到10,每次计算所有样本到所属簇质心的距离平方和(SSE,也叫惯性值inertia)。K越大,SSE天然越小,但下降速度会有一个从“快速下降”到“平缓下降”的拐点,这个拐点就是“肘部”。其背后的逻辑是:K还没到真实分组数时,每增加一个簇能显著降低组内误差;K超过真实分组数后,增加的簇只是在把原本就已经很紧凑的组再切碎,误差下降收益很小。画出来就是一根折线,弯折点对应的K就是候选值。第二种是轮廓系数(Silhouette Score),它同时衡量两个指标:一个样本到同簇其他样本的平均距离(a),以及到最近的其他簇样本的平均距离(b),轮廓系数=(b-a)/max(a,b)。这个值的范围是-1到1,越接近1说明样本离自己的簇越近、离其他簇越远,聚类效果越好。这个指标比SSE更能告诉我们“分出来的群是不是真的边界清晰”。在实际代码里,我会做一个综合评估:import numpy as np import pandas as pd from sklearn.cluster import KMeans from sklearn.preprocessing import StandardScaler from sklearn.metrics import silhouette_score import matplotlib.pyplot as plt # 假设 X_std 已经是对数变换 + 标准化后的特征矩阵 inertias = [] silhouettes = [] for k in range(2, 11): km = KMeans(n_clusters=k, n_init=10, random_state=42) labels = km.fit_predict(X_std) inertias.append(km.inertia_) silhouettes.append(silhouette_score(X_std, labels)) # 画肘部图 plt.figure(figsize=(12, 4)) plt.subplot(1, 2, 1) plt.plot(range(2, 11), inertias, marker='o') plt.xlabel('K') plt.ylabel('SSE / Inertia') plt.title('Elbow Method') plt.subplot(1, 2, 2) plt.plot(range(2, 11), silhouettes, marker='o') plt.xlabel('K') plt.ylabel('Silhouette Score') plt.title('Silhouette Score') plt.tight_layout() plt.show()从图中的变化趋势中可以观察到,当K=4或K=5时,SSE的下降幅度已经明显放缓,且轮廓系数在这两个值附近达到相对高点,我就会把K候选锁定在4到5之间。只讲一个最终结果当然是不负责任的,但综合两个指标和业务可解释性,最终采用K=4作为细分人群的类别数,分别是“高价值活跃型”“潜力成长型”“价格敏感型”和“低活跃沉睡型”,这是后面画像分析的重点。3.2 运行聚类训练:初始化方式、随机种子与结果稳定性确定K值之后,正式跑聚类。但这里还有个很多人忽视的细节:K-Means对初始化中心点敏感,不同的起点可能收敛到不同的局部最优解。sklearn里KMeans默认使用K-Means++初始化方法,它会尽量让初始质心之间距离远一些,大大降低落到局部最优的概率。此外n_init=10表示随机初始化10次并选择SSE最小的一次,这个参数在旧版本中默认是10,新版本中略有调整,但显式写上更保险。还要设置random_state。这个参数我每次都会强调,因为它决定了可复现性。你这次跑出来的分组结果,下次再跑如果结果完全变了,那不是数据变了,是你没设随机种子。尤其是在你需要在不同版本或不同同事之间对比结果的时候,固定种子可以让整个分析过程对账清晰。我习惯设定为42,这个数字其实不重要,重要的是在报告里写清楚“本次分析使用K=4、random_state=42”,所有结果都基于这一个版本。训练代码非常简洁,但我建议加一步:把聚类标签回填到原始数据集里,方便后面做画像分析。k = 4 final_model = KMeans(n_clusters=k, init='k-means++', n_init=10, random_state=42) cluster_labels = final_model.fit_predict(X_std) # 回填标签到原始数据 analysis_df['cluster'] = cluster_labels回填之后,第一步看每个簇的样本量和占比。这里要提醒一句:K-Means倾向于产出大小差不多的簇,如果跑出来一个簇只占0.1%,一个簇占了70%,大概率不是算法问题,而是特征构造或者离群点处理出了问题。我在第一个版本的模型里就出现过“1人簇”,排查下来发现是某个用户的退货率异常高,又没有做截尾处理,结果他一个人被单独分了出来。3.3 聚类质量评估:不只是看指标,还要看业务可解释性很多教程到这里就结束了,但我会建议再多走一步:算一下每个簇在各个特征维度上的均值,对比总体均值,做成一张明细表,判断“这个簇到底长什么样”。轮廓系数高只能说明“数学上分得开”,不代表“业务上看得懂”。一个优质的聚类结果,必须每个簇都能讲出一个故事。下面就是我当时跑出来的簇中心对比数据,为了便于阅读,我已经把标准化后的均值还原成了业务口径:用户群人数占比平均购买次数平均客单价平均折扣率最近购买间隔(天)品类集中度簇031.7%3.258.20.2445.8中等簇128.4%12.686.40.1811.3较低簇225.8%2.144.70.4731.6较高簇314.1%1.396.80.0992.4高光看这张表,你就能大致给每个簇起名字:簇0是平均用户,人数最多,各项指标都在中等水平,运营策略应该是“激活与向上转化”。簇1是活跃忠诚用户,买得多、隔得短、折扣率低,对平台有很强的信任度,适合推新品和会员升级。簇2是价格敏感用户,折扣率高到0.47,也就是几乎一半的订单都靠优惠拉动,适合用券和满减活动维护。簇3是低频高价值用户,买得少但客单价极高,这类人可能只是来买特定品类,适合做定向召回和专属服务。走到这一步,一个K-Means聚类项目才算真正跑完。接下来你才能拿着这套标签去做人群画像、做策略分层、做个性化触达。实操过程到这里是闭环的,但很多人都在中间某个环节出了问题导致结果不能落地,后面我专门写一节讲讲最常见的几个坑怎么排查。4. 聚类结果怎么用:三张图看懂人群画像与运营策略4.1 人群画像可视化:从“数字簇”变成“有血有肉的人”聚类结果回填后,模型的工作就算完成了,但分析的工作才刚开始。纯看表格里的均值差异,业务同事很难共情。我会习惯性做三张图,让数据自己说话。第一张图是特征雷达图。把每个簇的标准化值画在同一张图上,每个角是一个特征,能一眼看出簇与簇之间的“形状差异”。比如簇2的价格敏感群,在“折扣率”这个维度上会突出一个大角,而簇1的活跃用户则在“购买频次”和“近度”上占优。雷达图的好处是非常直观,你在复盘会上指一下“这个角翘起来的群,就是优惠券依赖人群”,全场立刻就懂。第二张图是二维散点图。利用主成分分析PCA把多维特征压缩到两个主成分上,再按聚类标签着色。虽然有过信息损失,但能看到簇与簇之间的边界是否重叠严重。如果两个簇交叉很多、边界模糊,说明这两个群在行为特征上差异还不够大,要么重新调K,要么增加新的区分特征。第三张图是簇内典型样本摘录。我的做法是每个簇随机抽取3个真实脱敏用户,把他们的原始行为列出来,例如“最近30天浏览了18个SKU、下了2单、平均折扣0.35、偏好品类是零食”。这种做法在实际工作中最打动人,因为运营同学一看到具体场景,就会自动代入“这类人我应该怎么发消息”。我在项目复盘时发现一个很实用的技巧:不要尝试描述“所有用户”,先锚定两个最特殊的簇,比如“价格敏感群”和“低频高价值群”,分别为它们设计完整的人物故事,再反过来用故事解释数据特征。这就是所谓“由特例到普遍”的表达方式,在现场沟通中非常高效。4.2 从洞察到动作:不同人群的差异化运营策略分群本身不是目的,目的是让运营动作更精准。我见过很多项目死在“分完群就解散了”——因为没有把标签转化成运营策略。下面是我在“某商城”项目里针对4个簇实际落地过的运营动作,列出来供参考:用户群核心洞察运营动作预期指标活跃忠诚用户高频、近度好、信任度高新品首发体验官邀请、会员等级加速复购率提升、客单价提升平均用户行为中规中矩、没有明显偏好个性化推荐优化、每周精选推送次月转化率、活跃度价格敏感用户折扣驱动、品类集中限时秒杀、满减券包、低价试用装转化率提升、过敏率回访低频高价值用户买的少、买得贵、需求明确私人导购、专属客服、稀缺品定向推送年度复购次数增加这里面有一个重要原则:同一个用户群,不要同时推太多类型的活动。比如价格敏感用户,你一边给他发满减券,一边给他推高客单价的新品,他大概率只点开优惠券那个消息。一次只围绕一个核心动机去刺激,效果最好。从数据反馈上,我当时跟进了一期活动,针对价格敏感用户发了五元无门槛券,整体核销率比对照组提升了1.6倍,但这个群的客单价没有明显提升,反而略有下降,因为他们在券的诱惑下选择了更多低价商品。这说明策略是有效的,但要非常清楚地知道你设定的北极星指标是什么——“唤醒沉睡用户”和“提升客单价”本质上是两件事,不要被中间指标带偏。4.3 分群结果的迭代更新:模型不是一次性的用户行为是会变化的,上个月还是活跃忠诚用户,这个月可能就变成了平均用户。所以聚类模型不能“一次建模,终身使用”。我建议以月度或季度为周期,重新跑一次K-Means分群,对比相同人群的占比变化和流动性。有一个比较简单的方法可以追踪群体的迁移:上一次的聚类结果作为标签,本月的用户行为作为特征,然后用同样的K和随机种子重新建模,再统计“上个月的簇X用户在本月落入了哪些簇”。比如上个月的“高价值活跃型”有26%本月落到了“平均用户”,那说明这部分用户正在流失,需要提前介入。这种迁移分析比单纯看活跃率变化更有预警意义。做这部分工作时,保持特征口径一致性特别重要。如果你的特征从“近90天购买次数”改成了“近30天购买次数”,人群画像的变化就没法区分是“用户变了”还是“口径变了”。提供稳定的口径,记录每次迭代的特征版本号,是长期可持续运行的基础。5. 常见问题与排错实录:K-Means实战中那些坑5.1 聚类结果全是“均匀大饼”,找不到业务解释怎么办跑一遍K-Means,出来4个簇,每个簇的均值跟总体均值差不多,差异小到没法讲故事。这个问题出现的频率远超想象。而且它在数据上不会报任何错误,你只能靠“解读不了”来发现问题。从我的排错经验看,最常见的起因是特征选择太宽泛,混进了大量不相关的字段。比如你把“注册设备型号编码”也塞进特征矩阵,这种高基数的类别特征在标准化后会带来大量噪声,把所有用户都“拉平”了。另一个常见原因是K值选得太小或太大,在错误的粒度下看人群,差异当然不明显。排错思路是:先减少特征维度,只保留最核心的5到7个行为指标,再跑一次聚类看轮廓系数是否有上升。如果依然没有业务解释力,就把问题的层次降到“单特征分布对比”——直接看每个簇在各个特征上的标准差,找出差异最大的两三个维度,围绕这几个维度重新构建故事。我在一次跨境电商项目的经验是,砍掉8个冗余特征后,聚类结果从“看不了”变成了“能精准定位三类人群”,这说明冗余特征对聚类的影响非常实在。5.2 离群点干扰严重:一个用户撑起一个簇我前面已经提过这个坑,但这里值得再深入展开一下。K-Means的目标函数是SSE最小化,离群点因为距离大,会在计算质心时产生不成比例的影响力。当某一个用户的“90天购买金额”是5000元,而其余用户的中位数只有200元时,这个用户在距离计算中几乎等于“独立王国”。解决办法除了Winsorize截尾,还可以在进入聚类前先用IQR(四分位距)检测离群值。IQR等于上四分位数减去下四分位数,经验法则是,高于(上四分位数+1.5倍IQR)或低于(下四分位数-1.5倍IQR)的值视为离群点。对于真实业务数据,直接删除极端值并不是好选择,因为那些极端值可能是你最重要的“ VIP用户”。更稳的做法是单独把离群点抽出来,作为“特殊人群”单独分析,不参与主体聚类,让常规用户的分群更清晰,然后为离群点单独制定服务策略。我在“某商城”项目里就是先把“近90天消费超过5000元”的用户单独取出,大概占总人数的1.2%,直接归为“高净值人群”,剩下的98.8%进入K-Means分群,最终4个簇的业务解释都很干净。这其实就是“从算法驱动到业务规则驱动”的折中方案。5.3 K值怎么选都有理,陷入选择困难症肘部法则的“肘部”有时候并不明显,轮廓系数在你关注的K范围也相差不大,这时你就会陷入K值选择的纠结。我的经验是:多选两个K跑业务验证,而不是在数学指标里死磕。具体方法是:分别在K=4和K=6的模型下抽出任意两个簇,列出它们的画像数据和典型用户路径,给业务同事看,问一个很简单的问题:“这两个分群下的运营策略你能各写出三条吗?”。如果K=6时你依然能轻松写出差异化策略,那说明6个簇的粒度是可接受的;如果写到第四个簇时已经开始胡编乱造,那说明K=4更符合业务承载能力。数据指标帮你缩小范围,业务解释力帮你做最终决定。还有一个小技巧:把K=4和K=6的聚类结果做个交叉表,看K=6的哪些簇是从K=4的某个簇里分裂出来的。如果分裂出来的两个子簇又能讲出不同的故事,那K=6确实更好;如果只是随机切碎,那说明K=4最合适。5.4 标准化后还是被单一维度主导有些伙伴跑来问我:“我明明做了标准化,为什么跑出来还是只按某一个字段分类?”这种情况通常是因为这个字段本身携带的信息量太大,比如“最近一次购买距今超过XX天”这种特征,可能直接等于“是否流失”,它天然就把人群分成两堆。处理方式有两种。第一种是对特征做“业务约束”:如果不想让聚类过度关注这个维度,可以降低它的权重,最直接的做法是标准化后乘一个小于1的系数。第二种是增加相似维度进行“稀释”:比如你担心“购买频次”独大,可以同时加入“访问频率”“加购频率”等多个相关维度,让刻画“活跃度”的信息分散到多个特征中,而不是押注在一个指标上。我自己更推荐第二种,因为它保留的信息更完整。踩过这个坑之后,我也养成了一个习惯:聚类跑完后,顺手跑一个各特征与聚类标签之间的相关性分析。如果某一个特征与标签的相关系数接近1,就说明模型基本上是在“靠这一道题做分组”,这往往不是好事,需要重新调整特征组合。最后分享一点个人经验如果你正在学K-Means做用户分群,我的建议是:从数据清洗和特征工程入手,而不是急于调参和换算法。一个模型能不能用,七分在特征,三分在算法。先把你手头的数据吃透,想清楚每个字段能回答什么业务问题,再去考虑K值、初始化、评估指标。在项目里,我见过最多的情况不是模型跑不出来,而是模型跑出来了但没人看得懂、更没人敢用。如果你能避免这一点,就已经超过大多数人的水平。另一个经验是,别追求一次做到完美。先快速跑一个能解释的版本,跟业务对齐,再迭代特征和参数,比在后台纠结一周有效得多。市场细分不是一锤子买卖,它更像是你每月对用户的一次重新认识,迭代本身就是工作的常态。