seurat-PrepSCTFindMarkers源码解析

一、FindAllMarkers()函数报错

最近事情太多,好久没有写,断更很久,惭愧。
前几天,将服务器的seurat包升级到4.1.0版本,在跑单细胞转录组流程的时候发现在FindAllMarkers步骤报错,想细究下原因。
前面的流程是我使用SCTransform方法对单细胞数据进行标准化;
代码如下:

DefaultAssay(seurat_obj) <- "SCT" 
Idents(seurat_obj) <- "cluster"
markers_all <- FindAllMarkers(object = seurat_obj, min.pct = 0.25, logfc.threshold = 0.25, only.pos=T)

报错的截图:


提示的信息是我需要在执行FindMarkers()函数前,先运行PrepSCTFindMarkers()函数。


二、PrepSCTFindMarkers()函数的用途

去年,对于SCTransform归一化处理后的单细胞数据如何进行差异基因表达分析,我就很纠结,之前写了一篇随笔《sctransform预处理后,如何进行差异表达分析》。如今,在2022年1月14日,seurat终于对SCTransform归一化处理后如何合理进行差异表达分析,官宣了PrepSCTFindMarkers()函数。

seurat团队应该是慎之又慎,才宣布此函数,很想看看它具体做了一些什么操作。

查了下PrepSCTFindMarkers()的说明
该函数的说明文档:
https://rdrr.io/cran/Seurat/man/PrepSCTFindMarkers.html
https://cran.r-project.org/web/packages/Seurat/Seurat.pdf

函数用途:

Prepare object to run differential expression on SCT assay with multiple models

详细说明:

Given a merged object with multiple SCT models, this function uses minimum of the median UMI (calculated using the raw UMI counts) of individual objects to reverse the individual SCT regression model using minimum of median UMI as the sequencing depth covariate. The counts slot of the SCT assay is replaced with recorrected counts and the data slot is replaced with log1p of recorrected counts.

大致翻译一下:
对多样本的单细胞转录组数据整合后生成的seurat对象,提前是已经进行了SCTransform归一化处理。针对SCT模式下,进行差异表达分析,需要对seurat对象进行预处理,修正 SCT 模式下的counts矩阵和data矩阵。该函数计算每个样本的UMI中位数(使用原始 UMI 计数计算),取其最小值,然后应用于每个样本的SCT回归模型,使用UMI 中位数的最小值作为测序深度协变量。SCT模式下的counts矩阵被重新校正的counts值所取代,同时,data矩阵也被重新校正counts的 log1p 替换。

换句话说,用SCT模式的data矩阵做差异表达分析,不是很妥,需要预先用PrepSCTFindMarkers()函数校正,校正后的data矩阵,更适合做下游的差异基因表达分析。

函数用法:

PrepSCTFindMarkers(object, assay = "SCT", verbose = TRUE)

那为什么用原先的SCT模式的data做差异表达分析不行?
作者在曾在seurat的discussions部分中也给出了一段说明:

We had anticipated extending Seurat to actively support DE using the pearson residuals of sctransform, but have decided not to do so. In some cases, Pearson residuals may not be directly comparable across different datasets, particularly if there are batch effects that are unrelated to sequencing depth. While it is possible to correct these differences using the SCTransform-based integration workflow for the purposes of visualization/clustering/etc., we do not recommend running differential expression directly on Pearson residuals. Instead, we recommend running DE on the standard RNA assay.

我们曾期望扩展Seurat的功能以支持使用sctransform的pearson残差进行DE分析,但已决定不这样做。在某些情况下,皮尔逊残差可能无法在不同的数据集之间直接进行比较,尤其是当存在与测序深度无关的批次效应时。虽然为了可视化/聚类等目的,可以使用基于SCTransform的集成工作流来纠正这些差异,但我们不建议直接在Pearson残差上运行差分表达式。相反,我们建议在标准RNA分析中运行DE分析。

我们可以这样理解,Satija团队本来期望用sctransform的皮尔逊残差进行DE分析,但是他们发现,Pearson残差可能无法在不同数据集之间直接进行比较,决定不这么做。基于SCTransform 的分析工作流可以剔除实验差异以实现可基因可视化和聚类等功能。但是他们建议在标准RNA assay中运行DE分析。

给出的案例是:
针对SCTransform归一化流程,在FindMarkers之前需要运行PrepSCTFindMarkers()函数。

data("pbmc_small") 
pbmc_small1 <- SCTransform(object = pbmc_small, variable.features.n = 20) 
pbmc_small2 <- SCTransform(object = pbmc_small, variable.features.n = 20) 
pbmc_merged <- merge(x = pbmc_small1, y = pbmc_small2) 
pbmc_merged <- PrepSCTFindMarkers(object = pbmc_merged) 
markers <- FindMarkers( 
  object = pbmc_merged, 
  ident.1 = "0", 
  ident.2 = "1", 
  assay = "SCT" 
) 
pbmc_subset <- subset(pbmc_merged, idents = c("0", "1")) 
markers_subset <- FindMarkers( 
  object = pbmc_subset, 
  ident.1 = "0", 
  ident.2 = "1", 
  assay = "SCT", 
  recorrect_umi = FALSE 
)

三、PrepSCTFindMarkers()函数解析

源码位置:https://github.com/satijalab/seurat/blob/f1b2593ea72f2e6d6b16470dc7b9e9b645179923/R/differential_expression.R

library(Seurat)
data("pbmc_small") 
pbmc_small1 <- SCTransform(object = pbmc_small, variable.features.n = 20) 
pbmc_small2 <- SCTransform(object = pbmc_small, variable.features.n = 20) 
pbmc_merged <- merge(x = pbmc_small1, y = pbmc_small2)
pbmc_merged
# An object of class Seurat 
# 450 features across 160 samples within 2 assays 
# Active assay: SCT (220 features, 0 variable features)
# 1 other assay present: RNA

下面我们进入到PrepSCTFindMarkers()函数中,代码较长,我们分解一下:
step1:判断seurat对象的SCT模型的数目,如果只有一个样本,跳出此函数,不需要进行SCT模型的counts和data数据校正;
在我们的案例中,有2个样本,即两个SCR模型。

if (length(x = levels(x = object[[assay]])) == 1) {
    if (verbose) {
      message("Only one SCT model is stored - skipping recalculating corrected counts")
    }
    return(object)
  }

step2:此处调用了SCTResults函数,计算每个SCT模型的UMI中位数。先不细究该函数。

observed_median_umis <- lapply(
  X = SCTResults(object = object[[assay]], slot = "cell.attributes"),
  FUN = function(x) median(x[, "umi"])
)
observed_median_umis
# $model1
# [1] 180
# 
# $model1.1
# [1] 180

step3:计算min_median_umi

model.list <- slot(object = object[[assay]], name = "SCTModel.list")
model.list
# $model1
# An sctransform model.
# Model formula:  y ~ log_umi 
# Parameters stored for 220 features, 80 cells
# $model1.1
# An sctransform model.
# Model formula:  y ~ log_umi 
# Parameters stored for 220 features, 80 cells

median_umi.status <- lapply(X = model.list,
                            FUN = function(x) { return(tryCatch(
                              expr = slot(object = x, name = 'median_umi'),
                              error = function(...) {return(NULL)})
                            )})
# median_umi.status
# $model1
# [1] 180
# 
# $model1.1
# [1] 180
if (any(is.null(x = unlist(x = median_umi.status)))){
  # For old SCT objects  median_umi is set to median umi as calculated from obserbed UMIs
  slot(object = object[[assay]], name = "SCTModel.list") <- lapply(X = model.list,
                                                                   FUN = UpdateSlots)
  SCTResults(object = object[[assay]], slot = "median_umi") <- observed_median_umis
  
}
model_median_umis <- SCTResults(object = object[[assay]], slot = "median_umi")
min_median_umi <- min(unlist(x = observed_median_umis))

step4:生成校正的corrected_counts矩阵
step5:对corrected_counts取log1p,生成校正的corrected_data矩阵
时间太赶,先大致记录下,后面再补充。

你可能感兴趣的:(seurat-PrepSCTFindMarkers源码解析)