
做数据分析和机器学习的朋友应该都有一个共同的体验模型跑出来不难难的是结果摆在你面前的时候你怎么把它讲清楚、用起来。尤其是KMeans聚类这是入门机器学习几乎绕不开的第一个无监督算法很多人跟着教程敲完几行代码Stata的输出窗口里蹦出一张矩阵表然后盯着屏幕就开始发懵——这个表到底在说什么我的样本被分成了几类这些类靠不靠谱为什么我换个随机种子之后分组结果就不一样了这篇文章就是来解决这个问题的。我会用Stata环境下的一个KMeans聚类实际案例把聚类分析的完整链路走一遍重点放在“结果怎么看”上顺带把KMeans的核心原理、K值选择、效果评估这些关键环节都交代清楚。无论你是刚接触机器学习的经济管理类研究生还是在科研中需要做样本分组、客户分群的实务工作者这篇文章都能让你少走不少弯路。1. 为什么聚类结果总是看不懂1.1 聚类结果常见的三种“读不懂”先聊聊我见过的高频困惑场景基本可以归为三类。第一种对着Stata的矩阵输出发呆。KMeans跑完之后输出的是组内平方和、组间平方和、还有F检验这些统计量乍一看跟F检验和方差分析长得有点像但又不知道具体该看哪个数字来判断聚类效果好还是不好。第二种聚类中心表不知道该怎么解释。Stata给出了每一类在各个变量上的均值数值有大有小正负都有但就是不知道这些数值的组合说明了一类什么样的人群。比如同样是客户分群到底哪些变量真正把类别区分开了区分度有多大很多人说不清。第三种K值选择全凭感觉。有人直接拍脑袋定一个3类或者4类有人试图用循环跑K2到K10的所有模型但跑完之后不知道用什么指标来定夺最终的聚类个数。这一点在国内外的教程里其实写得很零散很多人最终也不知道怎么从指标上给自己一个明确的答案。这些问题看起来是操作层面的但根源在于对KMeans“结果到底是什么”这件事缺乏一个系统的解释框架。只要把框架搭起来所有“看不懂”都会迎刃而解。1.2 先建立一个结果解释的整体框架我自己的经验是任何聚类分析的结果解读都可以拆成四个递进的层次。第一层是数学层面的结果也就是各种平方和、F统计量、轮廓系数这些数值回答的是“这些类到底分得开分不开”的问题。第二层是业务层面的结果也就是每一类的量化特征和标签化描述回答的是“分出来的每一类都是谁”的问题。第三层是确认层面的结果也就是把聚类类别作为分组变量去检验其他变量是否存在显著差异回答的是“这个分类方案是不是真的有意义”的问题。第四层是稳定层面的结果也就是换一种随机种子或者换一个初始中心点看看分类结果会不会剧变回答的是“这个聚类结果能不能让人放心”的问题。这四个层次层层递进缺一不可。只完成第一层就是对着数学公式发呆只完成第二层容易陷入拍脑袋式的业务解释。把四个层次都走完你的聚类分析才算真正有说服力。下面我从原理到实操一层层拆开讲。2. KMeans在跑什么核心逻辑先讲透2.1 物以类聚的数学表达KMeans的数学目标其实不难它做的事情可以用一句话概括把n个样本划分成K个簇使得每个样本到它所属簇中心的距离平方和最小。这个目标函数写作[ J \sum_{k1}^{K} \sum_{x \in C_k} | x - \mu_k |^2 ]其中(C_k)表示第k个簇(\mu_k)是第k个簇的中心点也就是质心等于该簇所有样本的均值(| x - \mu_k |^2)是样本到质心的欧氏距离平方。目标就是让这个J最小化换句话说每个样本离自己所属那类的中心越近越好类内的紧凑程度越高越好。这个目标函数翻译成人话就是你希望每一类内部的人都尽可能相似而不同类之间的人尽可能不同。所谓“物以类聚”聚得好的标志就是同类扎堆、异类分离。很多人会问既然目标是明确的为什么KMeans还要一遍遍迭代而不是一次性算出来因为这个问题属于NP难问题无法在多项式时间内求出全局最优解。所以KMeans采用了一种启发式的迭代策略先随便K个中心点然后把样本分配到最近的中心再根据分配结果重新计算中心点如此反复直到中心点不再变化为止。这就是Lloyd算法的基本流程也是绝大多数统计软件里KMeans的实现方式。2.2 K值是怎么来的肘部法则与轮廓系数KMeans使用前必须回答的第一个问题就是K取多少。最常见的参考工具是肘部法则。它的大致思路是跑不同K值的模型记录每个K值对应的组内平方和也就是目标函数J的值然后把K和J画成折线图。你会发现随着K变大J一定在下降因为类越多每类内部的样本越少、越紧凑平方和自然越小。但关键是下降的速度有一个明显的拐点在这个拐点之后继续增加K带来的平方和下降幅度会显著变小。这个拐点就像人的手肘所以叫肘部法则。K就取拐点对应的值。不过肘部法则有一个实操上的坑有时候拐点并不明显尤其是数据量不大但变量维度较高的时候曲线会很平滑。这种情况下我就会辅以另一个指标——轮廓系数Silhouette Coefficient。轮廓系数的计算逻辑稍微复杂一点但解释起来很直观。对于每一个样本i定义a(i)为样本到同簇其他样本的平均距离这个值越小说明样本在簇内越融洽再定义b(i)为样本到其他各个簇的平均距离的最小值这个值越大说明样本和其他簇分得越开。单个样本的轮廓系数就是(b(i) − a(i)) / max(a(i), b(i))取值在−1到1之间。把所有样本的轮廓系数取平均就是整体轮廓系数。这个值越接近1说明聚类效果越好越接近−1说明样本被分到了错误的簇。我在实际项目中通常的做法是先画肘部图确定一个大概的候选范围再在这个范围内比较平均轮廓系数选最大值对应的K。两个指标综合起来比任何单一指标都可靠。2.3 初始中心点和迭代收敛Stata是怎么处理的理解了迭代目标和K值选择还必须理解初始中心点的问题。Stata的KMeans实现中初始中心点的选择有多种选项。比较常用的是Kmeans初始化方法也就是先随机挑K个观测作为初始中心或者使用Kmeans方法让初始中心尽量分散。选择哪种初始化方法在样本量小、变量维度高的时候差别会非常明显。我自己的经验是在Stata里如果用的是官方内置命令默认或指定的初始化方法不同跑出来的分类结果偶尔会有变化这不是bug而是启发式算法的正常特性——不同起点有可能收敛到不同的局部最优解。因此一次性的聚类结果并不能保证是最优的。Stata里可以通过设置多个起始值start()选项来自动尝试多组初始中心最后保留目标函数最小的那组结果。这一点极其重要我在后面实操部分会再详细展开。3. Stata实操从数据准备到跑出结果3.1 数据准备与标准化这一步不当回事结果全废很多人觉得聚类分析的数据准备就是“把变量放进去跑一下”其实这一步决定了后续所有分析的有效性。最核心的问题是量纲。假设你要做客户分群同时用到了年收入和年龄两个变量。年收入可能是几万到几十万的量级年龄是十几到几十的量级。如果直接用原始数据跑KMeans距离计算会完全被年收入主导因为欧氏距离对数值大的变量天然敏感。这时候年龄变量的区分作用会被严重稀释。你确实也能跑出一堆结果但这堆结果基本只反映了一个变量的差异算不上真正的多维聚类。所以在聚类之前必须对连续型变量做标准化处理。在Stata里可以直接用summary命令查看各变量的均值和标准差然后用egen配合std()函数生成标准化变量比如* 假设原始变量是income和age summarize income age * 生成标准化后的变量命名为income_std和age_std egen income_std std(income) egen age_std std(age)标准化之后每个变量的均值都是0标准差都是1此时再计算欧氏距离各个变量就站在同一尺度上了。对分类变量和虚拟变量要单独考虑一般不直接放进欧氏距离的计算因为0/1变量和连续变量的距离含义完全不同强行混用会得到很怪异的聚类结果。我通常的做法是先只用连续变量做主聚类虚拟变量放进去做辅助描述或者单独跑一遍对比。数据准备还有一个细节值得留意缺失值。KMeans本身不处理缺失值Stata在运行KMeans相关命令时会默认剔除含有缺失值的观测但在剔除之前你最好自己想清楚样本量会损失多少。如果缺失比例高需要先做多重插补或者其他方式的缺失值处理而不是直接丢数据。3.2 Stata实现KMeans命令与关键参数接下来就是核心操作。Stata里实现KMeans的途径有三条分别是使用第三方命令、官方命令和官方新命令。先说明一下KMeans这种算法属于无监督学习的范畴和常见的判别分析、聚类分析层次聚类在实现上并不一样。传统Stata里面的cluster命令主要是层次聚类和基于划分的KMeans之前的老式实现而现在最新版Stata开始内置一些机器学习命令但很多时候大家还是会用到外部命令比如kmeans。这里给出一套最稳妥、兼容性最强的操作方式。在Stata中执行KMeans的经典命令格式是* 安装kmeans命令如果没有安装 ssc install kmeans * 执行KMeans聚类指定聚类数为4指定初始随机种子确保可复现 set seed 2024 kmeans income_std age_std, k(4) start(random(10)) iterate(100)这段命令里需要解释几个关键参数k(4)不用多说就是聚成4类。start(random(10))的意思是生成10组随机初始中心点从中选出目标函数最小的一组这比只用一组初始点多了一份保险。iterate(100)是最大迭代次数一般数据量不是特别大的情况下100次足够收敛。还有一个参数也值得一提就是metic或者叫measure用于指定距离度量方式。默认是欧氏距离这也是KMeans最标准的搭配。除非你有明确的理论理由否则不要随便换成其他距离因为KMeans的目标函数本身就是在欧氏距离的框架下推导出来的换距离之后目标函数和算法的一致性会被破坏。跑完之后Stata会在结果窗口输出一张表格包含组内平方和、组间平方和、总平方和、各类样本数、以及每一类的变量均值等信息。这就到了本文最核心的部分——结果解读。3.3 如何确定K值结合Stata输出的实际流程我在实际项目中确定K值的时候一定会走一遍下面这个流程。第一步用循环跑K2到K8的KMeans把每次的组内平方和保存下来。Stata里可以用forvalues循环加post命令来保存结果也可以手动记录。第二步画肘部图。我可以把K和组内平方和整理成一个临时数据集用twoway line画折线图观察拐点位置。第三步对候选K值计算轮廓系数。Stata没有直接输出轮廓系数的官方命令但可以编写一个小程序或者借助Python等其他工具计算然后把结果记录下来辅助决策。完整流程看起来繁琐但熟练之后也就几分钟的事。确定K值这件事没有任何捷径跑多组对比是必须付出的成本。用一组盲目选定的K值直接出结论后续所有的业务解释和统计推断都是空中楼阁。4. 聚类结果怎么看逐项拆解4.1 第一眼看矩阵组内平方和与组间平方和跑完KMeans之后Stata输出的结果矩阵通常是这样的结构项目数值示例解读Between sum of squares12156.32组间平方和反映各类中心点之间的离散程度Within sum of squares3850.47组内平方和反映各类内部样本的紧凑程度Total sum of squares16006.79总平方和等于组间加组内Pseudo F42.35组间均方与组内均方的比值越大说明类间分离越好我一般先看组间平方和占总平方和的比例。这个比例可以简单计算为组间平方和除以总平方和。比例越高说明聚类结果解释掉的变异越多类间差异越大效果越好。一般这个比例在60%以上我就会觉得结果可以接受低于50%就会怀疑K值选得不对或者变量选得不合适。Pseudo F统计量也很有参考价值。它不是标准F检验的那个p值意义上的F因为聚类分析的前提假设并不满足严格的独立性和正态性F统计量在这里只能作为“伪F”来使用用于从相对意义上比较不同K值的聚类效果。Pseudo F越大聚类结构越明显。你可以跑多个K值看哪个K对应的Pseudo F最大或出现明显峰值这本身就是一种K值选择的辅助手段。这里我要特别提醒一句不要因为Stata输出了F统计量就报出p值然后说“聚类效果差异显著”。这不是假设检验的F这里不存在传统意义上的原假设和备择假设。许多论文里把伪F当真正的F用审稿人一抓一个准。4.2 第二眼看中心表各类的量化画像矩阵输出之后Stata通常还会给出聚类中心表。这张表每一行是一个类别每一列是一个聚类变量单元格里的数值就是该类别在这个变量上的均值标准化后。看这张表的方法是把每一行当作一类人的平均画像来看。比如表格里有这么两行类别income_std均值age_std均值样本量11.820.952602-0.740.1138830.03-1.24410第一类在收入标准化变量上的均值是1.82年龄标准化变量上是0.95说明这一类总体上是高收入偏高年龄群体。第二类在收入上是负的年龄接近于零偏向低收入群体年龄居中。第三类收入在零附近年龄负得比较厉害说明年龄偏小收入水平中等。这样把每一行的数值转化成自然语言描述每一类就有了清晰的画像。这个环节是最容易体现分析水平的不要停留在“第一类均值1.82”这种复读机式的表述上而要问自己一个问题这组均值的组合在真实世界里对应的是一群什么样的人做客户分群类别画像会直接关联到运营策略做区域分类画像会关联到资源配置。另外看中心表的时候还要做横向对比。比如收入这个变量各类之间的均值相差越大说明这个变量对区分聚类的贡献越大。如果某个变量在所有类别里都差不多说明它没有提供多少区分的增量下次聚类可以把这个变量剔除。4.3 第三眼看个体归属样本都被分到了哪一类类别画像看完之后下一步是确认每个样本的分组结果并把分组变量拼回原始数据。这一步在Stata里是必须做的因为你后续的统计分析、业务报表全部要依赖这个分类结果。通常是在KMeans运行之后用predict命令生成一个分类变量然后跟原始ID变量合并在一起。* 假设聚类结果存储在变量 _cluster 中 predict cluster_id, cluster * 查看每个类别的样本量和描述统计 tabulate cluster_id这里的tabulate会给出每个类别的频数和占比直观地告诉你分类的分布是否均衡。如果某一个类占了90%以上的样本这个K值大概率是不合适的如果有一个类只有个位数的样本也说明K值可能偏大或者数据里有明显的离群点干扰了聚类过程。我通常还会在这个环节把原始关键变量的描述性统计按类别分拆出来形成一张“分类别描述统计表”。比如用tabstat按cluster_id分组计算原始变量注意不是标准化变量的均值、中位数、标准差这样在做业务汇报的时候可以直接用原始变量口径解释不用每次都换算标准化数值听众也更容易听懂。4.4 可视化叠加散点图和类别标记聚类分析尤其是KMeans强烈建议配一张可视化图否则很难对“类分得开不开”有直观感受。Stata里的做法是用前两个方差贡献最大的主成分作为坐标轴来画散点图用聚类类别作为颜色或标记。如果直接用原始变量画散点图变量一多就画不出来而主成分分析可以帮助你把高维数据压到两维同时尽量保留原始变异。* 先做主成分分析 pca income_std age_std predict pc1 pc2, score * 按聚类类别画散点图 twoway (scatter pc2 pc1 if cluster_id 1, mcolor(red)) /// (scatter pc2 pc1 if cluster_id 2, mcolor(blue)) /// (scatter pc2 pc1 if cluster_id 3, mcolor(green)), /// legend(label(1 类别1) label(2 类别2) label(3 类别3))看这张图的时候如果类别之间边界清晰颜色块各自聚集、很少重叠说明当前KMeans结果在低维投影下具有很好的分离度。如果不同颜色的点交织在一起界限模糊就要回头审查K值是否合适变量是否选对是否存在大量离群点。可视化不能替代统计指标但它能帮助你快速怀疑、快速定位问题。5. 聚类结果到底好不好效果评估5.1 类别差异显著性用外部变量做确认KMeans本身是无监督算法它不会告诉你分出来的类别在业务上有无意义。但你可以引入聚类变量之外的变量也就是外部变量来检验一下分类结果的有效性。这种做法叫作“聚类效度的外部验证”。思路很简单如果聚类方案真的反映了样本在某种结构特征上的差异那么这一分类结果在其他相关变量上也应该表现出显著差异。比如你基于消费行为数据做客户分群分完之后去看不同类别在客户满意度、复购率这些外部变量上是否存在显著差异。如果差异显著说明分类是有实际意义的如果完全不显著那就要怀疑分类是否只是算法上的一堆数字游戏。在Stata里操作方式是用方差分析或者非参数检验。连续型外部变量可以选择ANOVA在Stata里就是* 考察外部变量satisfaction在不同类别间是否有差异 anova satisfaction i.cluster_id如果数据明显不满足正态性用Kruskal-Wallis非参数检验替代在Stata里是kruskalwallis satisfaction, by(cluster_id)这里要强调一点既然KMeans内部跑出来都伴随伪F统计量那么聚类变量的类间均值差异做ANOVA检验是必然显著的因为算法本身就朝着“类内紧凑、类间分离”的方向去迭代的。所以真正的确认意义只在于外部变量也就是那些没有参与聚类计算的变量。这也是我坚持在做完KMeans之后一定要预留一部分业务特征变量来做外部验证的原因。5.2 稳定性检验换初始点结果还是这批人吗KMeans的启发式本质决定了结果对初始中心点选择是敏感的。稳定性的检验策略其实很朴素用多组不同的随机种子跑KMeans看看每次得到的分类结果是否高度一致。具体做法可以分成两步。第一步固定K值改变随机种子的数值跑多次KMeans并保存每次的分类结果变量。第二步用聚类结果的交叉一致性进行评价。最简单的办法是观察每次各类别的样本量是否大致稳定更进一步可以计算两次聚类结果的混淆矩阵看大部分样本是否落在相同的簇对应关系中。Stata里操作也不复杂核心就是多次设置set seed然后重复运行KMeans命令。比如forvalues i 1/10 { set seed i kmeans income_std age_std, k(4) start(random(5)) iterate(100) predict cluster_i, cluster }跑完之后对比cluster_1到cluster_10的分类一致性。如果绝大多数样本的分类结果是稳定的说明这个聚类结构是数据自身强烈支持的如果每次分类结果像抽签一样变来变去这个KMeans结果就只能用来做探索性参考不能作为任何决策的依据同时需要重新审视变量选取和K值确定。这一环节的重要性很多教程都不会强调但在实证研究和企业分析中极为重要。试想你在论文里写了一组客户分群结果审稿人问你“换一个随机种子是否还是同样的分类”你可以在附录里附上一张多次运行的稳定性对比表。这个细节在答辩和评审中是非常加分的。5.3 类别画像描述为每一类赋予可操作含义如果说前两步是确认聚类“分得开、分得稳”那么类别画像这一步就是让聚类结果“说得出人话”。我给每类写画像的时候通常分三个层次。第一层是规模层也就是这一类有多少样本、占总体的比例如何决定后续针对该类投入的资源体量。第二层是特征层以聚类中心表为核心结合原始变量描述性统计归纳出这类成员的典型特征组合。第三层是行动层根据特征组合给出业务或研究上可落地的建议。例如一个高收入、高年龄段、偏好长期持有的客户群对应的行动策略可能就是优先推送稳健型产品服务方式上侧重一对一沟通。在Stata中做这个环节最常用的就是tabstat命令按类别输出描述统计表tabstat income age satisfaction, by(cluster_id) statistics(mean sd median n) format(%9.2f)tabstat输出的表格会自动按类别分行每一列是变量每一行是一个类别信息密度高直接可以复制到Word或Excel里做进一步加工。我自己在所有聚类项目里都会用这个命令生成一张“类别描述基准表”它既是业务解释的原材料也是写好分析报告的数据底座。6. 常见问题排查与个人经验6.1 常见问题速查表我在多次实操中遇到过许多看起来奇怪但背后原因都很有规律的问题在这里整理成一张速查表方便大家在跑KMeans时对照排查。现象可能原因排查与解决方案数据没标准化聚类结果被某个变量主导变量量纲差异大对所有连续变量做z-score标准化检查各类中心表上变量贡献度不同随机种子聚类结果差异很大数据聚类结构弱或初始点选择过于随意使用多个随机起始值增加start()数量检查是否需要剔除离群值某一类样本量极小甚至只有1个K值过大或数据中存在明显离群点可视化检查离群点尝试减小K值对离群点单独处理各类之间边界模糊投影图严重重叠变量选择不佳或类别本质上是连续过度而非离散分类重新筛选聚类变量或考虑改用其他聚类算法如DBSCAN、高斯混合模型伪F全部非常大数据点数量多或者变量维度高不要单独依赖伪F结合轮廓系数和业务解释一起判断标准化后聚类结果还是不稳定数据本身可能没有明显的集群结构接受现实明确说明聚类为探索性分析或增加更多变量后再试聚类结果画图时中文或类别标签乱码Stata图形字体或变量标签设置问题检查变量label和图形legend设置尽量使用英文标签这张表可以节省你很多上网搜问题的时间每一个现象我都实际碰到过基本都是这几种原因。6.2 我在实操中的几个心得写到最后分享几个我多次使用Stata做KMeans后沉淀下来的个人心得。第一个心得是在跑聚类之前一定要先想清楚“分出来的类要用来干什么”。如果你的目标只是描述性分群那K值选择可以稍微宽容一些如果你的目标是用聚类结果做后续预测模型的输入特征那分类稳定性就非常重要建议多做几次验证选一个最稳妥的知识。目标不同你在K值选择、变量筛选上的取舍标准是完全不同的。第二个心得是聚类结果最好跟业务方或合作方一起解读。有时候统计上高度显著的分类在业务上未必有实际可操作性。反过来业务方认为很重要的分群逻辑在KMeans的数学框架下可能并不成立。数据科学家不能闭门造车把统计结果翻译成业务语言再从业务反馈里校准分析方向这个来回循环的过程才让聚类分析真正产生价值。第三个心得比较冷门但非常实用在Stata中做完KMeans之后记得对分类变量打上值标签让它变成一个标准的类别变量。后续做回归、画图、做表格都会非常方便。不设置标签的话类别1、2、3在输出的时候看起来很抽象一旦样本量多、变量多自己都会搞混。label define cluster_lbl 1 高收入中老年 2 低收入群体 3 年轻中等收入 label values cluster_id cluster_lbl这样聚类类别就会以有意义的名字出现在所有后续输出里。第四个心得务必要把随机种子固定好。KMeans在任何软件里跑出来只要涉及随机初始化就应该设定随机种子。这不只是为了可复现性也是学术规范的要求。你写论文或者做报告的时候审阅人随时可能要求你重新运行一遍来验证结果如果不设种子你拿不出完全一致的结果会非常被动。踩过几次坑之后我的习惯是在命令文件的顶部统一设置set seed同时把跑聚类时的版本、日期都记录下来。看似不起眼的操作在项目复现和结果追溯的时候帮了我大忙。做数据分析很多时候最值钱的不是模型多复杂而是结果可复现、逻辑可追溯、解释可闭环。KMeans入门门槛不高但把它用得扎实、讲得清楚同样能体现一个分析师真正的功底。