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:

  1. 做批次校正前的快速探索:mvp 的计算速度比 vst 快一个数量级。当你只想快速看一下数据概况时,用 mvp 非常高效。
  2. 处理超大规模数据:我手头有一个 10 万细胞的样本,用 vst 要跑 3 分钟,用 mvp 只需要 20 秒。时间差距很明显。
  3. 需要兼容旧代码:如果你在维护一套老项目,里面的 FindVariableFeatures 用的就是 mvp,那继续用下去也没问题。

一张图看懂 mvp 的完整流程

下面这张 SVG 图,把 mvp 的整个数据处理流程画清楚了。你看一眼,就能明白数据是怎么从原始 count 矩阵,一步步变成高变基因列表的。

mvp方法高变基因选择流程 原始count矩阵 标准化(NormalizeData) 计算均值和方差 loess拟合均值-方差曲线 计算标准化方差 实际方差/拟合方差 排序取top nfeatures 输出高变基因列表 参数:y.cutoff=0.5 nCount_RNA + log1p 转换 按基因逐行计算

实际项目中的配置建议

假设你正在做一个 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 算出来的均值-方差曲线会非常离谱。这时候,先把低质量细胞过滤掉,比调任何参数都管用。