Chapter 5 Overview

5.1 Introduction

This chapter provides an overview of the framework of a typical scRNA-seq analysis workflow (Figure 5.1). Subsequent chapters will describe each analysis step in more detail.

Schematic of a typical scRNA-seq analysis workflow. Each stage (separated by dashed lines) consists of a number of specific steps, many of which operate on and modify a `SingleCellExperiment` instance.

Figure 5.1: Schematic of a typical scRNA-seq analysis workflow. Each stage (separated by dashed lines) consists of a number of specific steps, many of which operate on and modify a SingleCellExperiment instance.

5.2 Experimental Design

Before starting the analysis itself, some comments on experimental design may be helpful. The most obvious question is the choice of technology, which can be roughly divided into:

  • Droplet-based: 10X Genomics, inDrop, Drop-seq
  • Plate-based with unique molecular identifiers (UMIs): CEL-seq, MARS-seq
  • Plate-based with reads: Smart-seq2
  • Other: sci-RNA-seq, Seq-Well

Each of these methods have their own advantages and weaknesses that are discussed extensively elsewhere (Mereu et al. 2019; Ziegenhain et al. 2017). In practical terms, droplet-based technologies are the current de facto standard due to their throughput and low cost per cell. Plate-based methods can capture other phenotypic information (e.g., morphology) and are more amenable to customization. Read-based methods provide whole-transcript coverage, which is useful in some applications (e.g., splicing, exome mutations); otherwise, UMI-based methods are more popular as they mitigate the effects of PCR amplification noise. The choice of method is left to the reader’s circumstances - we will simply note that most aspects of our analysis pipeline are technology-agnostic.

The next question is how many cells should be captured, and to what depth they should be sequenced. The short answer is “as much as you can afford to spend”. The long answer is that it depends on the aim of the analysis. If we are aiming to discover rare cell subpopulations, then we need more cells. If we are aiming to characterize subtle differences, then we need more sequencing depth. As of time of writing, an informal survey of the literature suggests that typical droplet-based experiments would capture anywhere from 10,000 to 100,000 cells, sequenced at anywhere from 1,000 to 10,000 UMIs per cell (usually in inverse proportion to the number of cells). Droplet-based methods also have a trade-off between throughput and doublet rate that affects the true efficiency of sequencing.

For studies involving multiple samples or conditions, the design considerations are the same as those for bulk RNA-seq experiments. There should be multiple biological replicates for each condition and conditions should not be confounded with batch. Note that individual cells are not replicates; rather, we are referring to samples derived from replicate donors or cultures.

5.3 Obtaining a count matrix

Sequencing data from scRNA-seq experiments must be converted into a matrix of expression values that can be used for statistical analysis. Given the discrete nature of sequencing data, this is usually a count matrix containing the number of UMIs or reads mapped to each gene in each cell. The exact procedure for quantifying expression tends to be technology-dependent:

  • For 10X Genomics data, the CellRanger software suite provides a custom pipeline to obtain a count matrix. This uses STAR to align reads to the reference genome and then counts the number of unique UMIs mapped to each gene.
  • Pseudo-alignment methods such as alevin can be used to obtain a count matrix from the same data with greater efficiency. This avoids the need for explicit alignment, which reduces the compute time and memory usage.
  • For other highly multiplexed protocols, the scPipe package provides a more general pipeline for processing scRNA-seq data. This uses the Rsubread aligner to align reads and then counts UMIs per gene.
  • For CEL-seq or CEL-seq2 data, the scruff package provides a dedicated pipeline for quantification.
  • For read-based protocols, we can generally re-use the same pipelines for processing bulk RNA-seq data.
  • For any data involving spike-in transcripts, the spike-in sequences should be included as part of the reference genome during alignment and quantification.

After quantification, we import the count matrix into R and create a SingleCellExperiment object. This can be done with base methods (e.g., read.table()) followed by applying the SingleCellExperiment() constructor. Alternatively, for specific file formats, we can use dedicated methods from the DropletUtils (for 10X data) or tximport/tximeta packages (for pseudo-alignment methods). Depending on the origin of the data, this requires some vigilance:

  • Some feature-counting tools will report mapping statistics in the count matrix (e.g., the number of unaligned or unassigned reads). While these values can be useful for quality control, they would be misleading if treated as gene expression values. Thus, they should be removed (or at least moved to the colData) prior to further analyses.
  • Be careful of using the ^ERCC regular expression to detect spike-in rows in human data where the row names of the count matrix are gene symbols. An ERCC gene family actually exists in human annotation, so this would result in incorrect identification of genes as spike-in transcripts. This problem can be avoided by using count matrices with standard identifiers (e.g., Ensembl, Entrez).

5.4 Data processing and downstream analysis

In the simplest case, the workflow has the following form:

  1. We compute quality control metrics to remove low-quality cells that would interfere with downstream analyses. These cells may have been damaged during processing or may not have been fully captured by the sequencing protocol. Common metrics includes the total counts per cell, the proportion of spike-in or mitochondrial reads and the number of detected features.
  2. We convert the counts into normalized expression values to eliminate cell-specific biases (e.g., in capture efficiency). This allows us to perform explicit comparisons across cells in downstream steps like clustering. We also apply a transformation, typically log, to adjust for the mean-variance relationship.
  3. We perform feature selection to pick a subset of interesting features for downstream analysis. This is done by modelling the variance across cells for each gene and retaining genes that are highly variable. The aim is to reduce computational overhead and noise from uninteresting genes.
  4. We apply dimensionality reduction to compact the data and further reduce noise. Principal components analysis is typically used to obtain an initial low-rank representation for more computational work, followed by more aggressive methods like \(t\)-stochastic neighbor embedding for visualization purposes.
  5. We cluster cells into groups according to similarities in their (normalized) expression profiles. This aims to obtain groupings that serve as empirical proxies for distinct biological states. We typically interpret these groupings by identifying differentially expressed marker genes between clusters.

Additional steps such as data integration and cell annotation will be discussed in their respective chapters.

5.5 Quick start

Here, we use the a droplet-based retina dataset from Macosko et al. (2015), provided in the scRNAseq package. This starts from a count matrix and finishes with clusters (Figure 5.2) in preparation for biological interpretation. Similar workflows are available in abbreviated form in the Workflows.,

sce <- MacoskoRetinaData()

# Quality control.
is.mito <- grepl("^MT-", rownames(sce))
qcstats <- perCellQCMetrics(sce, subsets=list(Mito=is.mito))
filtered <- quickPerCellQC(qcstats, percent_subsets="subsets_Mito_percent")
sce <- sce[, !filtered$discard]

# Normalization.
sce <- logNormCounts(sce)

# Feature selection.
dec <- modelGeneVar(sce)
hvg <- getTopHVGs(dec, prop=0.1)

# Dimensionality reduction.
sce <- runPCA(sce, ncomponents=25, subset_row=hvg)
sce <- runUMAP(sce, dimred = 'PCA', external_neighbors=TRUE)

# Clustering.
g <- buildSNNGraph(sce, use.dimred = 'PCA')
sce$clusters <- factor(igraph::cluster_louvain(g)$membership)

# Visualization.
plotUMAP(sce, colour_by="clusters")
UMAP plot of the retina dataset, where each point is a cell and is colored by the cluster identity.

Figure 5.2: UMAP plot of the retina dataset, where each point is a cell and is colored by the cluster identity.

Session Info

R Under development (unstable) (2020-03-23 r78035)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Ubuntu 18.04.4 LTS

Matrix products: default
BLAS:   /home/luna/Software/R/trunk/lib/
LAPACK: /home/luna/Software/R/trunk/lib/

 [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C              
 [3] LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8    
 [7] LC_PAPER=en_US.UTF-8       LC_NAME=C                 
 [9] LC_ADDRESS=C               LC_TELEPHONE=C            

attached base packages:
[1] parallel  stats4    stats     graphics  grDevices utils     datasets 
[8] methods   base     

other attached packages:
 [1] scran_1.15.22               scater_1.15.28             
 [3] ggplot2_3.3.0               scRNAseq_2.1.7             
 [5] SingleCellExperiment_1.9.2  SummarizedExperiment_1.17.3
 [7] DelayedArray_0.13.7         BiocParallel_1.21.2        
 [9] matrixStats_0.56.0          Biobase_2.47.3             
[11] GenomicRanges_1.39.2        GenomeInfoDb_1.23.13       
[13] IRanges_2.21.5              S4Vectors_0.25.13          
[15] BiocGenerics_0.33.2         BiocStyle_2.15.6           
[17] OSCAUtils_0.0.2            

loaded via a namespace (and not attached):
 [1] bitops_1.0-6                  bit64_0.9-7                  
 [3] httr_1.4.1                    tools_4.0.0                  
 [5] R6_2.4.1                      irlba_2.3.3                  
 [7] vipor_0.4.5                   uwot_0.1.8                   
 [9] DBI_1.1.0                     colorspace_1.4-1             
[11] withr_2.1.2                   tidyselect_1.0.0             
[13] gridExtra_2.3                 processx_3.4.2               
[15] bit_1.1-15.2                  curl_4.3                     
[17] compiler_4.0.0                BiocNeighbors_1.5.2          
[19] labeling_0.3                  bookdown_0.18                
[21] scales_1.1.0                  callr_3.4.2                  
[23] rappdirs_0.3.1                stringr_1.4.0                
[25] digest_0.6.25                 rmarkdown_2.1                
[27] XVector_0.27.1                pkgconfig_2.0.3              
[29] htmltools_0.4.0               limma_3.43.5                 
[31] dbplyr_1.4.2                  fastmap_1.0.1                
[33] highr_0.8                     rlang_0.4.5                  
[35] RSQLite_2.2.0                 shiny_1.4.0.2                
[37] DelayedMatrixStats_1.9.0      farver_2.0.3                 
[39] dplyr_0.8.5                   RCurl_1.98-1.1               
[41] magrittr_1.5                  BiocSingular_1.3.2           
[43] GenomeInfoDbData_1.2.2        Matrix_1.2-18                
[45] Rcpp_1.0.4                    ggbeeswarm_0.6.0             
[47] munsell_0.5.0                 viridis_0.5.1                
[49] lifecycle_0.2.0               edgeR_3.29.1                 
[51] stringi_1.4.6                 yaml_2.2.1                   
[53] zlibbioc_1.33.1               BiocFileCache_1.11.4         
[55] AnnotationHub_2.19.7          grid_4.0.0                   
[57] blob_1.2.1                    dqrng_0.2.1                  
[59] promises_1.1.0                ExperimentHub_1.13.5         
[61] crayon_1.3.4                  lattice_0.20-40              
[63] cowplot_1.0.0                 locfit_1.5-9.2               
[65] knitr_1.28                    ps_1.3.2                     
[67] pillar_1.4.3                  igraph_1.2.5                 
[69] glue_1.3.2                    BiocVersion_3.11.1           
[71] evaluate_0.14                 BiocManager_1.30.10          
[73] vctrs_0.2.4                   httpuv_1.5.2                 
[75] gtable_0.3.0                  purrr_0.3.3                  
[77] assertthat_0.2.1              xfun_0.12                    
[79] rsvd_1.0.3                    mime_0.9                     
[81] xtable_1.8-4                  RSpectra_0.16-0              
[83] later_1.0.0                   viridisLite_0.3.0            
[85] tibble_2.1.3                  AnnotationDbi_1.49.1         
[87] beeswarm_0.2.3                memoise_1.1.0                
[89] statmod_1.4.34                interactiveDisplayBase_1.25.0


Macosko, E. Z., A. Basu, R. Satija, J. Nemesh, K. Shekhar, M. Goldman, I. Tirosh, et al. 2015. “Highly parallel genome-wide expression profiling of individual cells using nanoliter droplets.” Cell 161 (5): 1202–14.

Mereu, Elisabetta, Atefeh Lafzi, Catia Moutinho, Christoph Ziegenhain, Davis J. MacCarthy, Adrian Alvarez, Eduard Batlle, et al. 2019. “Benchmarking Single-Cell Rna Sequencing Protocols for Cell Atlas Projects.” bioRxiv. doi:10.1101/630087.

Ziegenhain, C., B. Vieth, S. Parekh, B. Reinius, A. Guillaumet-Adkins, M. Smets, H. Leonhardt, H. Heyn, I. Hellmann, and W. Enard. 2017. “Comparative Analysis of Single-Cell RNA Sequencing Methods.” Mol. Cell 65 (4): 631–43.