细胞聚类(clustering analysis)

Learning Objectives:
  • Describe methods for evaluating the number of principal components used for clustering
  • Perform clustering of cells based on significant principal components

Now that we have our high quality cells integrated, we want to know the different cell types present within our population of cells.


Goals:

Challenges:

Recommendations:


1 Set up

读取上一节中完成质控和整合的单细胞数据seurat_integrated

library(Seurat)
seurat_integrated <- readRDS("output/scRNA-seq_online/integrated_seurat.rds")
seurat_integrated
An object of class Seurat 
28130 features across 29629 samples within 2 assays 
Active assay: SCT (14065 features, 3000 variable features)
 3 layers present: counts, data, scale.data
 1 other assay present: RNA
 4 dimensional reductions calculated: pca, umap.unintegrated, integrated.cca, umap.integrated
head(seurat_integrated, 5)
                      orig.ident nCount_RNA nFeature_RNA sample
ctrl_AAACATACAATGCC-1          1       2344          874   ctrl
ctrl_AAACATACATTTCC-1          1       3125          896   ctrl
ctrl_AAACATACCAGAAA-1          1       2578          725   ctrl
ctrl_AAACATACCAGCTA-1          1       3261          979   ctrl
ctrl_AAACATACCATGCA-1          1        746          362   ctrl
                      log10GenesPerUMI  mitoRatio                 cells
ctrl_AAACATACAATGCC-1        0.8728630 0.01962457 ctrl_AAACATACAATGCC-1
ctrl_AAACATACATTTCC-1        0.8447596 0.01792000 ctrl_AAACATACATTTCC-1
ctrl_AAACATACCAGAAA-1        0.8384933 0.01551590 ctrl_AAACATACCAGAAA-1
ctrl_AAACATACCAGCTA-1        0.8512622 0.01379945 ctrl_AAACATACCAGCTA-1
ctrl_AAACATACCATGCA-1        0.8906861 0.02144772 ctrl_AAACATACCATGCA-1
                      nCount_SCT nFeature_SCT      S.Score    G2M.Score Phase
ctrl_AAACATACAATGCC-1       1598          864  0.010526369  0.011803814   G2M
ctrl_AAACATACATTTCC-1       1575          735  0.010251663  0.015119823   G2M
ctrl_AAACATACCAGAAA-1       1563          671 -0.019803499 -0.015779795    G1
ctrl_AAACATACCAGCTA-1       1587          775 -0.032093208  0.013380044   G2M
ctrl_AAACATACCATGCA-1       1086          374  0.008301833 -0.008402066     S
                           mitoFr unintegrated_clusters seurat_clusters
ctrl_AAACATACAATGCC-1      Medium                     4               5
ctrl_AAACATACATTTCC-1      Medium                     0               0
ctrl_AAACATACCAGAAA-1      Medium                    14              14
ctrl_AAACATACCAGCTA-1         Low                     1               6
ctrl_AAACATACCATGCA-1 Medium high                    16              12
                      integrated_clusters
ctrl_AAACATACAATGCC-1                   5
ctrl_AAACATACATTTCC-1                   0
ctrl_AAACATACCAGAAA-1                  14
ctrl_AAACATACCAGCTA-1                   6
ctrl_AAACATACCATGCA-1                  12

2 决定后续分析的主成分

To overcome the extensive technical noise in the expression of any single gene for scRNA-seq data, Seurat assigns cells to clusters based on their PCA scores derived from the expression of the integrated most variable genes, with each PC essentially representing a “metagene” that combines information across a correlated gene set. Determining how many PCs to include in the clustering step is therefore important to ensure that we are capturing the majority of the variation, or cell types, present in our dataset.

It is useful to explore the PCs prior to deciding which PCs to include for the downstream clustering analysis.

2.1 通过热图判断需要包括的主成分

One way of exploring the PCs is using a heatmap to visualize the most variant genes for select PCs with the genes and cells ordered by PCA scores. The idea here is to look at the PCs and determine whether the genes driving them make sense for differentiating the different cell types.

The cells argument specifies the number of cells with the most negative or postive PCA scores to use for the plotting. The idea is that we are looking for a PC where the heatmap starts to look more “fuzzy”, i.e. where the distinctions between the groups of genes is not so distinct.

# Explore heatmap of PCs
DimHeatmap(seurat_integrated, 
           dims = 1:9, 
           cells = 500, 
           balanced = TRUE)

This method can be slow and hard to visualize individual genes if we would like to explore a large number of PCs. In the same vein and to explore a large number of PCs, we could print out the top 10 (or more) positive and negative genes by PCA scores driving the PCs.

# Printing out the most variable genes driving PCs
print(x = seurat_integrated[["pca"]], 
      dims = 1:10, 
      nfeatures = 5)
PC_ 1 
Positive:  IGKC, GNLY, RPL3, RPL13, RPS18 
Negative:  FTL, CCL2, CCL8, CXCL10, TIMP1 
PC_ 2 
Positive:  GNLY, CCL5, GZMB, NKG7, PRF1 
Negative:  IGKC, IGHM, CD74, HLA-DRA, CD79A 
PC_ 3 
Positive:  PABPC1, RPS18, RPL13, RPL10, RPS6 
Negative:  GNLY, IGKC, GZMB, CCL5, NKG7 
PC_ 4 
Positive:  CCL4, CCL3, CCL4L2, CCL2, CCL8 
Negative:  FTL, TIMP1, GNLY, HLA-DRA, VMO1 
PC_ 5 
Positive:  CCL4, CCL3, CCL4L2, TIMP1, VMO1 
Negative:  CCL2, CCL7, CCL8, IGKC, GNLY 
PC_ 6 
Positive:  FTL, CXCL8, CCL4L2, CXCL3, S100A8 
Negative:  CXCL10, CCL8, ISG15, IGLC2, APOBEC3A 
PC_ 7 
Positive:  IGKC, CXCL10, VMO1, TIMP1, FCGR3A 
Negative:  IGLC2, IGLC3, IGHM, CD74, HLA-DRA 
PC_ 8 
Positive:  HBB, HBA2, HBA1, SNCA, HBG2 
Negative:  IGLC2, PPBP, HLA-DRA, CD74, IGLC3 
PC_ 9 
Positive:  PPBP, PF4, GNG11, CAVIN2, TUBB1 
Negative:  GNLY, FTL, TXN, RPL10, RPL3 
PC_ 10 
Positive:  TXN, HSPB1, HSPA1A, HSPA1B, HLA-DRA 
Negative:  IGLC2, TIMP1, VMO1, PPBP, IGHM 

2.2 通过肘图(elbow plot)判断需要包括的主成分

The elbow plot is another helpful way to determine how many PCs to use for clustering so that we are capturing majority of the variation in the data. The elbow plot visualizes the standard deviation of each PC, and we are looking for where the standard deviations begins to plateau. Essentially, where the elbow appears is usually the threshold for identifying the majority of the variation. However, this method can be quite subjective.

Let’s draw an elbow plot using the top 40 PCs:

# Plot the elbow plot
ElbowPlot(object = seurat_integrated, 
          ndims = 40)

Based on this plot, we could roughly determine the majority of the variation by where the elbow occurs around PC8 - PC10, or one could argue that it should be when the data points start to get close to the X-axis, PC30 or so. This gives us a very rough idea of the number of PCs needed to be included, we can extract the information visualized here in a more quantitative manner, which may be a bit more reliable.

While the above 2 methods were used a lot more with older methods from Seurat for normalization and identification of variable genes, they are no longer as important as they used to be. This is because the SCTransform method is more accurate than older methods,基于SCTransform的标准化流程中不再需要判断纳入的主成分数量,可以纳入更多的主成分(Seurat-基于SCTransform的单细胞数据标准化.

Why is selection of PCs more important for older methods?

The older methods incorporated some technical sources of variation into some of the higher PCs, so selection of PCs was more important. SCTransform estimates the variance better and does not frequently include these sources of technical variation in the higher PCs.

In theory, with SCTransform, the more PCs we choose the more variation is accounted for when performing the clustering, but it takes a lot longer to perform the clustering. Therefore for this analysis, we will use the first 40 PCs to generate the clusters.

3 聚类(Cluster the cells)

Seurat uses a graph-based clustering approach using a K-nearest neighbor approach, and then attempts to partition this graph into highly interconnected ‘quasi-cliques’ or ‘communities’ (见Seurat-细胞聚类). A nice in-depth description of clustering methods is provided in the SVI Bioinformatics and Cellular Genomics Lab course.

3.1 Find neighbors

The first step is to construct a K-nearest neighbor (KNN) graph based on the euclidean distance in PCA space.

Image source: Analysis of Single cell RNA-seq data

  • Edges are drawn between cells with similar features expression patterns.
  • Edge weights are refined between any two cells based on shared overlap in their local neighborhoods.

This is done in Seurat by using the FindNeighbors() function(这里不需要运行,因为在上一节中我们已经在整合后运行了FindNeighbors):

# Determine the K-nearest neighbor graph(不需运行)
seurat_integrated <- FindNeighbors(seurat_integrated, 
                                   dims = 1:40, 
                                   reduction = "integrated.cca")

3.2 Find clusters

Next, Seurat will iteratively group cells together with the goal of optimizing the standard modularity function.

We will use the FindClusters() function to perform the graph-based clustering. The resolution is an important argument that sets the “granularity” of the downstream clustering and will need to be optimized for every individual experiment. For datasets of 3,000 - 5,000 cells, the resolution set between 0.4-1.4 generally yields good clustering. Increased resolution values lead to a greater number of clusters, which is often required for larger datasets.

The FindClusters() function allows us to enter a series of resolutions and will calculate the “granularity” of the clustering. This is very helpful for testing which resolution works for moving forward without having to run the function for each resolution.

# Determine the clusters for various resolutions                                
seurat_integrated <- FindClusters(object = seurat_integrated,
                                  resolution = c(0.4, 0.6, 0.8, 1.0, 1.4))
Modularity Optimizer version 1.3.0 by Ludo Waltman and Nees Jan van Eck

Number of nodes: 29629
Number of edges: 1128935

Running Louvain algorithm...
Maximum modularity in 10 random starts: 0.9211
Number of communities: 14
Elapsed time: 4 seconds
Modularity Optimizer version 1.3.0 by Ludo Waltman and Nees Jan van Eck

Number of nodes: 29629
Number of edges: 1128935

Running Louvain algorithm...
Maximum modularity in 10 random starts: 0.9019
Number of communities: 17
Elapsed time: 4 seconds
Modularity Optimizer version 1.3.0 by Ludo Waltman and Nees Jan van Eck

Number of nodes: 29629
Number of edges: 1128935

Running Louvain algorithm...
Maximum modularity in 10 random starts: 0.8864
Number of communities: 22
Elapsed time: 4 seconds
Modularity Optimizer version 1.3.0 by Ludo Waltman and Nees Jan van Eck

Number of nodes: 29629
Number of edges: 1128935

Running Louvain algorithm...
Maximum modularity in 10 random starts: 0.8727
Number of communities: 26
Elapsed time: 4 seconds
Modularity Optimizer version 1.3.0 by Ludo Waltman and Nees Jan van Eck

Number of nodes: 29629
Number of edges: 1128935

Running Louvain algorithm...
Maximum modularity in 10 random starts: 0.8511
Number of communities: 28
Elapsed time: 4 seconds

4 Visualize clusters of cells

To visualize the cell clusters, there are a few different dimensionality reduction techniques that can be helpful. The most popular methods include t-distributed stochastic neighbor embedding (t-SNE) and Uniform Manifold Approximation and Projection (UMAP) techniques.

Both methods aim to place cells with similar local neighborhoods in high-dimensional space together in low-dimensional space. These methods will require you to input number of PCA dimentions to use for the visualization, we suggest using the same number of PCs as input to the clustering analysis. Here, we will proceed with the UMAP method for visualizing the clusters.

We can only visualize the results of one resolution setting at a time. If we look at the metadata of our Seurat object(seurat_integrated@meta.data), you should observe a separate column for each of the different resolutions calculated.

# Explore resolutions
head(seurat_integrated@meta.data, 5)
                      orig.ident nCount_RNA nFeature_RNA sample
ctrl_AAACATACAATGCC-1          1       2344          874   ctrl
ctrl_AAACATACATTTCC-1          1       3125          896   ctrl
ctrl_AAACATACCAGAAA-1          1       2578          725   ctrl
ctrl_AAACATACCAGCTA-1          1       3261          979   ctrl
ctrl_AAACATACCATGCA-1          1        746          362   ctrl
                      log10GenesPerUMI  mitoRatio                 cells
ctrl_AAACATACAATGCC-1        0.8728630 0.01962457 ctrl_AAACATACAATGCC-1
ctrl_AAACATACATTTCC-1        0.8447596 0.01792000 ctrl_AAACATACATTTCC-1
ctrl_AAACATACCAGAAA-1        0.8384933 0.01551590 ctrl_AAACATACCAGAAA-1
ctrl_AAACATACCAGCTA-1        0.8512622 0.01379945 ctrl_AAACATACCAGCTA-1
ctrl_AAACATACCATGCA-1        0.8906861 0.02144772 ctrl_AAACATACCATGCA-1
                      nCount_SCT nFeature_SCT      S.Score    G2M.Score Phase
ctrl_AAACATACAATGCC-1       1598          864  0.010526369  0.011803814   G2M
ctrl_AAACATACATTTCC-1       1575          735  0.010251663  0.015119823   G2M
ctrl_AAACATACCAGAAA-1       1563          671 -0.019803499 -0.015779795    G1
ctrl_AAACATACCAGCTA-1       1587          775 -0.032093208  0.013380044   G2M
ctrl_AAACATACCATGCA-1       1086          374  0.008301833 -0.008402066     S
                           mitoFr unintegrated_clusters seurat_clusters
ctrl_AAACATACAATGCC-1      Medium                     4               4
ctrl_AAACATACATTTCC-1      Medium                     0               9
ctrl_AAACATACCAGAAA-1      Medium                    14              18
ctrl_AAACATACCAGCTA-1         Low                     1               6
ctrl_AAACATACCATGCA-1 Medium high                    16              14
                      integrated_clusters SCT_snn_res.0.4 SCT_snn_res.0.6
ctrl_AAACATACAATGCC-1                   5               0               0
ctrl_AAACATACATTTCC-1                   0               2               1
ctrl_AAACATACCAGAAA-1                  14               4               5
ctrl_AAACATACCAGCTA-1                   6               4               5
ctrl_AAACATACCATGCA-1                  12               5               6
                      SCT_snn_res.0.8 SCT_snn_res.1 SCT_snn_res.1.4
ctrl_AAACATACAATGCC-1               5             2               4
ctrl_AAACATACATTTCC-1               0             0               9
ctrl_AAACATACCAGAAA-1              14            17              18
ctrl_AAACATACCAGCTA-1               6             8               6
ctrl_AAACATACCATGCA-1              12            14              14

To choose a resolution to start with, we often pick something in the middle of the range like 0.6 or 0.8. We will start with a resolution of 0.8 by assigning the identity of the clusters using the Idents() function.

# Assign identity of clusters
Idents(seurat_integrated) <- "SCT_snn_res.0.8"

Now, we can plot the UMAP to look at how cells cluster together at a resolution of 0.8:

# Calculation of UMAP
# DO NOT RUN (calculated in the last lesson)
seurat_integrated <- RunUMAP(seurat_integrated, 
                             dims = 1:40,
                             reduction = "integrated.cca", # 更改降维来源为整合后的"integrated.cca"
                             reduction.name = "umap.integrated") 
# Plot the UMAP
DimPlot(seurat_integrated,
        reduction = "umap.integrated",
        label = FALSE,
        label.size = 6)

It can be useful to explore other resolutions as well. It will give you a quick idea about how the clusters would change based on the resolution parameter. For example, let’s switch to a resolution of 0.4:

# Assign identity of clusters
Idents(object = seurat_integrated) <- "SCT_snn_res.0.4"

# Plot the UMAP
DimPlot(seurat_integrated,
        reduction = "umap.integrated",
        label = FALSE,
        label.size = 6)

4.1 载入案例数据

How does your UMAP plot compare to the one above?

It is possible that there is some variability in the way your clusters look compared to the image in this lesson. In particular you may see a difference in the labeling of clusters. This is an unfortunate consequence of slight variations in the versions of packages (mostly Seurat dependencies).

If your clusters look identical to what’s in the lesson, please go ahead to the next section.


If your clusters do look different from what we have in the lesson, please follow the instructions provided below.

Inside your data folder you will see a folder called additional_data. It contains the seurat_integrated object that we have created for the class. Let’s load in the object to your R session and overwrite the existing one:

load(bzfile("data/scRNA-seq_online/additional_data/seurat_integrated.RData.bz2"))
seurat_integrated
An object of class Seurat 
31130 features across 29629 samples within 3 assays 
Active assay: integrated (3000 features, 3000 variable features)
 2 layers present: data, scale.data
 2 other assays present: RNA, SCT
 2 dimensional reductions calculated: pca, umap
head(seurat_integrated, 5)
                      orig.ident nCount_RNA nFeature_RNA
ctrl_AAACATACAATGCC-1       ctrl       2344          874
ctrl_AAACATACATTTCC-1       ctrl       3124          895
ctrl_AAACATACCAGAAA-1       ctrl       2578          725
ctrl_AAACATACCAGCTA-1       ctrl       3260          978
ctrl_AAACATACCATGCA-1       ctrl        746          362
                                      seq_folder nUMI nGene log10GenesPerUMI
ctrl_AAACATACAATGCC-1 ctrl_raw_feature_bc_matrix 2344   874        0.8728630
ctrl_AAACATACATTTCC-1 ctrl_raw_feature_bc_matrix 3125   896        0.8447596
ctrl_AAACATACCAGAAA-1 ctrl_raw_feature_bc_matrix 2578   725        0.8384933
ctrl_AAACATACCAGCTA-1 ctrl_raw_feature_bc_matrix 3261   979        0.8512622
ctrl_AAACATACCATGCA-1 ctrl_raw_feature_bc_matrix  746   362        0.8906861
                       mitoRatio                 cells sample     S.Score
ctrl_AAACATACAATGCC-1 0.01962457 ctrl_AAACATACAATGCC-1   ctrl  0.04330502
ctrl_AAACATACATTTCC-1 0.01792000 ctrl_AAACATACATTTCC-1   ctrl  0.02661900
ctrl_AAACATACCAGAAA-1 0.01551590 ctrl_AAACATACCAGAAA-1   ctrl -0.04670650
ctrl_AAACATACCAGCTA-1 0.01379945 ctrl_AAACATACCAGCTA-1   ctrl -0.05832833
ctrl_AAACATACCATGCA-1 0.02144772 ctrl_AAACATACCATGCA-1   ctrl  0.03929605
                        G2M.Score Phase      mitoFr nCount_SCT nFeature_SCT
ctrl_AAACATACAATGCC-1  0.05422631   G2M      Medium       1572          829
ctrl_AAACATACATTTCC-1  0.05159679   G2M      Medium       1572          718
ctrl_AAACATACCAGAAA-1 -0.04841661    G1      Medium       1553          648
ctrl_AAACATACCAGCTA-1  0.05045960   G2M         Low       1576          756
ctrl_AAACATACCATGCA-1 -0.02995512     S Medium high       1075          363
                      integrated_snn_res.0.4 integrated_snn_res.0.6
ctrl_AAACATACAATGCC-1                      2                      1
ctrl_AAACATACATTTCC-1                      0                      2
ctrl_AAACATACCAGAAA-1                      0                      3
ctrl_AAACATACCAGCTA-1                      0                      3
ctrl_AAACATACCATGCA-1                      5                      6
                      integrated_snn_res.0.8 integrated_snn_res.1
ctrl_AAACATACAATGCC-1                      2                    2
ctrl_AAACATACATTTCC-1                      1                    0
ctrl_AAACATACCAGAAA-1                      3                   15
ctrl_AAACATACCAGCTA-1                      3                    3
ctrl_AAACATACCATGCA-1                      4                   12
                      integrated_snn_res.1.4 seurat_clusters
ctrl_AAACATACAATGCC-1                      5               5
ctrl_AAACATACATTTCC-1                      0               0
ctrl_AAACATACCAGAAA-1                     19              19
ctrl_AAACATACCAGCTA-1                      3               3
ctrl_AAACATACCATGCA-1                     13              13

Warning

由于这里的案例数据是基于Seurat V5之前的版本创建的,所以数据结构和基于Seurat V5的结果有所差异。比较重要的区别是,这里的Seurat对象的没有layer结构;同时有一个“integrated” assay,用于存放整合后的信息,其类型仍属于SCT assay。而一个典型的经过SCTransform和整合的Seurat V5对象如下图所示(来自Seurat-整合):

可以看到没有“integrated” assay,因此,为了和最新的Seurat V5流程保持一致,我们后续把本案例中的“integrated” assay看作整合后的Seurat V5的“SCT” assay。


4.2 再次检查不同分辨率下的细胞分群情况

After loading seurat_integrated.RData.bz2, we now re-check the object clusters with different resolution (0.4, 0.6, 0.8, 1.0, 1.4).

# 查看不同分辨率下的细胞分群情况
apply(seurat_integrated@meta.data[ ,grep("integrated_snn_res.", 
                                         colnames(seurat_integrated@meta.data))], 
      2, 
      table)
$integrated_snn_res.0.4

   0    1   10   11   12    2    3    4    5    6    7    8    9 
6715 5899  456  280  124 3661 2680 2377 2166 2143 1217 1177  734 

$integrated_snn_res.0.6

   0    1   10   11   12   13   14    2    3    4    5    6    7    8    9 
5443 3667 1176  467  464  288  124 3403 3306 2631 2382 2137 1679 1249 1213 

$integrated_snn_res.0.8

   0    1   10   11   12   13   14   15   16    2    3    4    5    6    7    8 
4220 3718 1208 1174  858  468  459  289  124 3649 3004 2164 1959 1810 1646 1504 
   9 
1375 

$integrated_snn_res.1

   0    1   10   11   12   13   14   15   16   17   18   19    2   20   21    3 
3392 3269 1158 1152  952  876  873  650  520  462  282  176 3041  124   23 2668 
   4    5    6    7    8    9 
2508 1881 1643 1509 1261 1209 

$integrated_snn_res.1.4

   0    1   10   11   12   13   14   15   16   17   18   19    2   20   21   22 
2886 2497 1211 1174  874  838  832  802  766  657  655  629 2130  468  459  357 
  23   24   25   26    3    4    5    6    7    8    9 
 292  175  124   23 2011 1884 1827 1646 1587 1489 1336 
# 批量绘制不同分辨率下的UMAP图
library(ggplot2)
library(patchwork)
lapply(grep("integrated_snn_res.",
            colnames(seurat_integrated@meta.data), 
            value = TRUE),
       function(res) {
         Idents(seurat_integrated) <-  res
         DimPlot(seurat_integrated,
                 reduction = "umap",
                 label = FALSE,
                 label.size = 4) +
           ggtitle(res) +
           theme_bw()
         }) |>
  wrap_plots(ncol = 2)


We will now continue with the 0.8 resolution to check the quality control metrics and known markers for the anticipated cell types.

# Assign identity of clusters
Idents(seurat_integrated) <- "integrated_snn_res.0.8"

# Plot the UMAP
DimPlot(seurat_integrated,
        reduction = "umap",
        label = TRUE,
        label.size = 6)

saveRDS(seurat_integrated, file = "output/scRNA-seq_online/seurat_clustered.rds")

R version 4.3.2 (2023-10-31)
Platform: aarch64-apple-darwin20 (64-bit)
Running under: macOS Sonoma 14.3

Matrix products: default
BLAS:   /Library/Frameworks/R.framework/Versions/4.3-arm64/Resources/lib/libRblas.0.dylib 
LAPACK: /Library/Frameworks/R.framework/Versions/4.3-arm64/Resources/lib/libRlapack.dylib;  LAPACK version 3.11.0

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

time zone: Asia/Shanghai
tzcode source: internal

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

other attached packages:
[1] patchwork_1.2.0    ggplot2_3.4.4      Seurat_5.0.1       SeuratObject_5.0.1
[5] sp_2.1-2          

loaded via a namespace (and not attached):
  [1] deldir_2.0-2           pbapply_1.7-2          gridExtra_2.3         
  [4] rlang_1.1.3            magrittr_2.0.3         RcppAnnoy_0.0.21      
  [7] spatstat.geom_3.2-7    matrixStats_1.2.0      ggridges_0.5.5        
 [10] compiler_4.3.2         png_0.1-8              vctrs_0.6.5           
 [13] reshape2_1.4.4         stringr_1.5.1          pkgconfig_2.0.3       
 [16] fastmap_1.1.1          ellipsis_0.3.2         labeling_0.4.3        
 [19] utf8_1.2.4             promises_1.2.1         rmarkdown_2.25        
 [22] purrr_1.0.2            xfun_0.41              jsonlite_1.8.8        
 [25] goftest_1.2-3          later_1.3.2            spatstat.utils_3.0-4  
 [28] irlba_2.3.5.1          parallel_4.3.2         cluster_2.1.6         
 [31] R6_2.5.1               ica_1.0-3              stringi_1.8.3         
 [34] RColorBrewer_1.1-3     spatstat.data_3.0-4    reticulate_1.34.0     
 [37] parallelly_1.36.0      lmtest_0.9-40          scattermore_1.2       
 [40] Rcpp_1.0.12            knitr_1.45             tensor_1.5            
 [43] future.apply_1.11.1    zoo_1.8-12             sctransform_0.4.1     
 [46] httpuv_1.6.13          Matrix_1.6-5           splines_4.3.2         
 [49] igraph_1.6.0           tidyselect_1.2.0       abind_1.4-5           
 [52] rstudioapi_0.15.0      yaml_2.3.8             spatstat.random_3.2-2 
 [55] codetools_0.2-19       miniUI_0.1.1.1         spatstat.explore_3.2-5
 [58] listenv_0.9.0          lattice_0.22-5         tibble_3.2.1          
 [61] plyr_1.8.9             withr_3.0.0            shiny_1.8.0           
 [64] ROCR_1.0-11            evaluate_0.23          Rtsne_0.17            
 [67] future_1.33.1          fastDummies_1.7.3      survival_3.5-7        
 [70] polyclip_1.10-6        fitdistrplus_1.1-11    pillar_1.9.0          
 [73] KernSmooth_2.23-22     plotly_4.10.4          generics_0.1.3        
 [76] RcppHNSW_0.5.0         munsell_0.5.0          scales_1.3.0          
 [79] globals_0.16.2         xtable_1.8-4           glue_1.7.0            
 [82] lazyeval_0.2.2         tools_4.3.2            data.table_1.14.10    
 [85] RSpectra_0.16-1        RANN_2.6.1             leiden_0.4.3.1        
 [88] dotCall64_1.1-1        cowplot_1.1.2          grid_4.3.2            
 [91] tidyr_1.3.0            colorspace_2.1-0       nlme_3.1-164          
 [94] cli_3.6.2              spatstat.sparse_3.0-3  spam_2.10-0           
 [97] fansi_1.0.6            viridisLite_0.4.2      dplyr_1.1.4           
[100] uwot_0.1.16            gtable_0.3.4           digest_0.6.34         
[103] progressr_0.14.0       ggrepel_0.9.5          farver_2.1.1          
[106] htmlwidgets_1.6.4      htmltools_0.5.7        lifecycle_1.0.4       
[109] httr_1.4.7             mime_0.12              MASS_7.3-60.0.1