densvis 1.17.0
Non-linear dimensionality reduction techniques such as t-SNE (Maaten and Hinton 2008)
and UMAP (McInnes, Healy, and Melville 2020) produce a low-dimensional embedding that summarises
the global structure of high-dimensional data. These techniques can be
particularly useful when visualising high-dimensional data in a biological
setting.
However, these embeddings may not accurately represent the local density
of data in the original space, resulting in misleading visualisations where
the space given to clusters of data does not represent the fraction of the
high dimensional space that they occupy.
densvis
implements the density-preserving objective function described by
(Narayan, Berger, and Cho 2020) which aims to address this deficiency by including a
density-preserving term in the t-SNE and UMAP optimisation procedures.
This can enable the creation of visualisations that accurately capture
differing degrees of transcriptional heterogeneity within different cell
subpopulations in scRNAseq experiments, for example.
We will illustrate the use of densvis
using simulated data.
We will first load the densvis
and Rtsne
libraries
and set a random seed to ensure the t-SNE visualisation is reproducible
(note: it is good practice to ensure that a t-SNE embedding is robust
by running the algorithm multiple times).
library("densvis")
library("Rtsne")
library("uwot")
library("ggplot2")
theme_set(theme_bw())
set.seed(14)
data <- data.frame(
x = c(rnorm(1000, 5), rnorm(1000, 0, 0.2)),
y = c(rnorm(1000, 5), rnorm(1000, 0, 0.2)),
class = c(rep("Class 1", 1000), rep("Class 2", 1000))
)
ggplot() +
aes(data[, 1], data[, 2], colour = data$class) +
geom_point(pch = 19) +
scale_colour_discrete(name = "Cluster") +
ggtitle("Original co-ordinates")
Density-preserving t-SNE can be generated using the densne
function. This function returns a matrix of t-SNE co-ordinates.
We set dens_frac
(the fraction of optimisation steps that consider
the density preservation) and dens_lambda
(the weight given to density
preservation relative to the standard t-SNE objective) each to 0.5.
fit1 <- densne(data[, 1:2], dens_frac = 0.5, dens_lambda = 0.5)
ggplot() +
aes(fit1[, 1], fit1[, 2], colour = data$class) +
geom_point(pch = 19) +
scale_colour_discrete(name = "Class") +
ggtitle("Density-preserving t-SNE") +
labs(x = "t-SNE 1", y = "t-SNE 2")
If we run t-SNE on the same data, we can see that the density-preserving objective better represents the density of the data,
fit2 <- Rtsne(data[, 1:2])
ggplot() +
aes(fit2$Y[, 1], fit2$Y[, 2], colour = data$class) +
geom_point(pch = 19) +
scale_colour_discrete(name = "Class") +
ggtitle("Standard t-SNE") +
labs(x = "t-SNE 1", y = "t-SNE 2")
A density-preserving UMAP embedding can be generated using the densmap
function. This function returns a matrix of UMAP co-ordinates. As with t-SNE,
we set dens_frac
(the fraction of optimisation steps that consider
the density preservation) and dens_lambda
(the weight given to density
preservation relative to the standard t-SNE objective) each to 0.5.
fit1 <- densmap(data[, 1:2], dens_frac = 0.5, dens_lambda = 0.5)
ggplot() +
aes(fit1[, 1], fit1[, 2], colour = data$class) +
geom_point(pch = 19) +
scale_colour_discrete(name = "Class") +
ggtitle("Density-preserving t-SNE") +
labs(x = "t-SNE 1", y = "t-SNE 2")
If we run UMAP on the same data, we can see that the density-preserving objective better represents the density of the data,
fit2 <- umap(data[, 1:2])
ggplot() +
aes(fit2[, 1], fit2[, 2], colour = data$class) +
geom_point(pch = 19) +
scale_colour_discrete(name = "Class") +
ggtitle("Standard t-SNE") +
labs(x = "t-SNE 1", y = "t-SNE 2")
sessionInfo()
#> R Under development (unstable) (2024-10-21 r87258)
#> Platform: x86_64-pc-linux-gnu
#> Running under: Ubuntu 24.04.1 LTS
#>
#> Matrix products: default
#> BLAS: /home/biocbuild/bbs-3.21-bioc/R/lib/libRblas.so
#> LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.12.0
#>
#> 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
#>
#> time zone: America/New_York
#> tzcode source: system (glibc)
#>
#> attached base packages:
#> [1] stats graphics grDevices utils datasets methods base
#>
#> other attached packages:
#> [1] ggplot2_3.5.1 uwot_0.2.2 Matrix_1.7-1 Rtsne_0.17
#> [5] densvis_1.17.0 BiocStyle_2.35.0
#>
#> loaded via a namespace (and not attached):
#> [1] sass_0.4.9 utf8_1.2.4 generics_0.1.3
#> [4] lattice_0.22-6 digest_0.6.37 magrittr_2.0.3
#> [7] evaluate_1.0.1 grid_4.5.0 bookdown_0.41
#> [10] fastmap_1.2.0 jsonlite_1.8.9 tinytex_0.53
#> [13] BiocManager_1.30.25 fansi_1.0.6 scales_1.3.0
#> [16] jquerylib_0.1.4 cli_3.6.3 rlang_1.1.4
#> [19] basilisk.utils_1.19.0 munsell_0.5.1 withr_3.0.2
#> [22] cachem_1.1.0 yaml_2.3.10 FNN_1.1.4.1
#> [25] tools_4.5.0 dir.expiry_1.15.0 parallel_4.5.0
#> [28] dplyr_1.1.4 colorspace_2.1-1 filelock_1.0.3
#> [31] basilisk_1.19.0 reticulate_1.39.0 assertthat_0.2.1
#> [34] vctrs_0.6.5 R6_2.5.1 png_0.1-8
#> [37] magick_2.8.5 lifecycle_1.0.4 pkgconfig_2.0.3
#> [40] bslib_0.8.0 pillar_1.9.0 gtable_0.3.6
#> [43] glue_1.8.0 Rcpp_1.0.13 xfun_0.48
#> [46] tibble_3.2.1 tidyselect_1.2.1 highr_0.11
#> [49] knitr_1.48 farver_2.1.2 htmltools_0.5.8.1
#> [52] rmarkdown_2.28 labeling_0.4.3 compiler_4.5.0