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 对异常值非常敏感。如果某个物种在个别样本中异常高,它可能会被单独分为一类。检查一下聚类中心,如果某类只有一个成员,建议考虑去除异常值后重跑。
一个小技巧:如果你用层次聚类后想输出分组标签,可以用
#### 什么时候用哪个?
我总结了一张决策清单,供你在实际项目中快速选择:
1. 样本数 < 2000,且你想看样本间的亲缘关系 → **层次聚类**
2. 样本数 > 5000,只需要知道样本属于哪个大类 → **K-means**
3. 想同时看树状图和精确分组 → **层次聚类 + K-means 标签**
4. 数据中包含大量零值(稀疏矩阵)→ 考虑用 Jaccard 或 Bray-Curtis 距离的层次聚类
好了,两种聚类方法的核心思想和用法就讲到这里。下一节我们会把这些方法真正用在热图绘制中,看看如何通过聚类结果控制热图的排序和注释条带。
scipy.cluster.hierarchy.fcluster 在指定高度切树。比如 fcluster(Z, t=0.5*max_d, criterion='distance')。