stopifnot(R.version$major >= 4) # requires R4
if (compareVersion(R.version$minor, "3.1") < 0) warning("We recommend >= R4.3.1")
stopifnot(compareVersion(as.character(BiocManager::version()), "3.18") >= 0)
stopifnot(compareVersion(as.character(packageVersion("Seurat")), "5.0.0") >= 0)
scRNA normalization and clustering
Template developed with materials from https://hbctraining.github.io/main/.
This code is in this revision.
# parameters
## Cell cycle markers for c.elegans, human, mouse, D. rerio, and D. melanogaster can be found here: https://github.com/hbc/tinyatlas/tree/1e2136a35e773f14d97ae9cbdb6c375327b2dd2b/cell_cycle
## This files needs gene_name and phase columns to work with this template
<- "https://github.com/bcbio/resources/raw/refs/heads/main/singlecell/human_cell_cycle.csv"
cell_cycle_file <- "https://github.com/bcbio/bcbioR-test-data/raw/refs/heads/main/singlecell/tiny.rds"
seurat_obj <- "./seurat_clust.rds"
seurat_output invisible(list2env(params, environment()))
source(project_file)
Overview
- Project: name_hbcXXXXX
- PI: person name
- Analyst: person in the core
- Experiment: short description
- Aim: short description
Dataset
The Seurat object used as input for this report was prepared with the thresholds detailed below applied.
-nGenes > nFeature_RNA_cutoff
-nUMI > nCount_RNA_cutoff
-complexity > Log10GenesPerUMI_cutoff
-percent mitochondrial reads < mitoRatio_cutoff
# Source cell cycle markers
<- read_csv(cell_cycle_file)
cc_markers stopifnot(c("gene_name", "phase") %in% colnames(cc_markers))
# Loading QC'd object
if (isUrl(seurat_obj)) {
<- readRDS(url(seurat_obj))
seurat_qc else {
} <- readRDS(seurat_obj)
seurat_qc
}
DefaultAssay(seurat_qc) <- "RNA"
# Define color scales for up to 24 clusters/samples
<- RColorBrewer::brewer.pal(8, "Dark2")
colsD <- RColorBrewer::brewer.pal(8, "Set2")
colsM <- RColorBrewer::brewer.pal(8, "Pastel2")
colsL # Stack same colors from dark to pastel
<- unlist(strsplit(paste(colsD, colsM, colsL, sep = "_"), "_"))
cols3 <- c(unlist(strsplit(paste(colsD, colsM, sep = "_"), "_")), "deepskyblue2") cols2
After filtering, each sample contributed the following number of cells to the analysis:
table(seurat_qc$orig.ident)
S1 S2 S3
480 566 844
Sources of variability Log normalization
In this section, we look at potential confounding variables in our (post-QC) dataset, to determine whether their effect needs to be accounted for before normalizing and integrating the data.
To enable meaningful visualization of the data, we apply a minimal normalization to our raw data (log-normalization). We then identify the top 2000 most variable genes across the log-normalized data, i.e. those with the greatest variability in expression level from one cell to the next. Finally, we calculate principal components (PCs) based on these top 2000 most variable genes, and use the first 50 PCs to derive reduced UMAP (Uniform Manifold Approximation and Projection) components.
We start with log normalization because it is good to observe the data and any trends using a simple transformation. More complex methods like SCT can alter the data in a way that is not as intuitive to interpret.
# NOTE run the chunk below to create this object, and loading will be used while
# knitting to speed up the rendering
<- readRDS("seurat_lognorm.rds") seurat_lognorm
# Normalize data
<- NormalizeData(seurat_qc,
seurat_lognorm normalization.method = "LogNormalize",
scale.factor = 10000
)
# Find variable genes (largest dispersion in expression across cells)
<- FindVariableFeatures(seurat_lognorm, nfeatures = 2000)
seurat_lognorm
# Scale and center data
<- ScaleData(seurat_lognorm, model.use = "linear")
seurat_lognorm
# Calculate PCs and UMAP
<- RunPCA(seurat_lognorm)
seurat_lognorm <- RunUMAP(seurat_lognorm, 1:40)
seurat_lognorm
saveRDS(seurat_lognorm, file = "seurat_lognorm.rds")
Examine highly variable genes
Highly variable gene selection is extremely important since many downstream steps are computed only on these genes. Seurat allows us to access the ranked highly variable genes with the VariableFeatures() function. We can additionally visualize the dispersion of all genes using Seurat’s VariableFeaturePlot(), which shows a gene’s average expression across all cells on the x-axis and variance on the y-axis. Ideally we want to use genes that have high variance since this can indicate a change in expression depending on populations of cells. Adding labels using the LabelPoints() helps us understand which genes will be driving shape of our data.
# Identify the 15 most highly variable genes
<- VariableFeatures(seurat_lognorm)
ranked_variable_genes <- ranked_variable_genes[1:15]
top_genes
# Plot the average expression and variance of these genes
# With labels to indicate which genes are in the top 15
<- VariableFeaturePlot(seurat_lognorm)
p LabelPoints(plot = p, points = top_genes, repel = TRUE)
Warning in scale_x_log10(): log-10 transformation introduced infinite values.
Sample x covariates
We then use the UMAP reduction to explore our dataset and assess how different variables influence cell clustering. Throughout this report, UMAP representations are split by various covariates, to enable checking for potential phenotype-specific clustering.
## Below is an example plot, change the group.by and split.by parameters to make plots with your own covariates.
UMAPPlot(seurat_lognorm, group.by = "orig.ident") + ggtitle("UMAP")
Cell cycle
The phase of the cell cycle that cells are in at the time of sample preparation can introduce some variability in the transcriptome that we are not interested in exploring.
To examine cell cycle variation in our data, we assign a score to each cell, derived from the overall expression level of known markers of the G2/M and S phase in that cell. We then display the cells, color-coded by inferred cell cycle phase, on our UMAP.
Unless cells very strongly cluster by phase of the cell cycle (which is not the case here), we do not recommend to regress out the effect of the cell cycle.
# Step 1 - Get cell cycle markers
# Compute cell cycle score for each cell
## NOTE use the right column names for cc_markers if they are different than
# external_gene_name and phase
<- CellCycleScoring(seurat_lognorm,
seurat_lognorm g2m.features = cc_markers$gene_name[cc_markers$phase == "G2/M"],
s.features = cc_markers$gene_name[cc_markers$phase == "S"]
)
## Plot cell cycle (grouped by) along with covariates (split.by). Add in your covariates of interest
UMAPPlot(seurat_lognorm, group.by = "Phase") + ggtitle("UMAP")
mitoRatio
The mitochondrial to nuclear gene ratio (mitoRatio) is a marker of cellular stress and might also affect cell clustering. For this dataset, we have seen during QC that the fraction of mitochondrial genes was negligible (which is good). Therefore, we do not expect the need to regress out this variable for normalization purposes, but it’s always good to check.
## This custom function by Amelie Jule creates great plots for looking at different QC parameters across the UMAP
<- function(seurat_object,
signaturePlot
gene_signature,reduction = "umap",
split_var = NULL,
pt_size = 0.5) {
<- FeaturePlot(seurat_object,
g1 features = gene_signature,
reduction = reduction,
split.by = split_var,
order = TRUE,
pt.size = pt_size,
combine = FALSE
)
<- min(pull(seurat_object@meta.data, gene_signature))
min_val <- max(pull(seurat_object@meta.data, gene_signature))
max_val <- scale_color_gradientn(
fix_params colours = c("grey80", "blue"),
limits = c(min_val, max_val)
)
<- lapply(g1, function(x) {
g2 + fix_params +
x theme_minimal() +
theme(
legend.position = "bottom",
plot.title = element_text(hjust = 0.5)
+
) ggtitle("")
})
g2
}
### Here we apply the function. gene_signature is whatever qc metric you care about and split_var should be a covariate of interest. Adjust the titles to match the covariate groups. If you have more than 2 covariate groups then you will have multiple plots g[[3]]....g[[n]]
<- signaturePlot(seurat_lognorm,
g gene_signature = "mitoRatio",
split_var = "orig.ident"
)
Warning: The `slot` argument of `FetchData()` is deprecated as of SeuratObject 5.0.0.
ℹ Please use the `layer` argument instead.
ℹ The deprecated feature was likely used in the Seurat package.
Please report the issue at <https://github.com/satijalab/seurat/issues>.
Error in pull(seurat_object@meta.data, gene_signature): could not find function "pull"
1]] + ggtitle("S1") | g[[2]] + ggtitle("S2") g[[
Error: object 'g' not found
nUMIs (nCount)
### Here we apply the function. gene_signature is whatever qc metric you care about and split_var should be a covariate of interest. Adjust the titles to match the covariate groups. If you have more than 2 covariate groups then you will have multiple plots g[[3]]....g[[n]]
<- signaturePlot(seurat_lognorm,
g gene_signature = "nCount_RNA",
split_var = "subj"
)
Error in pull(seurat_object@meta.data, gene_signature): could not find function "pull"
1]] + ggtitle("S1") | g[[2]] + ggtitle("S2") g[[
Error: object 'g' not found
nGenes (nFeature)
### Here we apply the function. gene_signature is whatever qc metric you care about and split_var should be a covariate of interest. Adjust the titles to match the covariate groups. If you have more than 2 covariate groups then you will have multiple plots g[[3]]....g[[n]]
<- signaturePlot(seurat_lognorm,
g gene_signature = "nFeature_RNA",
split_var = "orig.ident"
)
Error in pull(seurat_object@meta.data, gene_signature): could not find function "pull"
1]] + ggtitle("S1") | g[[2]] + ggtitle("S2") g[[
Error: object 'g' not found
Complexity
### Here we apply the function. gene_signature is whatever qc metric you care about and split_var should be a covariate of interest. Adjust the titles to match the covariate groups. If you have more than 2 covariate groups then you will have multiple plots g[[3]]....g[[n]]
<- signaturePlot(seurat_lognorm,
g gene_signature = "Log10GenesPerUMI",
split_var = "orig.ident"
)
Error in pull(seurat_object@meta.data, gene_signature): could not find function "pull"
1]] + ggtitle("S1") | g[[2]] + ggtitle("S2") g[[
Error: object 'g' not found
SCT Normalization
Now that we have established which effects are observed in our data, we can use the SCTransform method to regress out these effects. The SCTransform method was proposed as a better alternative to the log transform normalization method that we used for exploring sources of unwanted variation. The method not only normalizes data, but it also performs a variance stabilization and allows for additional covariates to be regressed out.
All genes cannot be treated the same, as such, the SCTransform method constructs a generalized linear model (GLM) for each gene with UMI counts as the response and sequencing depth as the explanatory variable. Information is pooled across genes with similar abundances, to regularize parameter estimates and obtain residuals which represent effectively normalized data values which are no longer correlated with sequencing depth.
We searched for the top 3000 genes with the largest variability in expression level from cell to cell after SCT-normalization, and re-calculated our principal and UMAP components based on the SCT-normalized data for these top genes.
We keep each sample separate for SCT normalization.
# NOTE run the chunk below to create this object, and loading will be used while
# knitting to speed up the rendering
<- readRDS("seurat_sctnorm.rds") seurat_sctnorm
# NOTE: this should be ran previous rendering to prepare the object
## Note that this single command replaces NormalizeData(), ScaleData(), and FindVariableFeatures()
## SCT can be run with and without regressing out variables. Generally we do not regress out covariates. However, we provide both options below.
"RNA"]] <- split(seurat_lognorm[["RNA"]],
seurat_lognorm[[f = seurat_lognorm$orig.ident
)
<- SCTransform(seurat_lognorm,
seurat_sctnorm vst.flavor = "v2",
variable.features.n = 3000
)<- RunPCA(seurat_sctnorm)
seurat_sctnorm <- RunUMAP(seurat_sctnorm, 1:40)
seurat_sctnorm
saveRDS(seurat_sctnorm, file = "seurat_sctnorm.rds")
Look at UMAPs post SCT
The plots below show the same variables as before, this time displayed on the UMAP calculated after applying SCT-normalization.
We qualitatively reviewed the “structure” in our normalized data projection . We were particularly interested in seeing whether similar cell populations across samples clustered together (i.e. overlapped on the UMAP).
DefaultAssay(seurat_sctnorm) <- "SCT"
UMAPPlot(seurat_sctnorm, group.by = "orig.ident") + ggtitle("UMAP")
Cell cycle
The phase of the cell cycle that cells are in at the time of sample preparation can introduce some variability in the transcriptome that we are not interested in exploring.
To examine cell cycle variation in our data, we assign a score to each cell, derived from the overall expression level of known markers of the G2/M and S phase in that cell. We then display the cells, color-coded by inferred cell cycle phase, on our UMAP.
Unless cells very strongly cluster by phase of the cell cycle (which is not the case here), we do not recommend to regress out the effect of the cell cycle.
# Step 1 - Get cell cycle markers
## Cell cycle markers for c.elegans, human, mouse, D. rerio, and D. melanogaster can be found here: https://github.com/hbc/tinyatlas/tree/1e2136a35e773f14d97ae9cbdb6c375327b2dd2b/cell_cycle
# Compute cell cycle score for each cell
<- CellCycleScoring(seurat_sctnorm,
seurat_sctnorm g2m.features = cc_markers$gene_name[cc_markers$phase == "G2/M"],
s.features = cc_markers$gene_name[cc_markers$phase == "S"]
)
## Plot cell cycle (grouped by) along with covariates (split.by). Add in your covariates of interest
UMAPPlot(seurat_sctnorm, group.by = "Phase", split.by = "orig.ident") + ggtitle("UMAP")
mitoRatio
The mitochondrial to nuclear gene ratio (mitoRatio) is a marker of cellular stress and might also affect cell clustering. For this dataset, we have seen during QC that the fraction of mitochondrial genes was negligible (which is good). Therefore, we do not expect the need to regress out this variable for normalization purposes, but it’s always good to check.
## This custom function by Amelie Jule creates great plots for looking at different QC parameters across the UMAP
<- function(seurat_object,
signaturePlot
gene_signature,reduction = "umap",
split_var = NULL,
pt_size = 0.5) {
<- FeaturePlot(seurat_object,
g1 features = gene_signature,
reduction = reduction,
split.by = split_var,
order = TRUE,
pt.size = pt_size,
combine = FALSE
)
<- min(pull(seurat_object@meta.data, gene_signature))
min_val <- max(pull(seurat_object@meta.data, gene_signature))
max_val <- scale_color_gradientn(
fix_params colours = c("grey80", "blue"),
limits = c(min_val, max_val)
)
<- lapply(g1, function(x) {
g2 + fix_params +
x theme_minimal() +
theme(
legend.position = "bottom",
plot.title = element_text(hjust = 0.5)
+
) ggtitle("")
})
g2
}
### Here we apply the function. gene_signature is whatever qc metric you care about and split_var should be a covariate of interest. Adjust the titles to match the covariate groups. If you have more than 2 covariate groups then you will have multiple plots g[[3]]....g[[n]]
<- signaturePlot(seurat_sctnorm,
g gene_signature = "mitoRatio",
split_var = "orig.ident"
)
Error in pull(seurat_object@meta.data, gene_signature): could not find function "pull"
1]] + ggtitle("S1") | g[[2]] + ggtitle("S2") g[[
Error: object 'g' not found
nUMIs (nCount)
### Here we apply the function. gene_signature is whatever qc metric you care about and split_var should be a covariate of interest. Adjust the titles to match the covariate groups. If you have more than 2 covariate groups then you will have multiple plots g[[3]]....g[[n]]
<- signaturePlot(seurat_sctnorm,
g gene_signature = "nCount_RNA",
split_var = "orig.ident"
)
Error in pull(seurat_object@meta.data, gene_signature): could not find function "pull"
1]] + ggtitle("S1") | g[[2]] + ggtitle("S2") g[[
Error: object 'g' not found
nGenes (nFeature)
### Here we apply the function. gene_signature is whatever qc metric you care about and split_var should be a covariate of interest. Adjust the titles to match the covariate groups. If you have more than 2 covariate groups then you will have multiple plots g[[3]]....g[[n]]
<- signaturePlot(seurat_sctnorm,
g gene_signature = "nFeature_RNA",
split_var = "orig.ident"
)
Error in pull(seurat_object@meta.data, gene_signature): could not find function "pull"
1]] + ggtitle("S1") | g[[2]] + ggtitle("S2") g[[
Error: object 'g' not found
Complexity
### Here we apply the function. gene_signature is whatever qc metric you care about and split_var should be a covariate of interest. Adjust the titles to match the covariate groups. If you have more than 2 covariate groups then you will have multiple plots g[[3]]....g[[n]]
<- signaturePlot(seurat_sctnorm,
g gene_signature = "Log10GenesPerUMI",
split_var = "orig.ident"
)
Error in pull(seurat_object@meta.data, gene_signature): could not find function "pull"
1]] + ggtitle("S1") | g[[2]] + ggtitle("S2") g[[
Error: object 'g' not found
Integration
CCA integration
# NOTE run the chunck below to create this object, and loading will be used while
# knitting to speed up the rendering
<- readRDS("seurat_cca.rds") seurat_cca
# NOTE: this should be ran previous rendering to prepare the object
## Note that this single command replaces NormalizeData(), ScaleData(), and FindVariableFeatures()
## SCT can be run with and without regressing out variables. Generally we do not regress out covariates. However, we provide both options below.
## To properly integrate with harmony we split our object by sample first.
<- SplitObject(seurat_lognorm, split.by = "orig.ident")
split_sctnorm # NOTE If we have a large dataset, then we might need to adjust the limit for allowable object sizes within R
# options(future.globals.maxSize = 4000 * 1024^2)
for (i in 1:length(split_sctnorm)) {
<- SCTransform(split_sctnorm[[i]],
split_sctnorm[[i]] vst.flavor = "v2",
variable.features.n = 3000
)
}
<- SelectIntegrationFeatures(
integ_features object.list = split_sctnorm,
nfeatures = 3000
)<- PrepSCTIntegration(
split_sctnorm object.list = split_sctnorm,
anchor.features = integ_features
)
# Find best buddies - can take a while to run
<- FindIntegrationAnchors(
integ_anchors object.list = split_sctnorm,
normalization.method = "SCT",
anchor.features = integ_features
)# Integrate across conditions
<- IntegrateData(
seurat_cca anchorset = integ_anchors,
normalization.method = "SCT"
)
[1] 1
[1] 2
[1] 3
# Rejoin the layers in the RNA assay that we split earlier
"RNA"]] <- JoinLayers(seurat_cca[["RNA"]])
seurat_cca[[
# Run PCA
<- RunPCA(object = seurat_cca)
seurat_cca
# Run UMAP
<- RunUMAP(seurat_cca,
seurat_cca reduction.name = "umap.cca",
dims = 1:40
)
saveRDS(seurat_cca, file = "seurat_cca.rds")
<- DimPlot(seurat_lognorm,
p1 group.by = "orig.ident",
reduction = "umap"
+
) theme(legend.position = "bottom") +
ggtitle("pre-integration")
<- DimPlot(seurat_cca,
p2 group.by = "orig.ident",
reduction = "umap.cca"
+
) theme(legend.position = "bottom") +
ggtitle("post-integration")
| p2 p1
Harmony
If cells cluster by sample, condition, batch, dataset, modality, this integration step can greatly improve the clustering and the downstream analyses.
To integrate, we will use the shared highly variable genes (identified using SCTransform) from each group, then, we will “integrate” or “harmonize” the groups to overlay cells that are similar or have a “common set of biological features” between groups.
We use Harmony
, which is based on a transformation of principal components (PCs) to find similarities across datasets. Here we group samples by the original sample id.
# NOTE run the chunck below to create this object, and loading will be used while
# knitting to speed up the rendering
<- readRDS("seurat_harmony.rds") seurat_harmony
## Here seurat will integrate on the level of sample id. If you want to integrate on other aspects the SCT normalization will need to be done with all of the data together.
# seurat_sctnorm[["RNA"]] <- split(seurat_sctnorm[["RNA"]], f = seurat_sctnorm$orig.ident)
<- IntegrateLayers(
seurat_harmony object = seurat_sctnorm,
method = HarmonyIntegration,
orig.reduction = "pca",
new.reduction = "harmony",
assay = "SCT", verbose = FALSE
)<- RunPCA(seurat_harmony)
seurat_harmony <- RunUMAP(seurat_harmony,
seurat_harmony reduction = "harmony",
dims = 1:40,
reduction.name = "umap.harmony"
)saveRDS(seurat_harmony, file = "seurat_harmony.rds")
Pre vs. Post integration
Clustering
For single-modality scRNA-seq analysis, Seurat
clusters the cells using a Louvain clustering approach. First, a K-nearest neighbor (KNN) graph is built, where cells are connected if they have a similar transcriptome, as determined from their scores on the first PCs. Then, the graph is partitioned into “communities” or “clusters” of interconnected cells that are more tightly connected with each other than with cells outside of the corresponding cluster.
A limitation of this approach is that the number of identified clusters depends on the chosen resolution, a parameter that must be set by the user and does not necessarily reflect the underlying biology of the dataset. For most single-cell datasets, a resolution of 0.1 to 1 will provide a reasonable number of clusters. Complex datasets with multiple cell types may require a larger resolution, and vice versa.
The code below will generate clusters for resolutions 0.1, 0.2, 0.4, 0.6, 0.8, and 1.0. Note that the names of the cluster will include important information about which slot in seurat was used for clustering.
Harmony with log normalized data : SNN_res.
Harmony with SCT normalized data : SCT_res.
CCA : integrated_snn_res.
After generating these clusters we will examine them below using a tree based approach and simply overlaying cluster ids onto the umap. We recommend keeping all of the clusters in the metadata as you move forward in case you want to go back and try a different one. To switch between different resolutions you simply need to reset your cell identities to the correct column in the metadata. For example: Idents(object = seurat_clust) <- "SCT_res.0.2"
# NOTE use seurat_harmony or seurat_cca
# seurat_clust <- FindNeighbors(seurat_harmony, assay = "SCT",
# reduction = "harmony", dims = 1:40)
<- FindNeighbors(seurat_cca, assay = "SCT", dims = 1:40)
seurat_clust # check graph names names(seurat_harmony@graphs)
# DefaultAssay(object = seurat_harmony[["pca"]])
<- FindClusters(
seurat_clust object = seurat_clust,
resolution = c(0.1, 0.2, 0.4, 0.6, 0.8, 1.0),
verbose = FALSE
)
Clustering Tree
We build a clustering tree using the clustree package to show how cells move as the clustering resolution is increased. Each cluster forms a node in the tree and edges are constructed by considering the cells in a cluster at a lower resolution (say 𝑘=2) that end up in a cluster at the next highest resolution (say 𝑘=3). By connecting clusters in this way we can see how clusters are related to each other, which are clearly distinct and which are unstable. The size of each node is related to the number of samples in each cluster and the color indicates the clustering resolution. Edges are colored according to the number of samples they represent and the transparency shows the incoming node proportion, the number of samples in the edge divided by the number of samples in the node it points to.
library(clustree)
<- seurat_clust@meta.data
meta <- na.omit(meta)
meta
## Change the prefix to match your clusters
# NOTE: this if you have run HARMONY
# prefix_clu <- "SNN_res."
# show_this <- "umap.harmony"
# NOTE: this if you have run CCA
<- "integrated_snn_res."
prefix_clu <- "umap.cca"
show_this clustree(meta, prefix = prefix_clu)
Visualize clusters
We take a look at how the clusters look at resolutions 0.1, 0.2,0.4, and 0.6
0.1
<- 0.1
cluster_res Idents(object = seurat_clust) <- paste0(prefix_clu, cluster_res)
DimPlot(seurat_clust,
reduction = show_this,
split.by = "orig.ident",
label = TRUE
)
0.2
<- 0.2
cluster_res Idents(object = seurat_clust) <- paste0(prefix_clu, cluster_res)
DimPlot(seurat_clust,
reduction = show_this,
split.by = "orig.ident",
label = TRUE
)
0.4
<- 0.4
cluster_res Idents(object = seurat_clust) <- paste0(prefix_clu, cluster_res)
DimPlot(seurat_clust,
reduction = show_this,
split.by = "orig.ident",
label = TRUE
)
0.6
<- 0.6
cluster_res Idents(object = seurat_clust) <- paste0(prefix_clu, cluster_res)
DimPlot(seurat_clust,
reduction = show_this,
split.by = "orig.ident",
label = TRUE
)
saveRDS(seurat_clust, file = seurat_output)
R session
List and version of tools used for the QC report generation.
sessionInfo()
R version 4.5.1 (2025-06-13)
Platform: x86_64-pc-linux-gnu
Running under: Ubuntu 22.04.5 LTS
Matrix products: default
BLAS: /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3
LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.20.so; LAPACK version 3.10.0
locale:
[1] LC_CTYPE=C.UTF-8 LC_NUMERIC=C LC_TIME=C.UTF-8
[4] LC_COLLATE=C.UTF-8 LC_MONETARY=C.UTF-8 LC_MESSAGES=C.UTF-8
[7] LC_PAPER=C.UTF-8 LC_NAME=C LC_ADDRESS=C
[10] LC_TELEPHONE=C LC_MEASUREMENT=C.UTF-8 LC_IDENTIFICATION=C
time zone: UTC
tzcode source: system (glibc)
attached base packages:
[1] stats graphics grDevices utils datasets methods base
other attached packages:
[1] future_1.58.0 readr_2.1.5 R.utils_2.13.0 R.oo_1.27.1
[5] R.methodsS3_1.8.2 grafify_5.0.0.1 ggprism_1.0.6 clustree_0.5.1
[9] ggraph_2.2.1 ggplot2_3.5.2 patchwork_1.3.1 DT_0.33
[13] data.table_1.17.8 rmarkdown_2.29 knitr_1.50 harmony_1.2.3
[17] Rcpp_1.0.14 Seurat_5.3.0 SeuratObject_5.1.0 sp_2.2-0
loaded via a namespace (and not attached):
[1] RcppAnnoy_0.0.22 splines_4.5.1 later_1.4.2
[4] tibble_3.3.0 polyclip_1.10-7 rpart_4.1.24
[7] fastDummies_1.7.5 lifecycle_1.0.4 Rdpack_2.6.4
[10] vroom_1.6.5 globals_0.18.0 lattice_0.22-6
[13] MASS_7.3-65 backports_1.5.0 magrittr_2.0.3
[16] Hmisc_5.2-3 plotly_4.11.0 yaml_2.3.10
[19] httpuv_1.6.16 sctransform_0.4.2 spam_2.11-1
[22] spatstat.sparse_3.1-0 reticulate_1.42.0 cowplot_1.1.3
[25] pbapply_1.7-2 minqa_1.2.8 RColorBrewer_1.1-3
[28] abind_1.4-8 Rtsne_0.17 purrr_1.1.0
[31] nnet_7.3-20 tweenr_2.0.3 ggrepel_0.9.6
[34] irlba_2.3.5.1 listenv_0.9.1 spatstat.utils_3.1-4
[37] goftest_1.2-3 RSpectra_0.16-2 spatstat.random_3.4-1
[40] fitdistrplus_1.2-4 parallelly_1.45.0 codetools_0.2-20
[43] ggforce_0.5.0 tidyselect_1.2.1 farver_2.1.2
[46] lme4_1.1-37 viridis_0.6.5 matrixStats_1.5.0
[49] base64enc_0.1-3 spatstat.explore_3.4-3 jsonlite_2.0.0
[52] tidygraph_1.3.1 progressr_0.15.1 Formula_1.2-5
[55] ggridges_0.5.6 survival_3.8-3 emmeans_1.11.2
[58] tools_4.5.1 ica_1.0-3 glue_1.8.0
[61] gridExtra_2.3 xfun_0.52 mgcv_1.9-1
[64] dplyr_1.1.4 withr_3.0.2 numDeriv_2016.8-1.1
[67] BiocManager_1.30.25 fastmap_1.2.0 boot_1.3-31
[70] digest_0.6.37 R6_2.6.1 mime_0.13
[73] estimability_1.5.1 colorspace_2.1-1 scattermore_1.2
[76] tensor_1.5.1 spatstat.data_3.1-6 RhpcBLASctl_0.23-42
[79] tidyr_1.3.1 generics_0.1.4 graphlayouts_1.2.2
[82] httr_1.4.7 htmlwidgets_1.6.4 uwot_0.2.3
[85] pkgconfig_2.0.3 gtable_0.3.6 lmtest_0.9-40
[88] htmltools_0.5.8.1 carData_3.0-5 dotCall64_1.2
[91] scales_1.4.0 png_0.1-8 spatstat.univar_3.1-4
[94] reformulas_0.4.1 rstudioapi_0.17.1 tzdb_0.5.0
[97] reshape2_1.4.4 curl_6.4.0 checkmate_2.3.2
[100] nlme_3.1-168 nloptr_2.2.1 cachem_1.1.0
[103] zoo_1.8-14 stringr_1.5.1 KernSmooth_2.23-26
[106] parallel_4.5.1 miniUI_0.1.2 foreign_0.8-90
[109] pillar_1.10.2 grid_4.5.1 vctrs_0.6.5
[112] RANN_2.6.2 promises_1.3.2 car_3.1-3
[115] xtable_1.8-4 cluster_2.1.8.1 htmlTable_2.4.3
[118] evaluate_1.0.3 mvtnorm_1.3-3 cli_3.6.5
[121] compiler_4.5.1 crayon_1.5.3 rlang_1.1.6
[124] future.apply_1.20.0 labeling_0.4.3 plyr_1.8.9
[127] stringi_1.8.7 viridisLite_0.4.2 deldir_2.0-4
[130] lmerTest_3.1-3 lazyeval_0.2.2 spatstat.geom_3.4-1
[133] Matrix_1.7-3 RcppHNSW_0.6.0 hms_1.1.3
[136] bit64_4.6.0-1 shiny_1.10.0 rbibutils_2.3
[139] ROCR_1.0-11 igraph_2.1.4 memoise_2.0.1
[142] bit_4.6.0