Reduced dimension plotting is one
of the essential tools for the analysis of single cell data. However, as
the number of cells/nuclei in these these plots increases, the
usefulness of these plots decreases. Many cells are plotted on top of
each other obscuring information, even when taking advantage of
transparency settings. This package provides binning strategies of
cells/nuclei into hexagon cells. Plotting summarized information of all
cells/nuclei in their respective hexagon cells presents information
without obstructions. The package seemlessly works with the two most
common object classes for the storage of single cell data;
SingleCellExperiment
from the SingleCellExperiment
package and Seurat
from the Seurat package.
In order to demonstrate the capabilities of the schex package, I will
use the a dataset of Peripheral Blood Mononuclear Cells (PBMC) freely
available from 10x Genomics. There are 2,700 single cells that were
sequenced on the Illumina NextSeq 500. This data is handly availabe in
the TENxPBMCData
package.
tenx_pbmc3k <- TENxPBMCData(dataset = "pbmc3k")
#> see ?TENxPBMCData and browseVignettes('TENxPBMCData') for documentation
#> downloading 1 resources
#> retrieving 1 resource
#> loading from cache
rownames(tenx_pbmc3k) <- uniquifyFeatureNames(
rowData(tenx_pbmc3k)$ENSEMBL_ID,
rowData(tenx_pbmc3k)$Symbol_TENx
)
In the next few sections, I will perform some simple quality control steps including filtering and normalization. I will then calculate various dimension reductions and cluster the data. These steps do by no means constitute comprehensive handling of the data. For a more detailed guide the reader is referred to the following guides:
I filter cells with high mitochondrial content as well as cells with low library size or feature count.
rowData(tenx_pbmc3k)$Mito <- grepl("^MT-", rownames(tenx_pbmc3k))
colData(tenx_pbmc3k) <- cbind(
colData(tenx_pbmc3k),
perCellQCMetrics(tenx_pbmc3k,
subsets = list(Mt = rowData(tenx_pbmc3k)$Mito)
)
)
rowData(tenx_pbmc3k) <- cbind(
rowData(tenx_pbmc3k),
perFeatureQCMetrics(tenx_pbmc3k)
)
tenx_pbmc3k <- tenx_pbmc3k[, !colData(tenx_pbmc3k)$subsets_Mt_percent > 50]
libsize_drop <- isOutlier(tenx_pbmc3k$total,
nmads = 3, type = "lower", log = TRUE
)
feature_drop <- isOutlier(tenx_pbmc3k$detected,
nmads = 3, type = "lower", log = TRUE
)
tenx_pbmc3k <- tenx_pbmc3k[, !(libsize_drop | feature_drop)]
I filter any genes that have 0 count for all cells.
I normalize the data by using a simple library size normalization.
I use both Principal Components Analysis (PCA) and Uniform Manifold Approximation and Projection (UMAP) in order to obtain reduced dimension representations of the data. Since there is a random component in the UMAP, I will also set a seed.
At this stage in the workflow we usually would like to plot aspects of our data in one of the reduced dimension representations. Instead of plotting this in an ordinary fashion, I will demonstrate how schex can provide a better way of plotting this.
First, I will calculate the hexagon cell representation for each cell
for a specified dimension reduction representation. I decide to use
nbins=40
which specifies that I divide my x range into 40
bins. Note that this might be a parameter that you want to play around
with depending on the number of cells/ nuclei in your dataset.
Generally, for more cells/nuclei, nbins
should be
increased.
First I plot how many cells are in each hexagon cell. This should be
relatively even, otherwise change the nbins
parameter in
the previous calculation.
Next I colour the hexagon cells by some meta information, such as the majority of cells cluster membership and the median total count in each hexagon cell.
While for plotting the cluster membership the outcome is not too different from the classic plot, it is much easier to observe differences in the total count.
For convenience there is also a function that allows the calculation
of label positions for factor variables. These can be overlayed with the
package ggrepel
.
Finally, I will visualize the gene expression of the POMGNT1 gene in the hexagon cell representation.
gene_id <- "POMGNT1"
plot_hexbin_feature(tenx_pbmc3k,
type = "logcounts", feature = gene_id,
action = "mean", xlab = "UMAP1", ylab = "UMAP2",
title = paste0("Mean of ", gene_id)
)
Again it is much easier to observe differences in gene expression using the hexagon cell representation than the classic representation.
We can overlay the gene expression data with the clusters for convenience.
schex
output as ggplot
objectsThe schex
packages renders ordinary ggplot
objects and thus these can be treated and manipulated using the ggplot
grammar.
For example the non-data components of the plots can be changed using
the function theme
.
gene_id <- "CD19"
gg <- schex::plot_hexbin_feature(tenx_pbmc3k,
type = "logcounts", feature = gene_id,
action = "mean", xlab = "UMAP1", ylab = "UMAP2",
title = paste0("Mean of ", gene_id)
)
gg + theme_void()
The fact that schex
renders ggplot
objects
can also be used to save these plots. Simply use ggsave
in
order to save any created plot.
To find the details of the session for reproducibility, use this:
sessionInfo()
#> R version 4.4.2 (2024-10-31)
#> Platform: x86_64-pc-linux-gnu
#> Running under: Ubuntu 24.04.1 LTS
#>
#> Matrix products: default
#> BLAS: /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3
#> LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.26.so; LAPACK version 3.12.0
#>
#> locale:
#> [1] LC_CTYPE=en_US.UTF-8 LC_NUMERIC=C
#> [3] LC_TIME=en_US.UTF-8 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
#>
#> time zone: Etc/UTC
#> tzcode source: system (glibc)
#>
#> attached base packages:
#> [1] stats4 stats graphics grDevices utils datasets methods
#> [8] base
#>
#> other attached packages:
#> [1] ggrepel_0.9.6 scran_1.35.0
#> [3] scater_1.35.0 scuttle_1.17.0
#> [5] TENxPBMCData_1.24.0 HDF5Array_1.35.2
#> [7] rhdf5_2.51.1 DelayedArray_0.33.3
#> [9] SparseArray_1.7.2 S4Arrays_1.7.1
#> [11] abind_1.4-8 Matrix_1.7-1
#> [13] igraph_2.1.2 Seurat_5.1.0
#> [15] SeuratObject_5.0.2 sp_2.1-4
#> [17] dplyr_1.1.4 schex_1.21.0
#> [19] SingleCellExperiment_1.29.1 SummarizedExperiment_1.37.0
#> [21] Biobase_2.67.0 GenomicRanges_1.59.1
#> [23] GenomeInfoDb_1.43.2 IRanges_2.41.2
#> [25] S4Vectors_0.45.2 BiocGenerics_0.53.3
#> [27] generics_0.1.3 MatrixGenerics_1.19.0
#> [29] matrixStats_1.4.1 ggplot2_3.5.1
#> [31] rmarkdown_2.29
#>
#> loaded via a namespace (and not attached):
#> [1] spatstat.sparse_3.1-0 httr_1.4.7 RColorBrewer_1.1-3
#> [4] tools_4.4.2 sctransform_0.4.1 utf8_1.2.4
#> [7] R6_2.5.1 lazyeval_0.2.2 uwot_0.2.2
#> [10] rhdf5filters_1.19.0 withr_3.0.2 gridExtra_2.3
#> [13] progressr_0.15.1 cli_3.6.3 spatstat.explore_3.3-3
#> [16] fastDummies_1.7.4 labeling_0.4.3 entropy_1.3.1
#> [19] sass_0.4.9 spatstat.data_3.1-4 ggridges_0.5.6
#> [22] pbapply_1.7-2 systemfonts_1.1.0 parallelly_1.40.1
#> [25] limma_3.63.2 RSQLite_2.3.9 FNN_1.1.4.1
#> [28] ica_1.0-3 spatstat.random_3.3-2 ggbeeswarm_0.7.2
#> [31] fansi_1.0.6 lifecycle_1.0.4 yaml_2.3.10
#> [34] edgeR_4.5.1 BiocFileCache_2.15.0 Rtsne_0.17
#> [37] grid_4.4.2 blob_1.2.4 promises_1.3.2
#> [40] dqrng_0.4.1 ExperimentHub_2.15.0 crayon_1.5.3
#> [43] miniUI_0.1.1.1 lattice_0.22-6 beachmat_2.23.4
#> [46] cowplot_1.1.3 KEGGREST_1.47.0 sys_3.4.3
#> [49] maketools_1.3.1 pillar_1.9.0 knitr_1.49
#> [52] metapod_1.15.0 future.apply_1.11.3 codetools_0.2-20
#> [55] leiden_0.4.3.1 glue_1.8.0 V8_6.0.0
#> [58] spatstat.univar_3.1-1 data.table_1.16.4 vctrs_0.6.5
#> [61] png_0.1-8 spam_2.11-0 gtable_0.3.6
#> [64] cachem_1.1.0 xfun_0.49 mime_0.12
#> [67] survival_3.7-0 statmod_1.5.0 bluster_1.17.0
#> [70] fitdistrplus_1.2-1 ROCR_1.0-11 nlme_3.1-166
#> [73] bit64_4.5.2 filelock_1.0.3 RcppAnnoy_0.0.22
#> [76] bslib_0.8.0 irlba_2.3.5.1 vipor_0.4.7
#> [79] KernSmooth_2.23-24 colorspace_2.1-1 DBI_1.2.3
#> [82] tidyselect_1.2.1 bit_4.5.0.1 compiler_4.4.2
#> [85] curl_6.0.1 BiocNeighbors_2.1.2 plotly_4.10.4
#> [88] scales_1.3.0 lmtest_0.9-40 hexbin_1.28.5
#> [91] rappdirs_0.3.3 stringr_1.5.1 digest_0.6.37
#> [94] goftest_1.2-3 spatstat.utils_3.1-1 XVector_0.47.0
#> [97] htmltools_0.5.8.1 pkgconfig_2.0.3 dbplyr_2.5.0
#> [100] fastmap_1.2.0 rlang_1.1.4 htmlwidgets_1.6.4
#> [103] UCSC.utils_1.3.0 shiny_1.10.0 farver_2.1.2
#> [106] jquerylib_0.1.4 zoo_1.8-12 jsonlite_1.8.9
#> [109] BiocParallel_1.41.0 BiocSingular_1.23.0 magrittr_2.0.3
#> [112] GenomeInfoDbData_1.2.13 dotCall64_1.2 patchwork_1.3.0
#> [115] Rhdf5lib_1.29.0 munsell_0.5.1 Rcpp_1.0.13-1
#> [118] viridis_0.6.5 reticulate_1.40.0 stringi_1.8.4
#> [121] zlibbioc_1.52.0 MASS_7.3-61 AnnotationHub_3.15.0
#> [124] plyr_1.8.9 parallel_4.4.2 listenv_0.9.1
#> [127] deldir_2.0-4 Biostrings_2.75.2 splines_4.4.2
#> [130] tensor_1.5 locfit_1.5-9.10 spatstat.geom_3.3-4
#> [133] RcppHNSW_0.6.0 buildtools_1.0.0 reshape2_1.4.4
#> [136] ScaledMatrix_1.15.0 BiocVersion_3.21.1 evaluate_1.0.1
#> [139] BiocManager_1.30.25 tweenr_2.0.3 httpuv_1.6.15
#> [142] RANN_2.6.2 tidyr_1.3.1 purrr_1.0.2
#> [145] polyclip_1.10-7 future_1.34.0 scattermore_1.2
#> [148] ggforce_0.4.2 rsvd_1.0.5 xtable_1.8-4
#> [151] RSpectra_0.16-2 later_1.4.1 viridisLite_0.4.2
#> [154] tibble_3.2.1 memoise_2.0.1 beeswarm_0.4.0
#> [157] AnnotationDbi_1.69.0 cluster_2.1.8 globals_0.16.3
#> [160] concaveman_1.1.0