Load packages
# we would like to demonstrate the hybrid strategy using Seurat workflow
library(Seurat)
library(cowplot)
# packages for cell hashing demultiplexing
library(HTOreader)
library(cellhashR) # this package integrated the following methods: multiseq, htodemux, gmm_demux, bff_raw, bff_cluster and dropletutils
Step 1 Load data from 10X folder
We load 8pool-CA dataset from the 10X folder using Seurat
# Load in the RNA UMI matrix for doublet negative (DN) dataset and antigen
# specific (AS) dataset
<- Read10X(data.dir = "../data/8pool-Carr-RNA/filtered_feature_bc_matrix/") DN.data
split different modalities: Cell hashing (HTO), antigen probes (Probe) and CITE-seq surface protein panel (ADT) In 8pool DN and AS datasets, we have applied 8 cell hashing tags, 18 antigen probes, and a large CITE-seq panel
<- DN.data[["Antibody Capture"]][1:18, ] # expression of antigen probes, Carrier data set doesn't have this measurement
DN.probe <- DN.data[["Antibody Capture"]][19:26, ] # expression of cell hashing tags
DN.hto <- DN.data[["Antibody Capture"]][27:163, ] # expression of CITE-seq surface protein panel DN.ADT
Step 2 Create Seurat object and attached multi-modality data
# create Seurat object
<- CreateSeuratObject(counts = DN.data$`Gene Expression`, project = "Carr")
DN
# create multi-modality assay and attach them to Seurat object
"HTO"]] <- CreateAssayObject(counts = DN.hto)
DN[["ADT"]] <- CreateAssayObject(counts = DN.ADT)
DN[[
# percent of MT genes
"percent.mt"]] <- PercentageFeatureSet(object = DN, pattern = "^MT-")
DN[[# percent of IG genes (for B cell study)
"percent.ig"]] <- PercentageFeatureSet(object = DN, pattern = "^IG[HKL]") DN[[
Step 3 Pre-process
Filter out unwanted cells (optional)
<- subset(DN, subset = nFeature_RNA > 200 & nFeature_RNA < 2500 & percent.mt <
DN 10)
Normalize HTO and ADT data
<- NormalizeData(DN, assay = "HTO", normalization.method = "CLR")
DN <- NormalizeData(DN, assay = "ADT", normalization.method = "CLR") DN
Step 4 Cell hashing demultiplexing
1) Demultiplexing of cell hashing using existing methods integrated in cellhashR: multiseq, gmm_demux, bff_raw, bff_cluster, dropletutils
load data (we hide the plots of this step)
# parse cell hashing output, providing a barcode whitelist. (please refer to
# cellhashR tutorial for more information:
# https://github.com/BimberLab/cellhashR?tab=readme-ov-file#example)
<- ProcessCountMatrix(rawCountData = "../data/8pool-Carr-RNA/filtered_feature_bc_matrix/",
barcodeData barcodeWhitelist = c("hashtag1", "hashtag2", "hashtag3", "hashtag4", "hashtag5",
"hashtag6", "hashtag7", "hashtag8"), datatypeName = "Antibody Capture")
rownames(barcodeData) <- c("S282", "S283", "S284", "S344", "S397", "S417", "S421",
"S423")
QC (we hide the plots of this step)
# Create QC plots of barcode normalization
PlotNormalizationQC(barcodeData)
Calling cell hashing using different methods (we hide the plots of this step)
# Generate the final cell hashing calls
<- GenerateCellHashingCalls(barcodeMatrix = barcodeData, methods = c("multiseq",
calls "gmm_demux", "bff_raw", "bff_cluster", "dropletutils"))
# Inspect negative cells:
SummarizeCellsByClassification(calls = calls, barcodeMatrix = barcodeData)
2) Demultiplexing of cell hashing using Seurat (htodemux)
<- NormalizeData(DN, assay = "HTO", normalization.method = "CLR")
DN <- HTODemux(DN, assay = "HTO", positive.quantile = 0.99) DN
3) Demultiplexing of cell hashing using HTOreader
<- HTOClassification(DN, assay = "HTO", method = "log")
DN
<- PlotHTO(DN, assay = "HTO", method = "log")
plots plot_grid(plotlist = plots, nrow = 2)
4)integrate HTOreader, Seurat results with cellhashR results
rownames(calls) <- calls$cellbarcode
<- names(DN@active.ident)
DN_barcode <- sub("-1", "", DN_barcode)
DN_barcode <- intersect(DN_barcode, calls$cellbarcode)
DN_hq_barcode
<- calls[DN_hq_barcode, ]
calls_hq
<- DN$HTOid
HTOid.hq names(HTOid.hq) <- sub("-1", "", names(HTOid.hq))
$HTOid <- HTOid.hq[rownames(calls_hq)]
calls_hq
<- DN$hash.ID
Seuratid.hq names(Seuratid.hq) <- sub("-1", "", names(Seuratid.hq))
$seurat <- as.character(Seuratid.hq[rownames(calls_hq)]) calls_hq
<- matrix(data = c(table(calls_hq$multiseq), table(calls_hq$gmm_demux),
res_table table(calls_hq$bff_raw), table(calls_hq$bff_cluster), table(calls_hq$dropletutils),
table(calls_hq$HTOid), table(calls_hq$seurat)), nrow = 7, ncol = 10, byrow = TRUE,
dimnames = list(c("multiseq", "gmm_demux", "bff_raw", "bff_cluster", "dropletutils",
"HTOreader", "htodemux(Seurat)"), names(table(calls_hq$multiseq))))
# singlet rate
<- rowSums(res_table[, which(colnames(res_table) %in% rownames(barcodeData))])
singlet_count <- singlet_count/rowSums(res_table)
singlet_rate
<- cbind(res_table, singlet_count, singlet_rate)
final_table colnames(final_table) <- c(colnames(res_table), "singlet", "singlet rate")
Cell hashing Demultiplexing results:
final_table
## Doublet Negative S282 S283 S284 S344 S397 S417 S421 S423
## multiseq 2103 160 312 1738 1546 1548 1866 1196 780 1635
## gmm_demux 2631 27 310 1696 1498 1483 1706 1165 730 1638
## bff_raw 1673 579 0 1786 1515 1585 2094 1179 794 1679
## bff_cluster 456 404 0 2011 1713 1771 2380 1376 890 1883
## dropletutils 1801 599 267 1769 1463 1512 1953 1184 791 1545
## HTOreader 1893 170 317 1771 1502 1560 2055 1174 780 1662
## htodemux(Seurat) 2759 125 302 1698 1389 1476 1635 1148 727 1625
## singlet singlet rate
## multiseq 10621 0.8243558
## gmm_demux 10226 0.7936976
## bff_raw 10632 0.8252096
## bff_cluster 12024 0.9332505
## dropletutils 10484 0.8137224
## HTOreader 10821 0.8398789
## htodemux(Seurat) 10000 0.7761565
We found two BFF models failed to identify S282. The best singlet rate is 83.9%, average rate is ~80% (exlcuding BFF models due to failure of S282)
Step 4, Apply hybrid strategy: combine cell hashing demultiplexing with SNP-based method
In order to apply hybrid demultiplexing, we need to add SNP-based labels into Seurat object:
# add soupercell labels into Seurat object
<- read.csv("./8pool/clusters.tsv", sep = "\t")
souporcell.res $identity <- souporcell.res$status
souporcell.res$identity[which(souporcell.res$status == "singlet")] <- paste0(souporcell.res$status[which(souporcell.res$status ==
souporcell.res"singlet")], souporcell.res$assignment[which(souporcell.res$status == "singlet")])
rownames(souporcell.res) <- souporcell.res$barcode
$souporcell <- souporcell.res[names(DN@active.ident), "identity"] DN
Then we reveal cell labels using hybrid demultiplexing (“HybridDemultiplexing” function in HTOreader package). In this case, we use HTOreader result for cell hashing demultiplexing
<- HybridDemultiplexing(object = DN, cellhashing_label = "HTOid", genotype_label = "souporcell",
DN hto_names = c("S282", "S283", "S284", "S344", "S397", "S417", "S421", "S423"))
## We matched:
## S282 with singlet6
## S283 with singlet3
## S284 with singlet2
## S344 with singlet1
## S397 with singlet0
## S417 with singlet4
## S421 with singlet7
## S423 with singlet5
## The convergence score between cell hashing demultiplexing and SNP-based demultiplexing is: 0.993251476239573
## Hybrid demultiplexing done! Results in hybridID
table(DN$hybridID)
##
## doublet S282 S283 S284 S344 S397 S417
## 1173 278 1859 1629 1613 2243 1252
## S421 S423 unassigned
## 857 1749 233
BTW, The Hybrid strategy is highly flexiable to cell hashing demultiplexing method and quality, it works with all cell hashing methods. For example:
# Hybrid demultiplexing using HTOdemux (Seurat)
<- HybridDemultiplexing(object = DN, cellhashing_label = "hash.ID", genotype_label = "souporcell",
DN hto_names = c("S282", "S283", "S284", "S344", "S397", "S417", "S421", "S423"))
## We matched:
## S282 with singlet6
## S283 with singlet3
## S284 with singlet2
## S344 with singlet1
## S397 with singlet0
## S417 with singlet4
## S421 with singlet7
## S423 with singlet5
## The convergence score between cell hashing demultiplexing and SNP-based demultiplexing is: 0.993932038834951
## Hybrid demultiplexing done! Results in hybridID
table(DN$hybridID)
##
## doublet S282 S283 S284 S344 S397 S417
## 1213 279 1859 1628 1614 2242 1256
## S421 S423 unassigned
## 865 1749 181
Furthermore, The Hybrid strategy even works with two BFF models that fail to identify S282. HybridDemultiplexing correctly linked singlet6 with S282.
rownames(calls) <- paste0(calls$cellbarcode, "-1")
$bff_cluster <- calls[names(DN@active.ident), "bff_cluster"]
DN
<- HybridDemultiplexing(object = DN, cellhashing_label = "bff_cluster", genotype_label = "souporcell",
DN hto_names = c("S282", "S283", "S284", "S344", "S397", "S417", "S421", "S423"))
## The missing HTO S282 is assigned to singlet6
## We matched:
## S283 with singlet3
## S284 with singlet2
## S344 with singlet1
## S397 with singlet0
## S417 with singlet4
## S421 with singlet7
## S423 with singlet5
## S282 with singlet6
## The convergence score between cell hashing demultiplexing and SNP-based demultiplexing is: 0.985078676071622
## Hybrid demultiplexing done! Results in hybridID
We observe that results from hybrid runs using various cell hashing methods may exhibit minor variations. This is due to our adoption of a conservative strategy, which categorizes all Case 2/3 inconsistency as unassigned to guarantee the accuracy and precision of the outcomes. For detailed information, please refer to the Methods section of our paper.
table(DN$hybridID)
##
## doublet S282 S283 S284 S344 S397 S417
## 311 235 1846 1615 1599 2222 1240
## S421 S423 unassigned
## 893 1737 1188
Step 5, hybrid demultiplexing for multiple datasets from the same group of donors (optional)
In this case, we have 8pool-AS, which shares the same group of human donors but doesn’t have cell hashing for some reason. Using Hybrid demultiplexing, we’re still able to handle it. We load the 8pool-AS data first:
<- Read10X(data.dir = "../data/8pool-AS-RNA/filtered_feature_bc_matrix/")
AS.data
<- AS.data[["Antibody Capture"]][1:18, ] # expression of antigen probes
AS.probe <- AS.data[["Antibody Capture"]][19:26, ] # expression of cell hashing tags, Antigen specific data set doesn't have this measurement, they have 0 counts
AS.hto <- AS.data[["Antibody Capture"]][27:163, ] # expression of CITE-seq surface protein panel
AS.ADT
<- CreateSeuratObject(counts = AS.data$`Gene Expression`, project = "AS")
AS
"ADT"]] <- CreateAssayObject(counts = AS.ADT)
AS[["Probe"]] <- CreateAssayObject(counts = AS.probe)
AS[[
"percent.mt"]] <- PercentageFeatureSet(object = AS, pattern = "^MT-")
AS[["percent.ig"]] <- PercentageFeatureSet(object = AS, pattern = "^IG[HKL]")
AS[[
<- subset(AS, subset = nFeature_RNA > 200 & nFeature_RNA < 2500 & percent.mt <
AS 10)
<- NormalizeData(AS, assay = "Probe", normalization.method = "CLR")
AS <- NormalizeData(AS, assay = "ADT", normalization.method = "CLR") AS
We ran souporcell on the entire 8pool dataset (AS + DN) so that the genotypes are across two datasets. We annotate souporcell genotype clusters to 8pool-AS dataset
<- read.csv("./8pool/clusters.tsv", sep = "\t")
souporcell.res $identity <- souporcell.res$status
souporcell.res$identity[which(souporcell.res$status == "singlet")] <- paste0(souporcell.res$status[which(souporcell.res$status ==
souporcell.res"singlet")], souporcell.res$assignment[which(souporcell.res$status == "singlet")])
rownames(souporcell.res) <- souporcell.res$barcode
$souporcell <- souporcell.res[names(AS@active.ident), "identity"] AS
We get high singlet rate for 8pool-AS using hybrid demultiplexing and saved reagents of cell hashing on 8pool-AS. Beside the economy, there is a more critical reason for not using cell hashing in 8pool-AS dataset: we aim to measure the binding affinity between antigen specific B cells and our antigen probes. We don’t want any disruptions from the cell hashing due to they share the same channel(Antibody Capture)
Singlet rate of hybrid on 8pool-AS:
1 - length(which(AS$souporcell %in% c("doublet", "unassigned")))/length(AS$souporcell)) *
(100
## [1] 84.76845
table(AS$souporcell)
##
## doublet singlet0 singlet1 singlet2 singlet3 singlet4 singlet5
## 416 391 868 71 130 447 28
## singlet6 singlet7 unassigned
## 118 232 5
Supplementary, hybrid demultiplexing for dataset that has high doublet rate
We have illustrated the effectiveness of hybrid demultiplexing on “high quality” data, where cell hashing and SNP-based methods are highly consistent with each other. However, there are instances where the two modalities do not align well. In such cases, our hybrid demultiplexing approach adeptly identifies significant misalignment issues via the convergence score and issues a warning to the user. Discrepancies between the two modalities often suggest a high doublet rate or a complex genetic background, necessitating careful examination by users with relevant biological expertise. To exemplify this, we use the dataset labeled ‘9pool-CA’:
.9pool.data <- Read10X(data.dir = "../data/9pool-CA-RNA/filtered_feature_bc_matrix/") DN
## 10X data contains more than one type and is being returned as a list containing matrices of each type.
.9pool.hto <- DN.9pool.data[["Antibody Capture"]][138:146, ]
DN.9pool.data[["Antibody Capture"]] <- DN.9pool.data[["Antibody Capture"]][1:137,
DN
].9pool <- CreateSeuratObject(counts = DN.9pool.data$`Gene Expression`, project = "Carr")
DN.9pool[["HTO"]] <- CreateAssayObject(counts = DN.9pool.hto)
DN.9pool$subject <- "CA"
DN.9pool <- RenameCells(object = DN.9pool, add.cell.id = "CA")
DN.9pool[["percent.mt"]] <- PercentageFeatureSet(object = DN.9pool, pattern = "^MT-")
DN.9pool[["percent.ig"]] <- PercentageFeatureSet(object = DN.9pool, pattern = "^IG[HKL]")
DN.9pool <- subset(DN.9pool, subset = nFeature_RNA > 200 & nFeature_RNA < 2500 &
DN< 10)
percent.mt .9pool <- NormalizeData(DN.9pool, assay = "HTO", normalization.method = "CLR") DN
## Normalizing across features
Cell hashing demultiplexing using HTOreader
.9pool <- HTOClassification(DN.9pool, assay = "HTO", method = "log") DN
## Normalization method is: log
## Cutoff for S289 is 3.35181680088473
## Cutoff for S297 is 4.30147252317663
## Cutoff for S299 is 3.97183902805626
## Cutoff for S302 is 3.83673446295452
## Cutoff for S354 is 3.90049907157204
## Cutoff for S365 is 2.71044460199379
## Cutoff for S374 is 2.86249365243517
## Cutoff for S403 is 2.65461369188085
## Cutoff for S409 is 3.21699969089809
Load souporcell results. There are three runs, 1) using all cells, 2) only remove cells that expressed both B and T cell markers, 3) remove all doublet cells that identified by cell hashing
# souporcell results using all cells
<- read.table(file = "9pool/clusters.csv", header = TRUE, sep = ",", row.names = 1)
sampleID <- as.character(DN.9pool@meta.data[["orig.ident"]])
soupID <- 1
i for (barcode in names(DN.9pool@active.ident)) {
<- gsub("^[A-Za-z]+_", "", barcode)
barcode1 if (barcode1 %in% rownames(sampleID)) {
<- sampleID[barcode1, "identity"]
soupID[i] else {
} <- "unassigned"
soupID[i]
}<- i + 1
i
}.9pool$soupID1 <- soupID
DN
# souporcell results on the dataset, after excluding cells that expressed both
# B and T cell markers
<- read.table(file = "9pool/clusters1.csv", header = TRUE, sep = ",", row.names = 1)
sampleID <- as.character(DN.9pool@meta.data[["orig.ident"]])
soupID <- 1
i for (barcode in names(DN.9pool@active.ident)) {
<- gsub("^[A-Za-z]+_", "", barcode)
barcode1 if (barcode1 %in% rownames(sampleID)) {
<- sampleID[barcode1, "identity"]
soupID[i] else {
} <- "unassigned"
soupID[i]
}<- i + 1
i
}.9pool$soupID2 <- soupID
DN
# souporcell results on the dataset, , after excluding all doublet cells that
# identified by cell hashing
<- read.table(file = "9pool/clusters2.csv", header = TRUE, sep = ",", row.names = 1)
sampleID <- as.character(DN.9pool@meta.data[["orig.ident"]])
soupID <- 1
i for (barcode in names(DN.9pool@active.ident)) {
<- gsub("^[A-Za-z]+_", "", barcode)
barcode1 if (barcode1 %in% rownames(sampleID)) {
<- sampleID[barcode1, "identity"]
soupID[i] else {
} <- "unassigned"
soupID[i]
}<- i + 1
i
}.9pool$soupID3 <- soupID DN
We then run HybridDemultiplexing for using all three genotype labels. For soupID1, a low convergence score was identified, and a warning was poped. HybridID was set to the cell hashing labels cause the genotype labels are not accurate.
.9pool <- HybridDemultiplexing(object = DN.9pool, cellhashing_label = "HTOid",
DNgenotype_label = "soupID1", hto_names = DN.9pool.hto@Dimnames[[1]])
## We matched:
## S289 with singlet8
## S297 with singlet1
## S299 with singlet4
## S302 with singlet2
## S354 with singlet6
## S365 with singlet7
## S374 with singlet3
## S403 with singlet5
## S409 with singlet0
## The convergence score between cell hashing demultiplexing and SNP-based demultiplexing is: 0.106513222331048
## Warning in HybridDemultiplexing(object = DN.9pool, cellhashing_label = "HTOid",
## : Corrolation between your cell hashing demultiplexing and SNP-based
## demultiplexing is very poor. The convergence score is 0.106513222331048 , which
## is much lower than normal. In this case, we can not trust your SNP-based
## demultiplexing results. We set your cell hashing ID to the hybrid labels.
## However, a low convergance score usually indicates high doublet rates or even
## messed genetic background among different samples. We highly recommand the
## users to double check the data.
For soupID2, the convergence score was improved, but still too low. HybridID was set to the cell hashing labels cause the genotype labels are not accurate.
.9pool <- HybridDemultiplexing(object = DN.9pool, cellhashing_label = "HTOid",
DNgenotype_label = "soupID2", hto_names = DN.9pool.hto@Dimnames[[1]])
## We matched:
## S289 with singlet1
## S297 with singlet3
## S299 with singlet5
## S302 with singlet2
## S354 with singlet4
## S365 with singlet6
## S374 with singlet8
## S403 with singlet7
## S409 with singlet0
## The convergence score between cell hashing demultiplexing and SNP-based demultiplexing is: 0.552840009051822
## Warning in HybridDemultiplexing(object = DN.9pool, cellhashing_label = "HTOid",
## : Corrolation between your cell hashing demultiplexing and SNP-based
## demultiplexing is very poor. The convergence score is 0.552840009051822 , which
## is much lower than normal. In this case, we can not trust your SNP-based
## demultiplexing results. We set your cell hashing ID to the hybrid labels.
## However, a low convergance score usually indicates high doublet rates or even
## messed genetic background among different samples. We highly recommand the
## users to double check the data.
For soupID3, the convergence score was highly improved. HybridID was determined by both genotype labels and cell hashing labels.
.9pool <- HybridDemultiplexing(object = DN.9pool, cellhashing_label = "HTOid",
DNgenotype_label = "soupID3", hto_names = DN.9pool.hto@Dimnames[[1]])
## We matched:
## S289 with singlet7
## S297 with singlet5
## S299 with singlet3
## S302 with singlet0
## S354 with singlet6
## S365 with singlet1
## S374 with singlet4
## S403 with singlet8
## S409 with singlet2
## The convergence score between cell hashing demultiplexing and SNP-based demultiplexing is: 0.956576825616217
## Hybrid demultiplexing done! Results in hybridID
Session info
sessionInfo()
## R version 4.2.2 (2022-10-31)
## Platform: aarch64-apple-darwin20 (64-bit)
## Running under: macOS Ventura 13.0
##
## Matrix products: default
## BLAS: /Library/Frameworks/R.framework/Versions/4.2-arm64/Resources/lib/libRblas.0.dylib
## LAPACK: /Library/Frameworks/R.framework/Versions/4.2-arm64/Resources/lib/libRlapack.dylib
##
## locale:
## [1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
##
## attached base packages:
## [1] stats graphics grDevices utils datasets methods base
##
## other attached packages:
## [1] cellhashR_1.0.3 HTOreader_0.1.0 cowplot_1.1.1 SeuratObject_4.1.3
## [5] Seurat_4.3.0.1
##
## loaded via a namespace (and not attached):
## [1] Rtsne_0.16 colorspace_2.1-0 deldir_1.0-9
## [4] ellipsis_0.3.2 modeltools_0.2-23 ggridges_0.5.4
## [7] spatstat.data_3.0-1 rstudioapi_0.15.0 leiden_0.4.3
## [10] listenv_0.9.0 hash_2.2.6.2 ggrepel_0.9.3
## [13] flexmix_2.3-19 fansi_1.0.4 R.methodsS3_1.8.2
## [16] codetools_0.2-18 splines_4.2.2 cachem_1.0.8
## [19] knitr_1.43 polyclip_1.10-4 jsonlite_1.8.7
## [22] ica_1.0-3 cluster_2.1.4 R.oo_1.25.0
## [25] png_0.1-8 uwot_0.1.16 spatstat.sparse_3.0-2
## [28] shiny_1.7.4.1 sctransform_0.3.5 BiocManager_1.30.19
## [31] compiler_4.2.2 httr_1.4.6 Matrix_1.5-3
## [34] fastmap_1.1.1 lazyeval_0.2.2 cli_3.6.1
## [37] formatR_1.14 later_1.3.1 htmltools_0.5.5
## [40] tools_4.2.2 igraph_1.5.1 gtable_0.3.3
## [43] glue_1.6.2 RANN_2.6.1 reshape2_1.4.4
## [46] dplyr_1.1.2 Rcpp_1.0.11 scattermore_1.2
## [49] jquerylib_0.1.4 vctrs_0.6.3 nlme_3.1-161
## [52] spatstat.explore_3.2-1 progressr_0.13.0 lmtest_0.9-40
## [55] spatstat.random_3.1-5 xfun_0.39 stringr_1.5.0
## [58] globals_0.16.2 mime_0.12 miniUI_0.1.1.1
## [61] lifecycle_1.0.3 irlba_2.3.5.1 goftest_1.2-3
## [64] future_1.33.0 MASS_7.3-58.1 zoo_1.8-12
## [67] scales_1.2.1 spatstat.utils_3.0-3 promises_1.2.0.1
## [70] parallel_4.2.2 RColorBrewer_1.1-3 yaml_2.3.7
## [73] gridExtra_2.3 reticulate_1.30 pbapply_1.7-2
## [76] ggplot2_3.4.2 sass_0.4.6 stringi_1.7.12
## [79] rlang_1.1.1 pkgconfig_2.0.3 matrixStats_1.0.0
## [82] evaluate_0.21 lattice_0.20-45 tensor_1.5
## [85] ROCR_1.0-11 purrr_1.0.1 patchwork_1.1.2
## [88] htmlwidgets_1.6.2 tidyselect_1.2.0 parallelly_1.36.0
## [91] RcppAnnoy_0.0.21 bookdown_0.34 plyr_1.8.8
## [94] magrittr_2.0.3 R6_2.5.1 generics_0.1.3
## [97] pillar_1.9.0 fitdistrplus_1.1-11 prettydoc_0.4.1
## [100] abind_1.4-5 survival_3.5-0 sp_2.0-0
## [103] nnet_7.3-18 tibble_3.2.1 future.apply_1.11.0
## [106] KernSmooth_2.23-20 utf8_1.2.3 spatstat.geom_3.2-2
## [109] plotly_4.10.2 rmarkdown_2.23 grid_4.2.2
## [112] data.table_1.14.8 rmdformats_1.0.4 digest_0.6.33
## [115] xtable_1.8-4 tidyr_1.3.0 httpuv_1.6.11
## [118] R.utils_2.12.2 stats4_4.2.2 munsell_0.5.0
## [121] viridisLite_0.4.2 bslib_0.5.0