单细胞分析——如何确定合适的分辨率(resolution)

水滴石穿,非一日之功

本期内容同时发布于单细胞天地微信公众号。

在进行单细胞分析时,非常重要的一步就是聚类分群,而在这个过程当中经常令人困惑的是如何选择一个合适的分辨率(resolution),因为分辨率设置过大或过小都不利于我们后续的分析:设置过大会导致分群“过度”,理论上来讲每个细胞都是不同的,但这样过于细化的初步分群显然是没有意义的;相反设置过小会导致我们无法发现一些对我们可能比较重要的细胞亚群,这也丧失了单细胞数据本身的意义。

在这里分享3个可以供大家参考的单细胞数据降维聚类分群的resolution参数选定评估方法:

  • clustree

  • marker gene AUC值

  • ROGUE

上游分析

本次分析所使用的示例数据集为GSE197778,包含四个样本,首先使用Harmony进行批次效应的去除:

library(Seurat)
library(harmony)
library(dplyr)
library(ROGUE)

PATH = 'GSE197778'
samples = list.files(path = PATH)

sce_list = lapply(X = samples, FUN = function(sample){
  sce <- CreateSeuratObject(counts = Read10X(data.dir = file.path(PATH, sample)), 
                            project = sample,
                            min.cells = 10, 
                            min.features = 10)
  sce[['percent.mt']] <- PercentageFeatureSet(sce, pattern = '^MT-')
  return(sce)
})
#data qc
sce_list[[1]] <- subset(sce_list[[1]], subset = nFeature_RNA > 500 & nFeature_RNA < 5000)
sce_list[[2]] <- subset(sce_list[[2]], subset = nFeature_RNA > 500 & nFeature_RNA < 3000)
sce_list[[3]] <- subset(sce_list[[3]], subset = nFeature_RNA > 500 & nFeature_RNA < 3000)
sce_list[[4]] <- subset(sce_list[[4]], subset = nFeature_RNA > 500 & nFeature_RNA < 3000)
#merge sample
sce_all <- merge(x = sce_list[[1]], 
                 y = c(sce_list[[2]], sce_list[[3]], sce_list[[4]]),
                 add.cell.ids = samples)
sce_all <- subset(sce_all, subset = percent.mt < 10)
sce_all <- sce_all %>% 
  NormalizeData() %>% 
  FindVariableFeatures() %>% 
  ScaleData()
sce_all <- RunPCA(sce_all, features = VariableFeatures(sce_all), npcs = 30)
#run harmony
sce_all <- RunHarmony(sce_all, group.by.vars = 'orig.ident')

sce_all <- sce_all %>% 
  RunUMAP(reduction = "harmony", dims = 1:20) %>% 
  FindNeighbors(reduction = "harmony", dims = 1:20) 

seq <- seq(0.1, 1, by = 0.1)
for(res in seq){
  sce_all <- FindClusters(sce_all, resolution = res)
}

clustree

clustree通过可视化不同分辨率下不同cluster之间的交互关系来帮助我们选择合适的分辨率来进行下游分析。

library(clustree)
library(patchwork)
p1 <- clustree(sce_all, prefix = 'RNA_snn_res.') + coord_flip()
p2 <- DimPlot(sce_all, group.by = 'RNA_snn_res.0.3', label = T)
p1 + p2 + plot_layout(widths = c(3, 1))
Fig1.jpg

通过这个图我们会发现当resolution大于0.3后不同cluster在不同的分辨率下会存在越来越多的相互交织,这暗示着我们可能存在分群过度的情况了,所以在这里我们可以选择0.3作为初步的分群resolution来进行后面的分析。

marker gene AUC值

这个方法来自于今年6月发表于Nature上的一篇文章:Single-nucleus profiling of human dilated and hypertrophic cardiomyopathy。在其Method部分提到了他们确定resolution的方法:Then, Leiden resolution was increased from 0.05 to 1.0 in increments of 0.05 until a cluster emerged with no genes having area under the receiver operating characteristic curve (AUC) > 0.60 in predicting that class.

这个AUC值使用FindAllMarkers()函数就能实现,只需要设置test.use = ‘roc’即可,其实原理也很简单,主要是通过ROC分析来对marker基因的表达特异性进行评估,也就是将marker基因作为分类器来对细胞进行分类,用AUC值来评估分类的好坏,显然AUC值越接近于1或0就表示这个基因有非常好的细胞表达特异性,相反,如果鉴定出来的marker基因AUC值接近0.5就表明这个marker基因没有很好的细胞分类能力,这也提示我们这两个细胞亚群之间可能并没有非常显著的生物学差异,我们可能分群“分过度”了。

根据文章的描述,分群“恰到好处”的标准是,增加resolution直至出现一个细胞亚群,其所有marker基因的AUC值均低于0.6,我们就认为resolution再增加就会出现“过度分群”的状况。我们看一下不同分辨率下每个细胞亚群marker基因的AUC值情况:

data("pbmc3k.final")

seq <- seq(0.1, 1.5, by = 0.1)
for (res in seq){
  pbmc3k.final <- FindClusters(pbmc3k.final, resolution = res)
}
#res = 0.5
pbmc3k.final %>% 
  SetIdent(value = 'RNA_snn_res.0.5') %>% 
  FindAllMarkers(test.use = 'roc') %>% 
  filter(myAUC > 0.6) %>% 
  count(cluster, name = 'number')
##   cluster number
## 1       0     45
## 2       1      6
## 3       2     87
## 4       3     31
## 5       4     19
## 6       5    147
## 7       6     62
## 8       7    111
## 9       8    140
#res = 1.0
pbmc3k.final %>% 
  SetIdent(value = 'RNA_snn_res.1') %>% 
  FindAllMarkers(test.use = 'roc') %>% 
  filter(myAUC > 0.6) %>% 
  count(cluster, name = 'number')
##    cluster number
## 1        0     49
## 2        1      6
## 3        2     31
## 4        3     20
## 5        4    117
## 6        5     49
## 7        6    147
## 8        7     68
## 9        8      1
## 10       9    111
## 11      10    140
#res = 1.5
pbmc3k.final %>% 
  SetIdent(value = 'RNA_snn_res.1.5') %>% 
  FindAllMarkers(test.use = 'roc') %>% 
  filter(myAUC > 0.6) %>% 
  count(cluster, name = 'number')
##    cluster number
## 1        0      6
## 2        1     38
## 3        2     31
## 4        3     19
## 5        4    117
## 6        5     35
## 7        6     49
## 8        7    147
## 9        8      2
## 10       9     68
## 11      10    111
## 12      11    140

ROGUE

这个方法是由张泽民老师团队开发的,一个很好的、单纯的细胞亚群应该是其中的细胞基因表达具有相似的水平,没有具有显著差异的差异表达基因,ROGUE使用一个基于熵的方法来对一个细胞类群的“纯度”来进行评估。一个细胞类群的ROGUE值表征了该细胞类群的“纯度”:越接近于1表示细胞类群越“纯”,具体细节可以去看张泽民老师的文章:An entropy-based metric for assessing the purity of single cell populations。

首先使用ROGUE内置数据集进行分析:

library(ROGUE)
# inner data of package
expr <- readRDS('DC.rds')
meta <- readRDS('info.rds')

# filter low quality data
expr <- matr.filter(expr, min.cells = 10, min.genes = 10)

p1 <- rogue(expr, labels = meta$ct, samples = meta$Patient, 
            platform = 'UMI', span = 0.6) %>% 
      rogue.boxplot() + ggtitle('4 clusters')
p2 <- rogue(expr, labels = meta$`Major cell type`, samples = meta$Patient, 
            platform = 'UMI', span = 0.6) %>% 
      rogue.boxplot() + ggtitle('2 clusters')
p1 + p2
Fig2.jpg

可以看到这里的分群在不同的样本中都具有比较好的均一性。

下面用我们自己的GSE197778数据集进行测试:

sce_sub <- sce_all %>% subset(., downsample = 100)
expr <- GetAssayData(sce_sub, slot = 'counts') %>% as.matrix()
meta <- sce_sub@meta.data

rogue(expr = expr, 
      labels = meta$RNA_snn_res.0.3, 
      samples = meta$orig.ident, 
      platform = 'UMI',
      span = 0.6) %>% rogue.boxplot()
Fig3.jpg

最后,我们就可以利用细胞类型的基因marker对细胞进行注释啦,最终可以得到下面的结果:

sce_all <- SetIdent(object = sce_all, value = 'RNA_snn_res.0.3')
DotPlot(object = sce_all,
        features = c('PTPRC', 'CD3D', #T cell
                     'MS4A1', 'CD79A', #B cell
                     'JCHAIN', 'IGHG1', #plasma cell
                     'LYZ', 'CD68', 'FCGR3A', 'CD14', #myeloid cell
                     'EPCAM', 'CDH1', 'KRT19', #epithelial cell
                     'PECAM1', 'VWF', #endothelial cell
                     'COL1A1', 'COL1A2', 'DCN' #fibroblast cell
                     )) + 
  coord_flip()
#add cell type
#T cell
T_cluster <- c(0, 1, 4, 8, 14)
#B cell
B_cluster <- 2
#plasma cell
Plasma_cluster <- 9
#myeloid cell
Myeloid_cluster <- c(5, 13)
#epithelial cell
Epithelial_cluster <- 11
#endothelial
Endothelial_cluster <- c(7, 10)
#fibroblast
Fibroblast_cluster <- c(3, 6, 12)

sce_all@meta.data <- sce_all@meta.data %>% 
  mutate(celltype = case_when(
    RNA_snn_res.0.3 %in% T_cluster ~ 'T',
    RNA_snn_res.0.3 %in% B_cluster ~ 'B',
    RNA_snn_res.0.3 %in% Plasma_cluster ~ 'Plasma',
    RNA_snn_res.0.3 %in% Myeloid_cluster ~ 'Myeloid',
    RNA_snn_res.0.3 %in% Epithelial_cluster ~ 'Epithelial',
    RNA_snn_res.0.3 %in% Endothelial_cluster ~ 'Endotheliall',
    RNA_snn_res.0.3 %in% Fibroblast_cluster ~ 'Fibroblast'
  ))
sce_all@meta.data$celltype <- factor(sce_all@meta.data$celltype,
                                     levels = c('T', 'B', 'Plasma',
                                                'Myeloid', 'Epithelial',
                                                'Endotheliall', 'Fibroblast'))
p3 <- DimPlot(sce_all, group.by = 'celltype', label = T) + theme(legend.position = 'none')
p4 <- DotPlot(sce_all, 
              features = c('PTPRC', 'CD3D', #T cell
                           'MS4A1', 'CD79A', #B cell
                           'JCHAIN', 'IGHG1', #plasma cell
                           'LYZ', 'CD68', 'FCGR3A', 'CD14', #myeloid cell
                           'EPCAM', 'CDH1', 'KRT19', #epithelial cell
                           'PECAM1', 'VWF', #endothelial cell
                           'COL1A1', 'COL1A2', 'DCN' #fibroblast cell
                           ),
              group.by = 'celltype') + 
  coord_flip() + 
  theme(axis.text.x = element_text(hjust = 1, angle = 45),
        axis.title = element_blank()) +
  ggtitle(label = 'GSE197778')
p3 + p4
Fig4.jpg

最后祝大家暑期愉快~

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

推荐阅读更多精彩内容