Trajectory.Rmd
scVelo
is a widely used tool for trajectory analysis
that leverages spliced and unspliced RNA information, as calculated by
Velocyto, to predict the direction of cell differentiation. For more
information, see scVelo’s
documentation. SeuratExtend
includes a series of
functions that allow you to easily run everything in R. In this
tutorial, we use an example dataset (pbmc10k_10X
)
downloaded from the 10X
Genomics website, focusing on a small subset of myeloid cells for
simplicity.
First, we need to download the example data, including a small Seurat object and a corresponding small loom file generated by Velocyto. These files are stored on Zenodo:
library(Seurat)
library(SeuratExtend)
# Download the example Seurat Object
mye_small <- readRDS(url("https://zenodo.org/records/10944066/files/pbmc10k_mye_small_velocyto.rds", "rb"))
# Download the example velocyto loom file
loom_path <- file.path(tempdir(), "pbmc10k_mye_small.loom")
download.file("https://zenodo.org/records/10944066/files/pbmc10k_mye_small.loom",
loom_path,
mode = "wb") # Use binary mode for Windows compatibility
Note:
mode = "wb"
in the
download.file() function. This prevents Windows from modifying line
endings in the binary file, which would corrupt the HDF5 format. Mac and
Linux users don’t require this parameter, but including it is
harmless.scVelo
requires an AnnData
object from
Python’s Scanpy
library for its analyses.
SeuratExtend
makes this process seamless by integrating a
Seurat object and a velocyto loom file into a new AnnData
object, serving as a bridge between R and Python.
Note: If this is your first time running
Python-related functions (scVelo
, Palantir
,
etc.) in SeuratExtend
, it will prompt you to install a
conda environment named “seuratextend”. This environment automatically
installs all required Python packages. It is currently supported and
tested on Windows, Intel-based macOS (not Apple Silicon/M1/M2), and
Linux (Ubuntu 20.04) systems. Users with Apple Silicon devices who are
interested in contributing to the development of M1/M2 support are
welcome to reach out via GitHub Issues. More details on managing the
Anndata
object and Python dependencies are provided here:
Integration of Seurat
with Python Tools.
# Set up the path for saving the AnnData object in the HDF5 (h5ad) format
if (.Platform$OS.type == "windows") {
adata_path <- normalizePath(file.path(tempdir(), "mye_small.h5ad"), winslash = "/")
} else {
adata_path <- file.path(tempdir(), "mye_small.h5ad")
}
# Integrate Seurat Object and velocyto loom information into one AnnData object.
# This object will be stored at the specified path.
scVelo.SeuratToAnndata(
mye_small, # The downloaded example Seurat object
filename = adata_path, # Path where the AnnData object will be saved
velocyto.loompath = loom_path, # Path to the loom file
prefix = "sample1_", # Prefix for cell IDs in the Seurat object
postfix = "-1" # Postfix for cell IDs in the Seurat object
)
## Running scvelo 0.3.0 (python 3.10.12) on 2024-11-28 17:07.
## Filtered out 10891 genes that are detected 20 counts (shared).
## Normalized count data: X, spliced, unspliced.
## Extracted 2000 highly variable genes.
## Logarithmized X.
## computing neighbors
## finished (0:00:04) --> added
## 'distances' and 'connectivities', weighted adjacency matrices (adata.obsp)
## computing moments based on connectivities
## finished (0:00:00) --> added
## 'Ms' and 'Mu', moments of un/spliced abundances (adata.layers)
## computing velocities
## finished (0:00:00) --> added
## 'velocity', velocity vectors for each individual cell (adata.layers)
## computing velocity graph (using 1/28 cores)
## WARNING: Unable to create progress bar. Consider installing `tqdm` as `pip install tqdm` and `ipywidgets` as `pip install ipywidgets`,
## or disable the progress bar using `show_progress_bar=False`.
## finished (0:00:01) --> added
## 'velocity_graph', sparse matrix with cosine correlations (adata.uns)
Now, you’re ready to generate scVelo plots. The default dimension reduction method is typically UMAP, colored by cluster.
scVelo.Plot(color = "cluster", save = "umap1.png", figsize = c(5,4))
SeuratExtend
supports most commonly used parameters for
scVelo plots. You can customize the plot style, dimensionality reduction
method, color palette, displayed clusters, and more. For more detailed
information on customization and advanced features, refer to the
function documentation or the official
scVelo documentation.
Palantir is a
Python tool designed for aligning cells along differentiation
trajectories, determining cell fate, and calculating pseudotime.
SeuratExtend
enables these functionalities within R,
storing the results in a Seurat object for streamlined analysis.
We will use a subset of myeloid cells from the
pbmc10k_10X
dataset available on the 10X Genomics website. If
you have already loaded the Seurat object, you can skip this step.
Palantir aligns cells along differentiation trajectories by first
calculating the diffusion map. This is done in Python via
reticulate
, based on pre-calculated dimension reductions in
Seurat (e.g., PCA, harmony). The diffusion map (dm
) and
multiscale space (ms
) embeddings are then added back to the
Seurat object.
mye_small <- Palantir.RunDM(mye_small)
## Determing nearest neighbor graph...
# View the new dimensional reductions "dm" and "ms" in the Seurat Object:
mye_small@reductions
## $pca
## A dimensional reduction object with key PC_
## Number of dimensions: 50
## Number of cells: 1000
## Projected dimensional reduction calculated: FALSE
## Jackstraw run: FALSE
## Computed using assay: RNA
##
## $umap
## A dimensional reduction object with key UMAP_
## Number of dimensions: 2
## Number of cells: 1000
## Projected dimensional reduction calculated: FALSE
## Jackstraw run: FALSE
## Computed using assay: RNA
##
## $dm
## A dimensional reduction object with key DM_
## Number of dimensions: 50
## Number of cells: 1000
## Projected dimensional reduction calculated: FALSE
## Jackstraw run: FALSE
## Computed using assay: RNA
##
## $ms
## A dimensional reduction object with key MS_
## Number of dimensions: 2
## Number of cells: 1000
## Projected dimensional reduction calculated: FALSE
## Jackstraw run: FALSE
## Computed using assay: RNA
# View the first 2 ms dimensions:
DimPlot2(mye_small, reduction = "ms", group.by = "cluster", label = TRUE)
In this simple example, the multiscale space (‘ms’) reduction has only 2 dimensions, which makes additional dimension reduction unnecessary for visualization. However, in more complex datasets, ‘ms’ reductions often yield more dimensions, necessitating further reduction for effective visualization.
For such cases, one can create a new differentiation trajectory space by running UMAP based on the “ms” reduction. This approach allows for a more comprehensible display in a two-dimensional plot, especially when dealing with a higher-dimensional ‘ms’ space. The following code, although not run in this tutorial, illustrates how to implement UMAP on ‘ms’ with hypothetical ten dimensions and visualize the result:
# Assuming 'ms' has 10 dimensions:
# mye_small <- RunUMAP(mye_small, reduction = "ms", dims = 1:10)
# DimPlot2(mye_small, group.by = "cluster", label = TRUE, reduction = "umap")
If you’ve previously followed the scVelo tutorial, an
adata
object should already exist in your Python
environment. If you wish to incorporate the dimension reductions
calculated by Palantir (such as ‘ms’) into this adata
and
use it for visualization, you can do so with the following commands:
# Add the 'ms' dimension reduction to the existing AnnData object
adata.AddDR(mye_small, dr = "ms", scv.graph = TRUE)
# Generate a plot using the newly added 'ms' dimension reduction
scVelo.Plot(basis = "ms", color = "cluster", save = "umap3_ms.png", figsize = c(5,4))
To predict cell fates and calculate pseudotime, You can manually
select “start cells” using the CellSelector
function.
p <- DimPlot(mye_small, reduction = "ms", group.by = "cluster")
cells <- CellSelector(p)
Then, calculate pseudotime. Results are stored in
SeuratObj@misc$Palantir$Pseudotime
.
mye_small <- Palantir.Pseudotime(mye_small, start_cell = "sample1_GAGAGGTAGCAGTACG-1")
## Sampling and flocking waypoints...
## Time for determining waypoints: 0.0011072675387064615 minutes
## Determining pseudotime...
## Shortest path distances using 30-nearest neighbor graph...
## Time for shortest paths: 0.10401349465052287 minutes
## Iteratively refining the pseudotime...
## Correlation at iteration 1: 1.0000
## Entropy and branch probabilities...
## Markov chain construction...
## Identification of terminal states...
## Computing fundamental matrix and absorption probabilities...
## Project results to all cells...
ps <- mye_small@misc$Palantir$Pseudotime
head(ps)
## Pseudotime Entropy sample1_AAACCCAAGGCCCAAA.1
## sample1_GAGTCATGTACCCGCA-1 0.0369992 0.6087369 0.7024933
## sample1_TGGAGGAGTGTATACC-1 0.1307173 0.6093996 0.7017203
## sample1_CCCGGAAGTTGGCTAT-1 0.1137811 0.6082388 0.7030721
## sample1_CTGAGGCAGTCAGGGT-1 0.2990304 0.4924385 0.8056717
## sample1_GCCAACGTCCCGTGTT-1 0.1455877 0.6137009 0.6966202
## sample1_TCAAGCAGTGTGGTCC-1 0.0759786 0.6087872 0.7024348
## sample1_CCTCTAGGTGAACGGT.1
## sample1_GAGTCATGTACCCGCA-1 0.2975067
## sample1_TGGAGGAGTGTATACC-1 0.2982797
## sample1_CCCGGAAGTTGGCTAT-1 0.2969279
## sample1_CTGAGGCAGTCAGGGT-1 0.1943283
## sample1_GCCAACGTCCCGTGTT-1 0.3033798
## sample1_TCAAGCAGTGTGGTCC-1 0.2975652
# Visualize cell fate on UMAP
colnames(ps)[3:4] <- c("fate1", "fate2")
mye_small@meta.data[,colnames(ps)] <- ps
DimPlot2(mye_small, features = colnames(ps), reduction = "ms",
cols = list(Entropy = "D"), theme = NoAxes())
## Loading required package: viridis
## Loading required package: viridisLite
Another related tool, MAGIC (Markov Affinity-based Graph Imputation of Cells), denoises and smooths gene expression in scRNA-seq data. Here’s how to run it and observe the differences in gene expression:
mye_small <- Palantir.Magic(mye_small)
## Determing nearest neighbor graph...
## Warning: Layer counts isn't present in the assay object; returning NULL
# Creates a new assay "magic" in the Seurat object
mye_small <- NormalizeData(mye_small)
DimPlot2(mye_small, features = c("CD14", "magic_CD14", "FLT3", "magic_FLT3"),
cols = "A", theme = NoAxes())
While scVelo is a powerful tool for predicting differentiation potential, it may sometimes produce arrows pointing opposite the known differentiation trajectory, which is inconsistent with biological knowledge. For more details and examples, visit the CellRank documentation. In such cases, CellRank offers an alternative by utilizing pre-calculated pseudotime to create trajectory plots. However, it’s crucial to ensure that the pseudotime calculation aligns with validated biological knowledge to avoid misuse of the algorithm.
This tutorial assumes you have already completed the tutorials for velocyto and Palantir. We will use CellRank to build trajectory plots, continuing from where those tutorials left off.
adata
Object
First, we add the Pseudotime calculated by Palantir to the
adata
object. This information is saved in the
meta.data
of the Seurat Object. We transfer the relevant
column from Seurat to adata
:
adata.AddMetadata(mye_small, col = colnames(ps))
Next, we run the CellRank Kernel to compute the trajectory plots using the previously added Pseudotime information.
Cellrank.Compute(time_key = "Pseudotime")
The process for generating plots with CellRank is similar to that
used with scVelo.Plot
:
# Generate a plot using the 'ms' dimension reduction
Cellrank.Plot(color = "cluster", basis = "ms", save = "umap3_ms_cellrank.png")
Visualizing smoothed gene expression along a trajectory offers
insightful perspectives on cellular differentiation and development.
SeuratExtend
introduces two methods for such visualization,
leveraging the pseudotime calculated by tools like
Palantir
.
Assuming you have already executed Palantir.Pseudotime
as shown in a previous tutorial, you can create gene trend curves as
follows:
library(SeuratExtend)
# Basic gene trend curve visualization
GeneTrendCurve.Palantir(mye_small, features = c("CD14", "FCGR3A"))
You can customize the plots, for instance, by adding points, removing confidence intervals, and renaming cell fates:
# Accessing pseudotime and adjusting fate names
ps <- mye_small@misc$Palantir$Pseudotime
head(ps)
## Pseudotime Entropy sample1_AAACCCAAGGCCCAAA.1
## sample1_GAGTCATGTACCCGCA-1 0.0369992 0.6087369 0.7024933
## sample1_TGGAGGAGTGTATACC-1 0.1307173 0.6093996 0.7017203
## sample1_CCCGGAAGTTGGCTAT-1 0.1137811 0.6082388 0.7030721
## sample1_CTGAGGCAGTCAGGGT-1 0.2990304 0.4924385 0.8056717
## sample1_GCCAACGTCCCGTGTT-1 0.1455877 0.6137009 0.6966202
## sample1_TCAAGCAGTGTGGTCC-1 0.0759786 0.6087872 0.7024348
## sample1_CCTCTAGGTGAACGGT.1
## sample1_GAGTCATGTACCCGCA-1 0.2975067
## sample1_TGGAGGAGTGTATACC-1 0.2982797
## sample1_CCCGGAAGTTGGCTAT-1 0.2969279
## sample1_CTGAGGCAGTCAGGGT-1 0.1943283
## sample1_GCCAACGTCCCGTGTT-1 0.3033798
## sample1_TCAAGCAGTGTGGTCC-1 0.2975652
colnames(ps)[3:4] <- c("fate1", "fate2")
# Customized gene trend curve visualization
GeneTrendCurve.Palantir(
mye_small,
pseudotime.data = ps,
features = c("CD14", "FCGR3A"),
point = TRUE,
se = FALSE
)
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'
Another powerful method for visualizing gene expression dynamics is through heatmaps, which can illustrate how gene expression changes across pseudotime and for specific lineages:
# Creating a gene trend heatmap
GeneTrendHeatmap.Palantir(
mye_small,
features = c("CD14", VariableFeatures(mye_small)[1:10]),
pseudotime.data = ps,
magic = FALSE,
lineage = "fate1"
)
Apart from Palantir, Slingshot
is another valuable tool
for calculating pseudotime, particularly useful for mapping cellular
trajectories in single-cell RNA sequencing data.
To calculate pseudotime using Slingshot, we start by integrating it with our Seurat object:
library(SeuratExtend)
mye_small <- RunSlingshot(mye_small, group.by = "cluster", start.clus = "Mono CD14")
The Slingshot output is stored within the Seurat object. Let’s extract the pseudotime values and visualize them on the UMAP:
sling <- mye_small@misc$slingshot$PCA$SlingPseudotime
head(sling)
## DataFrame with 6 rows and 2 columns
## slingPseudotime_1 slingPseudotime_2
## <numeric> <numeric>
## sample1_GAGTCATGTACCCGCA-1 12.3665 12.3423
## sample1_TGGAGGAGTGTATACC-1 20.2568 20.3749
## sample1_CCCGGAAGTTGGCTAT-1 19.6071 19.7093
## sample1_CTGAGGCAGTCAGGGT-1 26.1367 28.3753
## sample1_GCCAACGTCCCGTGTT-1 17.0883 17.5357
## sample1_TCAAGCAGTGTGGTCC-1 16.5014 16.4016
mye_small@meta.data[,colnames(sling)] <- as.data.frame(sling)
DimPlot2(mye_small, features = colnames(sling), cols = "C", theme = NoAxes())
Similar to Palantir, Slingshot pseudotime allows for the creation of gene trend curves, illustrating the dynamics of gene expression along the computed trajectories:
GeneTrendCurve.Slingshot(mye_small, features = c("CD14", "FCGR3A"))
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'
Additionally, Slingshot pseudotime can be used to generate heatmaps, showcasing the progression of gene expression across different lineages:
GeneTrendHeatmap.Slingshot(
mye_small,
features = c("CD14", VariableFeatures(mye_small)[1:10]),
lineage = "slingPseudotime_2"
)
## Using id as id variables
SeuratExtend
incorporates numerous functions that
utilize Python packages such as Scanpy
,
scVelo
, Palantir
, and CellRank
.
This integration facilitates a seamless workflow between
Seurat
objects and AnnData
formats, crucial
for leveraging the comprehensive analytical capabilities of both R and
Python ecosystems.
To understand the mechanisms behind these functions, it is important
to grasp the fundamentals of how Seurat
and
AnnData
communicate. Initially, loom
files
serve as the intermediary; these h5 format files are widely used for
storing scRNA-seq data and are compatible with both R and Python. Tools
such as velocyto
and SCENIC
utilize loom
files, which can store gene expression and metadata but have
limitations, such as the inability to store dimensional reduction and
neighborhood graph information.
In SeuratExtend
, the conversion from a
Seurat
object to an AnnData
object does not
occur directly. Instead, loom
files act as a bridge—first
converting Seurat
to loom
and then from
loom
to AnnData.
The loss of dimensional
reduction information during this process is compensated by other
related functions within SeuratExtend
.
If it is your first time running functions related to
scVelo
, Palantir
, or CellRank
,
you will be automatically prompted to install a Conda environment named
“seuratextend”. This environment includes all the Python packages
required by SeuratExtend
and is managed through
reticulate
.
Note: Before creating the conda environment, ensure that Git is installed on your system, as it’s required for installing certain Python packages:
brew install git
) or Xcode Command Line Tools
(xcode-select --install
)sudo apt-get install git
sudo dnf install git
sudo yum install git
If you prefer to initiate the conda environment installation step manually, you can easily do so by running:
This function automatically detects your operating system and sets up the environment accordingly. It is currently supported and tested on Windows, Intel-based macOS (not Apple Silicon/M1/M2), and Linux (Ubuntu 20.04) systems. Users with Apple Silicon devices who are interested in contributing to the development of M1/M2 support are welcome to reach out via GitHub Issues.
In this section, we demonstrate the utility of functions designed to
bridge Seurat
objects with Python’s AnnData
structures, using a small pbmc
Seurat object as an example.
The process covers conversion to a loom
file, importing
into AnnData
, and appending additional metadata and
dimension reduction data.
First, we convert a Seurat
object to a loom
file which can then be used to create an AnnData
object:
Next, we import the loom
file into an
AnnData
object, which is handled by Python:
# Load the loom file into an AnnData object
adata.LoadLoom(loompath = pbmc_loom_path)
To view the newly created AnnData
object, we use
reticulate
to execute Python code:
# Print the AnnData object details
py_run_string("print(adata)")
## AnnData object with n_obs × n_vars = 500 × 12627
## obs: 'RNA_snn_res.0.5', 'cluster', 'nCount_RNA', 'nFeature_RNA', 'orig.ident', 'percent.mt', 'seurat_clusters'
## layers: 'counts'
AnnData
objects can store more than just gene expression
and metadata; here we add dimension reduction data from
Seurat
:
# Add dimension reduction data from Seurat to AnnData
adata.AddDR(pbmc)
# Print the updated AnnData object
py_run_string("print(adata)")
## AnnData object with n_obs × n_vars = 500 × 12627
## obs: 'RNA_snn_res.0.5', 'cluster', 'nCount_RNA', 'nFeature_RNA', 'orig.ident', 'percent.mt', 'seurat_clusters'
## obsm: 'X_pca', 'X_umap'
## layers: 'counts'
For a streamlined conversion that includes the steps of
Seu2Loom()
, adata.LoadLoom()
and
adata.AddDR()
, you can use a single function:
# Directly convert Seurat object to AnnData object
Seu2Adata(pbmc)
If new metadata columns are added to the Seurat
object,
they can also be incorporated into the AnnData
object:
# Add new metadata column to AnnData
pbmc$cluster2 <- pbmc$cluster
adata.AddMetadata(pbmc, col = "cluster2")
# Print the updated AnnData object
py_run_string("print(adata)")
## AnnData object with n_obs × n_vars = 500 × 12627
## obs: 'RNA_snn_res.0.5', 'cluster', 'nCount_RNA', 'nFeature_RNA', 'orig.ident', 'percent.mt', 'seurat_clusters', 'cluster2'
## obsm: 'X_pca', 'X_umap'
## layers: 'counts'
You can save the AnnData
object to a local file for
future use:
# Save the AnnData object to a local file
pbmc_adata_path <- file.path(tempdir(), "pbmc3k_small.h5ad")
adata.Save(pbmc_adata_path)
And load it back when needed:
# Load an existing AnnData object from file
adata.Load(pbmc_adata_path)
If you have completed the scVelo tutorial and saved the results, you
can load the AnnData
object and directly generate
plots:
# Load the scVelo tutorial results and generate a plot
adata.Load(adata_path)
scVelo.Plot(color = "cluster", figsize = c(5,4), save = "umap4_load_adata.png")
## R version 4.4.0 (2024-04-24)
## Platform: x86_64-pc-linux-gnu
## Running under: Ubuntu 20.04.6 LTS
##
## Matrix products: default
## BLAS: /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.9.0
## LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.9.0
##
## locale:
## [1] LC_CTYPE=en_US.UTF-8 LC_NUMERIC=C
## [3] LC_TIME=de_BE.UTF-8 LC_COLLATE=en_US.UTF-8
## [5] LC_MONETARY=de_BE.UTF-8 LC_MESSAGES=en_US.UTF-8
## [7] LC_PAPER=de_BE.UTF-8 LC_NAME=C
## [9] LC_ADDRESS=C LC_TELEPHONE=C
## [11] LC_MEASUREMENT=de_BE.UTF-8 LC_IDENTIFICATION=C
##
## time zone: Europe/Brussels
## tzcode source: system (glibc)
##
## attached base packages:
## [1] stats4 tools stats graphics grDevices utils datasets
## [8] methods base
##
## other attached packages:
## [1] slingshot_2.12.0 TrajectoryUtils_1.12.0
## [3] SingleCellExperiment_1.26.0 SummarizedExperiment_1.34.0
## [5] Biobase_2.64.0 GenomicRanges_1.56.1
## [7] GenomeInfoDb_1.40.1 IRanges_2.38.1
## [9] S4Vectors_0.42.1 BiocGenerics_0.50.0
## [11] MatrixGenerics_1.16.0 matrixStats_1.3.0
## [13] princurve_2.1.6 rlang_1.1.4
## [15] scales_1.3.0 dplyr_1.1.4
## [17] mgcv_1.9-1 nlme_3.1-165
## [19] reshape2_1.4.4 viridis_0.6.5
## [21] viridisLite_0.4.2 cowplot_1.1.3
## [23] RColorBrewer_1.1-3 ggplot2_3.5.1
## [25] reticulate_1.38.0 glue_1.7.0
## [27] hdf5r_1.3.11 SeuratExtend_1.1.0
## [29] SeuratExtendData_0.2.1 Seurat_5.1.0
## [31] SeuratObject_5.0.2 sp_2.1-4
##
## loaded via a namespace (and not attached):
## [1] RcppAnnoy_0.0.22 splines_4.4.0
## [3] later_1.3.2 tibble_3.2.1
## [5] polyclip_1.10-6 fastDummies_1.7.3
## [7] lifecycle_1.0.4 globals_0.16.3
## [9] lattice_0.22-6 MASS_7.3-61
## [11] magrittr_2.0.3 plotly_4.10.4
## [13] sass_0.4.9 rmarkdown_2.29
## [15] jquerylib_0.1.4 yaml_2.3.9
## [17] rlist_0.4.6.2 httpuv_1.6.15
## [19] sctransform_0.4.1 spam_2.10-0
## [21] spatstat.sparse_3.1-0 pbapply_1.7-2
## [23] abind_1.4-5 zlibbioc_1.50.0
## [25] Rtsne_0.17 purrr_1.0.2
## [27] rappdirs_0.3.3 GenomeInfoDbData_1.2.12
## [29] ggrepel_0.9.5 irlba_2.3.5.1
## [31] listenv_0.9.1 spatstat.utils_3.0-5
## [33] goftest_1.2-3 RSpectra_0.16-1
## [35] spatstat.random_3.2-3 fitdistrplus_1.2-1
## [37] parallelly_1.37.1 DelayedMatrixStats_1.26.0
## [39] pkgdown_2.0.7 leiden_0.4.3.1
## [41] codetools_0.2-20 DelayedArray_0.30.1
## [43] tidyselect_1.2.1 UCSC.utils_1.0.0
## [45] farver_2.1.2 spatstat.explore_3.2-7
## [47] jsonlite_1.8.8 progressr_0.14.0
## [49] ggridges_0.5.6 survival_3.7-0
## [51] systemfonts_1.1.0 ragg_1.3.2
## [53] ica_1.0-3 Rcpp_1.0.13
## [55] gridExtra_2.3 SparseArray_1.4.8
## [57] xfun_0.45 withr_3.0.0
## [59] fastmap_1.2.0 fansi_1.0.6
## [61] digest_0.6.36 R6_2.5.1
## [63] mime_0.12 textshaping_0.4.0
## [65] colorspace_2.1-0 scattermore_1.2
## [67] tensor_1.5 spatstat.data_3.1-2
## [69] utf8_1.2.4 tidyr_1.3.1
## [71] generics_0.1.3 data.table_1.15.4
## [73] httr_1.4.7 htmlwidgets_1.6.4
## [75] S4Arrays_1.4.1 uwot_0.2.2
## [77] pkgconfig_2.0.3 gtable_0.3.5
## [79] lmtest_0.9-40 XVector_0.44.0
## [81] htmltools_0.5.8.1 dotCall64_1.1-1
## [83] png_0.1-8 knitr_1.48
## [85] rstudioapi_0.16.0 cachem_1.1.0
## [87] zoo_1.8-12 stringr_1.5.1
## [89] KernSmooth_2.23-24 parallel_4.4.0
## [91] miniUI_0.1.1.1 desc_1.4.3
## [93] pillar_1.9.0 grid_4.4.0
## [95] vctrs_0.6.5 RANN_2.6.1
## [97] promises_1.3.0 xtable_1.8-4
## [99] cluster_2.1.6 evaluate_0.24.0
## [101] cli_3.6.3 compiler_4.4.0
## [103] crayon_1.5.3 future.apply_1.11.2
## [105] labeling_0.4.3 plyr_1.8.9
## [107] fs_1.6.4 stringi_1.8.4
## [109] deldir_2.0-4 munsell_0.5.1
## [111] lazyeval_0.2.2 spatstat.geom_3.2-9
## [113] Matrix_1.7-0 RcppHNSW_0.6.0
## [115] patchwork_1.2.0 sparseMatrixStats_1.16.0
## [117] bit64_4.0.5 future_1.33.2
## [119] shiny_1.8.1.1 highr_0.11
## [121] ROCR_1.0-11 igraph_2.0.3
## [123] memoise_2.0.1 bslib_0.4.2
## [125] bit_4.0.5