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 如何选择正确的距离度量?

这个问题没有标准答案。但我有个简单的判断流程:

  1. 先看数据类型:
    • 相对丰度(百分比)→ 考虑Bray-Curtis或欧氏距离
    • 绝对丰度(counts)→ 优先Bray-Curtis或曼哈顿
    • 已标准化的数据(Z-score)→ 欧氏距离稳妥
  2. 再看分析目标:
    • 想看趋势一致性 → 相关系数距离
    • 想看绝对差异 → 欧氏距离
    • 想看群落组成差异 → Bray-Curtis
  3. 别忘了尝试对比:
    • 我习惯做几个不同度量的热图对比看看
    • 如果聚类结果天差地别,说明你的数据对度量选择很敏感
    • 这时候可以用层级聚类树的可信度来辅助判断
一句话总结: 没有“最好”的距离度量,只有“最合适”的。理解每种度量的数学直觉,比死记公式重要得多。

4.5 距离度量的知识图谱

下面这张图把几种主要距离度量的关系和适用场景梳理了一下。你一看就明白。

距离度量选择图谱 距离度量 欧氏距离 曼哈顿距离 相关系数距离 关注绝对差异 同量纲数据 需先标准化 抗异常值 适用于离散数据 关注趋势一致性 时序/梯度数据 微生物组推荐:先试Bray-Curtis,再补相关系数 必要时做多度量对比验证

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.   ]]
小技巧: 如果同一批数据用不同距离度量得到截然不同的聚类结果,别急着改参数。先检查数据有没有异常值、有没有批次效应、标准化有没有做对。有时候问题不在距离度量本身,而在数据质量。