2020-12-16-单细胞转录组初探系列一

单细胞优秀示例之scRNA-seq包

Posted by DL on December 16, 2020

来源:生信技能树-jimmy-B站 来源:生信技能树-jimmy-github

1.安装并且加载scRNAseq

if (!requireNamespace("Rtsne"))
  install.packages("Rtsne")
if (!requireNamespace("FactoMineR"))
  install.packages("FactoMineR")
if (!requireNamespace("factoextra"))
  install.packages("factoextra")
if (!requireNamespace("BiocManager"))
  install.packages("BiocManager")
if (!requireNamespace("scater"))
  BiocManager::install("scater")
if (!requireNamespace("scRNAseq"))
  BiocManager::install("scRNAseq") 
if (!requireNamespace("M3Drop"))
  BiocManager::install("M3Drop") 
if (!requireNamespace("ROCR"))
  BiocManager::install("ROCR") 
library(scater)
library(scRNAseq)
library(ggplot2)
library(tidyr)
library(cowplot)
library("FactoMineR")
library("factoextra")
library("ROCR")

2.scRNAseq R包中的数据集

这个包内置的是 Pollen et al. 2014 数据集,人类单细胞细胞,分成4类,分别是 pluripotent stem cells 分化而成的 neural progenitor cells (“NPC”) ,还有 “GW16” and “GW21” ,“GW21+3” 这种孕期细胞,理解这些需要一定的生物学背景知识,如果不感兴趣,可以略过。

这个R包大小是50.6 MB,下载需要一点点时间,先安装加载它们。

这个数据集很出名,截止2019年1月已经有近400的引用了,后面的人开发R包算法都会在其上面做测试,比如 SinQC 这篇文章就提到:We applied SinQC to a highly heterogeneous scRNA-seq dataset containing 301 cells (mixture of 11 different cell types) (Pollen et al., 2014).

不过本例子只使用了数据集的4种细胞类型而已,因为 scRNAseq 这个R包就提供了这些,完整的数据是 23730 features, 301 samples, 地址为hemberg-lab, 这个网站非常值得推荐,简直是一个宝藏。

这里面的表达矩阵是由 RSEM (Li and Dewey 2011) 软件根据 hg38 RefSeq transcriptome 得到的,总是130个文库,每个细胞测了两次,测序深度不一样。

library(scRNAseq)
## ----- Load Example Data -----
fluidigm <- ReprocessedFluidigmData()
fluidigm
count <- floor(assays(fluidigm)$rsem_counts)
count[1:4,1:4] 
##          SRR1275356 SRR1274090 SRR1275251 SRR1275287
## A1BG              0          0          0          0
## A1BG-AS1          0          0          0          0
## A1CF              0          0          0          0
## A2M               0          0          0         33
sample_ann <- as.data.frame(colData(fluidigm))
DT::datatable(sample_ann)

3.先探索表型信息

前面说到,这个数据集是130个文库,每个细胞测了两次,测序深度不一样,这130个细胞,分成4类,分别是: pluripotent stem cells 分化而成的 neural progenitor cells (“NPC”) ,还有 “GW16” and “GW21” ,“GW21+3” 这种孕期细胞。

批量,粗略的看一看各个细胞的一些统计学指标的分布情况

box <- lapply(colnames(sample_ann[,1:19]),function(i) {
    dat <- sample_ann[,i,drop=F] 
    dat$sample=rownames(dat)
    ## 画boxplot 
   ggplot(dat, aes('all cells', get(i))) +
          geom_boxplot() +
          xlab(NULL)+ylab(i)
})
plot_grid(plotlist=box, ncol=5 )

rQh4PS.png

# ggsave(file="stat_all_cells.pdf")

很明显,他们可以有根据来进行分组,这里不再演示。 不过通常的文章并不会考虑如此多的细节,这里重点是批量,代码技巧非常值得你们学习。因为进行了简单探索,对表型数据就有了把握,接下来可以进行一定程度的过滤。

pa <- colnames(sample_ann[,c(1:9,11:16,18,19)])
tf <- lapply(pa,function(i) {
 # i=pa[1]
  dat <-  sample_ann[,i]  
  dat <- abs(log10(dat))
  fivenum(dat)
  (up <- mean(dat)+2*sd(dat))
  (down <- mean(dat)- 2*sd(dat) ) 
  valid <- ifelse(dat > down & dat < up, 1,0 ) 
})

tf <- do.call(cbind,tf)
choosed_cells <- apply(tf,1,function(x) all(x==1))
table(sample_ann$Biological_Condition)
## 
##   GW16   GW21 GW21+3    NPC 
##     52     16     32     30
sample_ann=sample_ann[choosed_cells,]
table(sample_ann$Biological_Condition)
## 
##   GW16   GW21 GW21+3    NPC 
##     36     11     23     29
count[1:4,1:4] 
##          SRR1274090 SRR1275287 SRR1275364 SRR1275269
## A1BG              0          0          0          0
## A1BG-AS1          0          0          0          0
## A1CF              0          0          0          0
## A2M               0         33          0         51
fivenum(apply(counts,1,function(x) sum(x>0) ))
##      A1CF     OR8G1 LINC01003    MRPS36     YWHAZ 
##         0         0         4        26        99
boxplot(apply(counts,1,function(x) sum(x>0) ))

rQ4UMj.png

fivenum(apply(counts,2,function(x) sum(x>0) ))
## SRR1275365 SRR1275345 SRR1275248 SRR1275273 SRR1274125 
##     1566.0     3043.0     4002.0     5706.5     8024.0
hist(apply(counts,2,function(x) sum(x>0) ))

rQ40Zq.png

choosed_genes=apply(counts,1,function(x) sum(x>0) )>0
table(choosed_genes)
## choosed_genes
## FALSE  TRUE 
##  9496 16759
counts <- counts[choosed_genes,]

4.接下来要利用自己的常规转录组数据分析知识

4.1 看看细胞之间的所有的基因的表达量的相关性

下面的计算,都是基于log后的表达矩阵。

dat <- log2(edgeR::cpm(counts) + 1)
dat[1:4, 1:4]
##          SRR1274090 SRR1275287 SRR1275364 SRR1275269
## A1BG              0   0.000000          0   0.000000
## A1BG-AS1          0   0.000000          0   0.000000
## A2M               0   4.216768          0   3.552694
## A2M-AS1           0   0.000000          0   0.000000
dat_back <- dat

先备份这个表达矩阵,后面的分析都用得上

exprSet <- dat_back
colnames(exprSet)
##  [1] "SRR1274090" "SRR1275287" "SRR1275364" "SRR1275269" "SRR1275263"
##  [6] "SRR1274117" "SRR1274089" "SRR1275248" "SRR1275345" "SRR1274125"
## [11] "SRR1275300" "SRR1275294" "SRR1274122" "SRR1275277" "SRR1275293"
## [16] "SRR1274128" "SRR1275348" "SRR1275245" "SRR1274084" "SRR1275342"
## [21] "SRR1275363" "SRR1275280" "SRR1275264" "SRR1275369" "SRR1274131"
## [26] "SRR1275351" "SRR1275256" "SRR1275355" "SRR1275258" "SRR1275260"
## [31] "SRR1275367" "SRR1275346" "SRR1274114" "SRR1275297" "SRR1275273"
## [36] "SRR1274126" "SRR1275290" "SRR1275274" "SRR1274121" "SRR1275341"
## [41] "SRR1275336" "SRR1274119" "SRR1274087" "SRR1274113" "SRR1275246"
## [46] "SRR1275267" "SRR1275289" "SRR1275360" "SRR1275358" "SRR1275352"
## [51] "SRR1274130" "SRR1275368" "SRR1275281" "SRR1275362" "SRR1275257"
## [56] "SRR1275350" "SRR1274129" "SRR1275298" "SRR1274123" "SRR1275343"
## [61] "SRR1275334" "SRR1274085" "SRR1274111" "SRR1275349" "SRR1275244"
## [66] "SRR1275344" "SRR1274088" "SRR1274116" "SRR1275243" "SRR1275301"
## [71] "SRR1275295" "SRR1275271" "SRR1274124" "SRR1275357" "SRR1275365"
## [76] "SRR1275268" "SRR1275361" "SRR1275266" "SRR1275288" "SRR1274133"
## [81] "SRR1275353" "SRR1275359" "SRR1274120" "SRR1275275" "SRR1275291"
## [86] "SRR1274086" "SRR1274112" "SRR1274118" "SRR1275340" "SRR1274115"
## [91] "SRR1275347" "SRR1274127" "SRR1275272" "SRR1275302" "SRR1275296"
## [96] "SRR1275354" "SRR1275259" "SRR1275285" "SRR1275366"
intall.packages("pheatmap")
library(pheatmap)
pheatmap::pheatmap(cor(exprSet))

rQ4oFK.png

group_list <- sample_ann$Biological_Condition
tmp <- data.frame(g = group_list)
rownames(tmp) <-  colnames(exprSet)
# 组内的样本的相似性应该是要高于组间的!
pheatmap::pheatmap(cor(exprSet), annotation_col = tmp)

rQ47WD.png

dim(exprSet)
## [1] 16759    99
exprSet = exprSet[apply(exprSet, 1, function(x)
sum(x > 1) > 5), ]
dim(exprSet)
## [1] 11337    99
dim(exprSet)
## [1] 11337    99
exprSet <-  exprSet[names(sort(apply(exprSet, 1, mad), decreasing = T)[1:500]), ]
dim(exprSet)
## [1] 500  99
M <-cor(log2(exprSet + 1))
tmp <- data.frame(g = group_list)
rownames(tmp) <-  colnames(M)
pheatmap::pheatmap(M, annotation_col = tmp)

rQ4jeI.png

table(sample_ann$LibraryName)
## 
##    GW16_1   GW16_10   GW16_12   GW16_13   GW16_14   GW16_15   GW16_16 
##         1         1         2         2         2         2         2 
##   GW16_17   GW16_18   GW16_19    GW16_2   GW16_20   GW16_21   GW16_22 
##         2         2         2         1         2         2         2 
##   GW16_23   GW16_24   GW16_25    GW16_4    GW16_5    GW16_6    GW21_1 
##         2         2         2         2         2         1         1 
##    GW21_2    GW21_3    GW21_5    GW21_6    GW21_7    GW21_8  GW21+3_1 
##         2         2         2         1         2         1         2 
## GW21+3_10 GW21+3_11 GW21+3_12 GW21+3_13 GW21+3_14 GW21+3_16  GW21+3_2 
##         2         1         1         1         1         1         2 
##  GW21+3_3  GW21+3_4  GW21+3_5  GW21+3_6  GW21+3_7  GW21+3_8  GW21+3_9 
##         2         1         2         2         2         1         2 
##     NPC_1    NPC_10    NPC_11    NPC_12    NPC_13    NPC_14    NPC_15 
##         2         2         2         2         2         2         2 
##     NPC_2     NPC_3     NPC_4     NPC_5     NPC_6     NPC_7     NPC_8 
##         2         2         2         1         2         2         2 
##     NPC_9 
##         2

可以看到,从细胞的相关性角度来看,到NPC跟另外的GW细胞群可以区分的很好,但是GW本身的3个小群体并没有那么好的区分度。

而且简单选取top的sd的基因来计算相关性,并没有很明显的改善。

但是可以看到每个细胞测了两次,所以它们的相关性要好于其它同类型的细胞。


4.2 对表达矩阵进行简单的层次聚类

如果计算机资源不够,这里可以先对基因进行一定程度的挑选,最简单的就是选取top的sd的基因,这里略。

dat <- dat_back
hc <- hclust(dist(t(dat))) 
plot(hc,labels = FALSE)

rQ5Vwq.png

clus <-  cutree(hc, 4) #对hclust()函数的聚类结果进行剪枝,即选择输出指定类别数的系谱聚类结果。
group_list <-  as.factor(clus) ##转换为因子属性
table(group_list) ##统计频数
## group_list
##  1  2  3  4 
## 29 25 39  6
table(group_list,sample_ann$Biological_Condition)   
##           
## group_list GW16 GW21 GW21+3 NPC
##          1    0    0      0  29
##          2   20    3      2   0
##          3   15    8     16   0
##          4    1    0      5   0

可以看到GW16和GW21是很难区分开来的,如果是普通的层次聚类的话。


4.3 看看最常规的PCA降维结果

降维算法很多,详情可以去自行搜索学习,比如:

  • 主成分分析PCA

  • 多维缩放(MDS)

  • 线性判别分析(LDA)

  • 等度量映射(Isomap)

  • 局部线性嵌入(LLE)

  • t-SNE

  • Deep Autoencoder Networks

这里只介绍 PCA 和 t-SNE

dat <- dat_back
dat <- t(dat)
dat <- as.data.frame(dat)
plate <- sample_ann$Biological_Condition # 这里定义分组信息
dat <-  cbind(dat, plate) #cbind根据列进行合并,即叠加所有列 #矩阵添加批次信息
dat[1:4, 1:4]
##            A1BG A1BG-AS1      A2M A2M-AS1
## SRR1274090    0        0 0.000000       0
## SRR1275287    0        0 4.216768       0
## SRR1275364    0        0 0.000000       0
## SRR1275269    0        0 3.552694       0
table(dat$plate)
## 
##   GW16   GW21 GW21+3    NPC 
##     36     11     23     29
# The variable plate (index = ) is removed
# before PCA analysis
dat.pca <- PCA(dat[, -ncol(dat)], graph = FALSE)
head(dat.pca$var$coord) ## 每个主成分的基因重要性占比
##                Dim.1       Dim.2       Dim.3        Dim.4         Dim.5
## A1BG      0.19046450  0.09601240 -0.17840553 -0.001507970 -0.0006057691
## A1BG-AS1 -0.02510451  0.29821319  0.03571804  0.020001929 -0.0105727109
## A2M       0.03403042  0.25458727  0.24264958  0.228512329  0.5414019044
## A2M-AS1   0.23140893  0.02900348 -0.07952678  0.356461354  0.1283450099
## A2ML1    -0.15776536  0.13831288  0.10065788  0.004060288 -0.0353422367
## A2MP1    -0.04068586 -0.05584736 -0.02857416  0.018287992  0.0069603680
head(dat.pca$ind$coord) ## 每个细胞的前5个主成分取值。
##                 Dim.1      Dim.2      Dim.3      Dim.4      Dim.5
## SRR1274090  40.251912 -13.231641 -12.358891 -20.038100 -12.704947
## SRR1275287   1.196637  15.386256  30.566235  14.262858  -4.852418
## SRR1275364 -34.731051 -14.782146  -7.716928   7.046918   1.951473
## SRR1275269  21.760471   3.307309  17.985263 -18.382512   9.270646
## SRR1275263  -3.313968 -15.856721   8.929275 -36.358830  20.275875
## SRR1274117  59.378486  16.453551  -5.098901  56.245455  19.257598
fviz_pca_ind(
      dat.pca,
      #repel =T,
      geom.ind = "point",
      # show points only (nbut not "text")
      col.ind = dat$plate,
      # color by groups
      #palette = c("#00AFBB", "#E7B800"),
      addEllipses = TRUE,
      # Concentration ellipses
      legend.title = "Groups"
) 

rQISBR.png

同样的,很明显可以看到NPC跟另外的GW细胞群可以区分的很好,但是GW本身的3个小群体并没有那么好的区分度。


4.4 接着是稍微高大上的tSNE降维

因为计算量的问题,这里先选取PCA后的主成分,然进行tSNE,当然,也有其它做法,比如选取变化高的基因,显著差异基因等等。

# 选取前面PCA分析的5个主成分。
dat_matrix <- dat.pca$ind$coord
# Set a seed if you want reproducible results
set.seed(42)
library(Rtsne) 
# 如果使用原始表达矩阵进行 tSNE耗时很可怕,dat_matrix = dat_back
# 出现Remove duplicates before running TSNE 则check_duplicated = FALSE
# tsne_out <- Rtsne(dat_matrix,pca=FALSE,perplexity=30,theta=0.0, check_duplicates = FALSE) # Run TSNE
tsne_out <- Rtsne(dat_matrix,perplexity=10)
plate <- sample_ann$Biological_Condition # 这里定义分组信息
plot(tsne_out$Y,col= rainbow(4)[as.numeric(as.factor(plate))], pch=19) 

rQIA3D.png


4.5 对PCA或者tSNE结果进行kmeans或者dbscan算法聚类

降维是降维,聚类是聚类,需要理解其中的区别。

降维与否,不同的降维算法选择,不同参数的选择得到的结果都不一样。

聚类也是一样,不同的算法,不同的参数。

# 前面我们的层次聚类是针对全部表达矩阵,这里我们为了节省计算量,可以使用tsne_out$Y这个结果
head(tsne_out$Y)
##             [,1]        [,2]
## [1,]  -5.5201858 -21.8803070
## [2,]  -0.2618134  -0.7269741
## [3,]  14.9423636  17.1572253
## [4,]  -6.6313221 -14.8290355
## [5,]  -4.6828620 -11.5136930
## [6,] -13.1192292 -18.8796564
opt_tsne=tsne_out$Y
table(kmeans(opt_tsne,centers = 4)$clust)
## 
##  1  2  3  4 
## 22 15 18 44
plot(opt_tsne,  col=kmeans(opt_tsne,centers = 4)$clust, pch=19, xlab="tSNE dim 1", ylab="tSNE dim 2")

rQIQ4f.png

library(dbscan)
plot(opt_tsne,  col=dbscan(opt_tsne,eps=3.1)$cluster, pch=19, xlab="tSNE dim 1", ylab="tSNE dim 2")

rQI8gg.png

table(dbscan(opt_tsne,eps=3.1)$cluster)
## 
##  0  1  2  3  4 
##  3 30 22 38  6
# 比较两个聚类算法区别
table(kmeans(opt_tsne,centers = 4)$clust,dbscan(opt_tsne,eps=3.1)$cluster)
##    
##      0  1  2  3  4
##   1  0  0  0 38  6
##   2  0 16  0  0  0
##   3  1 14  0  0  0
##   4  2  0 22  0  0

5.测试一下M3Drop这个单细胞转录组R包

5.1 首先构建M3Drop需要的对象

library(M3Drop) 
Normalized_data <- M3DropCleanData(counts, 
                                   labels = sample_ann$Biological_Condition , 
                                   is.counts=TRUE, min_detected_genes=2000)
dim(Normalized_data$data)
## [1] 13405    97
length(Normalized_data$labels)
## [1] 97
class(Normalized_data)
## [1] "list"
str(Normalized_data)
## List of 2
##  $ data  : num [1:13405, 1:97] 0 0 0 0 0 0 0 0 0 0 ...
##   ..- attr(*, "dimnames")=List of 2
##   .. ..$ : chr [1:13405] "A1BG" "A2M" "A2ML1" "AAAS" ...
##   .. ..$ : chr [1:97] "SRR1274090" "SRR1275287" "SRR1275364" "SRR1275269" ...
##  $ labels: chr [1:97] "NPC" "GW21+3" "GW16" "GW21" ...

这个包设计比较简单,并没有构建S4对象,只是一个简单的list而已。


5.2 统计学算法 Michaelis-Menten

需要深入读该文章,了解其算法,这里略过,总之它对单细胞转录组的表达矩阵进行了一系列的统计检验。

fits <- M3DropDropoutModels(Normalized_data$data)

rQIsv4.png

# Sum absolute residuals
data.frame(MM=fits$MMFit$SAr, Logistic=fits$LogiFit$SAr,
           DoubleExpo=fits$ExpoFit$SAr) 
##     MM Logistic DoubleExpo
## 1 1651     1646       4033
# Sum squared residuals
data.frame(MM=fits$MMFit$SSr, Logistic=fits$LogiFit$SSr,
           DoubleExpo=fits$ExpoFit$SSr)
##    MM Logistic DoubleExpo
## 1 403      345       1962

5.3 找差异基因

DE_genes <- M3DropDifferentialExpression(Normalized_data$data, 
                                         mt_method="fdr", mt_threshold=0.01)

rQIWUx.png

dim(DE_genes)
## [1] 180   3
head(DE_genes)
##            Gene      p.value      q.value
## ABCE1     ABCE1 2.844523e-05 3.020909e-03
## ACAT2     ACAT2 7.885891e-05 6.776306e-03
## ADGRB3   ADGRB3 1.083437e-04 8.443879e-03
## AMER2     AMER2 1.917979e-05 2.255308e-03
## ANKRD44 ANKRD44 6.749811e-05 6.113596e-03
## ANP32E   ANP32E 1.547604e-07 3.771932e-05

这里是针对上面的统计结果来的


5.4 针对差异基因画热图

par(mar=c(1,1,1,1)) 
heat_out <- M3DropExpressionHeatmap(DE_genes$Gene, Normalized_data$data, 
                                    cell_labels = Normalized_data$labels)

rQI58O.png

可视化了解一下找到的差异基因在不同的细胞类型的表达分布情况。


5.4 聚类

这里可以重新聚类后,针对自己找到的类别来分别找marker基因,不需要使用测试数据自带的表型信息。

cell_populations <- M3DropGetHeatmapCellClusters(heat_out, k=4)
library("ROCR") 
marker_genes <- M3DropGetMarkers(Normalized_data$data, cell_populations)
table(cell_populations,Normalized_data$labels)
##                 
## cell_populations GW16 GW21 GW21+3 NPC
##                1    0    0      0  29
##                2   14    8     19   0
##                3    4    1      2   0
##                4   16    2      2   0

5.5 每个类别的marker genes

head(marker_genes[marker_genes$Group==4,],20) 
##              AUC Group         pval
## ADGRV1 0.9707792     4 1.831217e-11
## TFAP2C 0.9451299     4 1.885637e-11
## EGR1   0.9409091     4 1.159852e-09
## PLCE1  0.9233766     4 7.676760e-11
## FOS    0.9058442     4 1.806229e-08
## SLC1A3 0.9048701     4 1.542660e-09
## AASS   0.9032468     4 3.136961e-08
## ITGB8  0.8886364     4 8.823909e-08
## BCAN   0.8844156     4 1.513264e-12
## NFIA   0.8831169     4 7.724889e-08
## PTPRZ1 0.8707792     4 3.596940e-07
## HES1   0.8590909     4 7.233415e-08
## DUSP10 0.8584416     4 7.865906e-09
## LTBP1  0.8545455     4 1.872381e-07
## LDLR   0.8487013     4 1.547451e-06
## FBXO32 0.8477273     4 3.810723e-09
## CLU    0.8467532     4 1.319982e-06
## CCND2  0.8422078     4 2.668888e-06
## JUN    0.8357143     4 8.979968e-07
## GPM6B  0.8350649     4 4.121035e-06
marker_genes[rownames(marker_genes)=="FOS",] 
##           AUC Group         pval
## FOS 0.9058442     4 1.806229e-08

也可以针对这些 marker genes去画热图,当然,得根据AUC和P值来挑选符合要求的差异基因去绘图。

par(mar=c(1,1,1,1)) 
choosed_marker_genes=as.character(unlist(lapply(split(marker_genes,marker_genes$Group), function(x) (rownames(head(x,20))))))
heat_out <- M3DropExpressionHeatmap(choosed_marker_genes, Normalized_data$data, cell_labels =  cell_populations)

rQI7KH.png

如果遇到Error in plot.new() : figure margins too large报错,则单独将heat_out这行命令复制出来运行


6.对感兴趣基因集进行注释

通常是GO/KEGG等数据库,通常是超几何分布,GSEA,GSVA等算法。

这里就略过。