CytoSig cytokine activity

CytoSig:转录组看起来像被哪些细胞因子作用过,不是 ELISA。Lollipop 是样本级 ACI 减去 CTRL。

全细胞 CytoSig ridge,再对中性粒谱系(GMP + Pre-Neut + Neut,与 08 一致)做亚型分析。conda cytosig。均值热图:每个细胞因子跨细胞 z-score 再取均值。组间 Wilcoxon:细胞级 BH,样本级原始 p。Lollipop 样式同 arhgap35 analysis.R 的 ggdotchart。group 以 CTRL 为对照。nrand=0。

pacman::p_load(
  Seurat, tidyverse, qs2, cowplot, tictoc, reticulate, ggpubr
)
knitr::opts_knit$set(root.dir = "/home/han/Desktop/guoqiang/stroke_bm/stroke_bm_Rproj")
setwd("/home/han/Desktop/guoqiang/stroke_bm/stroke_bm_Rproj")
source("codes/plot_colors.R")
source("codes/activity_helpers.R")
summarise <- dplyr::summarise
mutate <- dplyr::mutate
filter <- dplyr::filter
Sys.setenv(RETICULATE_PYTHON = "/home/han/anaconda3/envs/cytosig/bin/python")
res_path <- "results/cytosig"
obj_path <- "data/rdata/activity"
if (dir.exists(res_path)) {
  old_res <- list.files(res_path, full.names = TRUE)
  if (length(old_res) > 0) unlink(old_res, recursive = TRUE)
} else {
  dir.create(res_path, recursive = TRUE)
}
if (!dir.exists(obj_path)) dir.create(obj_path, recursive = TRUE)

nrand <- 0
alpha_ridge <- 1e4
traj_levels <- c(
  "GMP",
  "Promyelocyte", "MMP8 Pre-Neut", "MMP9 Pre-Neut",
  "S100A12 Neut", "SLC8A1 Neut", "ISG15 Neut"
)
seurat_object <- qs_read("data/rdata/seurat_object_final.qs2", nthreads = 10)
stopifnot(all(c("sample", "group", "annot_main", "annot_sub") %in%
  colnames(seurat_object[[]])))
seurat_object$group <- factor(seurat_object$group, levels = c("CTRL", "ACI"))
seurat_object$annot_main <- factor(
  as.character(seurat_object$annot_main),
  levels = names(annot_main_col)
)
if (length(Layers(seurat_object, search = "counts")) > 1) {
  seurat_object <- JoinLayers(seurat_object)
}
if (!"data" %in% Layers(seurat_object, search = "data")) {
  seurat_object <- NormalizeData(seurat_object, verbose = TRUE)
}
cell_meta <- FetchData(
  seurat_object,
  vars = c("sample", "group", "annot_main", "annot_sub")
) |>
  rownames_to_column("barcode")
print(table(cell_meta$annot_main, cell_meta$group))
          
            CTRL   ACI
  HSPC      1390  1976
  B         3641   664
  Plasma     445   109
  T         6894  3560
  NK         954   793
  Mono/DC   2060  1850
  pDC        363   103
  Pre-Neut  1216 12291
  Neut       885 16783
  Ery       4890   323

1. CytoSig ridge(全细胞)

给每个细胞打一套细胞因子响应分数。推断「像被什么刺激过」,不是测到的浓度。

用 CytoSig 签名对 log 表达做 ridge,得到每个细胞的细胞因子响应分数。

cs_qs2 <- file.path(obj_path, "cytosig_all.qs2")
if (cache_ok(cs_qs2, colnames(seurat_object))) {
  cs_wide <- qs_read(cs_qs2, nthreads = 10)
} else {
  use_python("/home/han/anaconda3/envs/cytosig/bin/python", required = TRUE)
  pd <- import("pandas", convert = FALSE)
  cs <- import("CytoSig.Util", convert = FALSE)
  sig <- pd$read_csv(py_to_r(cs$find_signature_path()), sep = "\t", index_col = 0L)
  sig_genes <- as.character(py_to_r(sig$index$tolist()))
  expr_genes <- intersect(rownames(seurat_object), sig_genes)
  stopifnot(length(expr_genes) >= 10)
  mat <- as.matrix(
    GetAssayData(seurat_object, assay = "RNA", layer = "data")[expr_genes, ]
  )
  py$expr <- r_to_py(mat)
  py$gene_names <- r_to_py(as.list(rownames(mat)))
  py$cell_names <- r_to_py(as.list(colnames(mat)))
  py$alpha_ridge <- alpha_ridge
  py$nrand <- as.integer(nrand)
  tic("CytoSig ridge_significance_test")
  py_run_string("
import pandas as pd
from CytoSig.Util import ridge_significance_test, find_signature_path
Y = pd.DataFrame(expr, index=list(gene_names), columns=list(cell_names))
sig = pd.read_csv(find_signature_path(), sep='\\t', index_col=0)
res = ridge_significance_test(
    sig, Y,
    alpha=alpha_ridge,
    alternative='two-sided',
    nrand=int(nrand),
    cnt_thres=10,
    flag_normalize=True,
    flag_const=False,
    verbose=True
)
zscore = res[2]
")
  toc()
  z <- py_to_r(py$zscore)
  cs_wide <- as.data.frame(t(as.matrix(z))) |>
    rownames_to_column("barcode")
  qs_save(cs_wide, cs_qs2, nthreads = 10)
}
ck <- features_from_wide(cs_wide)
dat <- bind_meta_acts(cell_meta, cs_wide)
stopifnot(nrow(dat) == nrow(cell_meta))
length(ck)
[1] 43
print(ck)
 [1] "Activin A" "BDNF"      "BMP2"      "BMP4"      "BMP6"      "CD40L"    
 [7] "CXCL12"    "EGF"       "FGF2"      "GCSF"      "GDF11"     "GMCSF"    
[13] "HGF"       "IFN1"      "IFNG"      "IFNL"      "IL10"      "IL12"     
[19] "IL13"      "IL15"      "IL17A"     "IL1A"      "IL1B"      "IL2"      
[25] "IL21"      "IL22"      "IL27"      "IL3"       "IL36"      "IL4"      
[31] "IL6"       "LIF"       "LTA"       "MCSF"      "NO"        "OSM"      
[37] "TGFB1"     "TGFB3"     "TNFA"      "TRAIL"     "TWEAK"     "VEGFA"    
[43] "WNT3A"    

2. 细胞大类:活性热图

哪些大类看起来对哪些细胞因子更敏感。颜色是相对高低。

热图:每个细胞因子先在细胞间 z-score,再按大类取均值。

mean_main <- mean_by_scaled(dat, ck, "annot_main")
p_hm_main <- plot_heatmap_mean(mean_main, ck, row_col = "annot_main") +
  scale_x_discrete(limits = names(annot_main_col))
heatmap fill 
    Min.  1st Qu.   Median     Mean  3rd Qu.     Max. 
-1.67646 -0.58416 -0.13992 -0.09939  0.33246  2.15273 
|x| q80 / q90 / q95: 0.82 / 1.04 / 1.23 
ggsave(file.path(res_path, "01_heatmap_annot_main.pdf"), p_hm_main, width = 8, height = 10, dpi = 600)
p_hm_main

mean_main_g <- mean_by_scaled(dat, ck, c("annot_main", "group"))
long_mg <- tidyr::pivot_longer(
  mean_main_g, dplyr::all_of(ck), names_to = "feature", values_to = "mean"
) |>
  dplyr::mutate(
    annot_main = factor(annot_main, levels = names(annot_main_col)),
    group = factor(group, levels = c("CTRL", "ACI"))
  )
p_hm_main_g <- ggplot(long_mg, aes(x = annot_main, y = feature, fill = mean)) +
  geom_tile(color = "white", linewidth = 0.4) +
  facet_wrap(~group, ncol = 2) +
  scale_fill_activity(long_mg$mean) +
  theme_classic() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1, size = 9)) +
  xlab(NULL) +
  ylab(NULL)
heatmap fill 
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
-1.9526 -0.5836 -0.1353 -0.1150  0.3159  2.2688 
|x| q80 / q90 / q95: 0.888 / 1.11 / 1.29 
ggsave(file.path(res_path, "01_heatmap_annot_main_group.pdf"), p_hm_main_g, width = 12, height = 10, dpi = 600)
p_hm_main_g

3. 细胞大类:CTRL vs ACI

各大类上 ACI 减 CTRL。红 = ACI 更高。

ck_cell_main <- add_fdr(cell_group_tests(dat, ck, strata_col = "annot_main")) |>
  dplyr::mutate(annot_main = factor(.data$stratum, levels = names(annot_main_col)))
ck_med_main <- sample_medians(dat, ck, extra_group = "annot_main")
ck_sample_main <- add_stars(sample_group_tests(ck_med_main, ck, strata_col = "annot_main")) |>
  dplyr::mutate(annot_main = factor(.data$stratum, levels = names(annot_main_col)))
write_csv(ck_cell_main, file.path(res_path, "02_wilcox_cell_annot_main.csv"))
write_csv(ck_sample_main, file.path(res_path, "02_wilcox_sample_annot_main.csv"))
print(dplyr::filter(ck_sample_main, .data$p < 0.05))
# A tibble: 194 × 10
   stratum feature n_sample_ctrl n_sample_aci median_ctrl median_aci
   <chr>   <chr>           <int>        <int>       <dbl>      <dbl>
 1 HSPC    BDNF                6            5      -4.05      -2.76 
 2 HSPC    BMP2                6            5      -1.96      -1.15 
 3 HSPC    BMP4                6            5      -1.27      -0.900
 4 HSPC    BMP6                6            5       0.728      0.303
 5 HSPC    CD40L               6            5      -0.693      0.625
 6 HSPC    CXCL12              6            5       1.10       1.35 
 7 HSPC    EGF                 6            5      -1.01       1.05 
 8 HSPC    GCSF                6            5      -1.98       0.142
 9 HSPC    GDF11               6            5      -1.23      -0.788
10 HSPC    HGF                 6            5      -2.55      -1.49 
# ℹ 184 more rows
# ℹ 4 more variables: delta_aci_minus_ctrl <dbl>, p <dbl>, star <chr>,
#   annot_main <fct>
p_d_s <- ggplot(
  dplyr::mutate(ck_sample_main, feature = factor(feature, levels = ck)),
  aes(x = annot_main, y = feature, fill = delta_aci_minus_ctrl)
) +
  geom_tile(color = "white", linewidth = 0.4) +
  geom_text(aes(label = star), size = 2.2) +
  scale_fill_activity(ck_sample_main$delta_aci_minus_ctrl) +
  theme_classic() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
  xlab(NULL) +
  ylab(NULL) +
  ggtitle("Sample-level ACI − CTRL (Wilcoxon p)")
heatmap fill 
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
-3.4506 -0.3221  0.1258  0.1778  0.6917  3.5940 
|x| q80 / q90 / q95: 1.07 / 1.52 / 1.96 
ggsave(file.path(res_path, "02_heatmap_sample_annot_main.pdf"), p_d_s, width = 8, height = 10, dpi = 600)
p_d_s

p_d_c <- ggplot(
  dplyr::mutate(ck_cell_main, feature = factor(feature, levels = ck)),
  aes(x = annot_main, y = feature, fill = delta_aci_minus_ctrl)
) +
  geom_tile(color = "white", linewidth = 0.4) +
  geom_text(aes(label = star), size = 2.2) +
  scale_fill_activity(ck_cell_main$delta_aci_minus_ctrl) +
  theme_classic() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
  xlab(NULL) +
  ylab(NULL) +
  ggtitle("Cell-level ACI − CTRL (BH)")
heatmap fill 
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
-2.5838 -0.3561  0.1159  0.1710  0.6928  3.3629 
|x| q80 / q90 / q95: 1.04 / 1.48 / 1.92 
ggsave(file.path(res_path, "02_heatmap_cell_annot_main.pdf"), p_d_c, width = 8, height = 10, dpi = 600)
p_d_c

4. 中性粒谱系:亚型活性热图

同一套细胞因子在 GMP / Pre-Neut / 成熟粒上的相对高低。

同一套分数,只看 GMP 到成熟粒。

dat_neut <- dat |>
  dplyr::filter(as.character(.data$annot_sub) %in% traj_levels) |>
  dplyr::mutate(
    annot_sub = factor(as.character(.data$annot_sub), levels = traj_levels)
  )
stopifnot(nrow(dat_neut) > 0)
print(table(dat_neut$annot_sub, dat_neut$group))
               
                CTRL  ACI
  GMP            204 1320
  Promyelocyte   154 1774
  MMP8 Pre-Neut  294 5696
  MMP9 Pre-Neut  768 4821
  S100A12 Neut   486 7570
  SLC8A1 Neut    280 8465
  ISG15 Neut     119  748
mean_sub <- mean_by_scaled(dat_neut, ck, "annot_sub")
p_hm_sub <- plot_heatmap_mean(mean_sub, ck, row_col = "annot_sub") +
  scale_x_discrete(limits = traj_levels)
heatmap fill 
    Min.  1st Qu.   Median     Mean  3rd Qu.     Max. 
-2.17251 -0.45296 -0.03157 -0.08672  0.34780  2.81312 
|x| q80 / q90 / q95: 0.793 / 1.04 / 1.52 
ggsave(file.path(res_path, "03_neut_heatmap_subtype.pdf"), p_hm_sub, width = 8, height = 10, dpi = 600)
p_hm_sub

mean_sub_g <- mean_by_scaled(dat_neut, ck, c("annot_sub", "group"))
long_sg <- tidyr::pivot_longer(
  mean_sub_g, dplyr::all_of(ck), names_to = "feature", values_to = "mean"
) |>
  dplyr::mutate(
    annot_sub = factor(annot_sub, levels = traj_levels),
    group = factor(group, levels = c("CTRL", "ACI"))
  )
p_hm_sub_g <- ggplot(long_sg, aes(x = annot_sub, y = feature, fill = mean)) +
  geom_tile(color = "white", linewidth = 0.4) +
  facet_wrap(~group, ncol = 2) +
  scale_fill_activity(long_sg$mean) +
  theme_classic() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1, size = 9)) +
  xlab(NULL) +
  ylab(NULL)
heatmap fill 
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
-2.9779 -0.6425 -0.1342 -0.1675  0.3171  3.9001 
|x| q80 / q90 / q95: 0.963 / 1.3 / 1.64 
ggsave(file.path(res_path, "03_neut_heatmap_subtype_group.pdf"), p_hm_sub_g, width = 12, height = 10, dpi = 600)
p_hm_sub_g

5. 中性粒谱系总体:lollipop

中性粒谱系总体:每种细胞因子一个点,高度是样本级 ACI−CTRL。红显著升高,蓝显著降低,灰不显著。

每个点一种细胞因子,高度是样本级 ACI 减 CTRL。先看 IFN 和集落刺激因子落在哪一侧,再对照亚型图看信号停在哪一段。

ck_cell_lineage <- add_fdr(cell_group_tests(dat_neut, ck))
ck_med_lineage <- sample_medians(dat_neut, ck)
ck_sample_lineage <- add_stars(sample_group_tests(ck_med_lineage, ck))
write_csv(ck_cell_lineage, file.path(res_path, "04_neut_wilcox_cell_lineage.csv"))
write_csv(ck_sample_lineage, file.path(res_path, "04_neut_wilcox_sample_lineage.csv"))
print(dplyr::filter(ck_sample_lineage, .data$p < 0.05) |>
  dplyr::arrange(dplyr::desc(delta_aci_minus_ctrl)))
# A tibble: 29 × 8
   feature n_sample_ctrl n_sample_aci median_ctrl median_aci
   <chr>           <int>        <int>       <dbl>      <dbl>
 1 IL10                6            5      0.408       4.06 
 2 GCSF                6            5      2.09        5.58 
 3 EGF                 6            5      0.246       3.49 
 4 NO                  6            5      2.28        5.23 
 5 IL1B                6            5     -0.0511      2.69 
 6 TGFB3               6            5      0.175       2.30 
 7 IL17A               6            5      1.83        3.38 
 8 LIF                 6            5     -0.935       0.551
 9 TRAIL               6            5      1.76        3.14 
10 VEGFA               6            5      0.335       1.62 
# ℹ 19 more rows
# ℹ 3 more variables: delta_aci_minus_ctrl <dbl>, p <dbl>, star <chr>
p_lolli_s <- plot_lollipop_delta(
  ck_sample_lineage,
  title = "Neut lineage (GMP + Pre-Neut + Neut), sample-level",
  p_col = "p"
)
ggsave(file.path(res_path, "04_neut_lollipop_sample.pdf"), p_lolli_s, width = 12, height = 4.2, dpi = 600)
p_lolli_s

p_lolli_c <- plot_lollipop_delta(
  ck_cell_lineage,
  title = "Neut lineage, cell-level (BH)",
  p_col = "padj"
)
ggsave(file.path(res_path, "04_neut_lollipop_cell.pdf"), p_lolli_c, width = 12, height = 4.2, dpi = 600)
p_lolli_c

6. 中性粒亚型:CTRL vs ACI

拆到亚型看信号停在哪一段。同样看样本级。

拆到亚型,看信号在前体还是成熟粒。组间仍看每例一个点。

ck_cell_sub <- add_fdr(cell_group_tests(dat_neut, ck, strata_col = "annot_sub")) |>
  dplyr::mutate(annot_sub = factor(.data$stratum, levels = traj_levels))
ck_med_sub <- sample_medians(dat_neut, ck, extra_group = "annot_sub")
ck_sample_sub <- add_stars(sample_group_tests(ck_med_sub, ck, strata_col = "annot_sub")) |>
  dplyr::mutate(annot_sub = factor(.data$stratum, levels = traj_levels))
write_csv(ck_cell_sub, file.path(res_path, "05_neut_wilcox_cell_subtype.csv"))
write_csv(ck_sample_sub, file.path(res_path, "05_neut_wilcox_sample_subtype.csv"))
print(dplyr::filter(ck_sample_sub, .data$p < 0.05))
# A tibble: 137 × 10
   stratum feature n_sample_ctrl n_sample_aci median_ctrl median_aci
   <chr>   <chr>           <int>        <int>       <dbl>      <dbl>
 1 GMP     BMP2                6            5      -1.69     -0.907 
 2 GMP     EGF                 6            5      -1.90      0.661 
 3 GMP     HGF                 6            5      -2.15     -1.42  
 4 GMP     IFNL                6            5       1.37      0.268 
 5 GMP     IL1B                6            5      -0.860     0.0343
 6 GMP     IL36                6            5       1.55      1.91  
 7 GMP     IL6                 6            5       1.58      2.35  
 8 GMP     NO                  6            5      -0.270     0.224 
 9 GMP     TRAIL               6            5       2.34      3.18  
10 GMP     VEGFA               6            5       0.845     1.84  
# ℹ 127 more rows
# ℹ 4 more variables: delta_aci_minus_ctrl <dbl>, p <dbl>, star <chr>,
#   annot_sub <fct>
p_sub_s <- ggplot(
  dplyr::mutate(ck_sample_sub, feature = factor(feature, levels = ck)),
  aes(x = annot_sub, y = feature, fill = delta_aci_minus_ctrl)
) +
  geom_tile(color = "white", linewidth = 0.4) +
  geom_text(aes(label = star), size = 2.2) +
  scale_fill_activity(ck_sample_sub$delta_aci_minus_ctrl) +
  theme_classic() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
  xlab(NULL) +
  ylab(NULL) +
  ggtitle("Subtype sample-level ACI − CTRL (Wilcoxon p)")
heatmap fill 
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
-2.0184 -0.1951  0.3366  0.3400  0.7837  2.9955 
|x| q80 / q90 / q95: 1.07 / 1.52 / 1.96 
ggsave(file.path(res_path, "05_neut_heatmap_sample_subtype.pdf"), p_sub_s, width = 8, height = 10, dpi = 600)
p_sub_s

p_sub_c <- ggplot(
  dplyr::mutate(ck_cell_sub, feature = factor(feature, levels = ck)),
  aes(x = annot_sub, y = feature, fill = delta_aci_minus_ctrl)
) +
  geom_tile(color = "white", linewidth = 0.4) +
  geom_text(aes(label = star), size = 2.2) +
  scale_fill_activity(ck_cell_sub$delta_aci_minus_ctrl) +
  theme_classic() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
  xlab(NULL) +
  ylab(NULL) +
  ggtitle("Subtype cell-level ACI − CTRL (BH)")
heatmap fill 
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
-1.9633 -0.2165  0.2766  0.3486  0.7943  3.1434 
|x| q80 / q90 / q95: 1.09 / 1.56 / 2.1 
ggsave(file.path(res_path, "05_neut_heatmap_cell_subtype.pdf"), p_sub_c, width = 8, height = 10, dpi = 600)
p_sub_c

sessionInfo()
R version 4.4.3 (2025-02-28)
Platform: x86_64-pc-linux-gnu
Running under: Ubuntu 24.04.3 LTS

Matrix products: default
BLAS:   /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.12.0 
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_US.UTF-8        LC_COLLATE=en_US.UTF-8    
 [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: Asia/Shanghai
tzcode source: system (glibc)

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

other attached packages:
 [1] ggpubr_0.6.0       reticulate_1.42.0  tictoc_1.2.1       cowplot_1.1.3     
 [5] qs2_0.1.7          lubridate_1.9.3    forcats_1.0.0      stringr_1.5.1     
 [9] dplyr_1.1.4        purrr_1.0.2        readr_2.1.5        tidyr_1.3.1       
[13] tibble_3.2.1       ggplot2_3.5.1      tidyverse_2.0.0    Seurat_5.2.1      
[17] SeuratObject_5.0.2 sp_2.1-4          

loaded via a namespace (and not attached):
  [1] RColorBrewer_1.1-3     jsonlite_1.8.9         magrittr_2.0.3        
  [4] spatstat.utils_3.1-1   farver_2.1.2           rmarkdown_2.29        
  [7] ragg_1.3.3             vctrs_0.6.5            ROCR_1.0-11           
 [10] spatstat.explore_3.3-3 rstatix_0.7.2          htmltools_0.5.8.1     
 [13] broom_1.0.7            Formula_1.2-5          sctransform_0.4.1     
 [16] parallelly_1.39.0      KernSmooth_2.23-26     htmlwidgets_1.6.4     
 [19] ica_1.0-3              plyr_1.8.9             plotly_4.10.4         
 [22] zoo_1.8-12             igraph_2.1.2           mime_0.12             
 [25] lifecycle_1.0.4        pkgconfig_2.0.3        Matrix_1.7-3          
 [28] R6_2.5.1               fastmap_1.2.0          fitdistrplus_1.2-1    
 [31] future_1.34.0          shiny_1.9.1            digest_0.6.37         
 [34] colorspace_2.1-1       patchwork_1.3.0        tensor_1.5            
 [37] RSpectra_0.16-2        irlba_2.3.5.1          textshaping_0.4.0     
 [40] labeling_0.4.3         progressr_0.15.1       fansi_1.0.6           
 [43] spatstat.sparse_3.1-0  timechange_0.3.0       httr_1.4.7            
 [46] polyclip_1.10-7        abind_1.4-8            compiler_4.4.3        
 [49] bit64_4.5.2            withr_3.0.2            backports_1.5.0       
 [52] carData_3.0-5          fastDummies_1.7.4      ggsignif_0.6.4        
 [55] MASS_7.3-65            tools_4.4.3            lmtest_0.9-40         
 [58] httpuv_1.6.15          future.apply_1.11.3    goftest_1.2-3         
 [61] glue_1.8.0             nlme_3.1-166           promises_1.3.2        
 [64] grid_4.4.3             Rtsne_0.17             cluster_2.1.8.1       
 [67] reshape2_1.4.4         generics_0.1.3         gtable_0.3.6          
 [70] spatstat.data_3.1-4    tzdb_0.4.0             data.table_1.16.2     
 [73] hms_1.1.3              car_3.1-3              stringfish_0.18.0     
 [76] utf8_1.2.4             spatstat.geom_3.3-4    RcppAnnoy_0.0.22      
 [79] ggrepel_0.9.6          RANN_2.6.2             pillar_1.9.0          
 [82] vroom_1.6.5            spam_2.11-0            RcppHNSW_0.6.0        
 [85] later_1.4.1            splines_4.4.3          lattice_0.22-6        
 [88] bit_4.5.0              survival_3.8-3         deldir_2.0-4          
 [91] tidyselect_1.2.1       miniUI_0.1.1.1         pbapply_1.7-2         
 [94] knitr_1.49             gridExtra_2.3          scattermore_1.2       
 [97] xfun_0.49              matrixStats_1.4.1      stringi_1.8.4         
[100] lazyeval_0.2.2         yaml_2.3.10            pacman_0.5.1          
[103] evaluate_1.0.1         codetools_0.2-20       cli_3.6.3             
[106] uwot_0.2.2             RcppParallel_5.1.9     systemfonts_1.1.0     
[109] xtable_1.8-4           munsell_0.5.1          Rcpp_1.0.13-1         
[112] globals_0.16.3         spatstat.random_3.3-2  png_0.1-8             
[115] spatstat.univar_3.1-1  parallel_4.4.3         dotCall64_1.2         
[118] listenv_0.9.1          viridisLite_0.4.2      scales_1.3.0          
[121] ggridges_0.5.6         crayon_1.5.3           rlang_1.1.4