scRNA-seq数据中的低质量细胞文库(library)来源,如解离过程中的细胞损伤或cDNA文库制备失败(如低效的逆转录或PCR扩增),这些通常表现为总计数低、表达基因少、线粒体或尖峰比例高的”细胞”。这些低质量的细胞文库会导致下游分析出现误导性结果。

为了提高生物学意义,需要在分析开始时删除低质量的细胞(cell),此步骤称为细胞的质量控制 (QC)。

一个关键假设是QC指标与每个细胞的生物状态无关。 较差的值(如低文库大小、高线粒体比例)被认为是由技术因素而不是生物过程引起的,这意味着低质量细胞过滤不会在下游分析中歪曲生物学意义。严重违反这一假设可能会导致细胞类型的丧失。

Code
library(scRNAseq)
library(ensembldb)
sce.416b <-LunSpikeInData(which="416b") 
sce.416b$block <- factor(sce.416b$block)
sce.416b
#> class: SingleCellExperiment 
#> dim: 46604 192 
#> metadata(0):
#> assays(1): counts
#> rownames(46604): ENSMUSG00000102693 ENSMUSG00000064842 ...
#>   ENSMUSG00000095742 CBFB-MYH11-mcherry
#> rowData names(1): Length
#> colnames(192): SLX-9555.N701_S502.C89V9ANXX.s_1.r_1
#>   SLX-9555.N701_S503.C89V9ANXX.s_1.r_1 ...
#>   SLX-11312.N712_S508.H5H5YBBXX.s_8.r_1
#>   SLX-11312.N712_S517.H5H5YBBXX.s_8.r_1
#> colData names(9): Source Name cell line ... spike-in addition block
#> reducedDimNames(0):
#> mainExpName: endogenous
#> altExpNames(2): ERCC SIRV

6.1 QC metrics

  • The library size is defined as the total sum of counts across all relevant features for each cell. Cells with small library sizes are of low quality. 文库大小定义为每个细胞所有基因的计数总和。库小表示细胞低质量。

  • The number of expressed features in each cell is defined as the number of endogenous genes with non-zero counts for that cell. Any cell with very few expressed genes is likely to be of poor quality. 每个细胞中表达的特征数被定义为该细胞具有非零计数的内源性基因的数量。低表达特征数表示细胞低质量。

  • The proportion of reads mapped to spike-in transcripts is calculated relative to the total count across all features (including spike-ins) for each cell.High proportions are indicative of poor-quality cells。 相对于每个细胞所有特征(包括spike-in)的总计数,计算映射到spike-in transcripts中的读数比例。高比例表示细胞质量差

  • In the absence of spike-in transcripts, the proportion of reads mapped to genes in the mitochondrial genome can be used. High proportions are indicative of poor-quality cells。在没有spike-in transcripts的情况下,可以使用映射到线粒体基因组中的读数比例。高比例表示细胞质量差。

函数perCellQCMetrics()可以计算以上 QC 指标,其中sum列包含每个细胞文库大小的总计数,detected列包含检测到的基因数。 subsets_Mito_percent列包含映射到线粒体基因组的读取比例,altexps_ERCC_percent列包含映射到 ERCC 转录本的读取比例。

Code
location <- SummarizedExperiment::rowRanges(sce.416b)
location
#> GRangesList object of length 46604:
#> $ENSMUSG00000102693
#> GRanges object with 1 range and 0 metadata columns:
#>       seqnames          ranges strand
#>          <Rle>       <IRanges>  <Rle>
#>   [1]        1 3073253-3074322      +
#>   -------
#>   seqinfo: 118 sequences (1 circular) from GRCm38 genome
#> 
#> $ENSMUSG00000064842
#> GRanges object with 1 range and 0 metadata columns:
#>       seqnames          ranges strand
#>          <Rle>       <IRanges>  <Rle>
#>   [1]        1 3102016-3102125      +
#>   -------
#>   seqinfo: 118 sequences (1 circular) from GRCm38 genome
#> 
#> $ENSMUSG00000051951
#> GRanges object with 1 range and 0 metadata columns:
#>       seqnames          ranges strand
#>          <Rle>       <IRanges>  <Rle>
#>   [1]        1 3205901-3671498      -
#>   -------
#>   seqinfo: 118 sequences (1 circular) from GRCm38 genome
#> 
#> ...
#> <46601 more elements>
is.mito <- any(seqnames(location)=="MT")

library(scuttle)
df <- perCellQCMetrics(sce.416b, subsets=list(Mito=is.mito))
colnames(df)
#>  [1] "sum"                   "detected"              "subsets_Mito_sum"     
#>  [4] "subsets_Mito_detected" "subsets_Mito_percent"  "altexps_ERCC_sum"     
#>  [7] "altexps_ERCC_detected" "altexps_ERCC_percent"  "altexps_SIRV_sum"     
#> [10] "altexps_SIRV_detected" "altexps_SIRV_percent"  "total"
summary(df$sum)
#>    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
#>   27084  856350 1111252 1165865 1328301 4398883
summary(df$subsets_Mito_percent)
#>    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
#>   4.593   7.294   8.139   8.146   9.035  15.617
summary(df$altexps_ERCC_percent)
#>    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
#>   2.242   4.291   6.031   6.412   8.126  19.429

或者 addPerCellQC()计算每个细胞的 QC 统计数据并将其附加到SingleCellExperimentcolData ,将所有相关信息保留在单个对象SingleCellExperiment中以供以后操作。

Code
sce.416b <- addPerCellQCMetrics(sce.416b, subsets=list(Mito=is.mito))
dim(colData(sce.416b))
#> [1] 192  21
colnames(colData(sce.416b))
#>  [1] "Source Name"              "cell line"               
#>  [3] "cell type"                "single cell well quality"
#>  [5] "genotype"                 "phenotype"               
#>  [7] "strain"                   "spike-in addition"       
#>  [9] "block"                    "sum"                     
#> [11] "detected"                 "subsets_Mito_sum"        
#> [13] "subsets_Mito_detected"    "subsets_Mito_percent"    
#> [15] "altexps_ERCC_sum"         "altexps_ERCC_detected"   
#> [17] "altexps_ERCC_percent"     "altexps_SIRV_sum"        
#> [19] "altexps_SIRV_detected"    "altexps_SIRV_percent"    
#> [21] "total"
sce.416b
#> class: SingleCellExperiment 
#> dim: 46604 192 
#> metadata(0):
#> assays(1): counts
#> rownames(46604): ENSMUSG00000102693 ENSMUSG00000064842 ...
#>   ENSMUSG00000095742 CBFB-MYH11-mcherry
#> rowData names(1): Length
#> colnames(192): SLX-9555.N701_S502.C89V9ANXX.s_1.r_1
#>   SLX-9555.N701_S503.C89V9ANXX.s_1.r_1 ...
#>   SLX-11312.N712_S508.H5H5YBBXX.s_8.r_1
#>   SLX-11312.N712_S517.H5H5YBBXX.s_8.r_1
#> colData names(21): Source Name cell line ... altexps_SIRV_percent total
#> reducedDimNames(0):
#> mainExpName: endogenous
#> altExpNames(2): ERCC SIRV

6.2 识别低质量细胞

6.2.1 固定阈值

识别低质量细胞的最简单方法是对 QC 指标应用固定阈值。 例如,如果细胞的文库大小低于 100,000 次读取;表达少于5,000个基因;spike-in比例超过10%;或线粒体比例高于10%,我们可能会认为它们的质量较低。

Code
qc.lib <- df$sum < 1e5
qc.nexprs <- df$detected < 5e3
qc.spike <- df$altexps_ERCC_percent > 10
qc.mito <- df$subsets_Mito_percent > 10
discard <- qc.lib | qc.nexprs | qc.spike | qc.mito

# 汇总
tibble(LibSize=sum(qc.lib), NExprs=sum(qc.nexprs),
    SpikeProp=sum(qc.spike), MitoProp=sum(qc.mito), Total=sum(discard))
LibSize NExprs SpikeProp MitoProp Total
3 0 19 14 33

6.2.2 自适应阈值

假设大部分数据集由高质量的细胞组成,然后,我们根据所有细胞中每个指标的绝对中位差(median absolute deviation ,MAD)来识别各种QC指标的异常值细胞。默认情况下,如果一个值距离中位数超过3个MAD,可以将其视为异常值。即此类过滤器将保留 99% 遵循正态分布的非异常值。

Code
#?perCellQCFilters
reasons <- perCellQCFilters(df, 
                            sub.fields=c("subsets_Mito_percent",
                                         "altexps_ERCC_percent"))
colSums(as.matrix(reasons)) # apply(as.matrix(reasons),2,sum)
#>              low_lib_size            low_n_features high_subsets_Mito_percent 
#>                         4                         0                         2 
#> high_altexps_ERCC_percent                   discard 
#>                         1                         6
summary(reasons$discard)
#>    Mode   FALSE    TRUE 
#> logical     186       6

# 提取自适应阈值
attr(reasons$low_lib_size, "thresholds")
#>    lower   higher 
#> 434082.9      Inf
attr(reasons$low_n_features, "thresholds")
#>    lower   higher 
#> 5231.468      Inf

6.2.3 outlyingness

根据每个细胞的 QC 指标识别高维空间中的异常值高outlyingness低质量, 在一定程度上降低了可解释性

Code
stats <- cbind(log10(df$sum), log10(df$detected),
    df$subsets_Mito_percent, df$altexps_ERCC_percent)

library(robustbase)
outlying <- adjOutlyingness(stats, only.outlyingness = TRUE)
multi.outlier <- isOutlier(outlying, type = "higher")
summary(multi.outlier)
#>    Mode   FALSE    TRUE 
#> logical     178      14

6.3 诊断图

观察QC指标的分布( Figure fig-diagnostic-plots )以识别低质量细胞是一种很好的做法。 在最理想的情况下,我们会看到正态分布,这些分布可以证明异常值检测中使用的 3 MAD 阈值是合理的。

在非正态分布中的很大一部分细胞表明QC指标可能与某些生物状态相关,可能导致过滤过程中不同细胞类型的损失;或者与细胞亚群的文库制备不一致。

Code
colData(sce.416b) <- cbind(colData(sce.416b), df)
sce.416b$block <- factor(sce.416b$block)

sce.416b$phenotype <- ifelse(grepl("induced", sce.416b$phenotype),
    "induced", "wild type")
sce.416b$discard <- reasons$discard

library(scater)
gridExtra::grid.arrange(
    plotColData(sce.416b, x="block", y="sum", colour_by="discard",
        other_fields="phenotype") + facet_wrap(~phenotype) + 
        scale_y_log10() + ggtitle("Total count"),
    plotColData(sce.416b, x="block", y="detected", colour_by="discard", 
        other_fields="phenotype") + facet_wrap(~phenotype) + 
        scale_y_log10() + ggtitle("Detected features"),
    plotColData(sce.416b, x="block", y="subsets_Mito_percent", 
        colour_by="discard", other_fields="phenotype") + 
        facet_wrap(~phenotype) + ggtitle("Mito percent"),
    plotColData(sce.416b, x="block", y="altexps_ERCC_percent", 
        colour_by="discard", other_fields="phenotype") + 
        facet_wrap(~phenotype) + ggtitle("ERCC percent"),
    ncol=1
)
Figure 6.1: 数据集中每个批次和表型的 QC 指标分布。每个点代表一个细胞,并分别根据其是否被丢弃而着色。

另一种有用的诊断图涉及线粒体计数与其他 QC 指标的比例。 目的是确认没有细胞同时具有大量总计数和大量线粒体计数,以确保不会无意中去除恰好具有高度代谢活性的高质量细胞(例如肝细胞)。

loading ZeiselBrainData
sce.zeisel <- scRNAseq::ZeiselBrainData()
sce.zeisel
#> class: SingleCellExperiment 
#> dim: 20006 3005 
#> metadata(0):
#> assays(1): counts
#> rownames(20006): Tspan12 Tshz1 ... mt-Rnr1 mt-Nd4l
#> rowData names(1): featureType
#> colnames(3005): 1772071015_C02 1772071017_G12 ... 1772066098_A12
#>   1772058148_F03
#> colData names(10): tissue group # ... level1class level2class
#> reducedDimNames(0):
#> mainExpName: endogenous
#> altExpNames(2): ERCC repeat

library(scater)
sce.zeisel <- aggregateAcrossFeatures(sce.zeisel, 
    id=sub("_loc[0-9]+$", "", rownames(sce.zeisel)))

#--- gene-annotation ---#
library(org.Mm.eg.db)
rowData(sce.zeisel)$Ensembl <- mapIds(org.Mm.eg.db, 
    keys=rownames(sce.zeisel), keytype="SYMBOL", column="ENSEMBL")
Code
sce.zeisel <- addPerCellQC(
  sce.zeisel,subsets=list(Mt=rowData(sce.zeisel)$featureType=="mito"))

qc <- quickPerCellQC(colData(sce.zeisel), 
    sub.fields=c("altexps_ERCC_percent", "subsets_Mt_percent"))
sce.zeisel$discard <- qc$discard

plotColData(sce.zeisel, x="sum", y="subsets_Mt_percent", colour_by="discard")

ZeiselBrainData中映射线粒体转录本的 UMI 百分比与 UMI 总数作图。每个点代表一个细胞,并根据它是否被视为低质量并丢弃分组。{#fig-mito_pct/sum width=672}

Code
plotColData(sce.zeisel, x="altexps_ERCC_percent", y="subsets_Mt_percent",
    colour_by="discard")

ZeiselBrainData中映射线粒体转录本的 UMI 比例与映射到 spike-in transcripts 的 UMI 比例作图。每个点代表一个细胞,并根据它是否被视为低质量并丢弃分组。{#fig-mito_pct/ERCC width=672}

6.4 细胞过滤

对于常规分析,删除是最直接的方法,避免保留低质量细胞的弊端。

Code
filtered <- sce.416b[,!reasons$discard]

标记低质量的细胞,并将它们保留在下游分析中,但是保留会降低方差建模的准确性,例如,需要使用更多的 PC 来抵消早期 PC 是由低质量细胞和其他细胞之间的差异引起的事实。

Code
marked <- sce.416b
marked$discard <- reasons$discard