monocle2单细胞测序方法

Monocle2 学习笔记

2020-04-11  本文已影响0人  caokai001

参考:

  1. 第六章 scRNA-seq数据分析

  2. 使用monocle包进行pseudotime分析

  3. monocle2 官方教程

  4. 单细胞转录组学习笔记-18-scRNA包学习Monocle2

  5. 实验记录10: 用Monocle进行伪时间分析 ***

  6. scRNA-seq拟时分析 || Monocle2 踩坑教程

测试所用的数据链接: https://pan.baidu.com/s/1TUysqHHbXrWGi7QGt-6L1g 提取码: uqgh

image.png

Monocle[7]是一个基于pseudotime分析的软件包。它通过从一系列差异表达的基因来描述细胞间的距离远近,由此得到类似树状的分叉图象来表示细胞间的差异性。 提供了聚类,pseudotime, 差异分析等多种功能,该项目的网址如下

https://cole-trapnell-lab.github.io/monocle-release/

image.png

主要内容

1. 读取数据
2.预处理过滤细胞
3. 细胞分群/聚类
    step1:dispersionTable()
    step2:plot_pc_variance_explained() 耗时
    step3: 进行降维和聚类
4.筛选基因(用于拟分析时序)
    4.1 一般过滤标准
    4.2 使用差异表达基因BEAM分析
5.推断发育轨迹
    5.1. 选合适基因
    5.2 降维
    5.3 细胞排序
小结:

主要介绍使用该R包进行pseudotime分析的步骤.

Tips : pseudotime分析本质上就是输入基因及其表达量矩阵,计算拟时序的值。并且选取一些基因,用于降维可视化。聚类只是降维后颜色填充而已.

1. 读取数据

monocle包有很多种读取数据的方式 ,这里只展示其中三种:(I)直接读取;(II)来源于seurat2 包; (III)来源于seurat3包。

Tips : 由于版本更替较快,seurat2 可以直接导入monocle2.但是seurat3却要手动写代码导入.

library(monocle)
library(DDRTree)
library(pheatmap)
library(data.table)
library(plyr)

下面以10x 数据为例,最好通过seurat对象直接读入monocle2 方便一些.

(1)直接读取: 当有三个文件,expr_matrix ; sample_info ;gene_annotation.

expr_matrix <- read.delim(nUMI.summary.file)
sample_info <- data.frame(sampleID=colnames(expr_matrix))
gene_annotation <- data.frame(symbol=rownames(expr_matrix))
pd <- new("AnnotatedDataFrame", data = sample_info)
fd <- new("AnnotatedDataFrame", data = gene_annotation)
d <- newCellDataSet(as.matrix(expr_matrix), phenoData = pd, featureData = fd)

(2)来源于seurat2 包,数据如下:

image.png
## 切换到工作目录,读入三个文本文件.
pbmc.data <- Read10X(data.dir = ".")
pbmc <- CreateSeuratObject(counts = pbmc.data, project = "10XPBMC")
## seurat2 可以直接读入
sce <- importCDS(pbmc)

(3) 来源于seurat3包:

差别之处,不能直接用importCDS 导入.

## 由seurat3 导入函数newimport 替代上面的importCDS功能.

newimport <- function(otherCDS, import_all = FALSE) {
  if(class(otherCDS)[1] == 'Seurat') {
    requireNamespace("Seurat")
    data <- otherCDS@assays$RNA@counts
    
    if(class(data) == "data.frame") {
      data <- as(as.matrix(data), "sparseMatrix")
    }
    
    pd <- tryCatch( {
      pd <- new("AnnotatedDataFrame", data = otherCDS@meta.data)
      pd
    },
    #warning = function(w) { },
    error = function(e) {
      pData <- data.frame(cell_id = colnames(data), row.names = colnames(data))
      pd <- new("AnnotatedDataFrame", data = pData)
      
      message("This Seurat object doesn't provide any meta data");
      pd
    })
    
    # remove filtered cells from Seurat
    if(length(setdiff(colnames(data), rownames(pd))) > 0) {
      data <- data[, rownames(pd)]
    }
    
    fData <- data.frame(gene_short_name = row.names(data), row.names = row.names(data))
    fd <- new("AnnotatedDataFrame", data = fData)
    lowerDetectionLimit <- 0
    
    if(all(data == floor(data))) {
      expressionFamily <- negbinomial.size()
    } else if(any(data < 0)){
      expressionFamily <- uninormal()
    } else {
      expressionFamily <- tobit()
    }
    
    valid_data <- data[, row.names(pd)]
    
    monocle_cds <- newCellDataSet(data,
                                  phenoData = pd,
                                  featureData = fd,
                                  lowerDetectionLimit=lowerDetectionLimit,
                                  expressionFamily=expressionFamily)
    
    if(import_all) {
      if("Monocle" %in% names(otherCDS@misc)) {
        otherCDS@misc$Monocle@auxClusteringData$seurat <- NULL
        otherCDS@misc$Monocle@auxClusteringData$scran <- NULL
        
        monocle_cds <- otherCDS@misc$Monocle
        mist_list <- otherCDS
        
      } else {
        # mist_list <- list(ident = ident)
        mist_list <- otherCDS
      }
    } else {
      mist_list <- list()
    }
    
    if(1==1) {
      var.genes <- setOrderingFilter(monocle_cds, otherCDS@assays$RNA@var.features)
      
    }
    monocle_cds@auxClusteringData$seurat <- mist_list
    
  } else if (class(otherCDS)[1] == 'SCESet') {
    requireNamespace("scater")
    
    message('Converting the exprs data in log scale back to original scale ...')
    data <- 2^otherCDS@assayData$exprs - otherCDS@logExprsOffset
    
    fd <- otherCDS@featureData
    pd <- otherCDS@phenoData
    experimentData = otherCDS@experimentData
    if("is.expr" %in% slotNames(otherCDS))
      lowerDetectionLimit <- otherCDS@is.expr
    else
      lowerDetectionLimit <- 1
    
    if(all(data == floor(data))) {
      expressionFamily <- negbinomial.size()
    } else if(any(data < 0)){
      expressionFamily <- uninormal()
    } else {
      expressionFamily <- tobit()
    }
    
    if(import_all) {
      # mist_list <- list(iotherCDS@sc3,
      #                   otherCDS@reducedDimension)
      mist_list <- otherCDS
      
    } else {
      mist_list <- list()
    }
    
    monocle_cds <- newCellDataSet(data,
                                  phenoData = pd,
                                  featureData = fd,
                                  lowerDetectionLimit=lowerDetectionLimit,
                                  expressionFamily=expressionFamily)
    # monocle_cds@auxClusteringData$sc3 <- otherCDS@sc3
    # monocle_cds@auxOrderingData$scran <- mist_list
    
    monocle_cds@auxOrderingData$scran <- mist_list
    
  } else {
    stop('the object type you want to export to is not supported yet')
  }
  
  return(monocle_cds)
}

导入monocle2

## 读入seurat3
pbmc.data <- Read10X(data.dir = ".")
pbmc <- CreateSeuratObject(counts = pbmc.data, project = "10XPBMC")

## 读入monocle2
sce <- newimport(pbmc)

本文采用第1种方式,读入文件

library(Seurat)
library(monocle)

expression_matrix <- readRDS("packer_embryo_expression.rds")
cell_metadata <- readRDS("packer_embryo_colData.rds")
gene_annotation <- readRDS("packer_embryo_rowData.rds")

## 数据框需要先保存为AnnotatedDataFrame 格式,才可以创建newCellDataSet 对象
# Error: CellDataSet 'phenoData' is class 'data.frame' but should be or extend 'AnnotatedDataFrame'
pd <- new("AnnotatedDataFrame", data = cell_metadata)
fd <- new("AnnotatedDataFrame", data = gene_annotation)

sce <- newCellDataSet(expression_matrix,
                      phenoData = pd, 
                      featureData = fd,
                      lowerDetectionLimit = 0.1, 
                      expressionFamily = VGAM::negbinomial.size()) # 默认参数

查看sce 类型:

> sce
CellDataSet (storageMode: environment)
assayData: 20222 features, 6188 samples 
  element names: exprs 
protocolData: none
phenoData
  sampleNames: AAACCTGCAAGACGTG-300.1.1 AAACCTGGTGTGAATA-300.1.1 ...
    TTTGTCAAGTACACCT-b02 (6188 total)
  varLabels: cell n.umi ... State (21 total)
  varMetadata: labelDescription
featureData
  featureNames: WBGene00010957 WBGene00010958 ... WBGene00007064 (20222 total)
  fvarLabels: id gene_short_name num_cells_expressed use_for_ordering
  fvarMetadata: labelDescription
experimentData: use 'experimentData(object)'
Annotation:

查看一下数据读入到monocle2里面,它的表达分布类型:

sce@expressionFamily

## Family:  negbinomial.size 
## 
## Negative-binomial distribution with size known
## 
## Links:    loge(mu)
## Mean:     mu
## Variance: mu * (1 + mu / size) for NB-2

​ 注意到构建CDS对象过程中有一个参数是:expressionFamily ,它是选择了一个数据分布,例如FPKM/TPM 值是log-正态分布的;UMIs和原始count值用负二项分布模拟的效果更好。负二项分布有两种方法,这里选用了negbinomial.size,另外一种negbinomial稍微更准确一点,但速度大打折扣,它主要针对非常小的数据集,如下表:

由于输入10x genomic 数count 类似,所以表达分布为 negbinomial.size()

Family function 数据类型 备注
negbinomial.size() 原始计数值, 比如UMIs counts Negative binomial distribution with fixed variance
negbinomial() 原始计数值, 比如UMIs counts 比negbinomial.size()精确一点, 但是非常慢.
tobit() FPKM, TPM Tobits are truncated normal distributions. 注意这点不是log-transformed FPKM/TPM
gaussianff() log-transformed FPKM/TPMs, Ct values from single-cell qPCR 符合高期分布的数值

2.预处理

和处理RNA-seq数据一样,monocle会有一些预处理的步骤,这包括估计size factors和dispersions,以及去除一些质量比较差的细胞。

cds <- estimateSizeFactors(cds)
cds <- estimateDispersions(cds)
# Removing 143 outliers

通过pDatafData两个函数,可以查看基因和细胞的相关信息,示意如下

image.png image.png

过滤细胞

在准备样品细胞的过程中,不可避免地会引入一下死的细胞,或者说是doublets(两个细胞被标记成一个细胞)。 这些细胞对下游分析的影响会比较大,它们很有可能会占去很大分析权重。所以我们需要把它们去除掉。 去除它们的办法就是设置一个比较合理的表达总值的上下限,然后把处于上下限外围的细胞都去除掉。

### detectGenes计算每一个细胞表达的基因个数.
sce <- detectGenes(sce, min_expr = 0.1)
### 保留在10个细胞中表达的基因,当做表达基因.
expressed_genes <- row.names(subset(fData(sce),
                                    num_cells_expressed >= 10))

我们将表达值在10以上的基因保存为expressed_genes。下面的步骤会用到保存好的表达的基因。

一般的,我们是没有数据告诉我们哪些细胞是死的或者空的,哪些细胞是doublets的。但是通常而方言,我们都会拿到RPC(reads per cell)的计数,我们可以人为的设定一下表达总值的上下限。

pData(sce)$Total_mRNAs <- Matrix::colSums(exprs(sce))
sce <- sce[, pData(sce)$Total_mRNAs < 1e6 ]

##设置上下限:
upper_bound <- 10^(mean(log10(pData(sce)$Total_mRNAs)) +
                     2*sd(log10(pData(sce)$Total_mRNAs)))
lower_bound <- 10^(mean(log10(pData(sce)$Total_mRNAs)) -
                     2*sd(log10(pData(sce)$Total_mRNAs)))

## 可视化
qplot(Total_mRNAs, data = pData(sce), geom = "density") +
  geom_vline(xintercept = lower_bound) +
  geom_vline(xintercept = upper_bound) + xlim(0, 6000)+
  theme_classic()

可视化RPC(reads per cell) 分布

image.png

过滤不合格细胞

## 保留下通过标准的细胞
sce <- sce[,pData(sce)$Total_mRNAs > lower_bound &
             pData(sce)$Total_mRNAs < upper_bound]

## 新的sce 数据,评估细胞表达基因数.
sce <- detectGenes(sce, min_expr = 0.1)

> sce
CellDataSet (storageMode: environment)
assayData: 20222 features, 5943 samples 
  element names: exprs 
protocolData: none
phenoData
  sampleNames: AAACCTGCAAGACGTG-300.1.1 AAACCTGGTGTGAATA-300.1.1
    ... TTTGTCAAGTACACCT-b02 (5943 total)
  varLabels: cell n.umi ... Total_mRNAs (20 total)
  varMetadata: labelDescription
featureData
  featureNames: WBGene00010957 WBGene00010958 ... WBGene00007064
    (20222 total)
  fvarLabels: id gene_short_name num_cells_expressed
  fvarMetadata: labelDescription
experimentData: use 'experimentData(object)'
Annotation: 

细胞数目有6188 变成现在的5943个.

检查过滤细胞的效果,过滤后的值基本上是符合lognormal分布的。

# Log-transform each value in the expression matrix.
L <- log(exprs(sce[expressed_genes,]))

# Standardize each gene, so that they are all on the same scale,
# Then melt the data with plyr so we can plot it easily
melted_dens_df <- melt(Matrix::t(scale(Matrix::t(L))))

# Plot the distribution of the standardized gene expression values.
qplot(value, geom = "density", data = melted_dens_df) +
  stat_function(fun = dnorm, size = 0.5, color = 'red') +
  xlab("Standardized log(counts)") +
  ylab("Density")

理想的图:

image.png

得到过滤后的矩阵,可以分别进行聚类和拟时序分析.

3. 细胞分群/聚类

不使用marker 基因的细胞分类方法

使用函数clusterCells(),根据整体的表达量对细胞进行分组。例如,细胞表达了大量的与成肌细胞相关的基因,但就是没有成肌细胞的marker--MYF5 ,我们依然可以判断这个细胞属于成肌细胞。

step1:dispersionTable()

首先就是判断使用哪些基因进行细胞分群。当然,可以使用全部基因,但这会掺杂很多表达量不高而检测不出来的基因,反而会增加噪音。挑有差异的,挑表达量不太低的

disp_table <- dispersionTable(sce)
unsup_clustering_genes <- subset(disp_table, 
                                 mean_expression >= 0.1) ## 数值因实验而不同
sce <- setOrderingFilter(sce, unsup_clustering_genes$gene_id) 
plot_ordering_genes(sce)
image.png

其中, setOrderingFilter函数设置了将来会用于分群的细胞。这一步对于分群非常关键,所以可以试着使用不同的方法来过滤基因,以期达到满意的效果。

plot_ordering_genes根据这些基因的平均表达水平展示了基因的表达差异程度,红线表示Monocle基于这种关系对分散的期望。 上图中实黑的为会使用的基因,灰色的是过滤掉的基因。

step2:plot_pc_variance_explained() 耗时

然后选一下主成分

plot_pc_variance_explained(cds, return_all = F,max_components = 100) # norm_method='log'
image.png

上图与Seurat中使用到的PCElbowPlot类似,我们可以看到大约多少的PC将会被我们使用。 可以看到大约在15的时候就已经到的平原区了。下面试着分群。

step3: 进行降维和聚类

根据上面👆的图,选择合适的主成分数量(这个很主观,可以多试几次),这里选前15个成分(需要注意的 使用主成分会影响后面结果)

关于处理批次效应:例如在芯片数据中经常会利用SVA的combat函数。
磨平批次效应实际上就是去掉各个组的前几个主成分

# 进行降维 ,设置降维维度15.
cds <- reduceDimension(sce, max_components = 2, norm_method = 'log',
                       num_dim = 15, reduction_method = "tSNE", verbose = TRUE)
# Remove noise by PCA ...
# Reduce dimension by tSNE ...



### 如果需要去除批次效应(batch 和 几群细胞基因表达量数目)
# cds <- reduceDimension(cds, max_components = 2, num_dim = 6,
#                          reduction_method = 'tSNE',
#                          residualModelFormulaStr = "~batch + #num_genes_expressed",
#                          verbose = T)



# 进行聚类 (指定cluster为4的时候,只能画出来3个,为什么?)
cds <- clusterCells(cds, num_clusters = 4) 
# Distance cutoff calculated to 4.826512 

## 查看聚类效果
head(cds@phenoData@data)


# 查看tSNE图
plot_cell_clusters(cds,x=1,y=2, color_by = "Cluster")

# 画具体基因的表达分布图
#plot_cell_clusters(HSMM, 1, 2, color = "Cluster",
# +                    markers = c("CDC20", "GABPB2"))
image.png

4.筛选基因(用于拟分析时序)

4.1 一般过滤标准

筛选基因没有一个固定标准,可以根据自己的需要灵活选择,这里只展示其中1种 .

disp_table <- dispersionTable(sce)
unsup_clustering_genes <- subset(disp_table, mean_expression >= 0.1)

## 得到用于拟时序分析基因
ordering_genes <- row.names(unsup_clustering_genes)
image.png

4.2 使用差异表达基因

对于差异表达分析,可以像RNA-seq一样,直接到不同属性的样品进行分析

fullModelFormulaStr : 分组进行差异分析的变量.

我们可以看到,只要有不同的pData属性,我们都可以做差异表达分析。比如State:

## 差异分析非常耗时,运行时间在几分钟至十几分钟不等
diff_test_res <- differentialGeneTest(sce[expressed_genes, ],
                                      fullModelFormulaStr = "Cluster")
sig_genes <- subset(diff_test_res, qval < 0.1)
sig_genes[1:6,c("gene_short_name", "pval", "qval")]

## 得到用于拟时序分析基因
ordering_genes <- row.names(sig_genes)

下面是可选部分👇

再比如 Pseudotime

diff_test_res <- differentialGeneTest(sce[expressed_genes, ],
                                      fullModelFormulaStr = "~sm.ns(Pseudotime)")
sig_genes <- subset(diff_test_res, qval < 0.1)
sig_genes[1:6,c("gene_short_name", "pval", "qval")]

作图(注意要将基因名变成character)

cg=as.character(head(sig_genes$gene_short_name))
# 普通图
plot_genes_jitter(sce[cg,], 
                  grouping = "State", ncol= 2)
# 还能上色
plot_genes_jitter(sce[cg,],
                  grouping = "State",
                  color_by = "State",
                  nrow= 3,
                  ncol = NULL )

还可以绘制热图

sig_genes <- sig_genes[order(sig_genes$qval), ]
plot_pseudotime_heatmap(sce[row.names(sig_genes)[1:20],],
                num_clusters = 3,
                cores = 1,
                show_rownames = T)

得到类似图

image.png

BEAM分析

BEAM分析的目的是比较分枝点与分枝末端的差异。

BEAM_res <- BEAM(sce[expressed_genes, ], branch_point = 1, cores = 4)
BEAM_res <- BEAM_res[order(BEAM_res$qval),]
BEAM_res <- BEAM_res[,c("gene_short_name", "pval", "qval")]
plot_genes_branched_heatmap(sce[row.names(subset(BEAM_res,
                                          qval < 1e-4)),],
                                          branch_point = 1,
                                          num_clusters = 4,
                                          cores = 1,
                                          use_gene_short_name = T,
                                          show_rownames = T)
image.png

可以针对基因做图

plot_genes_branched_pseudotime(sce[rownames(subset(BEAM_res, qval < 1e-8)), ], 
                                 branch_point = 1, color_by = "orig.ident", 
                                 ncol = 3)
image.png

上面是可选部分 👆.

5.推断发育轨迹

用上面过滤的基因进行降维, 以方便下一步将细胞分布到不同的状态之间,可视化拟时序得分.

5.1: choosing genes that define progress

5.2: reducing the dimensionality of the data

5.3: ordering the cells in pseudotime

5.1. 选合适基因

得到上面两种过滤的基因集:unsup_clustering_genes 或者 diff_test_res,选取合适基因进行排序.

## 以差异基因为例:
## ordering_genes <- row.names (subset(diff_test_res, qval < 0.01))
cds <- setOrderingFilter(cds, ordering_genes)
plot_ordering_genes(cds)
image.png

5.2 降维

默认使用DDRTree的方法进行降维分析,代码如下

cds <- reduceDimension(
  cds,
  max_components = 2,
  method = 'DDRTree')

5.3 细胞排序

然后就是细胞排序 ,代码如下:

cds <- orderCells(cds)

运行之后,对于每个细胞,会给出一个pseudotime值,示意如下

image.png

可视化代码:

plot_cell_trajectory(sce)
image.png
plot_cell_trajectory(sce, color_by = "Pseudotime")
image.png

其实就是将fData中对应的列设置为颜色,如果想要观察不同细胞亚型的分布,可以在fData中新增一列细胞对应的cluster ID, 然后用这一类来设置颜色。

对于pseudotime分析,我们需要明白它的基本输入就是一张基因在细胞中表达量的表格,与细胞的聚类结果无关,只不过在可视化的时候根据聚类的结果填充了颜色而已。

另外,plot_genes_in_pseudotime 可以对基因在不同细胞中的表达量变化进行绘图

plot_genes_in_pseudotime(cds[cg,],
                         color_by = "State")

小结:

【Workflow以及与Seurat的异同】

下限: image.png

其中X为基因表达总数, n 为细胞数,sd为表达水平方差
大概就是以一个大致的细胞表达水平为基准,表达量太高的跟太低的去除掉。

分析scRNA-seq的数据的关键在于如何对细胞进行cluster。这其中有很多的算法,而之后的降维分析比如tSNE其实主要还是为了数据图形化显示方便。在细胞分群之后,差异表达分析其实与第三章的RNA-seq并无二致,我们只需要对需要比较的因素做到心中有数即可。

上一篇 下一篇

猜你喜欢

热点阅读