资讯详情

R语言聚类分析实战:从K-means到DBSCAN原理与代码详解

📅 2026/9/15 16:15:35 | 华诺云谱 👁 阅读
R语言聚类分析实战:从K-means到DBSCAN原理与代码详解
这篇内容是系列笔记的第02篇。上一篇我记录了数据清洗和描述性统计的完整流程包括缺失值处理、异常值检测这些基础操作当时用的工具就是R语言加tidyverse那一套。学完描述统计之后下一步自然就是推断统计和多变量分析而聚类分析刚好是两者之间承上启下的一个模块它不需要事先知道标签却需要你对数据有足够深的理解才能用好。这篇笔记我打算把聚类分析的核心原理、R语言实现步骤、以及我在实际跑数据时踩过的坑一起整理出来给正在学数据分析的朋友一条可以照着走的路。如果你正在学R语言或者刚接触数据分析、想搞清楚“聚类到底怎么用代码实现”这篇内容应该能帮你省下不少到处翻文档的时间。我不会只贴代码会把每一步背后的判断逻辑也讲清楚——因为聚类分析这东西真正的难度不在跑通代码而在于理解算法在做什么、结果该怎么解读。1. 聚类分析的核心思路拆解1.1 聚类和分类到底有什么区别很多刚入门的同学会把聚类和分类混在一起这俩名字听着像逻辑上却是完全不同的两件事。分类是有监督学习训练数据里带着已知的类别标签模型学的是“特征到标签”的映射规则比如根据花萼长度、宽度把鸢尾花识别为setosa、versicolor或virginica。聚类则是无监督学习数据里没有任何标签算法要做的是根据样本之间的相似度把相似的样本自动归到同一组里。我打个比方。分类就像老师手里已经有了标准答案的试卷你要做的是学会答题套路聚类则是给你一堆形状各异的积木没有图纸你需要自己观察颜色、大小、形状把看起来是一类的积木摆在一起。这个区别决定了聚类分析的应用场景和结果解读方式——聚类结果没有标准答案同样的数据用不同算法、不同参数可能得到完全不同的分组而这未必是错的只是看数据的角度不同而已。1.2 聚类分析能用在哪些真实场景里聚类分析的应用范围非常广几乎任何需要“分群”需求的场景都能用上。用户分群电商平台根据购买记录、浏览行为把用户分成高价值用户、潜在用户、沉睡用户然后针对不同群体做差异化运营。图像分割把图片像素按颜色、纹理聚成几类实现简单的图像前景后景分离。异常检测聚类之后那些不属于任何一个簇、或者所在簇样本量极小的点很可能就是异常点比如信用卡欺诈交易。文本主题挖掘把文档按词频向量聚类找出相似主题的文档群。你可能发现这些场景有个共同点数据量大、变量维度高、且没有现成的标签信息。聚类分析就是用来从这种无标签数据中找到内在结构的技术。1.3 聚类分析的完整分析流程我自己在实际项目中总结了一套相对固定的流程基本每次做聚类都会按这个顺序走数据准备清洗缺失值、处理异常值把字符型变量转换成算法能处理的数值形式。特征工程这一步非常关键选哪些变量进入聚类直接决定结果是否合理。我一般会先做相关性分析把高度相关的变量剔除掉避免某个维度的信息被重复计算。标准化把所有变量缩放到同一量纲否则量级大的变量会主导距离计算。选择算法根据数据量、变量类型和业务需求在K-means、层次聚类、DBSCAN等算法里做选择。确定簇数通过肘部法则、轮廓系数等方法评估不同K值的表现。结果解读把聚类结果带回到原始数据里分析每个簇的变量均值、分布特征给每个簇“画像”。验证与迭代聚类质量是否稳定换个随机种子、换个子样本试试确认结果不是偶然。流程看起来不复杂但每一步里都有不少细节坑。我在后面几节里会针对R语言实现部分详细展开。2. 距离度量聚类算法的一切前提2.1 连续变量的距离算法聚类分析的核心操作是“算样本之间的距离”。距离的定义不同聚类结果可能南辕北辙。对连续数值型变量最常用的距离度量是欧氏距离其次是曼哈顿距离。欧氏距离两点之间的直线距离。假设有两个样本点p和q每个点有n个特征欧氏距离就是各维度差值的平方和再开根号。这是K-means和层次聚类默认使用的距离。曼哈顿距离两点在坐标系中沿坐标轴方向走过的总距离也就是各维度差值的绝对值之和。它对异常值没那么敏感在某些高维场景下反而更稳定。余弦相似度衡量两个向量方向的相似程度不关心长度大小。它在文本分析里特别常用比如判断两篇文章的用词倾向是否接近。欧氏距离的计算我举个简单例子。假设两个用户A和B在电商平台上的特征向量分别是(10, 5, 3)和(8, 6, 4)三个维度分别代表月购买次数、平均客单价百元、浏览商品数十次。欧氏距离就是sqrt((10-8)^2 (5-6)^2 (3-4)^2) sqrt(4 1 1) ≈ 2.45。这个数值越小说明两个用户的消费行为越接近。2.2 分类变量的距离算法现实数据里不可能全是数值型变量比如性别、地区、职业这些分类变量没法直接计算差值。这时候有两个处理思路哑变量编码把每个分类取值变成0/1的列。比如“地区”有北京、上海、广州三个取值就拆成三列样本属于哪个地区就在对应列填1其他填0。编码之后可以套用欧氏距离或曼哈顿距离。汉明距离直接统计两个样本在多少个变量上取值不同。适合分类变量比较多的情况。我在做用户分群的时候经常遇到混合型数据数值变量和分类变量同时存在。常规做法是把分类变量哑变量化之后合在一起算距离但这种做法有个问题哑变量会稀释数值变量在距离计算中的权重尤其当分类变量取值非常多的时候。更稳妥的做法是分类型分别计算距离最后加权合并或者直接用支持混合数据类型的距离函数比如Gower距离。R语言里的daisy()函数可以直接计算Gower距离后续配合层次聚类使用很方便。2.3 为什么数据标准化这么重要关于标准化我见过太多人直接忽略这一步然后聚类结果乱七八糟还找不到原因。我拿一个实际例子说明。假设一个数据集有两个特征年龄20-60和年收入5万-200万。如果不做任何处理直接算欧氏距离年龄的差距在收入面前几乎可以忽略不计。比如一个25岁年入5万的用户和另一个60岁年入180万的用户年龄差35收入差175万欧氏距离基本由收入决定年龄维度相当于废了。这样的聚类结果显然不符合预期。标准化的方法有好几种最常用的是z-score标准化每个值减去均值、除以标准差转换后的数据均值为0、标准差为1。R语言里用scale()函数一行代码就能搞定。还有min-max归一化把数据缩放到0-1区间适合数据分布比较均匀的情况。我个人的习惯是做聚类之前优先用z-score因为聚类算法通常假设数据大致符合对称分布z-score对偏态分布的包容性比min-max好一些。注意标准化要在切分训练集/测试集之前做并且要统一使用训练集的均值和标准差来转换测试集。虽然聚类一般没有严格的训练测试划分但如果你打算用聚类结果做预测建模这个顺序必须注意。3. R语言实操三种主流聚类算法对比实现3.1 准备环境和数据集我这次演示用的数据集是R语言内置的iris鸢尾花数据集150个样本4个数值特征花萼长宽、花瓣长宽按物种分为3类。做聚类的时候我会把标签列去掉假装不知道物种看看算法能不能通过特征把数据分成合理的组。步骤很简单先加载数据看看结构然后做标准化。# 加载内置数据集 data(iris) head(iris) str(iris) # 去掉标签列只保留数值特征 iris_features - iris[, 1:4] # z-score标准化 iris_scaled - scale(iris_features) # 确认标准化结果 summary(iris_scaled)标准化之后每个特征列也就是每列的均值会接近0标准差接近1。这一步是后面所有聚类算法的基础别偷懒跳过。3.2 K-means聚类最常用的快速方法K-means的原理不复杂就是把数据分成K个簇使得每个样本到所属簇中心的距离平方和最小。算法流程是随机初始化K个中心点把每个样本分配到离它最近的中心点所在的簇然后重新计算每个簇的中心点重复这个过程直到中心点不再变化或达到最大迭代次数。R语言里K-means聚类用kmeans()函数核心参数就三个x数据、centers簇数K、nstart随机初始化的次数多次运行取最优避免陷入局部最优解。# 设定聚类数为3随机初始化25次取SSE最小的一次结果 set.seed(123) # 固定随机种子确保结果可复现 km_result - kmeans(iris_scaled, centers 3, nstart 25) # 查看聚类结果 km_result$cluster # 每个样本的簇编号 km_result$centers # 每簇的中心点标准化后的值 km_result$tot.withinss # 簇内总平方和衡量聚类紧凑程度运行之后把聚类结果和真实的鸢尾花种类做个对照看看聚类准确率怎么样。# 交叉表聚类结果 vs 真实标签 table(km_result$cluster, iris$Species)如果聚类效果好你会发现每个簇基本对应一个物种。这个对照表是检验聚类效果最直接的手段——虽然实际业务里没有真实标签可以对照但在学习阶段这是很好的反馈。K-means有两个明显的短板。一是必须预先指定K值选错K聚类质量会很差二是对异常值和噪声敏感因为计算均值时异常点会把中心点往自己的方向拉偏。所以K-means适合数据分布比较均匀、没有太多离群点的场景。3.3 层次聚类不需要预先指定K层次聚类和K-means的思路完全不同它不要求你提前指定簇数而是构建一棵树状图你可以根据树状图任意选择在哪一层切开得到不同粒度的聚类结果。层次聚类分两种方向自底向上凝聚法每个样本先自成一簇然后逐步合并距离最近的两个簇直到所有样本合并成一整簇。这是最常用的方式。自顶向下分裂法所有样本一开始在一整簇里然后逐步分裂直到每个样本自成一簇。R语言里层次聚类的标准流程是先用dist()算距离矩阵再用hclust()做聚类最后用plot()画树状图。# 计算欧氏距离矩阵 dist_matrix - dist(iris_scaled, method euclidean) # 层次聚类使用ward.D2连接法最小化簇内方差 hc_result - hclust(dist_matrix, method ward.D2) # 画树状图 plot(hc_result, labels FALSE, main 层次聚类树状图) # 在高度4处切割得到3个簇 hc_clusters - cutree(hc_result, k 3) # 与真实标签对照 table(hc_clusters, iris$Species)hclust()里还有个关键参数是method也就是簇间距离的计算方式。我常用的有这几个ward.D2最小化合并后簇内的方差增量倾向于生成紧凑、大小相近的簇效果通常不错。complete取两个簇之间距离最远的两个点的距离生成的簇比较分散。average取两个簇所有点对距离的平均值介于单连接和全连接之间。single取两个簇之间最近的两个点的距离容易产生“链条效应”把不相干的点串在一起一般不太推荐。我的建议是默认先试ward.D2如果效果不理想再对比其他方法。层次聚类不用预先指定K这是它比K-means灵活的地方但它的缺点也很明显数据集一大距离矩阵就是n×n的大小内存和计算量都会爆炸。我一般只在样本量几千以内才用层次聚类。3.4 DBSCAN处理任意形状与噪声点K-means和层次聚类都偏向于发现“球形”的簇遇到不规则形状的数据就抓瞎了。DBSCAN基于密度的聚类算法则完全不同它不是靠距离中心点来判断归属而是通过“密度相连”来聚类的。DBSCAN有两个关键参数eps邻居半径两个点距离小于eps才被认为是邻居。minPts一个点半径为eps的邻域内至少要有多少个点才算核心点。算法从任意一个点出发如果它周围eps范围内的点数大于等于minPts它就是一个核心点算法会从这个点出发把密度相连的点全部归入同一个簇。那些不属于任何簇的点就被标记为噪声点。R语言里用dbscan包来实现# 安装并加载dbscan包 # install.packages(dbscan) library(dbscan) # 运行DBSCAN聚类 db_result - dbscan(iris_scaled, eps 0.5, minPts 5) # 查看聚类结果 table(db_result$cluster, iris$Species)DBSCAN最诱人的优势是不需要指定簇数能发现任意形状的簇还能自动识别噪声点。但它对eps参数非常敏感设小了大量点变成噪声设大了所有点合并成一个簇需要反复调参试错。而且如果数据集的密度差异很大一个全局的eps值很难同时适应所有区域这时候DBSCAN的表现就会很差。三种算法的核心特点我整理了一个表方便你选型时参考。算法是否需要指定K适用数据形状对噪声的处理适用数据量K-means需要球形簇敏感噪声会拉偏中心大样本也可以层次聚类不需要较灵活影响较小几千以内DBSCAN不需要任意形状自动识别并排除中等样本量选择哪种算法本质上是看你的数据长什么样、业务上关心什么。数据是标准的球形分布、样本量大优先考虑K-means样本量不大、想探索数据结构层次聚类更好数据形状不规则、有明显噪声DBSCAN合适。4. 聚类数K怎么确定4.1 肘部法则K-means需要预先指定K值这是很多人最头疼的问题。K选少了信息丢失严重不同类别的样本糊在一起K选多了原本该是一组的样本被硬拆成好几块也不利于业务解释。肘部法则的思路很简单计算不同K值下簇内误差平方和SSE也就是tot.withinss然后以K为横轴、SSE为纵轴画折线图。SSE会随着K增大单调递减但下降速度会越来越慢。折线图里那个“下降速度突然变缓”的拐点像手肘一样的那个位置就是最合适的K值。# 尝试1到10个簇记录每个K对应的总SSE sse - numeric(10) for (k in 1:10) { set.seed(123) km_tmp - kmeans(iris_scaled, centers k, nstart 25) sse[k] - km_tmp$tot.withinss } # 画肘部图 plot(1:10, sse, type b, xlab 聚类数K, ylab 簇内总平方和SSE)在iris数据上你大概率能看到K3处出现拐点之后SSE下降速度明显变缓。这就说明3个簇是合理的。不过肘部法则也有判断困难的时候——有些数据的折线图非常平滑没有明显的拐点这时就要结合业务经验和轮廓系数来做判断。4.2 轮廓系数轮廓系数给了我们一个量化指标来判断聚类质量。对每个样本计算它到自己簇内其他样本的平均距离a(i)再计算它到最近的其他簇所有样本的平均距离b(i)。单个样本的轮廓系数是(b(i) - a(i)) / max(a(i), b(i))取值在-1到1之间。全部样本的轮廓系数取平均就是整体的轮廓系数。轮廓系数越接近1说明样本离自己簇的距离明显小于离其他簇的距离聚类效果好接近0说明样本在簇的边界上模棱两可负数说明样本可能被分错了簇。R语言里可以用cluster包的silhouette()函数计算library(cluster) # 计算K3时的轮廓系数 sil_result - silhouette(km_result$cluster, dist(iris_scaled)) summary(sil_result) # 尝试多个K值比较平均轮廓系数 avg_sil - numeric(10) for (k in 2:10) { set.seed(123) km_tmp - kmeans(iris_scaled, centers k, nstart 25) sil_tmp - silhouette(km_tmp$cluster, dist(iris_scaled)) avg_sil[k] - mean(sil_tmp[, 3]) } # 哪个K平均轮廓系数最高 which.max(avg_sil)我实际使用下来的体会是肘部法则适合快速筛选候选K值轮廓系数适合在候选K里做最后决策。两个方法结合用比只依赖其中一个靠谱得多。5. 聚类结果的可视化与业务解读5.1 聚类结果的可视化方法跑完聚类只是第一步把结果画出来看才是真正理解数据的开始。但聚类数据通常是多维的没法直接画在二维平面里所以需要降维。最常用的做法是主成分分析PCA降维把原始特征压缩成两个主成分然后用散点图展示聚类结果。R语言实操代码如下# PCA降维到2维 pca_result - prcomp(iris_scaled, center TRUE, scale. TRUE) pca_scores - as.data.frame(pca_result$x[, 1:2]) # 把聚类结果加到降维后的数据框里 pca_scores$cluster - as.factor(km_result$cluster) # 用ggplot2画散点图 library(ggplot2) ggplot(pca_scores, aes(x PC1, y PC2, color cluster)) geom_point(size 2, alpha 0.8) labs(title K-means聚类结果PCA降维展示) theme_minimal()画完之后你会发现不同簇在散点图上有明显的分群趋势一眼就能看出聚类效果好不好。不过要注意PCA降维会损失一部分信息两个簇在二维图上完全重叠不代表聚类失败可能只是高维空间的信息被压缩掉了。5.2 聚类画像与业务解读聚类分析价值最大的环节其实是聚类画像。算法告诉你“这堆样本是一组”但“这一组样本到底有什么特征、该怎么命名、怎么运营”得靠自己分析。分析方法是把聚类结果和原始变量交叉看计算每个簇在各变量上的均值对比不同簇之间的差异。R语言里用aggregate()函数就行# 把标准化前的原始特征带回来按簇分组算各变量均值 iris_with_cluster - data.frame(iris_features, cluster km_result$cluster) # 按簇汇总各变量均值 aggregate(. ~ cluster, data iris_with_cluster, FUN mean)输出结果会显示每个簇的平均花萼长度、平均花瓣宽度等。我拿iris举例子假设第1簇的花瓣长度均值是1.5厘米左右实际根据你的聚类结果可能不同第2簇是4.2厘米第3簇是5.8厘米那么你就可以给这三个簇起名字“短花瓣品种组”“中等花瓣品种组”“长花瓣品种组”。在真实业务里也是同一套逻辑——把数值差异翻译成业务标签比如“高活跃低客单用户”“低活跃高客单用户”之类。我在实际运营场景中做用户分群时还会叠加一个步骤把各簇在关键指标上的均值和整体平均水平做对比标记出高于或低于整体20%以上的变量。这个简单的“显著特征识别”能帮你快速找到每个簇的差异化标签比单看均值表格高效得多。6. 常见问题与排查技巧实录6.1 常见问题速查表我在学习和实际做聚类分析的过程中积累了一些问题和对应的解决方案整理成一张表供你对照查阅。常见问题可能原因解决方案聚类结果每次跑都不一样随机初始化导致局部最优解不同设置随机种子set.seed()或调大nstart参数所有样本几乎聚成一类数据量表级差异太大某个变量主导距离检查是否做了标准化确认各维度方差是否接近某个簇只有一个样本K设置过大或者存在明显异常值减小K值或先用DBSCAN识别噪声点轮廓系数为负样本被分配到错误的簇调整K值尝试更换距离度量或算法变量很多但聚类效果很差维度灾难噪声变量干扰距离计算先做特征筛选或PCA降维再执行聚类大规模数据跑K-means极其缓慢默认迭代次数内反复更新收敛慢调大iter.max或者先抽样做初步聚类第一行那个问题最常被初学者问。K-means的初始中心点是随机选择的不同的初始点可能收敛到不同的局部最优解。固定随机种子能让结果可复现便于沟通和调试调大nstart则能从多个初始点中选择最优解。6.2 实操心得与避坑建议最后分享几个我在真实项目中总结出来的经验这些在教科书里不太会写但实际用起来非常关键。第一聚类之前一定要做变量筛选。我刚开始做用户分群的时候把所有能拿到的变量一股脑丢进模型结果跑出来的聚类结果完全没法解释。后来才发现有些变量和业务目标根本不相关它们的噪声把真正有区分度的信号给淹没了。现在我做聚类之前会先画相关性矩阵、做单变量分布检查把明显不影响业务决策的变量直接剔除掉剩下的核心特征再进模型。特征不贵多贵在有效。第二不要盲目相信轮廓系数。轮廓系数是衡量聚类“紧凑度”和“分离度”的统计指标但业务上最有意义的聚类未必是统计上最完美的聚类。我遇到过轮廓系数0.7的聚类结果分出来的组完全没法落地运营也见过轮廓系数只有0.4的结果反而能清晰地划分出高价值用户、潜力用户和流失风险用户。聚类分析的最终目标不是拿高分而是分出来的组在业务上可以被理解和行动。第三聚类结果的稳定性值得专门验证。有一次我用K-means做了一个分群模型换了个随机种子结果变化很大同一个用户在不同种子下被分到完全不同的组。后来发现是某个关键特征上有几个离群点严重影响了中心点的位置。从那以后我都会做稳定性检查跑多个随机种子看样本簇归属的一致性有多高或者对数据做自助采样重复聚类检查簇中心是否稳定。如果结果波动很大就要回头优化特征或调整算法而不是直接拿第一次的结果上生产。第四不同聚类算法合并使用效果往往更好。我现在的习惯是用层次聚类的树状图做“探路”快速了解数据结构形成初步假设再用K-means做正式聚类因为它在大量样本上更快、更稳定最后用DBSCAN做交叉验证看看K-means的结果里有没有明显不该聚在一起的样本。三种算法互相印证得出的结论比单一算法可靠得多。7. 从聚类到后续分析学习路径的衔接聚类分析并不是数据分析的终点。拿iris数据来说聚类之后你知道了样本可以被分成几个天然组下一步完全可以把“簇编号”当作一个新的标签列喂给决策树、随机森林、逻辑回归等有监督模型做分类预测。这就是“半监督学习”的雏形——先用无监督方法发现数据中的结构再把这个结构作为监督学习的训练标签。我在实际项目里用过一条不错的技术路线对一批没有标签的客户数据做K-means分群得到三个用户群然后给每个群贴上类别标签训练一个分类模型用于对新客户做实时分群预测。这个思路是通用的做完这篇笔记里的聚类实操之后你可以试着走一遍这个流程会发现自己对数据分析的理解又深了一层。后面我打算写一篇聚类分析的拓展篇专门讲高维数据下的聚类优化、Gower距离在混合型数据里的应用以及如何用聚类结果驱动业务决策。先把这篇笔记里的代码跑通、把基础打牢比什么都强。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。