Characterization of the Parvalbumin Interneurons in
Mouse Brain using GeoMx DSP
The ASOC Bioinformatics team generated this QC report, which includes stats and diagnostic plots of the GeoMx Digital Spatial (DSP) data.
This report was generated to support Dr. Jonathan Epp's talk at the Spatial Omics Seminar on November 7th, 2024, entitled "Impaired Parvalbumin Interneurons in the Retrosplenial Cortexas the Root of Sex-Dependent Vulnerability in Alzheimer's Disease." The workflow was revised from the original analysis to deliver step-by-step quality assessment and better visualization during the seminar.
WORKING_DIRECTORY
├── Annot
│ └── Annotation.xlsx
├── DCC
│ ├── DSP-*-?-???.dcc
└── PKC
└── *.pkc
# Do not run
baseDir <- "WORKING_DIRECTORY" # where the Annot/DCC/PKC/Settings folders are located
setwd(baseDir)
library(NanoStringNCTools)
library(GeoMxWorkflows)
library(GeomxTools)
library(stringr)
library(dplyr)
library(scales)
library(ggplot2)
library(ggsankey)
library(plotly)
library(reshape2)
library(EnvStats)
library(DescTools)
library(RColorBrewer)
dccFiles <- dir(file.path(baseDir, "DCC"), pattern = ".dcc$", full.names = TRUE, recursive = TRUE)
pkcFile <- dir(file.path(baseDir, "PKC"), pattern = ".pkc$", full.names = TRUE, recursive = TRUE)
annotFile <- dir(file.path(baseDir, "Annot"), pattern = ".xlsx$", full.names = TRUE, recursive = TRUE)
initSet <- readNanoStringGeoMxSet(
dccFiles = dccFiles,
pkcFiles = pkcFile,
phenoDataFile = annotFile,
phenoDataSheet = "Annotation",
phenoDataDccColName = "FileName",
protocolDataColNames = c("ROI", "AOI"),
experimentDataColNames = c("Panel")
)
dim(initSet)
## Features Samples
## 20175 95
assayData(initSet)$exprs[c(4:8), c(8:10)]
## DSP-1001660023394-A-A09.dcc DSP-1001660023394-A-A10.dcc
## RTS0060890 214 1
## RTS0060891 129 0
## RTS0060892 128 0
## RTS0060893 104 0
## RTS0060894 291 1
gSet <- shiftCountsOne(initSet, useDALogic = TRUE)
assayData(gSet)$exprs[c(4:8), c(8:10)]
## DSP-1001660023394-A-A09.dcc DSP-1001660023394-A-A10.dcc
## RTS0060890 214 1
## RTS0060891 129 1
## RTS0060892 128 1
## RTS0060893 104 1
## RTS0060894 291 1
module <- gsub(".pkc", "", annotation(gSet))
gSet <- setSegmentQCFlags(gSet, qcCutoffs = segmentQcParams)
qcMat <- sData(gSet)
qcMat$Segment <- factor(qcMat$Segment, levels = segmentOrder)
qcMat$Slide <- factor(qcMat$Slide, levels = slideOrder)
histQC(qcMat, "Trimmed (%)", segmentQC_colBy, segmentQC_rowBy, segmentQC_trimmedThre, cols = segmentCols)
histQC(qcMat, "Stitched (%)", segmentQC_colBy, segmentQC_rowBy, segmentQC_stitchedThre, cols = segmentCols)
histQC(qcMat, "Aligned (%)", segmentQC_colBy, segmentQC_rowBy, segmentQC_alignedThre, cols = segmentCols)
histQC(qcMat, "Saturated (%)", segmentQC_colBy, segmentQC_rowBy, segmentQC_saturatedThre, cols = segmentCols)
histQC(qcMat, "area", segmentQC_colBy, segmentQC_rowBy, segmentQC_areaThre, "log10", "AOI Area (log10)", cols = segmentCols)
histQC(qcMat, "nuclei", segmentQC_colBy, segmentQC_rowBy, segmentQC_nucleiThre, "log10", "AOI nuclei count", cols = segmentCols)
segmentQcResults <- protocolData(gSet)[["QCFlags"]]
flagColumns <- colnames(segmentQcResults)
qcSummary <- data.frame(Pass = colSums(!segmentQcResults[, flagColumns]), Warning = colSums(segmentQcResults[, flagColumns]))
segmentQcResults$QCStatus <- apply(segmentQcResults, 1L, function(x) {
ifelse(sum(x) == 0L, "PASS", "WARNING")
})
qcSummary["TOTAL FLAGS", ] <- c(sum(segmentQcResults[, "QCStatus"] == "PASS"), sum(segmentQcResults[, "QCStatus"] == "WARNING"))
qcSummary[, "TOTAL"] <- apply(qcSummary, 1, sum)
qcSummary
## Pass Warning TOTAL
## LowReads 95 0 95
## LowTrimmed 94 1 95
## LowStitched 94 1 95
## LowAligned 95 0 95
## LowSaturation 95 0 95
## LowNegatives 95 0 95
## LowNuclei 95 0 95
## LowArea 94 1 95
## TOTAL FLAGS 93 2 95
gSet <- gSet[, segmentQcResults$QCStatus == "PASS"]
dim(gSet)
## Features Samples
## 20175 93
negCol <- "NegGeoMean"
negativeGeoMeans <- esBy(
negativeControlSubset(gSet),
GROUP = "Module",
FUN = function(x) {
assayDataApply(x, MARGIN = 2, FUN = ngeoMean, elt = "exprs")
}
)
protocolData(gSet)[[negCol]] <- negativeGeoMeans
pData(gSet)[, negCol] <- sData(gSet)[[negCol]]
backgrounMat <- sData(gSet)
backgrounMat <- backgrounMat[, c("NegGeoMean", segmentQC_colBy, segmentQC_rowBy)]
backgrounMat$Segment <- factor(backgrounMat$Segment, levels = segmentOrder)
backgrounMat$Slide <- factor(backgrounMat$Slide, levels = slideOrder)
histQC(backgrounMat, "NegGeoMean", segmentQC_colBy, segmentQC_rowBy, loqCutoff, "log10", "GeoMean(negative probes)", cols = segmentCols)
gSet <- setBioProbeQCFlags(gSet, qcCutoffs = probeQcParams, removeLocalOutliers = FALSE)
probeQcResults <- fData(gSet)[["QCFlags"]]
qcDf <- data.frame(
Global_outlier = length(which(fData(gSet)[["QCFlags"]][, c("GlobalGrubbsOutlier")] == TRUE)),
Local_outlier = length(which(fData(gSet)[["QCFlags"]][, c("LowProbeRatio")] == TRUE))
)
qcDf
## Global_outlier Local_outlier
## 0 0
probeQcPassed <- subset(
gSet,
fData(gSet)[["QCFlags"]][, c("LowProbeRatio")] == FALSE &
fData(gSet)[["QCFlags"]][, c("GlobalGrubbsOutlier")] == FALSE
)
gSet <- probeQcPassed
dim(gSet)
## Features Samples
## 20175 93
newSet <- aggregateCounts(gSet)
newSet <- subset(newSet, fData(newSet)$TargetName != "NegProbe-WTX") # Remove negative probe in downstream analysis
dim(newSet)
## Features Samples
## 19962 93
lowLvqcDf <- data.frame(
Slide = pData(newSet)$Slide,
Group = pData(newSet)$Group,
Model = pData(newSet)$Model,
Sex = pData(newSet)$Sex,
Library = pData(newSet)$Library,
Segment = pData(newSet)$Segment,
Area = pData(newSet)$area,
Nuclei = pData(newSet)$nuclei
)
lowLvqcDf$Slide <- factor(lowLvqcDf$Slide, levels=slideOrder)
lowLvqcDf$Group <- factor(lowLvqcDf$Group, levels=groupOrder)
lowLvqcDf$Model <- factor(lowLvqcDf$Model, levels=modelOrder)
lowLvqcDf$Sex <- factor(lowLvqcDf$Sex, levels=sexOrder)
lowLvqcDf$Segment <- factor(lowLvqcDf$Segment, levels=segmentOrder)
dodge <- position_dodge(width = 0.5)
ggplot(data = lowLvqcDf, aes(x = Slide, y = Area, fill = Slide)) +
geom_violin(position = dodge, size = 0) +
geom_boxplot(width = 0.1, position = dodge, fill="white") +
scale_fill_manual(values = slideCols) +
labs(
title = "",
x = "",
y = "Area"
) +
theme_bw() +
theme(
axis.line = element_line(colour = "black"),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
panel.background = element_blank(),
axis.text.x = element_text(angle = 0, vjust = 0, hjust = 0.5),
legend.position = "none",
text = element_text(size = 12)
) +
scale_y_continuous(trans = "log10")
ggplot(data = lowLvqcDf, aes(x = Slide, y = Nuclei, fill = Slide)) +
geom_violin(position = dodge, size = 0) +
geom_boxplot(width = 0.1, position = dodge, fill="white") +
scale_fill_manual(values = slideCols) +
labs(
title = "",
x = "",
y = "#Nuclei"
) +
theme_bw() +
theme(
axis.line = element_line(colour = "black"),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
panel.background = element_blank(),
axis.text.x = element_text(angle = 0, vjust = 0, hjust = 0.5),
legend.position = "none",
text = element_text(size = 12)
) +
scale_y_continuous(trans = "log10")
scatterPlot <- ggplot(data = lowLvqcDf, aes(x = Area, y = Nuclei, color = Group, label = Library)) +
geom_point() +
scale_color_manual(values = groupCols) +
labs(
title = "",
x = "Area",
y = "#Nuclei"
) +
theme_bw() +
theme(
axis.line = element_line(colour = "black"),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
panel.background = element_blank(),
axis.text.x = element_text(angle = 0, vjust = 0, hjust = 0.5),
text = element_text(size = 12)
) +
scale_x_continuous(trans = "log10") +
scale_y_continuous(trans = "log10")
ggplotly(scatterPlot)
scatterPlot <- ggplot(data = lowLvqcDf, aes(x = Area, y = Nuclei, color = Model, label = Library)) +
geom_point() +
scale_color_manual(values = modelCols) +
labs(
title = "",
x = "Area",
y = "#Nuclei"
) +
theme_bw() +
theme(
axis.line = element_line(colour = "black"),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
panel.background = element_blank(),
axis.text.x = element_text(angle = 0, vjust = 0, hjust = 0.5),
text = element_text(size = 12)
) +
scale_x_continuous(trans = "log10") +
scale_y_continuous(trans = "log10")
ggplotly(scatterPlot)
scatterPlot <- ggplot(data = lowLvqcDf, aes(x = Area, y = Nuclei, color = Sex, label = Library)) +
geom_point() +
scale_color_manual(values = sexCols) +
labs(
title = "",
x = "Area",
y = "#Nuclei"
) +
theme_bw() +
theme(
axis.line = element_line(colour = "black"),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
panel.background = element_blank(),
axis.text.x = element_text(angle = 0, vjust = 0, hjust = 0.5),
text = element_text(size = 12)
) +
scale_x_continuous(trans = "log10") +
scale_y_continuous(trans = "log10")
ggplotly(scatterPlot)
scatterPlot <- ggplot(data = lowLvqcDf, aes(x = Area, y = Nuclei, color = Segment, label = Library)) +
geom_point() +
scale_color_manual(values = segmentCols) +
labs(
title = "",
x = "Area",
y = "#Nuclei"
) +
theme_bw() +
theme(
axis.line = element_line(colour = "black"),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
panel.background = element_blank(),
axis.text.x = element_text(angle = 0, vjust = 0, hjust = 0.5),
text = element_text(size = 12)
) +
scale_x_continuous(trans = "log10") +
scale_y_continuous(trans = "log10")
ggplotly(scatterPlot)
loqDf <- data.frame(row.names = colnames(newSet))
varNames <- paste0(c("NegGeoMean_", "NegGeoSD_"), module)
if (all(varNames[1:2] %in% colnames(pData(newSet)))) {
loqDf[, module] <- pData(newSet)[, varNames[1]] * (pData(newSet)[, varNames[2]]^loqCutoff)
}
statDf <- data.frame(
Slide = pData(newSet)$Slide,
Library = protocolData(newSet)$AOI,
Segment = pData(newSet)$Segment,
LOQ = loqDf[, 1]
)
statDf <- statDf[order(statDf$LOQ), ]
statDf$Slide <- factor(statDf$Slide, levels=slideOrder)
statDf$Segment <- factor(statDf$Segment, levels = segmentOrder)
statDf$Library <- factor(statDf$Library, levels = statDf$Library)
dodge <- position_dodge(width = 0.5)
suppressWarnings({
ggplot(data = statDf, aes(x = Slide, y = log10(LOQ), fill = Slide)) +
geom_violin(position = dodge, size = 0) +
geom_boxplot(width = 0.1, position = dodge, fill = "white") +
scale_fill_manual(values = slideCols) +
labs(
title = "",
x = "Slide",
y = "LOQ, log10"
) +
theme_bw() +
theme(
axis.line = element_line(colour = "black"),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
panel.background = element_blank(),
axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 0),
legend.position = "none",
text = element_text(size = 12)
) +
geom_hline(aes(yintercept = log10(loqMin)), lty = 2, col = "grey50")
})
loqDf[loqDf < loqMin] <- loqMin
pData(newSet)$LOQ <- loqDf
loqMat <- t(esApply(newSet, MARGIN = 1, FUN = function(x) {
x > LOQ[, module]
}))
loqMat <- loqMat[fData(newSet)$TargetName, ] # Ordering
pData(newSet)$GenesDetected <- colSums(loqMat, na.rm = TRUE)
pData(newSet)$GeneDetectionRate <- pData(newSet)$GenesDetected / nrow(newSet)
pData(newSet)$DetectionThreshold <- cut(pData(newSet)$GeneDetectionRate, breaks = geneDetectionRateBins, labels = geneDetectionRateBinLabels)
rateMat <- pData(newSet)
rateMat$Slide <- factor(rateMat$Slide, levels = slideOrder)
rateMat$Segment <- factor(rateMat$Segment, levels = segmentOrder)
suppressWarnings({
ggplot(rateMat, aes(x = DetectionThreshold)) +
geom_bar(aes(fill = Segment)) +
geom_text(stat = "count", aes(label = after_stat(count)), vjust = -0.5) +
scale_fill_manual(values = segmentCols) +
theme_bw() +
theme(
axis.line = element_line(colour = "black"),
panel.background = element_blank(),
axis.text.x = element_text(angle = 90, vjust = 0, hjust = 0),
text = element_text(size = 12),
legend.position = "none"
) +
scale_y_continuous(expand = expansion(mult = c(0, 0.1))) +
labs(
x = "Gene Detection Rate",
y = "Number of Segments"
) +
facet_grid(as.formula(paste("~", segmentQC_colBy)))
})
statDf <- data.frame(
Slide = pData(newSet)$Slide,
AOI = protocolData(newSet)$AOI,
Segment = pData(newSet)$Segment,
GenesDetected = colSums(loqMat, na.rm = TRUE),
Nuclei = pData(newSet)$nuclei
)
statDf$Slide <- factor(statDf$Slide, slideOrder)
statDf$Segment <- factor(statDf$Segment, segmentOrder)
for (segment in segmentOrder) {
tmpDf <- statDf[which(statDf$Segment == segment), ]
tmpDf <- tmpDf[order(tmpDf$GenesDetected, decreasing = F), ]
tmpDf$AOI <- factor(tmpDf$AOI, levels = tmpDf$AOI)
barplot <- ggplot(tmpDf, aes(x = AOI, y = GenesDetected, fill = Slide)) +
geom_bar(stat = "identity") +
scale_fill_manual(values = slideCols) +
theme_minimal() +
labs(
title = "",
x = "AOI"
) +
coord_flip() +
theme_bw() +
theme(
axis.line = element_line(colour = "black"),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
panel.background = element_blank(),
axis.text.x = element_text(angle = 0, vjust = 0, hjust = 0),
legend.position = "none"
) +
scale_x_discrete(guide = guide_axis(check.overlap = TRUE))
print(barplot)
rm(barplot)
}
newSet <- newSet[, pData(newSet)$GeneDetectionRate >= geneDetectionRateThre]
dim(newSet)
## Features Samples
## 19962 52
loqMat <- loqMat[, colnames(newSet)]
fData(newSet)$DetectedSegments <- rowSums(loqMat, na.rm = TRUE)
fData(newSet)$DetectionRate <- fData(newSet)$DetectedSegments / nrow(pData(newSet))
geneDetectionRateDf <- data.frame(
Gene = rownames(newSet),
Number = fData(newSet)$DetectedSegments,
DetectionRate = percent(fData(newSet)$DetectionRate)
)
geneDetectionRateDf <- geneDetectionRateDf[order(geneDetectionRateDf$Number, geneDetectionRateDf$DetectionRate, geneDetectionRateDf$Gene), ]
finalSet <- newSet[fData(newSet)$DetectionRate >= geneDetectionRateThre, ]
dim(finalSet)
## Features Samples
## 6875 52
annot <- pData(finalSet)
segmentIdx <- c()
for (segment in segmentOrder) segmentIdx <- c(segmentIdx, which(annot$Segment == segment))
rawMat <- assayData(finalSet)$exprs
colnames(rawMat) <- annot$Library
rawDf <- melt(rawMat)
colnames(rawDf) <- c("Gene", "Slide", "Expression")
rawDf$Segment <- rep(annot$Segment, each=nrow(rawMat))
rawDf$Segment <- factor(rawDf$Segment, levels = segmentOrder)
boxplot(log10(rawMat[, segmentIdx] + 1), col = segmentCols[annot$Segment][segmentIdx], names = sapply(str_split(colnames(rawMat), "-"), "[[", 2)[segmentIdx], pch = 20, xlab = "", ylab = "Raw count, log10", las = 3)
finalSet <- normalize(finalSet, norm_method = "quant", desiredQuantile = .75, toElt = "q_norm")
saveRDS(finalSet, file.path(outDir, "02_finalSet.GeoMx.RDS"))
normMat <- assayData(finalSet)$q_norm
colnames(normMat) <- annot$Library
normDf <- melt(normMat)
colnames(normDf) <- c("Gene", "Slide", "Expression")
normDf$Segment <- rep(annot$Segment, each=nrow(rawMat))
normDf$Segment <- factor(normDf$Segment, levels = segmentOrder)
boxplot(log10(normMat[, segmentIdx] + 1), col = segmentCols[annot$Segment][segmentIdx], names = sapply(str_split(colnames(rawMat), "-"), "[[", 2)[segmentIdx], pch = 20, xlab = "", ylab = "Upper-quartile norm, log10", las=3)
Dataset | #Samples | #Features |
---|---|---|
Initial | 95 | 20175 |
After QC (analysis-ready) | 52 | 6875 |
countDf <- pData(finalSet) %>% make_long(Sex, Model, Segment)
ggplot(countDf, aes(x = x, next_x = next_x, node = node, next_node = next_node, fill = factor(node), label = node)) +
geom_sankey(flow.alpha = .6, node.color = "gray30") +
geom_sankey_label(size = 3, color = "black", fill = "white") +
scale_fill_viridis_d(option = "A", alpha = 0.95) +
theme_sankey(base_size = 18) +
labs(
x = NULL,
y = NULL
) +
theme_bw() +
theme(
axis.line = element_blank(),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
panel.background = element_blank(),
axis.text.x = element_text(angle = 0, vjust = 0, hjust = 0.5),
axis.ticks.x = element_blank(),
axis.text.y = element_blank(),
axis.ticks.y = element_blank(),
legend.position = "none",
text = element_text(size = 12)
)
Step | Filter name | Threshold | Description |
---|---|---|---|
SegmentQC | minSegmentReads | 1000 | Minimum number of reads |
percentTrimmed | 80 | Minimum % of reads trimmed | |
percentStitched | 80 | Minimum % of reads stitched | |
percentAligned | 75 | Minimum % of reads aligned | |
percentSaturation | 50 | Minimum sequencing saturation (%) | |
minNuclei | 20 | Minimum number of nuclei estimated | |
minArea | 1000 | Minimum segment area | |
ProbeQC | minProbeRatio | 0.1 | Geometric mean of a given probe / geometric mean of all probe |
percentFailGrubbs | 20 | An outlier according to the Grubb’s test (%) | |
LOQ | loqCutoff | 2 | LOQ cut off value |
loqMin | 2 | LOQ minimum value | |
GeneQC | geneDetectionRateThre | 0.1 | Minimum gene detection rate |
sessionInfo()
## R version 4.4.1 (2024-06-14)
## Platform: aarch64-apple-darwin20
## Running under: macOS 15.0.1
##
## Matrix products: default
## BLAS: /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRblas.0.dylib
## LAPACK: /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRlapack.dylib; LAPACK version 3.12.0
##
## locale:
## [1] en_CA.UTF-8/en_CA.UTF-8/en_CA.UTF-8/C/en_CA.UTF-8/en_CA.UTF-8
##
## time zone: America/Edmonton
## tzcode source: internal
##
## attached base packages:
## [1] grid stats4 stats graphics grDevices utils datasets
## [8] methods base
##
## other attached packages:
## [1] DESeq2_1.44.0 SummarizedExperiment_1.34.0
## [3] MatrixGenerics_1.16.0 matrixStats_1.4.1
## [5] GenomicRanges_1.56.1 GenomeInfoDb_1.40.1
## [7] IRanges_2.38.1 RColorBrewer_1.1-3
## [9] DescTools_0.99.56 EnvStats_3.0.0
## [11] reshape2_1.4.4 plotly_4.10.4
## [13] ggsankey_0.0.99999 ggrepel_0.9.6
## [15] ggpubr_0.6.0 scales_1.3.0
## [17] dplyr_1.1.4 stringr_1.5.1
## [19] GeoMxWorkflows_1.10.0 GeomxTools_3.8.0
## [21] NanoStringNCTools_1.12.0 ggplot2_3.5.1
## [23] S4Vectors_0.42.1 Biobase_2.64.0
## [25] BiocGenerics_0.50.0
##
## loaded via a namespace (and not attached):
## [1] splines_4.4.1 tibble_3.2.1 cellranger_1.1.0
## [4] polyclip_1.10-7 lifecycle_1.0.4 rstatix_0.7.2
## [7] globals_0.16.3 lattice_0.22-6 MASS_7.3-61
## [10] crosstalk_1.2.1 backports_1.5.0 magrittr_2.0.3
## [13] sass_0.4.9 rmarkdown_2.28 jquerylib_0.1.4
## [16] yaml_2.3.10 spam_2.10-0 askpass_1.2.0
## [19] sp_2.1-4 reticulate_1.39.0 gld_2.6.6
## [22] cowplot_1.1.3 minqa_1.2.8 abind_1.4-8
## [25] zlibbioc_1.50.0 expm_1.0-0 Rtsne_0.17
## [28] purrr_1.0.2 tweenr_2.0.3 GenomeInfoDbData_1.2.12
## [31] listenv_0.9.1 pheatmap_1.0.12 BiocStyle_2.32.1
## [34] umap_0.2.10.0 RSpectra_0.16-2 parallelly_1.38.0
## [37] DelayedArray_0.30.1 codetools_0.2-20 ggforce_0.4.2
## [40] tidyselect_1.2.1 outliers_0.15 UCSC.utils_1.0.0
## [43] farver_2.1.2 lme4_1.1-35.5 jsonlite_1.8.9
## [46] e1071_1.7-16 progressr_0.14.0 systemfonts_1.1.0
## [49] tools_4.4.1 Rcpp_1.0.13 glue_1.8.0
## [52] SparseArray_1.4.8 xfun_0.47 ggthemes_5.1.0
## [55] withr_3.0.1 numDeriv_2016.8-1.1 BiocManager_1.30.25
## [58] fastmap_1.2.0 GGally_2.2.1 boot_1.3-31
## [61] fansi_1.0.6 openssl_2.2.2 digest_0.6.37
## [64] R6_2.5.1 colorspace_2.1-1 networkD3_0.4
## [67] utf8_1.2.4 tidyr_1.3.1 generics_0.1.3
## [70] data.table_1.16.0 class_7.3-22 httr_1.4.7
## [73] htmlwidgets_1.6.4 S4Arrays_1.4.1 ggstats_0.6.0
## [76] pkgconfig_2.0.3 gtable_0.3.5 Exact_3.3
## [79] XVector_0.44.0 htmltools_0.5.8.1 carData_3.0-5
## [82] dotCall64_1.1-1 SeuratObject_5.0.2 lmom_3.0
## [85] png_0.1-8 knitr_1.48 rstudioapi_0.16.0
## [88] rjson_0.2.23 uuid_1.2-1 nlme_3.1-166
## [91] nloptr_2.1.1 proxy_0.4-27 cachem_1.1.0
## [94] rootSolve_1.8.2.4 parallel_4.4.1 vipor_0.4.7
## [97] pillar_1.9.0 vctrs_0.6.5 car_3.1-2
## [100] beeswarm_0.4.0 evaluate_1.0.0 locfit_1.5-9.10
## [103] mvtnorm_1.3-1 cli_3.6.3 compiler_4.4.1
## [106] rlang_1.1.4 crayon_1.5.3 future.apply_1.11.2
## [109] ggsignif_0.6.4 labeling_0.4.3 plyr_1.8.9
## [112] ggbeeswarm_0.7.2 ggiraph_0.8.10 stringi_1.8.4
## [115] BiocParallel_1.38.0 viridisLite_0.4.2 lmerTest_3.1-3
## [118] munsell_0.5.1 Biostrings_2.72.1 lazyeval_0.2.2
## [121] Matrix_1.7-0 future_1.34.0 highr_0.11
## [124] igraph_2.0.3 broom_1.0.6 bslib_0.8.0
## [127] readxl_1.4.3