SCTransform:单细胞样本的标准化

Seurat从3.0版本引进了SCTransform这个函数用来对数据做标准化,并且这一个函数可以代替三个函数(NormalizeData, ScaleData, FindVariableFeatures)的运行。 且其对测序深度的校正效果要好于log标准化。(10万以内的细胞都建议使用SCT标准化)
SCTransform对测序深度的校正效果很好,也可用于矫正线粒体等因素的影响,但不能用于批次矫正。

sctransform方法学文章

官网:
https://cran.r-project.org/web/packages/sctransform/index.html
https://satijalab.org/seurat/archive/v3.0/sctransform_vignette.html

官网对这个函数的描述:A normalization method for single-cell UMI count data using a variance stabilizing transformation. The transformation is based on a negative binomial regression model with regularized parameters. As part of the same regression framework, this package also provides functions for batch correction, and data correction. See Hafemeister and Satija 2019 <doi:10.1186/s13059-019-1874-1> for more details. (这是一个用方差稳定变换对单细胞UMI count 数据标准化的方法,方差稳定变换是基于负二项回归。这个函数在对数据进行均一化的同时还可以去除线粒体红细胞等混杂因素的影响。)

1. NormalizeData, ScaleData, FindVariableFeatures和SCTransform结果的存放位置

示例数据下载:pbmc3k

#读入数据,创建seurat对象
pbmc <- Read10X('./filtered_gene_bc_matrices/hg19/')
pbmc <- CreateSeuratObject(pbmc,project = 'pbmc3k',min.cells = 3,min.features = 200)
## 质控
pbmc[["percent.mt"]] <- PercentageFeatureSet(pbmc, pattern = "^MT-")
pbmc <- subset(pbmc, subset = nFeature_RNA > 200 & nFeature_RNA < 2500 & percent.mt < 5)

常规NormalizeData, ScaleData, FindVariableFeatures操作

pbmc <- NormalizeData(pbmc)
pbmc <- FindVariableFeatures(pbmc, selection.method = "vst", nfeatures = 2000)
all.genes <- rownames(pbmc)
pbmc <- ScaleData(pbmc, features = all.genes)
View(pbmc)

这一步的结果都储存在pbmc@assays[["RNA"]]中。

再使用SCTransform进行标准化

pbmc <- SCTransform(pbmc, vars.to.regress = "percent.mt", verbose = FALSE)
View(pbmc)

再次查看pbmc的数据结构,新出现了一个pbmc@assays[["SCT"]]的槽,并将SCTransform的结果储存在中,结果中也有对应的原始矩阵,Normalize矩阵,scale后的矩阵和找到的var.features(SCTransform默认是找3000个var.features,上面的方法 默认是2000个)。
在常规分析中,使用少量的PC既能关注到关键的生物学差异,又能够不引入更多的技术差异,相当于一种保守性的做法。它会失去一些生物差异信息但是同时又在常规手段中比较安全。
但sctransform的归一化、标准化都做得不错,多输入一些PCs能提取更多的生物差异,并且兼顾不引入技术误差。sctransform认为:新增加的这1000个基因就包含了之前没有检测到的微弱的生物学差异。而且,即使使用全部的全部的基因去做下游分析,得到的结果也是和sctransform这3000个基因的结果相似

因此NormalizeData, ScaleData, FindVariableFeatures和SCTransform的结果存放在不同的slot中,不必担心进行了Normalize和scale再进行SCTransform会对数据过度处理,在后续处理数据的时候注意选择需要的矩阵即可。

SCTransform得到的结果都保存在pbmc[["SCT"]]中:
值得注意的是,SCTransform分析结果是@scale.data,@counts和@data都是用@scale.data反向计算出来的。

  • pbmc[["SCT"]]@scale.data :包含了残差数据,用作PCA的输入。这个数据不是稀疏矩阵,因此会占用大量内存。不过SCTransform函数计算的时候,为了节省内存,默认使用了return.only.var.genes = TRUE ,只对差异基因进行scale的结果。设置为FALSE相当于三步法的ScaleData(features=all.genes)。
  • 关于为什么SCT运行完基因数会减少

Christoph作了回复:sctransform的min_cells参数会忽略一部分细胞。如果想要所有细胞,调整参数即可。

  • RNA和SCT的区别

pbmc[["SCT"]]@counts :相当于细胞数据量相同情况下的值(也就是均一化了的UMI count值)。
pbmc[["SCT"]]@data:包含了上面count值的log-normalized结果,有利于后面可视化
目前可以使用pbmc[["SCT"]]@data结果进行差异分析和数据整合,实际上,官方更推荐直接使用残差值pbmc[["SCT"]]@scale.data。 但是seurat目前不支持,因此还得用@data的数据。

⚠️在运行完三步法标准化再运行SCT标准化,后续分析默认使用SCT矩阵。可以使用DefalutAssay函数可以查看目前默认用的是哪个矩阵。

2. 两种方法比较

library(VennDiagram)
library(gplots)
v1 <- pbmc@assays[['RNA']]@var.features
v2 <- pbmc@assays[['SCT']]@var.features
p <- venn.diagram(list(v1,v2), col=c("red","green"), alpha = c(0.5, 0.5),
                  category.names=c('pbmc','pbmc.sc'), filename=NULL, margin=0.15)    
grid.draw(p)

可以看到两种方法找到的高变基因还是有一些差别

3. SCTransform做了些什么

SCTransform利用了正则化负二项分布(regularized negative binomial regression)计算了技术噪音模型,得到的残差是归一化值,有正有负。正值表示:考虑到细胞群体中基因的平均表达量和细胞测序深度,某个细胞的某个基因所包含的UMIs比预测值要高。

首先提取出表达矩阵并对每个基因做简单的统计包括表达量均值,方差,以及可检测率(detection rate)。从一张均值和方差的散点图可以看到在均值较小的一定范围内,均值和方差有线性关系。均值再变大时,方差呈现过度分散(overdispersion)。

pbmc_data <- as.matrix(pbmc@assays$RNA@counts)
gene_attr <- data.frame(mean = rowMeans(pbmc_data), 
                        detection_rate = rowMeans(pbmc_data > 0), 
                        var = apply(pbmc_data, 1, var))
gene_attr$log_mean <- log10(gene_attr$mean)
gene_attr$log_var <- log10(gene_attr$var)
rownames(gene_attr) <- rownames(pbmc_data)
ggplot(gene_attr, aes(log_mean, log_var)) + geom_point(alpha = 0.3, shape = 16) + 
  geom_density_2d(size = 0.3) + geom_abline(intercept = 0, slope = 1, color = "red")

接下来我们画一张基因表达均值和基因可检测率的关系图,图中红线是假设基因表达均值符合柏松分布的期望可检测率。可以看到高表达和低表达的基因符合预期值,基因表达量局中的基因的可检测率低于预期。

x = seq(from = -3, to = 2, length.out = 1000)
poisson_model <- data.frame(log_mean = x, detection_rate = 1 - dpois(0, lambda = 10^x))
ggplot(gene_attr, aes(log_mean, detection_rate)) + geom_point(alpha = 0.3, shape = 16) + 
  geom_line(data = poisson_model, color = "red") + theme_gray(base_size = 8)

第三步来看对每个细胞,其总UMI计数和能检测到的基因数量是正相关的。

cell_attr <- data.frame(n_umi = colSums(pbmc_data), 
                        n_gene = colSums(pbmc_data > 0))
ggplot(cell_attr, aes(n_umi, n_gene)) + geom_point(alpha = 0.3, shape = 16) + geom_density_2d(size = 0.3)

综合以上观察,作者认为每个基因的表达量符合负二项分布且其在每个细胞内的表达量和该细胞的总基因表达量(total UMI)呈正相关。公式更为细节的地方包括复杂度调整(regularization)和对数联系函数(link function)。最终对每个基因在每个细胞的表达量是真实表达量和拟合后的表达预期值的差值。这就是为什么用SCTransform省略了ScaleData那一步,因为是先得到scaled data(皮尔逊残差),再去反推得到normalized data 和count data的。

我们跑一下SCTransform这个函数并看一下基因表达量的改变。我们挑选了三个基因MALAT1,RPL10和FTL。下图上半部显示矫正前,下半部显示矫正后基因表达量和测序深度的关系。可以看到经过SCTransform我们达到了目的,基因表达量不再和每个细胞的测序深度有相关性。

# run sctransform
set.seed(44)
vst_out <- sctransform::vst(pbmc_data, latent_var = c("log_umi"), return_gene_attr = TRUE, return_cell_attr = TRUE)

sctransform::plot_model(vst_out, pbmc_data, c("MALAT1", "RPL10", "FTL"), plot_residual = TRUE)

最后我们跑一下Seurat标准scale data流程,为保证可比性,我们regress out总基因表达量(total UMI)。同样我们画一下标准流程出来的残差和细胞总表达量的关系,可以看到同样的基因表达量不再和每个细胞的测序深度有相关性。SCTransform对比标准流程的优势可以看到对于像FTL这样的基因,用SCTransform后能明显看到其表达分为高和低两组细胞,而用标准流程此现象基本看不到。(pbmc3k的数据使用标准scale流程也可以分开)

pbmc <- NormalizeData(pbmc)
pbmc <- ScaleData(pbmc,vars.to.regress = c("nCount_RNA","percent.mt"), features = rownames(pbmc@assays$RNA@counts))

regular_df <- data.frame()
for(i in c("MALAT1", "RPL10", "FTL")){
  
  tmp <- data.frame(cell_log_umi = log10(pbmc@meta.data$nCount_RNA + 1),
                    scale.residual = pbmc@assays$RNA@scale.data[rownames(pbmc@assays$RNA@scale.data) == i , drop=FALSE],
                    gene = rep(i, length(log10(pbmc@meta.data$nCount_RNA + 1)))
  )
  
  regular_df <- rbind(regular_df, tmp)
  
}

ggplot(regular_df, aes(cell_log_umi, scale.residual)) + geom_point(alpha = 0.3, shape = 16) + 
  geom_density_2d(size = 0.3, colour="cadetblue1")+
  facet_wrap(.~factor(gene,levels=c("MALAT1", "RPL10", "FTL")))

参考:https://www.jianshu.com/p/44d30d3194e4

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容