# Zeisel mouse brain (STRT-Seq) ## Introduction Here, we examine a heterogeneous dataset from a study of cell types in the mouse brain [@zeisel2015brain]. This contains approximately 3000 cells of varying types such as oligodendrocytes, microglia and neurons. Individual cells were isolated using the Fluidigm C1 microfluidics system [@pollen2014lowcoverage] and library preparation was performed on each cell using a UMI-based protocol. After sequencing, expression was quantified by counting the number of unique molecular identifiers (UMIs) mapped to each gene. ## Data loading We obtain a `SingleCellExperiment` object for this dataset using the relevant function from the *[scRNAseq](https://bioconductor.org/packages/3.16/scRNAseq)* package. The idiosyncrasies of the published dataset means that we need to do some extra work to merge together redundant rows corresponding to alternative genomic locations for the same gene. ```r library(scRNAseq) sce.zeisel <- ZeiselBrainData() library(scater) sce.zeisel <- aggregateAcrossFeatures(sce.zeisel, id=sub("_loc[0-9]+$", "", rownames(sce.zeisel))) ``` We also fetch the Ensembl gene IDs, just in case we need them later. ```r library(org.Mm.eg.db) rowData(sce.zeisel)$Ensembl <- mapIds(org.Mm.eg.db, keys=rownames(sce.zeisel), keytype="SYMBOL", column="ENSEMBL") ``` ## Quality control ```r unfiltered <- sce.zeisel ``` The original authors of the study have already removed low-quality cells prior to data publication. Nonetheless, we compute some quality control metrics to check whether the remaining cells are satisfactory. ```r stats <- perCellQCMetrics(sce.zeisel, subsets=list( Mt=rowData(sce.zeisel)$featureType=="mito")) qc <- quickPerCellQC(stats, percent_subsets=c("altexps_ERCC_percent", "subsets_Mt_percent")) sce.zeisel <- sce.zeisel[,!qc$discard] ``` ```r colData(unfiltered) <- cbind(colData(unfiltered), stats) unfiltered$discard <- qc$discard gridExtra::grid.arrange( plotColData(unfiltered, y="sum", colour_by="discard") + scale_y_log10() + ggtitle("Total count"), plotColData(unfiltered, y="detected", colour_by="discard") + scale_y_log10() + ggtitle("Detected features"), plotColData(unfiltered, y="altexps_ERCC_percent", colour_by="discard") + ggtitle("ERCC percent"), plotColData(unfiltered, y="subsets_Mt_percent", colour_by="discard") + ggtitle("Mito percent"), ncol=2 ) ```
Distribution of each QC metric across cells in the Zeisel brain dataset. Each point represents a cell and is colored according to whether that cell was discarded.

(\#fig:unref-zeisel-qc-dist)Distribution of each QC metric across cells in the Zeisel brain dataset. Each point represents a cell and is colored according to whether that cell was discarded.

```r gridExtra::grid.arrange( plotColData(unfiltered, x="sum", y="subsets_Mt_percent", colour_by="discard") + scale_x_log10(), plotColData(unfiltered, x="altexps_ERCC_percent", y="subsets_Mt_percent", colour_by="discard"), ncol=2 ) ```
Percentage of mitochondrial reads in each cell in the Zeisel brain dataset, compared to the total count (left) or the percentage of spike-in reads (right). Each point represents a cell and is colored according to whether that cell was discarded.

(\#fig:unref-zeisel-qc-comp)Percentage of mitochondrial reads in each cell in the Zeisel brain dataset, compared to the total count (left) or the percentage of spike-in reads (right). Each point represents a cell and is colored according to whether that cell was discarded.

We also examine the number of cells removed for each reason. ```r colSums(as.matrix(qc)) ``` ``` ## low_lib_size low_n_features high_altexps_ERCC_percent ## 0 3 65 ## high_subsets_Mt_percent discard ## 128 189 ``` ## Normalization ```r library(scran) set.seed(1000) clusters <- quickCluster(sce.zeisel) sce.zeisel <- computeSumFactors(sce.zeisel, cluster=clusters) sce.zeisel <- logNormCounts(sce.zeisel) ``` ```r summary(sizeFactors(sce.zeisel)) ``` ``` ## Min. 1st Qu. Median Mean 3rd Qu. Max. ## 0.119 0.486 0.831 1.000 1.321 4.509 ``` ```r plot(librarySizeFactors(sce.zeisel), sizeFactors(sce.zeisel), pch=16, xlab="Library size factors", ylab="Deconvolution factors", log="xy") ```
Relationship between the library size factors and the deconvolution size factors in the Zeisel brain dataset.

(\#fig:unref-zeisel-norm)Relationship between the library size factors and the deconvolution size factors in the Zeisel brain dataset.

## Variance modelling In theory, we should block on the plate of origin for each cell. However, only 20-40 cells are available on each plate, and the population is also highly heterogeneous. This means that we cannot assume that the distribution of sampled cell types on each plate is the same. Thus, to avoid regressing out potential biology, we will not block on any factors in this analysis. ```r dec.zeisel <- modelGeneVarWithSpikes(sce.zeisel, "ERCC") top.hvgs <- getTopHVGs(dec.zeisel, prop=0.1) ``` We see from Figure \@ref(fig:unref-zeisel-var) that the technical and total variances are much smaller than those in the read-based datasets. This is due to the use of UMIs, which reduces the noise caused by variable PCR amplification. Furthermore, the spike-in trend is consistently lower than the variances of the endogenous gene, which reflects the heterogeneity in gene expression across cells of different types. ```r plot(dec.zeisel$mean, dec.zeisel$total, pch=16, cex=0.5, xlab="Mean of log-expression", ylab="Variance of log-expression") curfit <- metadata(dec.zeisel) points(curfit$mean, curfit$var, col="red", pch=16) curve(curfit$trend(x), col='dodgerblue', add=TRUE, lwd=2) ```
Per-gene variance as a function of the mean for the log-expression values in the Zeisel brain dataset. Each point represents a gene (black) with the mean-variance trend (blue) fitted to the spike-in transcripts (red).

(\#fig:unref-zeisel-var)Per-gene variance as a function of the mean for the log-expression values in the Zeisel brain dataset. Each point represents a gene (black) with the mean-variance trend (blue) fitted to the spike-in transcripts (red).

## Dimensionality reduction ```r library(BiocSingular) set.seed(101011001) sce.zeisel <- denoisePCA(sce.zeisel, technical=dec.zeisel, subset.row=top.hvgs) sce.zeisel <- runTSNE(sce.zeisel, dimred="PCA") ``` We have a look at the number of PCs retained by `denoisePCA()`. ```r ncol(reducedDim(sce.zeisel, "PCA")) ``` ``` ## [1] 50 ``` ## Clustering ```r snn.gr <- buildSNNGraph(sce.zeisel, use.dimred="PCA") colLabels(sce.zeisel) <- factor(igraph::cluster_walktrap(snn.gr)$membership) ``` ```r table(colLabels(sce.zeisel)) ``` ``` ## ## 1 2 3 4 5 6 7 8 9 10 11 12 13 14 ## 283 451 114 143 599 167 191 128 350 70 199 58 39 24 ``` ```r plotTSNE(sce.zeisel, colour_by="label") ```
Obligatory $t$-SNE plot of the Zeisel brain dataset, where each point represents a cell and is colored according to the assigned cluster.

(\#fig:unref-zeisel-tsne)Obligatory $t$-SNE plot of the Zeisel brain dataset, where each point represents a cell and is colored according to the assigned cluster.

## Interpretation We focus on upregulated marker genes as these can quickly provide positive identification of cell type in a heterogeneous population. We examine the table for cluster 1, in which log-fold changes are reported between cluster 1 and every other cluster. The same output is provided for each cluster in order to identify genes that discriminate between clusters. ```r markers <- findMarkers(sce.zeisel, direction="up") marker.set <- markers[["1"]] head(marker.set[,1:8], 10) # only first 8 columns, for brevity ``` ``` ## DataFrame with 10 rows and 8 columns ## Top p.value FDR summary.logFC logFC.2 logFC.3 ## ## Atp1a3 1 1.45982e-282 7.24035e-279 3.45669 0.0398568 0.0893943 ## Celf4 1 2.27030e-246 4.50404e-243 3.10465 0.3886716 0.6145023 ## Gad1 1 7.44925e-232 1.34351e-228 4.57719 4.5392751 4.3003280 ## Gad2 1 2.88086e-207 3.57208e-204 4.25393 4.2322487 3.8884654 ## Mllt11 1 1.72982e-249 3.81309e-246 2.88363 0.5782719 1.4933128 ## Ndrg4 1 0.00000e+00 0.00000e+00 3.84337 0.8887239 1.0183408 ## Slc32a1 1 2.38276e-110 4.04030e-108 1.92859 1.9196173 1.8252062 ## Syngr3 1 3.68257e-143 1.30462e-140 2.55531 1.0981258 1.1994793 ## Atp6v1g2 2 3.04451e-204 3.55295e-201 2.50875 0.0981706 0.5203760 ## Napb 2 1.10402e-231 1.82522e-228 2.81533 0.1774508 0.3046901 ## logFC.4 logFC.5 ## ## Atp1a3 1.241388 3.45669 ## Celf4 0.869334 3.10465 ## Gad1 4.050305 4.47236 ## Gad2 3.769556 4.16902 ## Mllt11 0.951649 2.88363 ## Ndrg4 1.140041 3.84337 ## Slc32a1 1.804311 1.92426 ## Syngr3 1.188856 2.47696 ## Atp6v1g2 0.616391 2.50875 ## Napb 0.673772 2.81533 ``` Figure \@ref(fig:unref-zeisel-heat-cell) indicates that most of the top markers are strongly DE in cells of cluster 1 compared to some or all of the other clusters. We can use these markers to identify cells from cluster 1 in validation studies with an independent population of cells. A quick look at the markers suggest that cluster 1 represents interneurons based on expression of *Gad1* and *Slc6a1* [@zeng2012largescale]. ```r top.markers <- rownames(marker.set)[marker.set$Top <= 10] plotHeatmap(sce.zeisel, features=top.markers, order_columns_by="label") ```
Heatmap of the log-expression of the top markers for cluster 1 compared to each other cluster. Cells are ordered by cluster and the color is scaled to the log-expression of each gene in each cell.

(\#fig:unref-zeisel-heat-cell)Heatmap of the log-expression of the top markers for cluster 1 compared to each other cluster. Cells are ordered by cluster and the color is scaled to the log-expression of each gene in each cell.

An alternative visualization approach is to plot the log-fold changes to all other clusters directly (Figure \@ref(fig:unref-zeisel-heat-lfc)). This is more concise and is useful in situations involving many clusters that contain different numbers of cells. ```r library(pheatmap) logFCs <- getMarkerEffects(marker.set[1:50,]) pheatmap(logFCs, breaks=seq(-5, 5, length.out=101)) ```
Heatmap of the log-fold changes of the top markers for cluster 1 compared to each other cluster.

(\#fig:unref-zeisel-heat-lfc)Heatmap of the log-fold changes of the top markers for cluster 1 compared to each other cluster.

## Session Info {-}
``` R version 4.2.1 (2022-06-23) Platform: x86_64-pc-linux-gnu (64-bit) Running under: Ubuntu 20.04.5 LTS Matrix products: default BLAS: /home/biocbuild/bbs-3.16-bioc/R/lib/libRblas.so LAPACK: /home/biocbuild/bbs-3.16-bioc/R/lib/libRlapack.so locale: [1] LC_CTYPE=en_US.UTF-8 LC_NUMERIC=C [3] LC_TIME=en_GB LC_COLLATE=C [5] LC_MONETARY=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8 [7] LC_PAPER=en_US.UTF-8 LC_NAME=C [9] LC_ADDRESS=C LC_TELEPHONE=C [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C attached base packages: [1] stats4 stats graphics grDevices utils datasets methods [8] base other attached packages: [1] pheatmap_1.0.12 BiocSingular_1.14.0 [3] scran_1.26.0 org.Mm.eg.db_3.16.0 [5] AnnotationDbi_1.60.0 scater_1.26.0 [7] ggplot2_3.3.6 scuttle_1.8.0 [9] scRNAseq_2.11.0 SingleCellExperiment_1.20.0 [11] SummarizedExperiment_1.28.0 Biobase_2.58.0 [13] GenomicRanges_1.50.0 GenomeInfoDb_1.34.0 [15] IRanges_2.32.0 S4Vectors_0.36.0 [17] BiocGenerics_0.44.0 MatrixGenerics_1.10.0 [19] matrixStats_0.62.0 BiocStyle_2.26.0 [21] rebook_1.8.0 loaded via a namespace (and not attached): [1] AnnotationHub_3.6.0 BiocFileCache_2.6.0 [3] igraph_1.3.5 lazyeval_0.2.2 [5] BiocParallel_1.32.0 digest_0.6.30 [7] ensembldb_2.22.0 htmltools_0.5.3 [9] viridis_0.6.2 fansi_1.0.3 [11] magrittr_2.0.3 memoise_2.0.1 [13] ScaledMatrix_1.6.0 cluster_2.1.4 [15] limma_3.54.0 Biostrings_2.66.0 [17] prettyunits_1.1.1 colorspace_2.0-3 [19] blob_1.2.3 rappdirs_0.3.3 [21] ggrepel_0.9.1 xfun_0.34 [23] dplyr_1.0.10 crayon_1.5.2 [25] RCurl_1.98-1.9 jsonlite_1.8.3 [27] graph_1.76.0 glue_1.6.2 [29] gtable_0.3.1 zlibbioc_1.44.0 [31] XVector_0.38.0 DelayedArray_0.24.0 [33] scales_1.2.1 edgeR_3.40.0 [35] DBI_1.1.3 Rcpp_1.0.9 [37] viridisLite_0.4.1 xtable_1.8-4 [39] progress_1.2.2 dqrng_0.3.0 [41] bit_4.0.4 rsvd_1.0.5 [43] metapod_1.6.0 httr_1.4.4 [45] RColorBrewer_1.1-3 dir.expiry_1.6.0 [47] ellipsis_0.3.2 farver_2.1.1 [49] pkgconfig_2.0.3 XML_3.99-0.12 [51] CodeDepends_0.6.5 sass_0.4.2 [53] dbplyr_2.2.1 locfit_1.5-9.6 [55] utf8_1.2.2 labeling_0.4.2 [57] tidyselect_1.2.0 rlang_1.0.6 [59] later_1.3.0 munsell_0.5.0 [61] BiocVersion_3.16.0 tools_4.2.1 [63] cachem_1.0.6 cli_3.4.1 [65] generics_0.1.3 RSQLite_2.2.18 [67] ExperimentHub_2.6.0 evaluate_0.17 [69] stringr_1.4.1 fastmap_1.1.0 [71] yaml_2.3.6 knitr_1.40 [73] bit64_4.0.5 purrr_0.3.5 [75] KEGGREST_1.38.0 AnnotationFilter_1.22.0 [77] sparseMatrixStats_1.10.0 mime_0.12 [79] xml2_1.3.3 biomaRt_2.54.0 [81] compiler_4.2.1 beeswarm_0.4.0 [83] filelock_1.0.2 curl_4.3.3 [85] png_0.1-7 interactiveDisplayBase_1.36.0 [87] statmod_1.4.37 tibble_3.1.8 [89] bslib_0.4.0 stringi_1.7.8 [91] highr_0.9 GenomicFeatures_1.50.0 [93] bluster_1.8.0 lattice_0.20-45 [95] ProtGenerics_1.30.0 Matrix_1.5-1 [97] vctrs_0.5.0 pillar_1.8.1 [99] lifecycle_1.0.3 BiocManager_1.30.19 [101] jquerylib_0.1.4 BiocNeighbors_1.16.0 [103] cowplot_1.1.1 bitops_1.0-7 [105] irlba_2.3.5.1 httpuv_1.6.6 [107] rtracklayer_1.58.0 R6_2.5.1 [109] BiocIO_1.8.0 bookdown_0.29 [111] promises_1.2.0.1 gridExtra_2.3 [113] vipor_0.4.5 codetools_0.2-18 [115] assertthat_0.2.1 rjson_0.2.21 [117] withr_2.5.0 GenomicAlignments_1.34.0 [119] Rsamtools_2.14.0 GenomeInfoDbData_1.2.9 [121] parallel_4.2.1 hms_1.1.2 [123] grid_4.2.1 beachmat_2.14.0 [125] rmarkdown_2.17 DelayedMatrixStats_1.20.0 [127] Rtsne_0.16 shiny_1.7.3 [129] ggbeeswarm_0.6.0 restfulr_0.0.15 ```