### 3、聚类分析基础:层次聚类与划分聚类(K-means) 做物种丰度热图,最核心的任务之一就是**给样本和物种排个有效的顺序**。排对了,热图才能一眼看出规律。排错了,热图就是一团乱麻。怎么排?靠聚类。 我早期做微生物组项目时,试过不聚类直接按丰度排序——出来的热图简直没法看,高丰度物种扎堆,样本没有区分度。从那以后我就记住了:热图一定要先聚类,再画图。说白了,聚类就是给数据“分帮派”,让相似的样本靠一起,相似的物种也靠一起。 本章我们就聚焦两类最常用的聚类方法:**层次聚类**(Hierarchical Clustering)和 **K-means 聚类**(划分聚类)。它们背后的逻辑完全不同,但都能帮我们把热图排得更合理。 聚类分析方法选择流程图 开始聚类分析 层次聚类 凝聚式、分裂式 K-means 聚类 预先指定聚类数 K 计算距离矩阵 欧氏/相关/曼哈顿 选择连接方式 ward/average/complete 选择 K 值 肘部法则/轮廓系数 输出排序 → 绘制热图 实际应用中 物种丰度热图 默认使用层次聚类 #### 层次聚类:一棵树看懂数据结构 层次聚类是我在热图分析中最常用的方法。它的核心思想很简单:**计算每两个样本(或物种)之间的距离,然后按距离最近的原则逐步合并,最终形成一棵树**。这棵树就叫树状图(dendrogram)。 整个过程就像“打牌时按牌面相近程度邻居归堆”——你先找到最像的两张牌放一起,然后继续找下一个最像的,一层层往上,最后所有牌都挂在一棵树上。 ##### 关键步骤 1. **计算距离矩阵**:确定样本间的“不相似度”。常用的有欧氏距离、1-皮尔逊相关系数、曼哈顿距离。 2. **选择连接方式**:决定两个集群之间的距离怎么算。常见的有: - Ward 法(最小化组内方差) - 平均连接(average) - 完全连接(complete) 3. **迭代合并**:每次合并最近的两个集群,直到所有样本聚成一棵大树。 4. **从树中提取分组**:在某个高度“切一刀”,就得到了若干个聚类。 我在项目中遇到过最坑的一个问题:**距离矩阵的尺度没统一**。比如有的样本丰度量级差100倍,如果不先标准化,距离会被数量大的基因完全主导。后来我习惯先用 `StandardScaler` 或 `MinMaxScaler` 做行归一化,再算距离。 **避坑指南**:我曾经用默认的欧氏距离+完全连接做了一组肠道菌群数据,结果树状图乱成一团,仔细排查发现欧氏距离对丰度高的菌种极度敏感,换成1-皮尔逊相关距离后立马清晰了。**你想想看,微生物组数据是成分数据,其实更适合基于相关系数的距离**。 ##### 如何用 Python 实现层次聚类 下面是实际项目中常用的代码片段,我会配合 `scipy.cluster.hierarchy` 和 `seaborn.clustermap` 来用。
import numpy as np
import pandas as pd
from scipy.cluster.hierarchy import linkage, dendrogram
from scipy.spatial.distance import pdist
import matplotlib.pyplot as plt

# 假设 otu_table 是样本×物种的丰度矩阵 (样本行, 物种列)
data = pd.read_csv('otu_relative_abundance.csv', index_col=0)

# 对样本(行)进行层次聚类
# 先转置?不,linkage 默认聚通行,所以我们直接对样本聚类
row_linkage = linkage(data, method='ward', metric='euclidean')

# 画树状图
fig, ax = plt.subplots(figsize=(8, 4))
dendrogram(row_linkage, labels=data.index, ax=ax, leaf_rotation=90)
plt.title('Sample Dendrogram (Ward, Euclidean)')
plt.ylabel('Distance')
plt.tight_layout()
plt.show()
嗯,这里要注意:`seaborn.clustermap` 内部其实也调用了 `scipy.cluster.hierarchy`,只是帮你自动排好了。我们手动做一遍是为了理解原理。 #### K-means 聚类:快而准的“分组机器” K-means 就是另一种思路——**不建树,直接分堆**。你要先告诉算法“我想分几堆”,然后它随机选 K 个中心点,不断迭代调整,直到每个样本都被分到离它最近的中心点那一组。 为什么在热图里也用它?有时候你不需要看完整的层次关系,只需要一个明确的“分组”,比如把物种分成高、中、低丰度三组。K-means 跑起来非常快,尤其是物种数量超过 5000 时,层次聚类可能会慢得让你想砸电脑。 ##### K-means 需要你做的决定 - **K 值怎么定?** 肘部法则(elbow method)看畸变程度下降曲线,或者用轮廓系数(silhouette score)评估聚类质量。 - **初始化策略**:多次运行选最优。`k-means++` 能有效避免随机初始化的不稳定性。 - **数据归一化**:记得归一化,不然 K-means 会给大数值变量过高的权重。 有个经验分享给你:如果做物种聚类,我一般会取 K = 3~5;做样本聚类,K 通常按实验组数来设定。但这只是起点,**千万别迷信 K 值**——实际中我见过分了 10 类还合理的情况。 ##### 代码示例
from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler

# 假设 data 是样本×物种,我们对物种聚类,所以需要转置
species_data = data.T   # 变成 物种×样本

# 标准化
scaler = StandardScaler()
scaled = scaler.fit_transform(species_data)

# 跑 K-means
k = 4
km = KMeans(n_clusters=k, random_state=42, n_init='auto')
labels = km.fit_predict(scaled)

# 将聚类标签加回数据框
species_data['Cluster'] = labels
print(species_data.groupby('Cluster').size())
#### 两种方法的核心区别 | 维度 | 层次聚类 | K-means | |------|----------|---------| | 聚类过程 | 自底向上或自顶向下建树 | 迭代分配中心点 | | 是否需要预设 K | 不需要,切树时决定 | 必须预设 K | | 结果形式 | 树状图 + 分组 | 只有分组 | | 计算复杂度 | O(n³) 最坏情况 | O(n·K·I) | | 适用场景 | 样本量 < 5000,想看层次关系 | 数据量大,只需要分组 | | 能否重用 | 无法增量更新 | 可增量训练(partial_fit) | #### 混合使用的实战技巧 我个人习惯在正式分析中**把两者结合起来**: 1. 先用层次聚类画完整的树状图,通过目视确定潜在的分组数量 K。 2. 再用 K-means 对同样的数据跑一次 K 值优化,用肘部法则验证。 3. 最后把 K-means 的标签作为热图的行/列注释条带,让读者直观看到“分几堆”。 这样做的好处是:树状图负责展示**全局结构**,K-means 标签负责提供**明确的离散分组**——热图一举两得。
最佳实践:在物种丰度热图中,行(物种)和列(样本)分别单独用层次聚类排序,但注释颜色条带可以用 K-means 的分组结果来着色。这样既有树的连续性,又有分堆的清晰性。
注意:K-means 对异常值非常敏感。如果某个物种在个别样本中异常高,它可能会被单独分为一类。检查一下聚类中心,如果某类只有一个成员,建议考虑去除异常值后重跑。
一个小技巧:如果你用层次聚类后想输出分组标签,可以用 scipy.cluster.hierarchy.fcluster 在指定高度切树。比如 fcluster(Z, t=0.5*max_d, criterion='distance')。
#### 什么时候用哪个? 我总结了一张决策清单,供你在实际项目中快速选择: 1. 样本数 < 2000,且你想看样本间的亲缘关系 → **层次聚类** 2. 样本数 > 5000,只需要知道样本属于哪个大类 → **K-means** 3. 想同时看树状图和精确分组 → **层次聚类 + K-means 标签** 4. 数据中包含大量零值(稀疏矩阵)→ 考虑用 Jaccard 或 Bray-Curtis 距离的层次聚类 好了,两种聚类方法的核心思想和用法就讲到这里。下一节我们会把这些方法真正用在热图绘制中,看看如何通过聚类结果控制热图的排序和注释条带。