4. 距离度量:欧氏距离、曼哈顿距离、相关系数
讲完数据预处理,咱们得聊聊热图的核心了——距离度量。说白了,就是怎么定义两个样本或物种“有多像”。
我刚开始做这个的时候也犯过嘀咕:不就是算个距离吗?直接用欧氏距离不就行了?结果做出来的热图,怎么看怎么不对劲。后来才明白,选错距离度量,聚类结果完全是两回事。
4.1 为什么距离度量这么重要?
热图聚类本质上是在做一件事:把相近的东西放在一起。怎么定义“相近”,全靠你选的距离函数。
不同的距离度量,会从不同的角度“看”数据。你想想看——
- 欧氏距离看的是“绝对差异”。数值大就是远,数值小就是近。
- 曼哈顿距离看的是“累积变化”。每一步的差值都算进去。
- 相关系数看的是“变化趋势”。两个物种一起涨一起跌,就算数值不同也算近。
我遇到过一位同学,用欧氏距离算微生物组的β多样性,结果把两个完全不同的群落聚到了一起。为什么?因为一个样本的丰度高一个数量级,欧氏距离直接炸了。改成交替度量后,结果就合理多了。
关键原则:
距离度量决定了聚类结果的“视角”。选之前先想清楚:你的数据是什么类型?你关注的差异是什么?
4.2 三大主流距离度量详解
4.2.1 欧氏距离(Euclidean Distance)
这是最直观的距离。就是两点之间的直线距离。
欧氏距离公式:
d(x, y) = sqrt( Σ (xi - yi)² )
在Python中的计算(scipy):
from scipy.spatial.distance import euclidean
sample_a = [10, 20, 5, 30]
sample_b = [12, 18, 8, 25]
dist = euclidean(sample_a, sample_b)
print(dist) # 输出:5.4772...
适用场景:
- 数据量纲一致时(比如都是相对丰度百分比)
- 关注绝对丰度差异时
- 数据没有极端异常值时
曾经踩过的坑:
我用欧氏距离处理过一批宏基因组数据,其中有个样本的丰度特别高(文库大小问题)。结果这个样本被单独分出来,其他所有样本被聚在一起。嗯,这其实是批次效应在作祟。以后再用欧氏距离,我一定先做标准化。
4.2.2 曼哈顿距离(Manhattan Distance)
也叫城市街区距离。想象你在网格状的城市里,只能沿街道走——不能穿楼。
曼哈顿距离公式:
d(x, y) = Σ |xi - yi|
Python计算:
from scipy.spatial.distance import cityblock
sample_a = [10, 20, 5, 30]
sample_b = [12, 18, 8, 25]
dist = cityblock(sample_a, sample_b)
print(dist) # 输出:12
对比欧氏距离:
| 特性 | 欧氏距离 | 曼哈顿距离 |
|---|---|---|
| 对异常值的敏感度 | 高(平方放大) | 低(线性累加) |
| 计算复杂度 | 略高(有平方根) | 更低(只有加减) |
| 适用数据类型 | 连续、同量纲 | 离散、存在0值多的 |
| 在微生物组中的表现 | 易受高丰度物种影响 | 更稳健 |
4.2.3 相关系数距离(Pearson Correlation Distance)
这个很有意思。它不看数值大小,只看变化趋势是不是一致。
相关系数距离公式:
d(x, y) = 1 - r(x, y)
其中 r(x, y) 是皮尔逊相关系数
Python计算:
from scipy.spatial.distance import correlation
sample_a = [10, 20, 5, 30]
sample_b = [12, 18, 8, 25]
# 注意:这里算的是距离(1 - 相关系数),不是相关系数本身
dist = correlation(sample_a, sample_b)
print(dist) # 输出:接近0表示高度正相关
我在项目里常用相关系数来处理时序数据。比如追踪某个菌群在患者治疗前后的变化——就算两个患者的丰度基数不同,只要变化趋势一致,相关系数就会把它们聚在一起。
实用建议:
如果数据经过Z-score标准化,欧氏距离和相关系数距离会产生相近的结果。因为标准化后的数据已经去掉了均值差异,剩下的就是形状差异。
4.3 其他常用距离度量
除了上面三种,微生物组分析里还有几种不得不提的度量:
- Bray-Curtis差异度:专为生态学设计,只考虑物种有无和丰度,不考虑共有0值。我个人觉得这是OTU/ASV数据的默认选择。
- Jaccard距离:只看物种的“有/无”,不管丰度。适合做快速筛查。
- UniFrac距离:结合了物种间的进化关系,算的是系统发育树上的距离。做进化分析时必用。
Bray-Curtis在Python中的计算:
from skbio.diversity.beta import bray_curtis
sample_a = [10, 20, 5, 30]
sample_b = [12, 18, 8, 25]
dist = bray_curtis(sample_a, sample_b)
print(dist) # 输出:0.110...(值在0-1之间)
4.4 如何选择正确的距离度量?
这个问题没有标准答案。但我有个简单的判断流程:
- 先看数据类型:
- 相对丰度(百分比)→ 考虑Bray-Curtis或欧氏距离
- 绝对丰度(counts)→ 优先Bray-Curtis或曼哈顿
- 已标准化的数据(Z-score)→ 欧氏距离稳妥
- 再看分析目标:
- 想看趋势一致性 → 相关系数距离
- 想看绝对差异 → 欧氏距离
- 想看群落组成差异 → Bray-Curtis
- 别忘了尝试对比:
- 我习惯做几个不同度量的热图对比看看
- 如果聚类结果天差地别,说明你的数据对度量选择很敏感
- 这时候可以用层级聚类树的可信度来辅助判断
一句话总结:
没有“最好”的距离度量,只有“最合适”的。理解每种度量的数学直觉,比死记公式重要得多。
4.5 距离度量的知识图谱
下面这张图把几种主要距离度量的关系和适用场景梳理了一下。你一看就明白。
4.6 实际应用中的注意事项
最后分享几个实操经验:
- 不要只看一种度量。 我个人习惯至少跑2-3种距离度量,对比下聚类结果。如果差异不大,说明数据稳健;如果差异大,反而能发现问题所在。
- 注意零膨胀问题。 微生物组数据里0非常多。欧氏距离对这种数据不敏感(两个0值之间距离为0,但0 vs 0.1的距离很小),而Bray-Curtis处理这种情况更好。
- 小心高丰度物种。 我曾经用欧氏距离处理一批数据,结果热图看起来全是蓝色——因为有一个高丰度物种把其他物种的差异全掩盖了。换成曼哈顿或相关系数后才看清楚细节。
- 别忘了看距离矩阵。 不要只看热图,把距离矩阵也打印出来看看。有时候你能从矩阵里直接发现异常样本或批次效应。
打印距离矩阵示例:
import numpy as np
from scipy.spatial.distance import pdist, squareform
# 原始数据矩阵:行为样本,列为物种
data = np.array([
[10, 20, 5, 30],
[12, 18, 8, 25],
[15, 22, 3, 28],
[9, 19, 6, 32]
])
# 计算欧氏距离矩阵
dist_matrix = squareform(pdist(data, metric='euclidean'))
print("距离矩阵(欧氏距离):")
print(dist_matrix)
# 输出:
# [[ 0. 5.477 7.071 3.317]
# [ 5.477 0. 5.568 6.557]
# [ 7.071 5.568 0. 8.246]
# [ 3.317 6.557 8.246 0. ]]
小技巧:
如果同一批数据用不同距离度量得到截然不同的聚类结果,别急着改参数。先检查数据有没有异常值、有没有批次效应、标准化有没有做对。有时候问题不在距离度量本身,而在数据质量。