聚类分析原理与R语言实战:K-means、层次聚类、DBSCAN全解析
发布时间:2026/9/15 17:50:07来源:尧图网络
最近把聚类分析这套东西重新系统地过了一遍从算法原理到R语言实现再到实际业务场景里的应用踩了不少坑也理清了很多以前模棱两可的概念。这篇是数据分析学习总结笔记的第2篇专门讲聚类分析以及它在R语言里的落地方式。如果你正在学数据分析或者工作中需要做用户分群、商品归类、异常检测这类事情这篇文章应该能帮你省不少时间。文里的代码都是可以直接复制运行的水平环境用RStudio就行数据我会用内置数据集和模拟数据来讲不需要额外找数据源。1. 先搞清楚聚类分析到底是干嘛的——它和分类根本是两回事很多人刚接触聚类时最容易混淆的就是聚类和分类。我当时学的时候也绕了一阵子后来发现只要抓住一个核心区别就通透了分类是监督学习聚类是无监督学习。什么意思分类是你手里已经有一批打好了标签的数据比如一万条用户记录里面已经标好了哪些是高价值用户、哪些是普通用户、哪些是流失风险用户你要做的是训练一个模型让它在面对新用户时能自动归入其中某一类。整个过程是有标准答案的。聚类则完全反过来。你手里只有一堆数据没有标签、没有答案甚至到底该分成几类你都不知道。你要做的是让算法根据数据本身的特征分布把长得像的样本自动归到一起让不太像的样本分开。聚类结果好不好也没有一个绝对的对错更多是看它在业务上能不能解释得通。这个区别决定了聚类分析在很多场景里是探索性的工具它的核心价值不是预测而是帮我们发现数据内在的结构。举几个实际例子你就有感觉了用户分群电商平台把用户按消费金额、购买频次、浏览时长等特征分组找出高价值活跃用户价格敏感型用户沉睡用户等不同群体然后针对每个群体做不同的运营策略。这是聚类在商业数据分析里最常见的应用也是问得最多的场景。商品归类零售企业根据商品的销售曲线、价格区间、库存周转率等特征把几千个SKU自动归成几大类辅助采购和定价决策。异常检测通过聚类找出远离所有簇中心的数据点这些点往往是刷单行为、设备故障、欺诈交易等异常情况。文本主题归纳把一堆新闻文档按词频特征聚合自动发现几个热点话题簇这是文本挖掘里常用的冷启动手段。在这些场景里你都没有标准答案只能靠数据的几何特征说话。所以在正式动手写代码之前我强烈建议你先想明白一个问题你手里的数据分完组之后是要给谁看、拿来做什么决策这会直接决定你后面选什么算法、分几类、怎么解读结果。2. 常用聚类算法对比K-means、层次聚类、DBSCAN到底该怎么选R语言里能实现聚类分析的包和函数非常多但万变不离其宗常用的就三大流派基于划分的K-means、基于层次的hclust、基于密度的DBSCAN。我一个个说清楚它们的原理、优点和致命短板。2.1 K-means最经典但也最容易栽跟头K-means的核心思想非常朴素。先指定一个K值也就是你想把数据分成几簇然后算法会随机选K个点作为初始中心接着反复执行两步第一步把每个样本分配到离它最近的中心点所在的簇第二步重新计算每个簇的中心也就是该簇所有点的均值再重复第一步。直到中心点不再移动或者迭代次数用尽聚类就完成了。我当年第一次学这个算法时总觉得就这么简单实际上它确实原理简单但有两个前提条件经常被忽略第一K值必须由你事先指定。算法自己不会告诉你该分成几类你给3就出3簇给5就出5簇。K值选得好不好直接决定聚类结果的业务价值。后面第6章我会专门讲怎么选K这里先留个钩子。第二K-means对初始中心点非常敏感。不同的随机初始点可能收敛到不同的局部最优解。这也是为什么R语言的kmeans函数里有个nstart参数让你从多个随机起点中挑一个最优结果——这个细节真的会决定聚类结果的可复现性实战中特别重要。K-means的适用场景是数据量较大几万到几十万条都没问题、样本特征维度适中、类别大致呈球形分布、簇与簇之间区分明显。它最大的短板是对离群点非常敏感几个异常值就能把簇中心拉偏还有就是它对非球形簇比如月牙形、环形效果极差。2.2 层次聚类不需要预先指定K但计算量感人层次聚类是另一条完全不同的路线。它不要求你先定K值而是递归地把数据合并或分裂最终生成一棵树状结构你随时可以从这棵树里切出你想要的分类数。具体来说有两种方向自底向上凝聚式一开始每个样本单独成一簇然后每次找距离最近的两个簇合并不断重复直到最后只剩一簇。自顶向下分裂式从所有样本一个整体开始不断把最不相似的簇拆开直到每个样本单独成簇。R语言里hclust函数默认实现的是凝聚式。层次聚类的最大优点就是不需要猜K值你聚类完之后看树状图dendrogram眼睛看到该切的地方切一刀就行非常直观。而且它不限于球形簇能处理很多不规则形状。但代价是计算复杂度高。我拿3万条用户数据跑过一次hclust跑了差不多两分钟还没出来而且内存占用非常大。所以如果你数据量超过两三万条我建议谨慎使用层次聚类或者先对数据进行抽样再去聚类。另外层次聚类一旦某个合并或分裂步骤做了错误决定后面是没有回头路可走的这跟K-means迭代优化的机制不一样所以它对距离计算方式比较敏感。2.3 DBSCAN密度聚类专治各种不规则形状DBSCAN的思路跟前面两种完全不同。它不看距离中心而是看密度。核心就两个参数邻域半径eps和最小样本数minPts。以此为基础把样本分成三类核心点邻域内样本数超过minPts、边界点落在核心点邻域内但不是核心点、噪声点既不满足核心点也不在核心点邻域内的点。DBSCAN最厉害的地方有三个不需要指定簇的数量它自己会找能识别任意形状的簇月牙形、环形、凹形都行天然能发现离群点被标为噪声点的就是它的缺点是参数不好调尤其是eps。设大了本来该分开的簇会被连到一起设小了一个簇会被拆成好几个碎块。我自己的经验是eps选多少没有一个通用办法基本靠看K距离图去做判断这部分在R里没有特别现成的函数经常要自己动手画。另外如果数据里各个簇的密度差异非常大DBSCAN的表现也不会太好因为一个全局eps没法同时适配不同密度的区域。2.4 怎么选从业务需求倒推学了三个算法以后肯定有人会问那我到底用哪个我个人的选择逻辑是判断条件推荐算法数据量大3万簇近似球形有业务先验知道大概分几类K-means数据量小1万想直观看到层次关系不确定分几类层次聚类簇形状不规则数据里有大量噪声/离群点DBSCAN快速跑通一个基线结果给业务方看个大概K-meansK取3~5这里多说一句这三种算法不是互相排斥的我经常会在一个项目里配合用先用层次聚类在小样本上摸清大概分几类合适再用K-means在全量数据上跑正式结果最后用DBSCAN去筛查那些离群点。组合拳打下来效果往往比单用任何一个都稳。距离度量这一点也值得提。不管是K-means还是层次聚类前提都是能算出样本之间的距离。最常用的是欧氏距离直接算特征空间里的直线距离适合连续型数值特征曼哈顿距离则是各维度绝对差之和适合某些特殊的业务场景余弦相似度更适合文本和高维稀疏向量R语言里用cosine函数或者在proxy包里指定methodcosine。3. R语言环境准备从安装到数据预处理这些坑一定要提前避开说是学习笔记但代码能不能跑通才是硬道理。先把环境搞定再做数据预处理不然直接上手聚类函数很容易出现报错而且报错信息还看不懂。3.1 R和RStudio的安装如果你用的是Windows直接去R语言官网下载安装包安装即可这个没什么好说的。需要提醒的是R语言官网默认给的是二进制安装包下载速度有时候比较慢国内可以找镜像站点一般高校或云厂商都有提供。如果你用的是macOS这里有一个特别容易踩的坑下载了R安装包之后双击安装完打开R控制台输入install.packages(factoextra)极有可能会报错说找不到某个编译环境。这是因为R里不少包比如factoextra依赖的类需要从源码编译而macOS上默认没有装gfortran编译器就会卡在安装这一步。解决办法是去编译器官网单独下载gfortran装好后再重启RStudio。我当时在这个问题上卡了快一个小时才明白R报错里的compiled from source是什么意思。RStudio是R的IDE本质上就是给R套了个好用的编辑器和可视化管理界面日常写脚本、看变量、画图都离不开它。装好R之后再去RStudio官网下载一个对应系统的安装包装完打开RStudio它会自动识别你系统里的R。装完以后在RStudio的控制台里执行以下命令把接下来要用的包一次性装齐install.packages(cluster) # 轮廓系数等聚类评估 install.packages(factoextra) # 聚类可视化太好用了 install.packages(dplyr) # 数据操作 install.packages(ggplot2) # 绘图 install.packages(readr) # 数据读取3.2 数据读入和基础清洗装包完成只是万里长征第一步。真正做聚类分析时你会发现80%的时间都花在数据清洗与预处理上真正跑聚类就那一行函数的事。先读数据。R语言里读csv最顺手的是readr包的read_csv速度比基础函数read.csv快不少而且不会把字符串默认转成因子省去很多麻烦library(readr) # 假设你有一个用户行为数据文件 data - read_csv(user_behavior.csv) # 快速看一下数据结构 str(data) # 每一列的类型和样例 summary(data) # 数值列的最小值、最大值、均值、缺失值情况 head(data) # 前几行数据长什么样数据读进来以后第一件事不是直接聚类而是检查数据质量。我会按顺序查这几样检查缺失值。R语言里缺失值用NA表示kmeans函数遇到NA会直接报错或者返回的结果缺失那一行所以必须处理。处理方式无非两种删掉含缺失值的行或者用均值/中位数填充。哪种好要看缺失比例如果缺失行占比低于5%直接删掉就行如果比较高建议用median(data$列名, na.rmTRUE)这种带na.rm参数的方式计算中位数去填因为均值容易受离群点影响。检查量纲差异。这一步是最关键的但也是新手最容易忽略的。假如你的数据里有一列叫消费金额范围是0到50000另一列叫购买次数范围是1到50如果你直接拿这两列去算欧氏距离消费金额这一列会完全主导距离计算购买次数那列基本不起作用。解决办法就是标准化R语言里一行代码# 只对数值列做标准化 data_scaled - scale(data[, c(amount, frequency, avg_order_value)]) # 标准化后每一列均值接近0标准差为1 # 可以验证一下 colMeans(data_scaled) # 接近0 apply(data_scaled, 2, sd) # 接近1scale函数做的事情很简单对每一列先减去这列的均值再除以标准差。标准化之后所有特征都在同一个尺度上才不至于某一个维度单独称王。检查离群点。K-means对离群点敏感所以在跑聚类前我一般会用箱线图或者直接看各个数值列的分位数把明显异常的值找出来。不是说一定要把离群点删掉而是你要心里有数知道它们在哪些位置后面聚类时它们很可能单独成一簇或者把簇中心拉偏。判断离群点可以用# 看各列的分位数判断是否存在极端值 summary(data[, c(amount, frequency)]) # 或者画箱线图 boxplot(data$amount, main消费金额分布)如果是明显的录入错误比如消费金额为负直接处理掉如果是真实存在的少数高价值用户我建议先保留等聚类结果出来再看它们落到了哪个簇这样反而能帮你发现一些高价值特殊群组。4. K-means聚类的R语言实现完整代码与参数调优环境装好、数据洗干净之后就可以正式开始聚类了。这一章我用一套完整的流程把K-means跑通然后把nstart、迭代次数这些容易被忽视的参数讲透。4.1 先用内置数据集跑一个最小可运行版本R语言里有一个非常经典的内置数据集iris鸢尾花数据150条样本、4个特征、3个品种做聚类演示再合适不过。我们用这个数据跑一个最小版本的K-means# 加载iris数据 data(iris) # 只取4个数值特征列去掉品种标签列 iris_features - iris[, 1:4] # 数据标准化 iris_scaled - scale(iris_features) # 指定聚类数为3设置随机种子保证结果可复现 set.seed(123) km_iris - kmeans(iris_scaled, centers 3, nstart 25) # 查看聚类结果的基本信息 km_iris运行完以后kmeans对象里包含几个关键的字段我逐个解释一下km_iris$cluster每个样本被分到哪个簇1到3之间的一个整数km_iris$centers每个簇的中心点坐标标准化后的值km_iris$size每个簇的样本数量km_iris$tot.withinss簇内平方和衡量样本离自己簇中心的距离总和这个值越小说明簇内越紧凑km_iris$betweenss簇间平方和衡量不同簇中心之间的分离程度越大说明簇之间分得越开4.2 给聚类结果画图把簇画出来才算完事跑完聚类不画图等于白做。因为聚类的最终目的是给人看的一张清晰的散点图比任何数据表格都有说服力。这里我强烈推荐factoextra包的fviz_cluster函数它会把高维数据用PCA降到二维然后把每个簇的分布画出来library(factoextra) # 可视化聚类结果 fviz_cluster(km_iris, data iris_scaled, palette Set2, ellipse.type norm, ggtheme theme_minimal())这张图出来以后你能立刻看出来三簇分得干不干净。如果簇之间有大量重叠说明样本特征区分度不够或者K值选得不太合适如果几个簇干净利落地分开那这个聚类结果就很有说服力。如果想用ggplot2自己画二维散点图适合只有两个特征或者想自定义样式的时候代码也很简单library(ggplot2) # 将聚类结果和原始数据合并成一个数据框 plot_data - as.data.frame(iris_scaled) plot_data$cluster - as.factor(km_iris$cluster) ggplot(plot_data, aes(x Sepal.Length, y Sepal.Width, color cluster)) geom_point(size 2, alpha 0.7) labs(title K-means聚类结果K3) theme_minimal()4.3 nstart参数为什么重要一个真实案例很多人第一次用kmeans函数时都是直接写kmeans(data, centers3)根本不加nstart。后来他发现同一个数据每次跑出来的结果居然不一样就开始怀疑代码有bug。这不是bug而是K-means的初始中心点是随机的不同起点收敛到的解不同。kmeans函数里的nstart参数的意思是让我从nstart个不同的随机初始中心点出发分别跑K-means最后选出簇内平方和最小的那个作为最终结果。nstart设成25或50是常见的做法多花一点点时间换来的是稳定得多、好得多的聚类效果。另外再强调一次set.seed(123)。这是为了固定随机数种子。也就是说你在自己的电脑上设置了种子跟我这边跑的随机序列完全一样最终结果也完全一样。写教程、做复现、跟别人对结果这个种子一定要设置不然你换了台电脑或换了个时间跑结果就会变。4.4 K-means的可视化优化簇中心和距离聚类图除了散点之外把簇中心画上去会更有信息量。我们可以手动计算并画出来# 看看簇中心长什么样 km_iris$centers # 对于二维特征可以直接把中心点添加到ggplot图里 center_df - as.data.frame(km_iris$centers) center_df$cluster - rownames(center_df) ggplot(plot_data, aes(x Sepal.Length, y Sepal.Width, color cluster)) geom_point(size 2, alpha 0.5) geom_point(data center_df, aes(x Sepal.Length, y Sepal.Width, color cluster), shape 8, size 5, stroke 2) labs(title K-means聚类结果及簇中心位置) theme_minimal()画出来之后就能很直观地看到三个菱形点基本落在对应簇的中央位置。簇中心的含义不仅是几何中心还能反过来指导业务比如用户分群后算出每个簇的消费金额均值、活跃天数均值这个均值向量就是该簇用户的画像你可以据此给不同群组起名字并制定策略。4.5 K-means实战里最容易踩的三个坑第一个坑数据没标准化就直接聚类。前面讲过量纲问题这里再强调一次。如果数据里有一列是消费金额几百到几万另一列是点击次数个位数没标准化时聚类结果基本只看消费金额。很多初学者跑完聚类发现簇和簇之间没法解释一查就是栽在这。第二个坑K值瞎猜。有人直接拍脑袋定K3结果业务方怎么看怎么不合理。K值没有一个公式可以直接算出来但后面第6章我会讲怎么用肘部法则和轮廓系数系统性地去选而不是靠运气。第三个坑不设随机种子。同一个脚本跑两次结果不一样你自己都解释不清跟别人协作时更是一团乱麻。记住一条纪律凡是涉及随机过程的代码第一行就是set.seed。5. 层次聚类的R语言实现树状图解读与实操细节层次聚类虽然在处理大数据量时吃力但你架不住它直观。尤其是面对小样本探索性分析的时候画一张树状图谁跟谁近、谁跟谁远一眼就能看明白。这一章把hclust的完整使用流程讲清楚。5.1 用hclust实现层次聚类最短代码还是用iris数据只需要三行核心代码# 1. 计算样本之间的距离矩阵 dist_matrix - dist(iris_scaled, method euclidean) # 2. 用Ward方法做层次聚类 hc - hclust(dist_matrix, method ward.D2) # 3. 画树状图 plot(hc, labels FALSE, main 层次聚类树状图)这里有几个关键的细节需要单独解释。dist函数计算的是任意两个样本之间的距离得到的dist_matrix是一个下三角矩阵存储了所有样本两两之间的欧氏距离。method参数还可以换manhattan曼哈顿距离、maximum切比雪夫距离等缺省是欧氏距离。hclust的method参数有很多种选择最常用的是ward.D2它的原理是合并两个簇时让合并后簇内离差平方和的增加量最小。用Ward方法得到的聚类簇与簇之间边界通常很清晰形状也比较紧凑适合大多数业务场景。其他还有complete最长距离法、average平均距离法等complete对离群点更稳健average则介于两者之间。我的实践经验是拿不准的时候默认用ward.D2基本不会出大错。5.2 树状图怎么看以及怎么从树上砍出簇树状图里最底部的每个小分支代表一个样本往上逐渐合并成大分支合并点的高度代表这两个簇之间的距离远近高度越低说明它们越相似。那么怎么决定分几类呢你的眼睛就是最好的工具。找一根横线去切树状图这根横线从上往下移动碰到几根竖线就代表分了几类。在实际操作里一般是看树状图里合并高度落差比较大的那条缝在缝的地方横着切一刀就是比较自然的分类方式。代码层面用cutree函数实现这个切的动作# 把树切成3个簇 hc_clusters - cutree(hc, k 3) # 查看前几个样本被分到了哪个簇 head(hc_clusters) # 对比一下真实的iris品种标签 table(hc_clusters, iris$Species)table输出就是一个小矩阵行是层次聚类分出来的3个簇列是真实的3个品种。你就能看到聚类结果和真实标签大概有多少重合这是评估聚类效果时很直接的手段。5.3 树状图的美化与标签优化直接plot出来的树状图默认带样本标签如果样本很多标签会密密麻麻挤成一团。实际做报告时我一般会去掉标签同时用rect.hclust函数在图上直接框出各个簇plot(hc, labels FALSE, main 层次聚类结果切为3簇, xlab 样本编号, ylab 高度) rect.hclust(hc, k 3, border c(#E7298A, #66A61E, #E6AB02))加了边框之后这张图拿去给业务方看他们一眼就能看懂系统把样本分成了三大块每块内部比较相似块和块之间差异比较大。5.4 层次聚类选K的经验先看树状图再验证在不知道K值该取多少时层次聚类最大的价值就是帮你侦察地形。用层次聚类在小样本比如随机抽5000条上画出树状图看看自然的分簇形态然后拿着这个K值去全量数据上跑K-means两个算法互相验证得到的结论会更扎实。我自己有个习惯先用hclust对抽样数据做一次快速聚类画完树状图基本就对分几类有数了。如果树状图里从第2个分支开始高度落差就很大说明分2类极好如果落差出现在第4、第5个分支那分4类或5类更有业务意义。这种视觉探索先行的思路比直接对着K值范围跑一大堆实验要高效得多。6. 聚类数K的选择与聚类质量评估肘部法则、轮廓系数和业务解释性的三角验证聚类分析最纠结的问题永远是到底该分几类K值取少了信息丢失严重取多了簇之间差异不明显业务方看了头大。这一章我讲三种成体系的方法你配合着用基本能锁定一个合理的K值。6.1 肘部法则画一条弯折曲线找那个拐点肘部法则的原理很简单。K值越大簇内样本离自己簇中心越近簇内平方和WSS理论上会一直下降。但问题是当K超过某个值后再增加K带来的簇内紧凑度提升幅度会急剧变小。把K值和对应的WSS画成折线图曲线会出现一个像手肘一样的拐点这个拐点就是相对合理的K值。在R里手写这个逻辑其实很清晰# 尝试K从1到10分别计算总簇内平方和 wss - numeric(10) for (k in 1:10) { set.seed(123) km - kmeans(iris_scaled, centers k, nstart 25) wss[k] - km$tot.withinss } # 画肘部法则图 plot(1:10, wss, type b, xlab 聚类数K, ylab 总簇内平方和(WSS), main 肘部法则WSS随K值的变化)画出来以后你会发现WSS曲线从K1到K3下降得非常陡过了K3之后曲线就开始变得平缓。这个在3附近拐弯的信号就是在告诉你分3类可能就够了再往下分收益不大。需要说明的是肘部法则的拐点经常是不明显的尤其在真实业务数据里曲线可能是平滑下降的这个时候就要结合其他指标来综合判断了。6.2 轮廓系数量化每个样本分得准不准轮廓系数解决的遗憾是WSS只能看整体看不出每个样本的分类可靠性。它的计算方法是对每个样本i计算它到所在簇其他所有样本的平均距离记作a(i)代表簇内不相似度计算它到最近的另一个簇所有样本的平均距离记作b(i)代表与最近邻簇的不相似度轮廓系数s(i) (b(i) - a(i)) / max(a(i), b(i))如果s(i)接近1说明样本i离自己的簇很近、离其他簇很远分类很可靠接近0说明样本i在簇边界上模棱两可接近-1说明它可能被分错了簇。把所有样本的轮廓系数取平均就得到整体轮廓系数。R语言里用cluster包的silhouette函数library(cluster) # 计算当前聚类结果的轮廓系数 sil - silhouette(km_iris$cluster, dist(iris_scaled)) # 提取平均轮廓系数 mean(sil[, 3])在K值选择上这组代码会非常有用# 尝试K从2到8计算每个K对应的平均轮廓系数 avg_sil - numeric(7) for (k in 2:8) { set.seed(123) km - kmeans(iris_scaled, centers k, nstart 25) sil - silhouette(km$cluster, dist(iris_scaled)) avg_sil[k-1] - mean(sil[, 3]) } # 看哪个K的轮廓系数最高 names(avg_sil) - 2:8 avg_sil平均轮廓系数的取值范围是-1到1通常在业务数据里能到0.3以上就已经算不错了超过0.5属于结构明显。如果某个K值对应的平均轮廓系数明显高于其他K这就是一个非常有说服力的选择依据。6.3 聚类结果的业务解释性所有指标最终服务于决策有一次我给一个电商项目做用户分群肘部法则说K4合适轮廓系数说K3最高最后我们却选了K5。为什么因为K5的时候每个簇都能对应一个清晰的业务标签高价值活跃用户、价格敏感型用户、沉睡用户、新用户、流失风险用户。这5个群组分别对应不同的运营动作领导看了直点头。反过来如果某个K值虽然统计指标很好但分出来的簇根本没法用一句业务语言概括那这个聚类结果就是统计上漂亮、业务上没用。所以我的建议是先用肘部法则缩小K的范围再用轮廓系数判断候选K里哪个在数据结构上更自然最后套进业务场景里验证能不能解释得通。这三个维度合在一起比任何单一指标都可靠。还值得多提一句的是fviz_cluster函数配合轮廓系数一起看效果很好。fviz里可以直接画轮廓系数的棒棒糖图fviz_silhouette(sil)每根柱子的高度代表样本的轮廓系数柱子颜色代表它属于哪个簇。如果某个簇里大多数样本的柱子都很矮甚至成负数说明这个簇可能分得不合理考虑调整K值或特征选择。7. 实战案例电商用户消费数据聚类R语言全流程演示学了这么多我们来一个综合案例模拟一次完整的电商用户分群。这个案例会串起前面所有步骤数据构造、预处理、K值选择、聚类执行、结果可视化、业务解读。7.1 构造模拟数据三个特征三个隐藏群组先读取构造好的数据。这里我直接模拟生成一份电商用户消费行为数据包含1000个用户每个用户有3个特征月均消费金额amount、月均购买次数frequency、平均客单价avg_order_value。为了让聚类效果更明显我让数据来自三个不同的真实群组set.seed(42) # 模拟用户数据 n - 1000 group1 - data.frame( amount rnorm(n/3, mean 3000, sd 400), frequency rnorm(n/3, mean 15, sd 3), avg_order_value rnorm(n/3, mean 200, sd 30) ) group2 - data.frame( amount rnorm(n/3, mean 800, sd 150), frequency rnorm(n/3, mean 5, sd 1.5), avg_order_value rnorm(n/3, mean 160, sd 25) ) group3 - data.frame( amount rnorm(n/3, mean 1500, sd 250), frequency rnorm(n/3, mean 3, sd 1), avg_order_value rnorm(n/3, mean 500, sd 60) ) # 合并并打乱顺序 data - rbind(group1, group2, group3) data - data[sample(1:n), ] rownames(data) - NULL head(data)这份模拟数据的设计很刻意第一群组是高频中客单的活跃用户第二群组是低频低客单的价格敏感型用户第三群组是低频高客单的奢侈品型用户。三个群组的真实边界并不完全清晰还带一定噪声正好用来检验聚类算法能不能把它们大致找回来。7.2 标准流程预处理、选K、跑聚类按前面讲的标准流程走一遍# 1. 标准化 data_scaled - scale(data) # 2. 用肘部法则初步判断K值范围 wss - numeric(10) for (k in 1:10) { set.seed(123) km - kmeans(data_scaled, centers k, nstart 25) wss[k] - km$tot.withinss } plot(1:10, wss, type b, xlab K, ylab WSS) # 3. 用轮廓系数进一步验证K3 avg_sil - numeric(8) for (k in 2:9) { set.seed(123) km - kmeans(data_scaled, centers k, nstart 25) sil - silhouette(km$cluster, dist(data_scaled)) avg_sil[k-1] - mean(sil[, 3]) } names(avg_sil) - 2:9 avg_sil运行完这组代码你大概率会看到轮廓系数在K3时最高因为它背后的数据就是三群生成的这其实是在验证整个流程的正确性。当然实际业务里没有真实标签可对照但这个流程是对的。7.3 跑正式聚类并输出画像确定K3后跑正式聚类并把每个簇的特征均值还原到原始量纲方便业务解读# 正式聚类 set.seed(123) final_km - kmeans(data_scaled, centers 3, nstart 25) # 把聚类标签合并回原始数据 data$cluster - final_km$cluster # 按簇计算各特征均值原始数据量纲 library(dplyr) cluster_profile - data %% group_by(cluster) %% summarise( count n(), avg_amount mean(amount), avg_frequency mean(frequency), avg_order_value mean(avg_order_value) ) cluster_profile输出结果大概长这样clustercountavg_amountavg_frequencyavg_order_value1330302515.120123358024.9158333515053.1502这时候每个簇的业务画像就非常清晰了簇1月均消费3000购买15次客单价200左右。这是高频次稳定消费的核心用户群日常走量适合做会员积分、满减促销维持活跃。簇2月均消费800左右购买5次客单价160左右。整体消费力偏低属于价格敏感型用户可以针对性发低频大额券或搭配低价商品促活。簇3月均消费1500左右购买次数仅3次但客单价高达500。典型低频高客单用户对价格不敏感适合推送新品、高附加值商品不需要频繁打扰。7.4 画图让结果更直观最后用fviz_cluster把三个簇画出来fviz_cluster(final_km, data data_scaled, ellipse.type norm, palette Set1, ggtheme theme_minimal(), main 电商用户消费行为聚类结果)图上一共三个点群基本能看出簇1和簇2在距离上比较近簇3明显隔得远一些。这个簇间距离本身也传递了业务信息高频用户和价格敏感用户在行为习惯上更接近高客单用户则自成体系运营策略需要彻底分开。8. 学习过程中的常见坑与经验总结这些教训花了大量时间才想明白最后这部分我想把学习聚类分析过程中踩过的坑集中整理一下这些坑有的是代码层面的有的是方法论层面的希望你能直接绕开。8.1 代码层报错和结果异常的常见原因问题一聚类结果每次都不一样。原因就是没设随机种子。kmeans和很多聚类算法都有随机初始化这一步不设种子的话每次运行的初始点不同结果自然不同。解决方法是跑之前先set.seed设哪个数字都行但一定要设。问题二数据里有NA值算法一跑就报错。K-means和hclust遇到缺失值都不会自动跳过而是直接报错或者返回NA。解决办法很简单在用scale函数之前先做一次完整数据检查# 查看每一列的缺失值数量 colSums(is.na(data))如果发现某些列缺失值特别多先要想想这列特征是不是真的有必要保留。如果缺失值不多可以用中位数填充也可以直接删掉有缺失值的行。问题三标准化之后聚类结果还是不对反而更差了。这种情况多半是数据里有离群点或者某些特征是分类变量。分类变量不能直接放进欧氏距离计算里比如性别、城市这类值你需要先转成数值比如用model.matrix做独热编码或者干脆剔除后再聚类。如果离群点太严重可以先单独把离群点抽出来分析再对剩下的样本聚类。8.2 方法论层聚类结果失效的系统性原因第一特征选得不对聚类结果一定不对。我见过一个案例拿用户ID和注册日期这类完全不含分群信息的特征去做聚类结果自然是一团糟。聚类分析里特征选择比算法选择更影响最终效果。选择特征时要想清楚这些特征能不能刻画样本之间的相似性它们在业务上是否能够形成差异化描述如果特征本身区分度低换什么算法都没用。第二聚类前必须做标准化但标准化方式要因场景而异。有时候你对不同特征做标准化以后某些特征的区分度反而被稀释了比如一个特征虽然有量纲差异但本身业务含义重大你就得考虑要不要给它额外加权。基础scale函数是全自动的想要手动加权的话可以在标准化后乘一个权重系数。第三K值永远没有正确答案。不要指望任何算法给你一个上帝视角的最优K。K值的选择本质是一个业务决策统计指标只能缩小范围最终要由业务方拍板。第四聚类结果一定要用业务逻辑去验证。如果分出来的簇里某个簇的消费均值显著高于其他簇但购买频次特别低你要去确认这批用户到底是什么人是不是代购、企业采购、还是数据异常。聚类只是给你一个数据视角的切分真正的解释工作还在人。第五多算法交叉验证。我强烈建议同一个数据集上至少跑两种算法如果K-means分3簇和层次聚类分3簇的样本归属基本一致那这个聚类结构就比较稳。如果两种算法结果差异巨大说明数据本身没有清晰的簇结构再执着于调参意义不大这时候应该回去重新审视特征或者扩大样本范围。8.3 跟SPSS、Python等其他工具怎么衔接热词里经常有人搜SPSS聚类分析和Python数据分析我简单说下我的感受。用SPSS做聚类和用R做聚类算法原理是一模一样的只是操作界面上不同。SPSS好处是点菜单就能出结果对不写代码的人友好但缺点也很明显流程固定、参数控制不够细、批量处理困难。R的优势在于全程可复现、可视化能力强、可以自由调整算法细节。我自己的使用习惯是小规模数据演示用SPSS或R都行但正式项目一定会用R或Python跑因为需要留下完整的代码记录方便回溯。Python那边主要用scikit-learn的KMeans、AgglomerativeClustering、DBSCAN语法比R要更面向对象一些。如果你已经在用Python做数据处理用scikit-learn做聚类完全没问题如果你本身就在R环境里做统计分析直接用R语言就好没必要为了流行硬换Python。聚类分析这个领域你熟悉的工具就是最好的工具关键是理解算法逻辑和数据预处理思路语言只是一个外壳。这一篇聚类分析写到这里就结束了。下一篇我打算写关联规则和协同过滤在推荐系统里的应用如果大家对这次的内容有什么疑问欢迎在评论区提出来我看到了会尽量回复。
网站建设高端定制企业官网