2. 什么是 Rank Normalization?基本概念归纳总结
我记得刚踏入生物信息学这行时,最头疼的问题之一就是 批次效应。明明是同一种细胞,A 实验室测出来的表达量跟 B 实验室的完全对不上。你想想看,这数据还能用吗?
后来我接触到一个非常巧妙的方法——Rank Normalization。说白了,它就是把数据的「数值大小」变成了「排名位置」。举个最直接的例子:两名学生的考试成绩分别是 85 分和 90 分,转换成排名就是第 2 名和第 1 名。Rank Normalization 就是把这个逻辑应用到整个数据矩阵上。
2.1 排序这件事
我们聊一下最基础的概念:排序。在生物学数据中,每个样本有成千上万个特征(比如基因表达量、蛋白质丰度、代谢物浓度)。这些特征值有高有低。Rank Normalization 的第一步,就是把这些数值按照大小排个序。
- 升序排列:最小值排第 1,最大值排最后。
- 降序排列:最大值排第 1,最小值排最后。
- 秩次分配:相同数值取平均排名(这个细节后面会讲)。
2.2 什么叫 Rank Normalization?
Rank Normalization 的数学定义其实很简单。对于一个包含 n 个观测值的向量 x = [x₁, x₂, ..., xₙ],我们把它转换成秩向量 R = [R₁, R₂, ..., Rₙ]。
变换公式如下:
Rᵢ = rank(xᵢ)
其中 rank(xᵢ) 表示 xᵢ 在所有观测值中的顺序位置。
举个例子:
原始数值: [0.2, 1.5, 3.8, 2.1, 0.9]
排序后: [0.2(1), 0.9(2), 1.5(3), 2.1(4), 3.8(5)]
秩次向量: [1, 3, 5, 4, 2]
看到没?原始数值的具体大小消失了,只保留了它们之间的相对顺序。
2.3 Rank Normalization 的算法步骤
实际做起来,步骤也就三步。
- 排序:对每个样本(或每个特征)的特征值进行排序,得到排序索引。
- 分配秩次:将排序索引映射为秩次。如果有相等值,取中位秩(比如两个第1名,都取1.5)。
- 归一化(可选):将秩次缩放到一个固定范围(比如 [0, 1] 或 [-3, 3])。
2.4 深入一点:分位数标准化
你可能听说过另一个概念:Quantile Normalization(分位数标准化)。跟 Rank Normalization 有什么区别?
| 特性 | Rank Normalization | Quantile Normalization |
|---|---|---|
| 核心操作 | 只排序,不改变分布形状 | 强制所有样本相同分布 |
| 输出类型 | 秩次(整数或小数) | 映射后的标准化值(连续) |
| 适用范围 | 非参数检验、批次效应校正 | 微阵列数据、RNA-seq 中低表达基因处理 |
| 对离群值影响 | 不敏感(只看位置) | 可能会拉偏整体分布 |
| 计算复杂度 | O(n log n) | O(n log n + n*k) |
2.5 核心知识体系
下面的图展示了 Rank Normalization 在多批次质控中的位置。
2.6 为什么 Rank Normalization 能消除批次效应?
这个问题很关键。批次效应本质上是系统性的偏移——A 批次的整体表达量比 B 批次高 20%。你想想看,如果我们只看数值大小,那 A 批次的每个基因都比 B 批次高,这会造成假阳性。
但 Rank Normalization 只关心基因在各自样本内的相对排序。A 批次中表达量最高的基因,在 A 批次的排序一定是第 1 位。B 批次中表达量最高的基因,在 B 批次的排序也一定是第 1 位。这样一来,批次间的系统偏移就被消除了,只保留了生物上的相对差异。
2.7 避坑指南
- 秩次平局陷阱:我曾经在分析蛋白质组学数据时,发现很多肽段的丰度值是 0。排序后,所有值为 0 的肽段都变成了平局。正确的做法是取平均秩次,比如两个 0 值,都取 1.5。不这么做的话,下游检验的统计量会失真。
- 样本量太小不行:如果某个批次只有 3 个样本,那排序后的秩次就 1, 2, 3 三个值,信息量严重丢失。一般来说,每个批次至少要有 10 个样本才能有效使用 Rank Normalization。
- 不要用在差异表达基因筛选后:Rank Normalization 应该在 原始表达量矩阵上使用,而不是筛选后的基因集上。否则会破坏整体分布结构。
2.8 实战示例(Python)
最后,我顺手给一个 Python 的实现,方便你理解:
import numpy as np
from scipy.stats import rankdata
def rank_normalization(matrix, axis=1):
"""
对矩阵进行 Rank Normalization
matrix: numpy array, shape (n_samples, n_features)
axis: 0 表示对每个特征排序,1 表示对每个样本排序
"""
ranks = np.apply_along_axis(rankdata, axis=axis, arr=matrix)
# 可选:缩放到 [0, 1]
n = matrix.shape[axis]
ranks_normalized = (ranks - 1) / (n - 1)
return ranks_normalized
# 示例数据(3个样本,5个基因)
data = np.array([
[0.2, 1.5, 3.8, 2.1, 0.9],
[0.5, 1.2, 4.0, 2.5, 0.8],
[0.3, 1.8, 3.5, 2.0, 1.0]
])
result = rank_normalization(data, axis=1)
print(result)
# 输出类似:
# [[0. 0.75 1. 0.5 0.25]
# [0. 0.5 1. 0.75 0.25]
# [0. 0.75 0.5 0.25 1. ]]
注意看,每个样本(每行)的最小值都变成了 0,最大值变成了 1。这就是 Rank Normalization 的魔力——把不同样本的数值映射到同一个尺度上。