提到“聚类分析及R实现”,我脑子里第一反应不是R包有多好用,而是前阵子某电商团队的运营同事抱着一份几百列的宽表来找我,说“帮我们聚个类,看看用户怎么分群”。表里有登录次数、加购数、优惠券使用率,还有几十个几乎全为0的稀疏变量。这种场景太常见了,但恰恰是最容易把聚类做砸的地方——因为聚类看着是个无监督算法,好像把数据丢进去就能出结果,实际上每一个关键选择(距离怎么算、变量怎么选、k怎么定、结果怎么解释)都在悄悄决定业务结论,而这些选择恰恰是教科书里最容易被忽略的部分。
我说这些不是劝退,而是因为这篇文章我会彻底摊开讲清楚:什么场景真正适合聚类分析、在R里从头到尾怎么做一次靠谱的聚类、K-Means和层次聚类分别怎么落地、以及我踩过哪些坑后总结下来的处理套路。零基础可以照着敲代码跑通全流程,有一定经验的人也能在变量选择、k值取舍、结果稳定性这些容易含糊的地方找到实用参考。
1. 聚类不是“无监督就随便跑”:先想清楚距离度量与业务目标
1.1 聚类分析的适用场景与误用陷阱
聚类的定位是探索性数据分析:在没有已知标签的前提下,把观测对象按照某种“相似度”划分成若干个内部紧凑、组间疏远的群体。它的产出是“数据自己说话”的群组结构,而不是一个带监督信号的预测模型。
但这里有个最常见的误用:很多人把聚类当成万能分类器。比如业务方说“这批用户我们想区分高价值、中价值、低价值”,如果你手头已经有一套明确的规则(比如消费金额阈值、频次阈值),这本质上是有监督或规则性的分类问题,聚完类反而可能分出一个“高消费但低活跃”的奇怪群组,让业务方更困惑。聚类真正的价值场景,是结构性探索:比如用户行为模式到底存在哪几类?物流线路是否天然可以按距离和时效划分成几组?问卷的态度题能否在回答模式上找到可解释的群体?
所以在动手前,我建议你先自问三件事:有没有明确的业务分组规则?有没有标签可以训练?分析目的是“找结构”还是“去做预测”?如果前两个答案都是“有”,别用聚类。
1.2 距离度量选择是隐藏的“总开关”
聚类算法内部定义的“相似”,本质是距离,而距离的选择直接决定了谁会和你分到一组。这是很多R代码教学没讲透但实战中极其关键的一环。
最常见的几种距离:
| 距离类型 | 计算公式思想 | 适合场景 |
|---|---|---|
| 欧氏距离 | 各维度差平方和的平方根 | 连续数值型特征、标准化后数据,最常用 |
| 曼哈顿距离 | 各维度绝对差之和 | 特征维度间相互独立、对异常值更稳健 |
| 余弦相似度 | 向量夹角的余弦值 | 文本、行为占比等高维稀疏数据 |
| 马氏距离 | 考虑变量相关性和尺度 | 变量间强相关、轮廓形状有差异的数据 |
举个例子:如果某电商团队做用户分群,特征包含“近30天登录天数”“加购次数”“下单次数”,这些变量量纲不同且明显正相关,直接用欧氏距离时,登录天数的绝对数值会把其他维度淹没,而且相关性会让距离“重复计算”同一信息。更稳妥的先做标准化,再看变量相关性,必要时做降维——这些我后面会细说。
反过来,如果是给文本向量做聚类,一个用户和一个商品都能表示成词频/词权重向量,此时余弦相似度远优于欧氏距离,因为欧氏距离会被向量的长度(冗余词数、文档长度)干扰,而余弦更关注方向上的重合。
我个人的习惯是:在跑第一次聚类前,先用数据可视化(散点图矩阵、相关热图)直观感受数据结构,再决定距离度量。这一步看似花时间,实际能帮你省掉后面大量无意义的调参过程。
2. R环境准备与数据清洗:聚类结果好坏一半在预处理
2.1 搭建最小可用的R工作环境
我的标配是R + RStudio,版本保持较新即可。聚类本身需要的包不多,但生态上最常用的组合是:
# 数据操作与清洗 install.packages("tidyverse") # 聚类算法与距离矩阵 install.packages("cluster") install.packages("factoextra") # 树状图美化(层次聚类用) install.packages("dendextend")如果一个包下载慢,可以设置国内镜像,这个在RStudio的Global Options里改一下就成。安装完成后,基础聚类流程基本就是 load 这三个命名空间。
2.2 缺失值、异常值与量纲问题的处理逻辑
聚类算法(尤其K-Means)底层依赖距离计算,而距离计算会被缺失值直接卡死,会被异常值严重拉偏,会被量纲差异主导。
第一步是缺失值处理。我见过有人直接na.omit()一把删,但如果缺失比例超过10%,删除样本会带来致命的偏倚;如果你有多个行为指标,很多人缺失恰恰意味“这类用户没做这个动作”,此时更合理的是把缺失映射为0或单独类。一个相对稳妥的通用做法是:业务含义上“0=没发生”的变量用0填充;随机缺失的数值型变量用中位数填充;缺失率超过一半的变量直接丢弃。
第二步是异常值处理。K-Means采用最小化组内平方和时,极端值会像磁铁一样把一个簇的中心拖走。我的经验是:先画箱线图看分布,然后对强偏态变量做对数或Box-Cox变换,比如“消费金额”这种长尾变量,直接取log1p(x)往往能让分布变得紧凑。变换完成后,再考虑是否真正剔除异常样本,而不是一开始就删。
第三步是标准化。这一步几乎是强制的:如果不标准化,“消费金额(千元级)”和“购买频次(个位数)”之间,距离完全被金额主导,聚类结果等同于只按金额一维排序,其他变量全被忽略。简单样例:
library(tidyverse) df_scaled <- df %>% select(user_id, log_revenue, purchase_cnt, browse_cnt) %>% mutate(across(where(is.numeric), scale))scale()默认是 z-score,即减去均值再除以标准差。这么做以后,每个变量对距离的贡献大致平权,聚类才有意义。
2.3 变量选择与降维:不是变量越多越好
聚类和回归不同,不存在“变量越多信息越多”的说法。原因有两个:一是维数灾难,随着维度增加,样本间的距离越来越趋向于相等,聚类结构反而被稀释;二是无关变量、强相关变量会刻意把结构推向某个方向。
我常用的处理策略是:
- 先做相关性矩阵,去掉相关系数超过0.8的冗余变量,保留业务解释性更强的那个;
- 对剩下的变量做主成分分析,看累计解释方差。当前几个主成分能解释70%以上的方差时,用主成分得分的2-5列去聚类,在R里就是
prcomp()之后取x[, 1:k]; - 更进阶一点,可以用 UMAP/t-SNE 做可视化辅助,但我始终提醒自己:可视化降维只是“展示结果”,不是“计算距离”的替代品,直接拿可视化坐标聚类会丢失很多信息。
我在一家物流平台做线路分群的模拟项目时就踩过这个教训:本来有几十个时效和成本指标,全部塞进去后聚类结果全是一团浆糊。换成PCA降维到3个主成分(累计解释78%方差)以后,聚类分群才终于呈现出“高频短途价低”“低频长途价高”“平衡型”三个有业务含义的类别——这个结果回头一对业务定义,解释性立刻快速上升。
3. K-Means在R中的完整落地:从标准化、k值选择到聚类结果解读
3.1 K-Means的迭代原理和R接口
K-Means的思路说起来很朴素:先把样本随机划分为k组,计算每组中心点;再重新计算每个样本到各中心点的距离,归入最近的中心;更新中心点;不断循环直到中心点不再变化或达到最大迭代次数。
这个算法快是快,也因此有两个明显的弱点:它对初始中心点敏感,容易陷入局部最优;它是基于球状簇的距离假设,对形状怪异的簇(如“月牙形”“环状”)不敏感。R里面的接口非常简单:
library(cluster) set.seed(123) km_res <- kmeans(df_scaled, centers = 4, nstart = 25, iter.max = 100) table(km_res$cluster)nstart = 25表示从25个不同的随机起点各跑一遍,最终返回组内平方和最小的结果。这是我认为最重要的一个参数:不设 nstart,等于让随机种子决定你的用户分群——这明显不能接受。
有时候我会把不同 k 值的模型结果存在列表里,方便统一看轮廓系数和中心点:
k_list <- lapply(2:8, function(k) { kmeans(df_scaled, centers = k, nstart = 25) }) names(k_list) <- paste0("k", 2:8)3.2 k值选择的实操套路:肘部法则、轮廓系数与业务口径
k值怎么定,是聚类实战里被问最多的问题。我从来不用单一指标拍板,而是“统计指标 + 业务可解释性”互相印证。
肘部法则:看不同k值下组内平方总和(WSS)的下降曲线,曲线由陡变缓的那个拐点就是统计意义上的k。在R里可以直接用factoextra的fviz_nbclust()看图和数值。
library(factoextra) fviz_nbclust(df_scaled, kmeans, method = "wss")轮廓系数:每个样本的轮廓系数取值范围[-1,1],接近1说明样本与自身簇内样本很紧凑,与邻近簇很远。平均轮廓系数越大越好,但同样不是越大越“正确”。
fviz_nbclust(df_scaled, kmeans, method = "silhouette")比起埋头看指标,我更常用的策略是:对候选k(比如3、4、5)逐一跑出结果,然后画中心点热图和簇均值表,让业务方直接判断哪一个分组更有解释空间。比如在模拟项目X里,k=4的统计轮廓最高,但把“低活跃低频”拆成了两个很小的群;k=3反而干净利落,每簇占比20%以上,业务能直接对应“核心用户”“成长用户”“沉睡用户”。最终拍板的是业务解释力,而不是比分高0.02的统计指标。
3.3 聚类结果的可视化与簇画像
跑完K-Means,最怕的是“得到标签,但不知道标签代表什么”。所以我有一个固定动作:输出每簇的均值和样本量,并做簇画像表。
df_final <- df %>% mutate(cluster = km_res$cluster) cluster_profile <- df_final %>% group_by(cluster) %>% summarise(across(where(is.numeric), mean), n = n()) cluster_profile如果数据维度较高,我会用主成分把结果投影到2D平面,用fviz_cluster()快速出一个轮廓图,先有个整体观感,再回头细看各簇的原始维度均值。fviz_cluster()的默认参数在数据量小于2000时很好用,更大样本建议先抽样再画,否则散点太重完全看不出簇的形状。
有一个我踩过的坑是:画完图才发现两个簇的重叠区域很大,但分组却是强行切开的。这说明K-Means的效果只是把连续分布硬切成了几块,而不是发现真正的自然簇。碰到这种情形,换聚类算法(比如高斯混合模型)或者重新检查变量选择会更靠谱,我们接下来谈层次聚类时会有另一套处理思路。
4. 层次聚类实操:hclust、距离矩阵与树状图剪枝的取舍
4.1 层次聚类适合什么场景
K-Means虽然快,但它要求你预先指定k,而且只产出扁平的簇标签。层次聚类则不需要预先指定k,它会把样本从“每个样本自己一类”开始,逐步合并最接近的两个簇,一直到最后全部归一,形成一棵树状图;你可以在任意高度“砍”这棵树,得到不同粒度的分组。
它天然适合:样本量不是特别大(几千以内)、想看到样本之间的层级关系、想根据粒度灵活调整分组。比如我在做某零售门店分群时,需要告诉管理层“门店可以先分4大区,再进一步分12支细类”,层次聚类的一棵树就能同时满足两个粒度的汇报需求,这是K-Means很难做到的。
它的代价也很直观:对n个样本,距离矩阵的复杂度就是n²,n上万之后整体计算会非常吃力;而且树状图一旦样本量超过几百,画出来就是一把刷子,谁也看不清。
4.2 在R中实现层次聚类与不同连接法的取舍
在R里实现层次聚类的流程非常直白:
d <- dist(df_scaled, method = "euclidean") hc <- hclust(d, method = "ward.D2") plot(hc)dist()计算样本间距离矩阵,hclust()做层次聚类。对于 method 参数,我强烈推荐ward.D2:它合并簇时最小化“组内方差增幅”,得到的簇更紧凑,而且簇的形状更接近K-Means那种球状。相比之下,single(最近邻)容易产生链条状的怪异长簇,complete对噪声稳健却可能切得过碎。如果你的业务特别在意“每个簇内部成员彼此接近”,complete 也可以作为备选,但多数情况下 ward.D2 是默认最优解。
如果数据量比较大,不要直接对全部样本做层次聚类,先抽样或先用K-Means粗聚类到50-80个“微型簇”,再对簇中心做层次聚类,这也是“两阶段聚类”的实际做法,能兼顾效率和层级表达。
4.3 剪枝定簇:cutree的正确使用与常见问题
树状图建好后,定簇就是“在某个高度切一刀”。R里的函数是cutree():
hc_cluster <- cutree(hc, k = 4)这一刀切在哪里,同样有统计方法和业务方法。统计上看轮廓系数在不同 k 下的表现,实用上我会先看树状图的“跨度”:从树顶往下看,最长的几条分支高度差对应最自然的簇边界。如果某次你切完发现某个簇只有一个样本,先别急着忽略——那往往是一个真实的离群结构,值得单独审视是异常数据还是小众业务群体。
这里还要强调一个散点:距离矩阵计算本身也会被标准化影响。所以层次聚类之前的数据预处理和K-Means完全一致,先清洗、再标准化,否则树状图会被人为量纲大的变量主宰。
另外,hclust默认只接受数值矩阵,当你面对混合变量(数值+分类)时,可以用daisy()函数(支持Gower距离)替代dist(),这样分类变量也可以参与聚类。这是我做问卷数据时常用的小技巧,Gower距离把数值变量做标准化处理、分类变量做重合度处理后统一到一个0-1的范围,实用度很高。
5. 踩坑复盘:聚类分析中5个高频错误及我的解决方案
5.1 不看数据分布,所有变量一股脑塞入
这是我见过最多、也最容易被忽略的坑。一位同学做某高校问卷的分群研究时,把100多道量表题全部作为聚类变量,结果聚出来的群跟“认真填答 vs 随便填答”完全挂钩,和任何态度维度都不相关。原因就是:大量高度相关的重复问项放大了同一维度的权重。
现在我的标准流程是:根据业务框架分词预设变量维度(比如“使用强度”“消费能力”“偏好倾向”),每个维度挑1-3个最具代表性的指标,而不是让算法从一堆冗余变量里自动筛选。这一步完成的质量基本决定了聚类质量的上限。
5.2 只用欧氏距离,拒绝其他度量
很多R教学从头到尾都拿欧氏距离举例,导致实战里大家不自觉地锁死在一种距离上。我在做文本主题聚类时,第一次用欧氏距离得到的结果完全无法解释,后来换成余弦距离,簇内的关键词才呈现出清晰的共现模式。
合理的做法是对同一数据跑“欧氏距离 + 余弦距离”两套结果,对比看簇的稳定性和可解释性。如果两个距离得到的簇结构高度一致,结果比较可信;如果差异很大,说明数据中可能不存在特别强的簇结构,或者某个距离度量更符合当前业务逻辑。
5.3 k值机械地选“肘部”,忽略业务含义
肘部法则是入门教材的第一课,但真实数据里WSS曲线的“肘”往往很不明显,曲线几乎平滑下降,你根本说不清拐点在哪个位置。更危险的是,很多WSS曲线的拐点对应的k完全不符合业务资源的承载能力(比如用户运营根本只有人力维护3-4个分层)。
我现在的处理原则是:统计指标负责给出候选区间(比如2-8),业务约束负责在区间里收敛。如果你有“固定维护资源”或“每簇最小样本量”这样的业务硬条件,就直接过滤掉不满足的k,再在剩下的选项里比较轮廓系数。
5.4 做完聚类不做稳定性验证
聚类是探索性方法,而且K-Means对初始值敏感,所以稳定性验证必不可少。我最常用也最容易落地的方式是“重抽样验证”:对原始数据做多次随机抽样(比如80%样本),每次跑相同的k值,然后比较各次样本的簇中心和全体样本的簇中心。只要中心点分布相对稳定,就说明结论不是随机种子带来的噪声。
另一个更轻量的稳定性检查方法:把全部样本聚类的结果作为种子中心,再用kmeans(..., centers = 初始中心)跑一遍,比较两轮标签的一致性(通过交叉表计算ARI)。R里有mclust包的adjustedRandIndex(),或者aricode包可以算 ARI 值。
5.5 把聚类结果直接当分类标签喂给监督模型
这算是“聚类后处理”的常见误区。有人做完聚类,把cluster标签拼到原表里,再去跑分类模型,做“预测用户属于哪一类”。这在逻辑上没有大问题,但要注意:聚类的类别是“当时数据结构”的产物,不是天然的类别映射;如果业务环境变化(比如大促活动改变行为分布),旧簇中心的含义可能彻底失效。
我建议如果你真要做“可复用的分群模型”,应该把“聚类中心”固化下来,新样本到来时只计算它到各个中心的距离,输出最近的那个簇作为标签,而不是每次整体重新聚类。这样统一标准,业务上才可控可迭代。
6. 把聚类结果讲成业务结论:可视化与汇报的实用套路
6.1 基础可视化:散点图、中心点热图与箱线图
聚类做完了,真正的考验是“怎么讲给别人听”。光贴一个fviz_cluster()的散点图是不够的,业务方看只会问:这图里的圈圈是什么?这些点为什么挨在一起?
我的标配是三张图表:
- 降维散点图——用PCA把样本投影到2D空间,按簇着色,领会整体分群效果;
- 簇中心热图——行是簇,列是标准化后的变量,颜色深浅体现均值高低,能一眼看出每簇的突出维度;
- 关键变量的分组箱线图——挑2-3个业务最关心的变量,按簇画箱线图,直观展示组间差异是否真实存在。
在R里做热图非常简洁:
library(pheatmap) pheatmap(cluster_profile[, -1], scale = "row", cluster_cols = FALSE, display_numbers = TRUE)如果要画箱线图,ggplot2就够了;关键变量选“业务上最有解释力”的,而不是选聚类里区分度最高的,因为后者可能只是数学结论,前者才是业务关心的。
6.2 给簇做“人设”:从数字到业务定义
我做完聚类之后几乎都会做一步“簇画像赋名”:给每一簇写一段描述,描述里必须包含“人群特征+行为特征+为什么值得重点关注”。比如在模拟项目X的用户分群结果里:
| 簇 | 均值画像 | 业务命名 | 后续动作 |
|---|---|---|---|
| 1 | 登录天数高、加购高、下单中 | 高频观望用户 | 定向促转化 |
| 2 | 登录低、消费高、频次低 | 高价值低频用户 | 提升复购频次 |
| 3 | 全部中低,活跃下降 | 沉默流失风险用户 | 召回干预 |
这种表达比“簇1有323人,簇2有512人”有效率一个数量级。每个人都能立刻读懂,也能盯着这张表提出下一步分析问题,而不是纠结统计细节。
6.3 汇报时一定要提醒的三件事
汇报降维散点图时我会主动提醒三点:第一,2D图上“看起来近”不代表原始特征空间里一定最近,因为2D只是压缩后的近似;第二,簇与簇之间如果没有明显的边界,说明数据本身是连续的,现在的分簇是“工程化切分”,业务解读时要诚实区分“自然簇”和“工具性分组”;第三,聚类的颗粒度永远不会自动匹配业务资源,最后怎么分组还是要靠业务定义来约束。
把这三件事在汇报开头讲清楚,对方对接下来的任何质疑都会少很多,因为你在结论前置了“探索性”的定位——聚类是帮业务提出好问题的工具,不是自动给出正确答案的黑盒。
我自己在反复做完多个聚类项目后最深的体会是:R提供了世界上最顺滑的聚类工具链,一个小时就能跑完整套流水线,但真正决定项目质量的反而是跑代码之前想清楚的那半小时——距离度量是否合理、变量是否冗余、k选择是否兼顾业务边界、结果是否足够稳定。这些“慢功夫”一开始做扎实,后面的可视化、汇报、模型固化全都事半功倍。如果你刚接触聚类分析,我建议别急着把课本里的数据例子跑完就完事,找一份你熟悉领域的真实数据(哪怕几十行也好),从上面的问题清单出发,亲手过一遍聚类分析及R实现的全过程。跑完你就发现,那些文档里没说破的判断,才是这顿饭的主菜。