In this tutorial we will cover about Differetial gene expression, which comprises an extensive range of topics and methods. In single cell, differential expresison can have multiple functionalities such as of identifying marker genes for cell populations, as well as differentially regulated genes across conditions (healthy vs control). We will also exercise on how to account the batch information in your test.
We can first load the data from the clustering session. Moreover, we
can already decide which clustering resolution to use. First let’s
define using the louvain
clustering to identifying
differentially expressed genes.
suppressPackageStartupMessages({
library(Seurat)
library(dplyr)
library(cowplot)
library(ggplot2)
library(pheatmap)
library(enrichR)
library(rafalib)
library(Matrix)
library(edgeR)
library(MAST)
})
<- readRDS("data/results/covid_qc_dr_int_cl.rds") alldata
# Set the identity as louvain with resolution 0.5
= "CCA_snn_res.0.5"
sel.clust
<- SetIdent(alldata, value = sel.clust)
alldata table(alldata@active.ident)
##
## 0 1 2 3 4 5 6 7 8 9
## 1536 1014 568 442 427 418 407 268 242 210
# plot this clustering
plot_grid(ncol = 3, DimPlot(alldata, label = T) + NoAxes(), DimPlot(alldata, group.by = "orig.ident") +
NoAxes(), DimPlot(alldata, group.by = "type") + NoAxes())
Let us first compute a ranking for the highly differential genes in each cluster. There are many different tests and parameters to be chosen that can be used to refine your results. When looking for marker genes, we want genes that are positivelly expressed in a cell type and possibly not expressed in the others.
# Compute differentiall expression
<- FindAllMarkers(alldata, log2FC.threshold = 0.2, test.use = "wilcox",
markers_genes min.pct = 0.1, min.diff.pct = 0.2, only.pos = TRUE, max.cells.per.ident = 50,
assay = "RNA")
We can now select the top 25 up regulated genes for plotting.
%>%
markers_genes group_by(cluster) %>%
top_n(-25, p_val_adj) -> top25
top25
We can now select the top 25 up regulated genes for plotting.
mypar(2, 5, mar = c(4, 6, 3, 1))
for (i in unique(top25$cluster)) {
barplot(sort(setNames(top25$avg_log2FC, top25$gene)[top25$cluster == i], F),
horiz = T, las = 1, main = paste0(i, " vs. rest"), border = "white", yaxs = "i")
abline(v = c(0, 0.25), lty = c(1, 2))
}
We can visualize them as a heatmap. Here we are selecting the top 5.
%>%
markers_genes group_by(cluster) %>%
top_n(-5, p_val_adj) -> top5
# create a scale.data slot for the selected genes
<- ScaleData(alldata, features = as.character(unique(top5$gene)), assay = "RNA")
alldata DoHeatmap(alldata, features = as.character(unique(top5$gene)), group.by = sel.clust,
assay = "RNA")
Another way is by representing the overal group expression and detection rates in a dot-plot.
DotPlot(alldata, features = rev(as.character(unique(top5$gene))), group.by = sel.clust,
assay = "RNA") + coord_flip()
We can also plot a violin plot for each gene.
# take top 3 genes per cluster/
%>%
top5 group_by(cluster) %>%
top_n(-3, p_val) -> top3
# set pt.size to zero if you do not want all the points to hide the violin
# shapes, or to a small value like 0.1
VlnPlot(alldata, features = as.character(unique(top3$gene)), ncol = 5, group.by = sel.clust,
assay = "RNA", pt.size = 0)
Your turn
Take a screen shot of those results and re-run the same code above with another test: “wilcox” (Wilcoxon Rank Sum test), “bimod” (Likelihood-ratio test), “roc” (Identifies ‘markers’ of gene expression using ROC analysis),“t” (Student’s t-test),“negbinom” (negative binomial generalized linear model),“poisson” (poisson generalized linear model), “LR” (logistic regression), “MAST” (hurdle model), “DESeq2” (negative binomial distribution).
The second way of computing differential expression is to answer which genes are differentially expressed within a cluster. For example, in our case we have libraries comming from patients and controls and we would like to know which genes are influenced the most in a particular cell type.
For this end, we will first subset our data for the desired cell cluster, then change the cell identities to the variable of comparison (which now in our case is the “type”, e.g. Covid/Ctrl).
# select all cells in cluster 1
<- subset(alldata, cells = colnames(alldata)[alldata@meta.data[, sel.clust] ==
cell_selection 2])
<- SetIdent(cell_selection, value = "type")
cell_selection # Compute differentiall expression
<- FindAllMarkers(cell_selection, log2FC.threshold = 0.2, test.use = "wilcox",
DGE_cell_selection min.pct = 0.1, min.diff.pct = 0.2, only.pos = TRUE, max.cells.per.ident = 50,
assay = "RNA")
We can now plot the expression across the “type”.
%>%
DGE_cell_selection group_by(cluster) %>%
top_n(-5, p_val) -> top5_cell_selection
VlnPlot(cell_selection, features = as.character(unique(top5_cell_selection$gene)),
ncol = 5, group.by = "type", assay = "RNA", pt.size = 0.1)
We can also plot these genes across all clusters, but split by “type”, to check if the genes are also up/downregulated in other celltypes.
VlnPlot(alldata, features = as.character(unique(top5_cell_selection$gene)), ncol = 5,
split.by = "type", assay = "RNA", pt.size = 0)
As you can see, we hwve many sex chromosome related genes among the top DE genes. And if you remember from the QC lab, we have inbalanced sex distribution among our subjects, so this may not be related to covid at all.
To remove some of the bias due to inbalanced sex in the subjects we can remove the sex chromosome related genes.
= read.csv("data/results/genes.table.csv") #was created in the QC exercise
gene.info
= gene.info$external_gene_name[!(gene.info$chromosome_name %in% c("X",
auto.genes "Y"))]
@active.assay = "RNA"
cell_selection= intersect(rownames(cell_selection), auto.genes)
keep.genes = cell_selection[keep.genes, ]
cell_selection
# then renormalize the data
= NormalizeData(cell_selection) cell_selection
Rerun differential expression:
# Compute differentiall expression
<- FindMarkers(cell_selection, ident.1 = "Covid", ident.2 = "Ctrl",
DGE_cell_selection logfc.threshold = 0.2, test.use = "wilcox", min.pct = 0.1, min.diff.pct = 0.2,
assay = "RNA")
# Define as Covid or Ctrl in the df and add a gene column
$direction = ifelse(DGE_cell_selection$avg_log2FC > 0, "Covid",
DGE_cell_selection"Ctrl")
$gene = rownames(DGE_cell_selection)
DGE_cell_selection
%>%
DGE_cell_selection group_by(direction) %>%
top_n(-5, p_val) %>%
arrange(direction) -> top5_cell_selection
VlnPlot(cell_selection, features = as.character(unique(top5_cell_selection$gene)),
ncol = 5, group.by = "type", assay = "RNA", pt.size = 0.1)
We can also plot these genes across all clusters, but split by “type”, to check if the genes are also up/downregulated in other celltypes/clusters.
VlnPlot(alldata, features = as.character(unique(top5_cell_selection$gene)), ncol = 5,
split.by = "type", assay = "RNA", pt.size = 0)
When we are testing for Covid vs Control we are running a DGE test for 3 vs 3 individuals. That will be very sensitive to sample differences unless we find a way to control for it. So first, lets check how the top DGEs are expressed across the individuals:
VlnPlot(cell_selection, group.by = "orig.ident", features = as.character(unique(top5_cell_selection$gene)),
ncol = 5, assay = "RNA", pt.size = 0)
As you can see, many of the genes detected as DGE in Covid are unique to one or 2 patients.
We can examine more genes with a DotPlot:
%>%
DGE_cell_selection group_by(direction) %>%
top_n(-20, p_val) -> top20_cell_selection
DotPlot(cell_selection, features = rev(as.character(unique(top20_cell_selection$gene))),
group.by = "orig.ident", assay = "RNA") + coord_flip()
As you can see, most of the DGEs are driven by the
covid_17
patient.
But it is also the sample with the highest number of cells:
table(cell_selection$orig.ident)
##
## covid_1 covid_15 covid_17 ctrl_13 ctrl_14 ctrl_5
## 90 38 170 66 63 141
So one obvious thing to consider is an equal amount of cells per individual so that the DGE results are not dominated by a single sample.
So we will use the downsample
option in the Seurat
function WhichCells
to select 30 cells per cluster:
<- SetIdent(cell_selection, value = "orig.ident")
cell_selection <- subset(cell_selection, cells = WhichCells(cell_selection, downsample = 30))
sub_data
table(sub_data$orig.ident)
##
## covid_1 covid_15 covid_17 ctrl_13 ctrl_14 ctrl_5
## 30 30 30 30 30 30
And now we run DGE analysis again:
<- SetIdent(sub_data, value = "type")
sub_data
# Compute differentiall expression
<- FindMarkers(sub_data, ident.1 = "Covid", ident.2 = "Ctrl", logfc.threshold = 0.2,
DGE_sub test.use = "wilcox", min.pct = 0.1, min.diff.pct = 0.2, assay = "RNA")
# Define as Covid or Ctrl in the df and add a gene column
$direction = ifelse(DGE_sub$avg_log2FC > 0, "Covid", "Ctrl")
DGE_sub$gene = rownames(DGE_sub)
DGE_sub
%>%
DGE_sub group_by(direction) %>%
top_n(-5, p_val) %>%
arrange(direction) -> top5_sub
VlnPlot(sub_data, features = as.character(unique(top5_sub$gene)), ncol = 5, group.by = "type",
assay = "RNA", pt.size = 0.1)
Plot as dotplot, but in the full dataset:
%>%
DGE_sub group_by(direction) %>%
top_n(-20, p_val) -> top20_sub
DotPlot(cell_selection, features = rev(as.character(unique(top20_sub$gene))), group.by = "orig.ident",
assay = "RNA") + coord_flip()
It looks much better now. But if we look per patient you can see that we still have some genes that are dominated by a single patient.
Why do you think this is?
One option is to treat the samples as pseudobulks and do differential expression for the 3 patients vs 3 controls. You do lose some information about cell variability within each patient, but instead you gain the advantage of mainly looking for effects that are seen in multiple patients.
However, having only 3 patients is probably too low, with many more patients it will work better to run pseudobulk analysis.
For a fair comparison we should have equal number of cells per sample when we create the pseudobulk, so we will use the subsampled object.
# get the count matrix for all cells
<- sub_data@assays$RNA@counts
DGE_DATA
# Compute pseudobulk
<- Matrix::sparse.model.matrix(~0 + sub_data$orig.ident)
mm <- DGE_DATA %*% mm pseudobulk
Then run edgeR:
# define the groups
= c("Covid", "Covid", "Covid", "Ctrl", "Ctrl", "Ctrl")
bulk.labels
<- DGEList(counts = pseudobulk, group = factor(bulk.labels))
dge.list <- filterByExpr(dge.list)
keep <- dge.list[keep, , keep.lib.sizes = FALSE]
dge.list
<- calcNormFactors(dge.list)
dge.list = model.matrix(~bulk.labels)
design
<- estimateDisp(dge.list, design)
dge.list
<- glmQLFit(dge.list, design)
fit <- glmQLFTest(fit, coef = 2)
qlf topTags(qlf)
## Coefficient: bulk.labelsCtrl
## logFC logCPM F PValue FDR
## S100A8 -4.552584 7.373352 51.79349 1.381039e-06 0.002285620
## S100A9 -3.029166 7.336543 36.72730 1.202005e-05 0.009946595
## JCHAIN 1.974193 6.973561 21.20913 2.440677e-04 0.134644040
## IFITM2 -1.421524 8.072767 17.70558 5.761012e-04 0.221733433
## CD69 -2.093496 10.885793 17.12660 6.698895e-04 0.221733433
## PHACTR1 -1.361440 8.055688 14.79029 1.268061e-03 0.349773550
## ZNF331 -1.754357 8.148455 14.08789 1.551784e-03 0.366886064
## TRIM22 -1.522696 7.694248 13.47184 1.860209e-03 0.384830837
## CCND3 -1.123951 7.896499 11.93334 2.979824e-03 0.469219331
## AUTS2 1.652672 6.566773 11.73182 3.176139e-03 0.469219331
As you can see, we have very few significant genes, actually only 2 with FDR < 0.1. Since we only have 3 vs 3 samples, we should not expect too many genes with this method.
Again as dotplot including all genes with FDR < 1:
<- topTags(qlf, 100)$table
res.edgeR $dir = ifelse(res.edgeR$logFC > 0, "Covid", "Ctrl")
res.edgeR$gene = rownames(res.edgeR)
res.edgeR
%>%
res.edgeR group_by(dir) %>%
top_n(-10, PValue) %>%
arrange(dir) -> top.edgeR
DotPlot(cell_selection, features = as.character(unique(top.edgeR$gene)), group.by = "orig.ident",
assay = "RNA") + coord_flip() + ggtitle("EdgeR pseudobulk") + RotatedAxis()
As you can see, even if we get few genes detected the seem to make sense across all the patients.
MAST has the option to add a random effect for the patient when running DGE analysis. It is quite slow, even with this small dataset, so it may not be practical for a larger dataset unless you have access to a compute cluster.
We will run MAST with and without patient info as random effect and compare the results
First, filter genes in part to speed up the process but also to avoid too many warnings in the model fitting step of MAST. We will keep genes that are expressed with at least 2 reads in 2 covid patients or 2 controls.
# select genes that are expressed in at least 2 patients or 2 ctrls with > 2
# reads.
= sapply(unique(cell_selection$orig.ident), function(x) rowSums(cell_selection@assays$RNA@counts[,
nPatient $orig.ident == x] > 2))
cell_selection= rowSums(nPatient[, 1:3] > 2)
nCovid = rowSums(nPatient[, 4:6] > 2)
nCtrl
= nCovid >= 2 | nCtrl >= 2
sel = cell_selection[sel, ] cell_selection_sub
Set up the MAST object.
# create the feature data
<- data.frame(primerid = rownames(cell_selection_sub))
fData = cell_selection_sub@meta.data
m $wellKey = rownames(m)
m
# make sure type and orig.ident are factors
$orig.ident = factor(m$orig.ident)
m$type = factor(m$type)
m
<- MAST::FromMatrix(exprsArray = as.matrix(x = cell_selection_sub@assays$RNA@data),
sca check_sanity = FALSE, cData = m, fData = fData)
First, run the regular MAST analysis without random effects
# takes a while to run, so save a file to tmpdir in case you have to rerun the
# code
= "tmp_dge"
tmpdir dir.create(tmpdir, showWarnings = F)
= file.path(tmpdir, "mast_bayesglm_cl1.Rds")
tmpfile1 if (file.exists(tmpfile1)) {
= readRDS(tmpfile1)
fcHurdle1 else {
} <- suppressMessages(MAST::zlm(~type, sca, method = "bayesglm", ebayes = T))
zlmCond <- suppressMessages(MAST::summary(zlmCond, doLRT = "typeCtrl"))
summaryCond <- summaryCond$datatable
summaryDt <- merge(summaryDt[summaryDt$contrast == "typeCtrl" & summaryDt$component ==
fcHurdle "logFC", c(1, 7, 5, 6, 8)], summaryDt[summaryDt$contrast == "typeCtrl" &
$component == "H", c(1, 4)], by = "primerid")
summaryDt<- stats::na.omit(as.data.frame(fcHurdle))
fcHurdle1 saveRDS(fcHurdle1, tmpfile1)
}
Then run MAST with glmer and random effect.
library(lme4)
= file.path(tmpdir, "mast_glme_cl1.Rds")
tmpfile2 if (file.exists(tmpfile2)) {
= readRDS(tmpfile2)
fcHurdle2 else {
} <- suppressMessages(MAST::zlm(~type + (1 | orig.ident), sca, method = "glmer",
zlmCond ebayes = F, strictConvergence = FALSE))
<- suppressMessages(MAST::summary(zlmCond, doLRT = "typeCtrl"))
summaryCond <- summaryCond$datatable
summaryDt <- merge(summaryDt[summaryDt$contrast == "typeCtrl" & summaryDt$component ==
fcHurdle "logFC", c(1, 7, 5, 6, 8)], summaryDt[summaryDt$contrast == "typeCtrl" &
$component == "H", c(1, 4)], by = "primerid")
summaryDt<- stats::na.omit(as.data.frame(fcHurdle))
fcHurdle2 saveRDS(fcHurdle2, tmpfile2)
}
Top genes with normal MAST:
= head(fcHurdle1[order(fcHurdle1$`Pr(>Chisq)`), ], 10)
top1 top1
$pval = fcHurdle1$`Pr(>Chisq)`
fcHurdle1$dir = ifelse(fcHurdle1$z > 0, "up", "down")
fcHurdle1%>%
fcHurdle1 group_by(dir) %>%
top_n(-10, pval) %>%
arrange(z) -> mastN
= mastN$primerid mastN
Top genes with random effect:
= head(fcHurdle2[order(fcHurdle2$`Pr(>Chisq)`), ], 10)
top2 top2
$pval = fcHurdle2$`Pr(>Chisq)`
fcHurdle2$dir = ifelse(fcHurdle2$z > 0, "up", "down")
fcHurdle2%>%
fcHurdle2 group_by(dir) %>%
top_n(-10, pval) %>%
arrange(z) -> mastR
= mastR$primerid mastR
As you can see, we have lower significance for the genes with the random effect added.
Dotplot for top10 genes in each direction:
= DotPlot(cell_selection, features = mastN, group.by = "orig.ident", assay = "RNA") +
p1 coord_flip() + RotatedAxis() + ggtitle("Regular MAST")
= DotPlot(cell_selection, features = mastR, group.by = "orig.ident", assay = "RNA") +
p2 coord_flip() + RotatedAxis() + ggtitle("With random effect")
+ p2 p1
Hypergeometric enrichment test
Having a defined list of differentially expressed genes, you can now look for their combined function using hypergeometric test:
# Load additional packages
library(enrichR)
# Check available databases to perform enrichment (then choose one)
::listEnrichrDbs() enrichR
# Perform enrichment
<- enrichr(genes = DGE_cell_selection$gene[DGE_cell_selection$avg_log2FC >
enrich_results 0], databases = "GO_Biological_Process_2017b")[[1]]
## Uploading data to Enrichr... Done.
## Querying GO_Biological_Process_2017b... Done.
## Parsing results... Done.
Some databases of interest:
GO_Biological_Process_2017b
KEGG_2019_Human
KEGG_2019_Mouse
WikiPathways_2019_Human
WikiPathways_2019_Mouse
You visualize your results using a simple barplot, for example:
par(mfrow = c(1, 1), mar = c(3, 25, 2, 1))
barplot(height = -log10(enrich_results$P.value)[10:1], names.arg = enrich_results$Term[10:1],
horiz = TRUE, las = 1, border = FALSE, cex.names = 0.6)
abline(v = c(-log10(0.05)), lty = 2)
abline(v = 0, lty = 1)
Besides the enrichment using hypergeometric test, we can also perform gene set enrichment analysis (GSEA), which scores ranked genes list (usually based on fold changes) and computes permutation test to check if a particular gene set is more present in the Up-regulated genes, amongthe DOWN_regulated genes or not differentially regulated.
= SetIdent(cell_selection, value = "type")
cell_selection
<- FindMarkers(cell_selection, ident.1 = "Covid", log2FC.threshold = -Inf,
DGE_cell_selection2 test.use = "wilcox", min.pct = 0.1, min.diff.pct = 0, only.pos = FALSE, max.cells.per.ident = 50,
assay = "RNA")
# Create a gene rank based on the gene expression fold change
<- setNames(DGE_cell_selection2$avg_log2FC, casefold(rownames(DGE_cell_selection2),
gene_rank upper = T))
Once our list of genes are sorted, we can proceed with the enrichment itself. We can use the package to get gene set from the Molecular Signature Database (MSigDB) and select KEGG pathways as an example.
library(msigdbr)
# Download gene sets
<- msigdbr::msigdbr("Homo sapiens")
msigdbgmt <- as.data.frame(msigdbgmt)
msigdbgmt
# List available gene sets
unique(msigdbgmt$gs_subcat)
## [1] "MIR:MIR_Legacy" "TFT:TFT_Legacy" "CGP" "TFT:GTRD"
## [5] "" "VAX" "CP:BIOCARTA" "CGN"
## [9] "GO:BP" "GO:CC" "IMMUNESIGDB" "GO:MF"
## [13] "HPO" "CP:KEGG" "MIR:MIRDB" "CM"
## [17] "CP" "CP:PID" "CP:REACTOME" "CP:WIKIPATHWAYS"
# Subset which gene set you want to use.
<- msigdbgmt[msigdbgmt$gs_subcat == "CP:WIKIPATHWAYS", ]
msigdbgmt_subset <- lapply(unique(msigdbgmt_subset$gs_name), function(x) {
gmt $gs_name == x, "gene_symbol"]
msigdbgmt_subset[msigdbgmt_subset
})names(gmt) <- unique(paste0(msigdbgmt_subset$gs_name, "_", msigdbgmt_subset$gs_exact_source))
Next, we will be using the GSEA. This will result in a table
containing information for several pathways. We can then sort and filter
those pathways to visualize only the top ones. You can select/filter
them by either p-value
or normalized enrichemnet score
(NES
).
library(fgsea)
# Perform enrichemnt analysis
<- fgsea(pathways = gmt, stats = gene_rank, minSize = 15, maxSize = 500)
fgseaRes <- fgseaRes[order(fgseaRes$pval, decreasing = T), ]
fgseaRes
# Filter the results table to show only the top 10 UP or DOWN regulated
# processes (optional)
<- fgseaRes$pathway[1:10]
top10_UP
# Nice summary table (shown as a plot)
dev.off()
plotGseaTable(gmt[top10_UP], gene_rank, fgseaRes, gseaParam = 0.5)
Your turn
Which KEGG pathways are upregulated in this cluster? Which KEGG pathways are dowregulated in this cluster? Change the pathway source to another gene set (e.g. “CP:WIKIPATHWAYS” or “CP:REACTOME” or “CP:BIOCARTA” or “GO:BP”) and check the if you get simmilar results?
Finally, lets save the integrated data for further analysis.
# saveRDS(alldata, 'data/3pbmc_qc_dr_int_cl_dge.rds') save the list of DGE
# results to a file.
write.csv(markers_genes, file = "data/3pbmc_qc_dr_int_cl_dge.csv")
sessionInfo()
## R version 4.1.3 (2022-03-10)
## Platform: x86_64-apple-darwin13.4.0 (64-bit)
## Running under: macOS Big Sur/Monterey 10.16
##
## Matrix products: default
## BLAS/LAPACK: /Users/asabjor/miniconda3/envs/scRNAseq2023/lib/libopenblasp-r0.3.21.dylib
##
## locale:
## [1] C/UTF-8/C/C/C/C
##
## attached base packages:
## [1] stats4 stats graphics grDevices utils datasets methods
## [8] base
##
## other attached packages:
## [1] fgsea_1.20.0 msigdbr_7.5.1
## [3] lme4_1.1-31 MAST_1.20.0
## [5] SingleCellExperiment_1.16.0 SummarizedExperiment_1.24.0
## [7] Biobase_2.54.0 GenomicRanges_1.46.1
## [9] GenomeInfoDb_1.30.1 IRanges_2.28.0
## [11] S4Vectors_0.32.4 BiocGenerics_0.40.0
## [13] MatrixGenerics_1.6.0 matrixStats_0.63.0
## [15] edgeR_3.36.0 limma_3.50.3
## [17] Matrix_1.5-3 rafalib_1.0.0
## [19] enrichR_3.1 pheatmap_1.0.12
## [21] ggplot2_3.4.0 cowplot_1.1.1
## [23] dplyr_1.0.10 SeuratObject_4.1.3
## [25] Seurat_4.3.0 RJSONIO_1.3-1.7
## [27] optparse_1.7.3
##
## loaded via a namespace (and not attached):
## [1] fastmatch_1.1-3 plyr_1.8.8 igraph_1.3.5
## [4] lazyeval_0.2.2 sp_1.6-0 splines_4.1.3
## [7] BiocParallel_1.28.3 listenv_0.9.0 scattermore_0.8
## [10] digest_0.6.31 htmltools_0.5.4 fansi_1.0.4
## [13] magrittr_2.0.3 tensor_1.5 cluster_2.1.4
## [16] ROCR_1.0-11 globals_0.16.2 spatstat.sparse_3.0-0
## [19] prettyunits_1.1.1 colorspace_2.1-0 ggrepel_0.9.2
## [22] xfun_0.36 crayon_1.5.2 RCurl_1.98-1.9
## [25] jsonlite_1.8.4 progressr_0.13.0 spatstat.data_3.0-0
## [28] survival_3.5-0 zoo_1.8-11 glue_1.6.2
## [31] polyclip_1.10-4 gtable_0.3.1 zlibbioc_1.40.0
## [34] XVector_0.34.0 leiden_0.4.3 DelayedArray_0.20.0
## [37] future.apply_1.10.0 abind_1.4-5 scales_1.2.1
## [40] DBI_1.1.3 spatstat.random_3.0-1 miniUI_0.1.1.1
## [43] Rcpp_1.0.10 progress_1.2.2 viridisLite_0.4.1
## [46] xtable_1.8-4 reticulate_1.27 htmlwidgets_1.6.1
## [49] httr_1.4.4 getopt_1.20.3 RColorBrewer_1.1-3
## [52] ellipsis_0.3.2 ica_1.0-3 farver_2.1.1
## [55] pkgconfig_2.0.3 sass_0.4.5 uwot_0.1.14
## [58] deldir_1.0-6 locfit_1.5-9.7 utf8_1.2.2
## [61] labeling_0.4.2 tidyselect_1.2.0 rlang_1.0.6
## [64] reshape2_1.4.4 later_1.3.0 munsell_0.5.0
## [67] tools_4.1.3 cachem_1.0.6 cli_3.6.0
## [70] generics_0.1.3 ggridges_0.5.4 evaluate_0.20
## [73] stringr_1.5.0 fastmap_1.1.0 yaml_2.3.7
## [76] goftest_1.2-3 babelgene_22.9 knitr_1.41
## [79] fitdistrplus_1.1-8 purrr_1.0.1 RANN_2.6.1
## [82] pbapply_1.7-0 future_1.30.0 nlme_3.1-161
## [85] mime_0.12 formatR_1.14 compiler_4.1.3
## [88] plotly_4.10.1 curl_4.3.3 png_0.1-8
## [91] spatstat.utils_3.0-1 tibble_3.1.8 bslib_0.4.2
## [94] stringi_1.7.12 highr_0.10 lattice_0.20-45
## [97] nloptr_2.0.3 vctrs_0.5.2 pillar_1.8.1
## [100] lifecycle_1.0.3 spatstat.geom_3.0-5 lmtest_0.9-40
## [103] jquerylib_0.1.4 RcppAnnoy_0.0.20 data.table_1.14.6
## [106] bitops_1.0-7 irlba_2.3.5.1 httpuv_1.6.8
## [109] patchwork_1.1.2 R6_2.5.1 promises_1.2.0.1
## [112] KernSmooth_2.23-20 gridExtra_2.3 parallelly_1.34.0
## [115] codetools_0.2-18 boot_1.3-28.1 MASS_7.3-58.2
## [118] assertthat_0.2.1 rjson_0.2.21 withr_2.5.0
## [121] sctransform_0.3.5 GenomeInfoDbData_1.2.7 hms_1.1.2
## [124] parallel_4.1.3 grid_4.1.3 minqa_1.2.5
## [127] tidyr_1.2.1 rmarkdown_2.20 Rtsne_0.16
## [130] spatstat.explore_3.0-5 shiny_1.7.4