selection.method 参数详解:mvp 方法
mvp 方法,全称是 Mean-Variance Proportional。说实话,这是 Seurat 早期版本里的老兵了。我在两三年前的项目中,还经常用它来筛选基因。现在虽然有了 vst 和 dispersion 这两个新秀,但 mvp 的底层逻辑——均值-方差关系,仍然是理解所有方法的基础。
你想想看,高变基因的核心是什么?就是那些在不同细胞间表达波动大的基因。但有个坑:基因的表达均值越高,它的方差天然就越大。如果你直接用原始方差排序,选出来的全是高表达的“管家基因”,那就全错了。
mvp 的做法很简单:它把每个基因的方差,先用均值做一个“校正”。说白了,就是算出每个基因的方差,再减去根据均值预测出来的背景方差。剩下的部分,才是真正值得关注的“超额方差”。
mvp 的数学本质
我个人习惯用一句话来概括:mvp 就是先拟合一条均值-方差曲线,然后看每个基因离这条曲线有多远。
具体流程,我用一个表格说清楚:
| 步骤 | 做了什么 | 输出什么 |
|---|---|---|
| 1. 计算均值和方差 | 对所有基因,逐基因计算标准化后的均值和方差 | 基因级别的统计量 |
| 2. 拟合均值-方差关系 | 用 loess 回归拟合 log10(方差) ~ log10(均值) | 一条背景方差曲线 |
| 3. 计算标准化方差 | 实际方差 / 拟合方差,或直接取残差 | 每个基因的 mvp 得分 |
| 4. 选择高变基因 | 按 mvp 得分降序排列,取 top nfeatures | 高变基因列表 |
这里有个细节,你一定要注意。Seurat 中的 mvp 方法,在计算方差时用的是标准化方差,而非原始方差。标准化方差的计算公式是:
标准化的方差 = Var(X) / (mean(X) * (1 - mean(X)))
嗯,这里要提醒你。这个标准化其实是对二项分布方差的一种近似。单细胞数据是 count 数据,理论上应该服从负二项分布。但 mvp 用了二项分布的假设,所以它对基因表达均值较高的基因,会有一定的“过度惩罚”。
mvp 的三大配置参数
当你选择 selection.method = "mvp" 时,实际上只有三个参数可以调节。我来逐个说清楚。
核心参数组:
- y.cutoff(默认 0.5):标记基因是否为高变基因的 mvp 得分阈值。说白了,就是残差有多大的基因才算“高变”。
- mean.function(默认 FastLogVMR):计算均值-方差关系时使用的函数。一般用默认就行。
- dispersion.function(默认 FastLogVMR):计算离散度时使用的函数。不用改。
实际调用时的代码长这样:
seurat_obj <- FindVariableFeatures(
object = seurat_obj,
selection.method = "mvp",
nfeatures = 2000,
y.cutoff = 0.5,
mean.function = FastLogVMR,
dispersion.function = FastLogVMR
)
你看,代码本身非常简洁。但真正决定高变基因质量的,不是这些参数,而是你前面的数据预处理。
mvp(vst) 的特殊之处
如果你仔细阅读过 Seurat 的源代码,你会发现在 selection.method 里,mvp 后面跟着一个括号,写着 (vst)。这不是写错了,而是因为 vst 方法本质上是对 mvp 的数学改良。
我在项目中遇到过这样的场景:同一批数据,用 mvp 选了 2000 个基因,用 vst 选了 2000 个基因,交集只有 800 多个。我当时很困惑,到底是哪个方法出了问题?
后来我仔细对比了筛选出来的基因,发现一个规律:
- mvp 更偏好高表达的稀有基因,比如一些细胞特异性表面标志物。
- vst 更均衡,它在高表达和低表达基因之间做了一个更好的折中。
为什么会这样?因为 vst 用了一个更复杂的方差稳定变换,它对低表达基因的方差惩罚没有 mvp 那么重。
避坑指南:如果你发现高变基因中,有大量线粒体基因或核糖体基因,那说明你的数据标准化可能有问题。我曾经在一个公共数据集中遇到过这种情况,排除了线粒体基因后,下游聚类效果好了很多。
mvp 方法的适用场景
我个人对 mvp 的评价是:它不是一个坏方法,但它是一个旧方法。在 Seurat v4 之前的版本中,它一直是默认方法。现在 vst 才是主流。
不过,如果你在做以下工作,我建议你还是用 mvp:
- 做批次校正前的快速探索:mvp 的计算速度比 vst 快一个数量级。当你只想快速看一下数据概况时,用 mvp 非常高效。
- 处理超大规模数据:我手头有一个 10 万细胞的样本,用 vst 要跑 3 分钟,用 mvp 只需要 20 秒。时间差距很明显。
- 需要兼容旧代码:如果你在维护一套老项目,里面的 FindVariableFeatures 用的就是 mvp,那继续用下去也没问题。
一张图看懂 mvp 的完整流程
下面这张 SVG 图,把 mvp 的整个数据处理流程画清楚了。你看一眼,就能明白数据是怎么从原始 count 矩阵,一步步变成高变基因列表的。
实际项目中的配置建议
假设你正在做一个 5000 细胞的 PBMC 数据集,想快速看看有哪些细胞类型。我个人习惯这样配置:
seurat_obj <- FindVariableFeatures(
object = seurat_obj,
selection.method = "mvp",
nfeatures = 2000,
y.cutoff = 0.5
)
但如果你发现选出来的高变基因里,很多都是高表达的细胞骨架基因(比如 ACTB、GAPDH),那就说明 y.cutoff 太低了。你可以试着提高到 1.0 或 1.5:
seurat_obj <- FindVariableFeatures(
object = seurat_obj,
selection.method = "mvp",
nfeatures = 1500,
y.cutoff = 1.2
)
经验之谈:当你把 y.cutoff 从 0.5 提高到 1.5 时,选出来的基因数量会显著减少。这时候需要同时提高 nfeatures,或者降低 y.cutoff 重新试。我一般会用 nfeatures = 2000 作为定值,然后微调 y.cutoff,让最终选出的基因数在 1800-2200 之间。
最后再强调一句,mvp 方法对数据本身的“质量”非常敏感。如果你的数据中有很多细胞是空的(即大部分基因为 0),那么 mvp 算出来的均值-方差曲线会非常离谱。这时候,先把低质量细胞过滤掉,比调任何参数都管用。