Summary
Cellular senescence is a cell state characterized by stable cell-cycle arrest accompanied by coordinated molecular, metabolic, and functional changes. Here, we present a protocol to classify senescent cells from single-cell or single-nucleus RNA sequencing data using gene expression activity scoring of a senescence gene set provided by the user. We describe steps for data-driven optimization of estimated proportion of senescent cells per cell type via enrichment testing. We then detail procedures for differential expression analysis to define senescence-associated transcriptional programs.
For complete details on the use and execution of this protocol, please refer to Lund et al.1
Subject areas: Bioinformatics, Genomics, Systems biology
Graphical abstract

Highlights
-
•
Steps for computing senescence scores from single-cell or single-nucleus RNA-seq data
-
•
Steps for defining senescence labels within cell types in single-cell data
-
•
Steps for deriving cell type-specific senescence DE signatures
-
•
Instructions on estimating proportions of senescent cells per cell type
Publisher’s note: Undertaking any experimental protocol requires adherence to local institutional guidelines for laboratory safety and ethics.
Cellular senescence is a cell state characterized by stable cell-cycle arrest accompanied by coordinated molecular, metabolic, and functional changes. Here, we present a protocol to classify senescent cells from single-cell or single-nucleus RNA sequencing data using gene expression activity scoring of a senescence gene set provided by the user. We describe steps for data-driven optimization of estimated proportion of senescent cells per cell type via enrichment testing. We then detail procedures for differential expression analysis to define senescence-associated transcriptional programs.
Before you begin
Cellular senescence is recognized as a fundamental biological program implicated in aging and diverse pathological conditions.2,3,4,5 However, establishing a rigorous and generalizable framework to identify senescent cells in single-cell (scRNA-seq) and single-nucleus (snRNA-seq) transcriptomic datasets remains a challenge in the field. In the associated research article1 we developed and validated a data-driven, cell type-specific transcriptional framework to define and quantify cellular senescence in snRNA-seq and scRNA-seq data derived from human brain or other tissue. This framework derives cell type-specific senescence scores from expression of a user-specified canonical senescence-associated gene set. These scores enable systematic quantification of senescence-associated transcriptional programs for each cell or nucleus in single-cell and single-nucleus datasets.
In the associated research article, senescent cells were identified by calculating continuous senescence activity scores for each nucleus or cell using AUCell, based on the expression of a user-specified set of canonical senescence genes previously established for brain tissue or other tissue types (Figure 1). Within each annotated cell type, cells were ranked by their AUCell senescence scores, and multiple thresholds were defined to select fixed proportions (user-specified) of top-scoring cells as putative senescent cells. We then evaluated these thresholds by identifying differentially expressed genes (DEGs) and testing their enrichment for user-specified set of canonical senescence-associated genes, selecting the threshold that best captured the transcriptional signature of the senescent cell population. The chosen thresholds were subsequently used to classify the corresponding proportion of top scoring cells or nuclei as senescent or non-senescent and to derive cell type-specific binary senescence differential expression (DE) signatures. Although optimized for human prefrontal cortex snRNA-seq and scRNA-seq data, this framework was validated in other human tissues and can be applied to any single-cell or single-nucleus transcriptomic dataset, provided that an appropriate, tissue-relevant senescence gene set is curated and validated for the biological context under study. Here, we present a use case using snRNA-seq data derived from brain tissue.
Note: All timings presented below are based on running this code on a computational cluster. Using a computational cluster is not necessary; however, to improve runtime efficiency for larger datasets, the workflow was configured to use parallel CPU processing with 30 CPU cores/workers on a single multi-core compute node. Reported timings reflect computational execution time in this environment rather than total hands-on analysis time, which may vary depending on dataset preprocessing, setup requirements and compute resources.
Figure 1.
Overview of the senescence classification pipeline
Gene expression data and a tissue- or cell type-specific gene list are used as inputs to calculate a senescence score for each cell using methods such as AUCell. The pipeline then iteratively evaluates candidate senescent cell proportion thresholds by performing differential expression (DE) analysis and testing enrichment for the input gene list to identify the optimal threshold that maximizes the enrichment score. Final outputs include senescence classification labels for each cell, optimal cell type-specific proportion thresholds, and differential expression results associated with senescence.
Innovation
This protocol establishes a validated, data-driven framework for defining cellular senescence for each cell type in a tissue-specific manner. Existing methods often rely on individual marker genes, bulk gene expression-derived signatures, or tissue- and cell type-agnostic classifiers, are often based on several assumptions due to our current incomplete definition of senescence, and do not account for transcriptional sparsity in scRNA-seq or snRNA-seq data or the heterogeneity of senescence programs. In addition, these approaches typically do not define the relative proportion of senescent cells directly from the data. In contrast, this workflow defines senescence with fewer assumptions as a context-dependent transcriptional state using a user-specified curated canonical gene set combined with continuous gene set activity scoring and data-driven threshold optimization within each annotated cell population.
A central innovation is the integration of continuous senescence activity scoring with a data-driven approach for selecting the proportion of cells classified as senescent based on DE analysis. Rather than assuming unvalidated proportion of senescent cells, candidate thresholds are systematically evaluated, and an optimal threshold is selected based on maximal enrichment of canonical senescence genes for DEGs upregulated in putative senescent cells. This approach anchors classification to biologically informed features of senescence and yields cell specific senescence labels, estimated proportion of senescent cells, and cell-type-specific senescence DE signatures. This workflow is modular and adaptable to other tissues, provided that an appropriate tissue-relevant senescence gene set is curated and that single-cell or single-nuclei gene expression dataset exists.
Preparation one: Set up inputs for senescence scoring and binary DE
-
1.
Load necessary packages and functions.
>library(Seurat)
>library(AUCell)
>library(dplyr)
>library(BiocParallel)
>library(GSEABase)
>library(dreamlet)
>library(foreach)
>library(doParallel)
>library(stringr)
>library(tidyr)
>library(limma)
>source(“https://raw.githubusercontent.com/BeckmannLab/brainCellularSenescenceAndStructure/main/functions/dreamletCompareClusters_edgeR.R”) #load in customized dreamlet function for DE analysis that is robust to sparse data
>source(“https://raw.githubusercontent.com/BeckmannLab/brainCellularSenescenceAndStructure/main/functions/processOneAssay_edgeR.R”)#load in second customized dreamlet function for DE analysis that is robust to sparse data
-
2.
Load your Seurat object.
>Seurat_object = readRDS(“PATH/your_seurat_object.rds”)
-
3.
Extract raw counts.
>data = GetAssayData(Seurat_object, slot = “counts”, assay = “RNA”)
-
4.
Define user-specified canonical senescence gene set (literature derived).
> sen_genes <-c(“4-HNE”, “AXL”, “BCL2”, “CCL2”, “CCL3”, “CCL4”, “CCL5”, “CDKN1A”, “CDKN2A”, “CDKN2B”, “CDKN2D”, “CSF1”, “CSF2RA”, “CXCL1”, “CXCL8”, “GLB1”, “H2AX”, “HMGB1”, “IGF1”, “IL1A”, “IL1B”, “IL27”, “IL6”, “LGALS3”, “LGALS3BP”, “LMNB1”, “MACROH2A1”, “MIF”, “MMP12”, “MMP3”, “MTOR”, “PCNA”, “PLAUR”, “SA-β-Gal”, “SATB1”, “SERPINE1”, “SPP1”, “STING1”, “TGFB1”, “TIMP2”, “TNF”, “TP53”,“ACVR1B”, “ANG”, “ANGPT1”,“ANGPTL4”, “AREG”, “AXL”, “BEX3”, “BMP2”, “BMP6”, “C3”, “CCL1”, “CCL13”, “CCL16”, “CCL2”, “CCL20”, “CCL24”, “CCL26”, “CCL3”, “CCL3L1”, “CCL4”, “CCL5”, “CCL7”, “CCL8”,
“CD55”, “CD9”, “CSF1”, “CSF2”, “CSF2RB”, “CST4”, “CTNNB1”,“CTSB”, “CXCL1”, “CXCL10”, “CXCL12”, “CXCL16”,”CXCL2”, “CXCL3”, “CXCL8”, “CXCR2”, “DKK1”, “EDN1”,”EGF”, “EGFR”, “EREG”, “ESM1”, “ETS2”, “FAS”, “FGF1”,”FGF2”, “FGF7”, “GDF15”, “GEM”, “GMFG”, “HGF”, “HMGB1”,“ICAM1”, “ICAM3”, “IGF1”, “IGFBP1”, “IGFBP2”, “IGFBP3”, “IGFBP4”, “IGFBP5”, “IGFBP6”, “IGFBP7”, “IL10”,”IL13”, “IL15”, “IL18”, “IL1A”, “IL1B”, “IL2”, “IL32”, “IL6”, “IL6ST”, “IL7”, “INHA”, “IQGAP2”, “ITGA2”, “ITPKA”,“JUN”, “KITLG”, “LCP1”, “MIF”, “MMP1”, “MMP10”,“MMP12”, “MMP13”, “MMP14”, “MMP2”, “MMP3”, “MMP9”, “NAP1L4”, “NRG1”, “PAPPA”, “PECAM1”, “PGF”, “PIGF”, “PLAT”,“PLAU”, “PLAUR”, “PTBP1”, “PTGER2”, “PTGES”, “RPS6KA5”,”SCAMP4”, “SELPLG”, “SEMA3F”, “SERPINB4”, “SERPINE1”, “SERPINE2”, “SPP1”, “SPX”, “TIMP2”, “TNF”,“TNFRSF10C”, “TNFRSF11B”, “TNFRSF1A”, “TNFRSF1B”, “TUBGCP2”, “VEGFA”, “VEGFC”, “VGF”, “WNT16”, “WNT2”)
>all_genes = unique(c(sen_genes))
>geneSets = GeneSet(all_genes, setName = “sen_list”)
-
5.
Define the threshold_ grid containing the potential senescence proportion thresholds to be tested for each cell type for the downstream workflow. These threshold values and cell type combinations can be customized based on biological question and dataset characteristics.
# Define the AUCell threshold grid used to classify senescence-positive cells.
# Each row corresponds to a unique combination of:
# (1) percent: descriptive label for the proportion of highest-scoring cells retained,
# (2) celltype: cell type in which the threshold was applied, and
# (3) threshold: AUCell quantile cutoff used to define positive cells.
#
# Thresholds were evaluated across five cutoffs:
# 0.99 (top 1%), 0.95 (top 5%), 0.90 (top 10%),
# 0.80 (top 20%), and 0.70 (top 30%).
#
# The resulting 'threshold_grid' object was used in downstream analyses to iteratively define AUCell-positive cells within each cell type.
> threshold_grid <- structure(list(percent = c(“1percent”,“1percent”,“1percent”,“1percent”,“1percent”,“1percent”,“1percent”,“5percent”,“5percent”,“5percent”,“5percent,“5percent”,“5percent”,“5percent”,“10percent”,“10percent”,“10percent”,“10percent”,“10percent”,“10percent”,“10percent”,“20percent”,“20percent”,“20percent”,“20percent”,“20percent”,“20percent”,“20percent”,“30percent”,“30percent”,“30percent”,“30percent”,“30percent”,“30percent”,“30percent”), celltype = c(“MG”,“Exc”,“Oli”,“Int”,“NonNeu”,“Ast”,“OPC”,“MG”,“Exc”,“Oli”,“Int”,“NonNeu”,“Ast”,“OPC”,“MG”,“Exc”,“Oli”,“Int”,“NonNeu”,“Ast”,“OPC”,“MG”,“Exc”,“Oli”,“Int”,“NonNeu”,“Ast”,“OPC”,“MG”,“Exc”,“Oli”,“Int”,“NonNeu”,“Ast”,“OPC”), threshold = c(0.99,0.99,0.99,0.99,0.99,0.99,0.99,0.95,0.95,0.95,0.95,0.95,0.95,0.95,0.90,0.90,0.90,0.90,0.90,0.90,0.90,0.80,0.80,0.80,0.80,0.80,0.80,0.80,0.70,0.70,0.70,0.70,0.70,0.70,0.70)), row.names = c(NA, -35L), class = “data.frame”)
Key resources table
| REAGENT or RESOURCE | SOURCE | IDENTIFIER |
|---|---|---|
| Deposited data | ||
| LBP snRNA-seq | Vornholt et al.6; synapse | syn65929068; syn65929069 |
| Code for analyses reported in this manuscript | Lund et al.1 | https://github.com/BeckmannLab/brainCellularSenescenceAndStructure |
| Software and algorithms | ||
| dreamlet v1.4.1 | Hoffman et al.7 | NA |
| edgeR v4.4.2 | Robinson et al.8 | NA |
| AUCell v1.28 | Aibar et al.9 | NA |
| Seurat v5.1.0 | Hao et al.10 | NA |
| R v4.4.1 | https://cran.r-project.org/bin/windows/base/old/ | NA |
| Limma v3.62.2 | Langaas et al.11 | NA |
| Dplyr v1.1.4 | Wickham et al.12 | NA |
| Tidyr 1.3.2 | Wickham et al.13 | NA |
| Stringr v1.6.0 | Wickham et al.14 | NA |
| Foreach v1.5.2 | Weston et al.15 | NA |
| doParallel v1.0.17 | Weston et al.16 | NA |
| BiocParallel 1.40.2 | Wang et al.17 | NA |
| GSEABase 1.68.0 | Morgan et al.18 | NA |
Step-by-step method details
Compute continuous senescence activity scores using AUCell
Timing: 24 s
-
1.
Run AUCell to compute continuous senescence activity scores for each cell using a raw gene expression count matrix and a curated senescence gene set. The resulting AUCX2 object contains per-cell AUCell enrichment scores, where higher scores indicate greater senescence-associated gene activity.
>AUCX2 <- AUCell_run(data,geneSets = geneSets,BPPARAM = BiocParallel::MulticoreParam(30)) #this uses 30 cores on the cluster, for non-parallel processing, use SerialParam()
-
2.
Save the AUCell object for downstream analyses.
>saveRDS(AUCX2, “AUCX2_sen_list.rds”)
Define threshold AUCell scores within each cell type
Timing: 1 min 8 s
-
3.
Subset the Seurat object according to the cell type specified in each row of the threshold_grid, which contains the cell type, percentile label, and AUCell quantile threshold to be tested. This enables AUCell thresholds to be applied independently within each cell type.
-
4.
Extract the corresponding AUCell senescence scores for cells within the selected cell type from the AUCX2 object. The resulting matrix (AUC_mat) contains one AUCell enrichment score per cell for that cell type.
-
5.
Compute the AUCell score quantile specified in threshold_grid for the current threshold. This value defines the cutoff used to identify the top-scoring cells as putative senescent cells within the selected cell type.
-
6.
Assign the following putative labels to cells: 1) senescent (TRUE) if their AUCell score is greater than or equal to the computed threshold, or 2) non-senescent (FALSE) otherwise. This generates a binary putative senescence label for each cell.
-
7.
Annotate each cell with its corresponding cell type, threshold label (e.g., 1percent, 5percent), and cell barcode for downstream analyses.
-
8.
Save the threshold-specific AUCell classification table as an.RDS object containing continuous AUCell scores and binary putative senescence labels for all cells.
-
9.
Append the threshold-specific results into a cumulative table (final_df) containing AUCell scores, putative senescence labels, cell type annotations, and threshold labels across all tested conditions for downstream pseudobulk aggregation and differential expression analyses.
> final_df = c()
resfinal=foreach(i = 1:nrow(threshold_grid)) %do% {
# Step 3. Subset the Seurat object according to the cell #type specified in the threshold_grid grid.
subset = subset(Seurat_object, celltype == threshold_grid[i,2])
# Step 4. Extract AUCell senescence scores for cells #within the selected cell type.
subset_aucx2 = AUCX2[,colnames(AUCX2) %in% colnames(subset) ]
AUC_mat <- t(getAUC(subset_aucx2))
AUC_mat = as.data.frame(AUC_mat)
# Step 5. Compute the AUCell score quantile threshold
# specified for the current condition.
threshold <- quantile(AUC_mat$sen_list, threshold_grid[i,3])
# Step 6. Classify cells as senescent (TRUE) or
# non-senescent (FALSE).
AUC_mat$sen <- AUC_mat$sen_list >= threshold
# Step 7. Annotate cells with the corresponding cell
#type, threshold label, and cell barcode.
AUC_mat$celltype = threshold_grid[i,2]
AUC_mat$percent = threshold_grid[i,1]
AUC_mat$cell = rownames(AUC_mat)
print(i)
# Step 8. Save the threshold-specific AUCell
#classification results.
saveRDS(AUC_mat, paste0(“path”, threshold_grid[i,2],”_146_AUC_mat_top_”, threshold_grid[i,1],”.RDS”))
# Step 9. Append results into a cumulative table for
# downstream pseudobulk aggregation and DE analysis.
final_df = rbind(AUC_mat, final_df)
}
Generate pseudobulk expression profiles
Timing: 20 s
-
10.
Define the threshold-specific senescence labels from final_df and extract Sample_ID from the cell barcode for each cell. The input is the cumulative AUCell classification table (step 8); the output is a per-cell annotation table linking each cell to a threshold label, senescence status, and sample of origin.
-
11.
Convert the Seurat object to a SingleCellExperiment object. The input is the filtered Seurat object containing raw counts and metadata; the output is a SingleCellExperiment object (sce) that can be used for pseudobulk aggregation.
-
12.
For each tested threshold, subset the cells belonging to that threshold and assign cell type-specific putative senescence labels such as Ast_TRUE and Ast_FALSE. The input is the AUCell classification table (step 8), and the output is a threshold-specific cell annotation table containing binary senescence status within each cell type.
-
13.
Reorder the columns of the AUCell-scored cells to those of the SingleCellExperiment object and add senescence labels and Sample_ID to colData of the SingleCellExperiment object. The input is the threshold-specific cell annotation table (step 8) plus sce; the output is an annotated SingleCellExperiment subset (sce2) with per-cell putative senescence labels and sample metadata.
-
14.
Aggregate raw counts into pseudobulk expression profiles by putative senescence label and sample using aggregateToPseudoBulk(). The input is the annotated SingleCellExperiment object with raw counts in the counts assay (sce2); the output is a pseudobulk object containing summed counts per sample and senescence group.
-
15.
Save each threshold-specific pseudobulk object, or load it if it already exists. The output is one.RDS pseudobulk file per threshold, which is then used in the downstream differential expression step.
> percentages = c(“1percent”,“5percent”,“10percent”, “20percent”, “30percent”)
# Step 10. Define threshold-specific senescence labels and
# extract Sample_ID from cell barcodes.
>all_sene = final_df %>%
mutate(Sample_ID = str_extract(cell, “ˆ[ˆ_]+”))
>folder = “path”
# Step 11. Convert the Seurat object to a SingleCellExperiment #object.
>sce = as.SingleCellExperiment(Seurat_object)
>foreach(i = percentages) %do% {
# Step 12. Subset cells for the current threshold and
# construct cell type-specific senescence labels.
sene = all_sene[which(all_sene$percent == i),]
sene$cell_sen = paste0(sene$celltype,“_”,sene$sen)
sen3 = sene[,c(“cell_sen”,“cell”,“Sample_ID”), drop = FALSE]
pb <- list()
# Step 13. Match AUCell-scored cells to the
# SingleCellExperiment object and assign metadata.
matches <- match(colnames(sce), sen3$cell)
valid_matches <- !is.na(matches)
sce2 <- sce[, valid_matches]
colData(sce2)$sen_auc <- as.character(sen3[matches[valid_matches], “cell_sen”, drop = TRUE])
colData(sce2)$Sample_ID = as.character(sen3[matches[valid_matches], “Sample_ID”, drop = TRUE])
print(i)
file = paste0(folder, paste0(“expression/pseudobulk_”, i, “.RDS”))
if (!file.exists(file)){
# Step 14. Aggregate raw counts into pseudobulk
# expression profiles by senescence label and sample.
pb = aggregateToPseudoBulk(sce2,
assay = “counts”,
cluster_id = “sen_auc”,
sample_id = “Sample_ID”,
BPPARAM = MulticoreParam(40))
saveRDS(pb, file)
# Step 15. Save the threshold-specific pseudobulk #object.
}else{
# Step 15. Load the pseudobulk object if it already exists.
pb=readRDS(file)
}
}
Perform DE using edgeR negative binomial modeling
Timing: 11 min
-
16.
Define grid for downstream DE analyses.
folder = “path”
cts = c(“Ast”,“Exc”,“Int”,“MG”,“NonNeu”,“Oli”,“OPC”)
ctsl = c(“ast”,“exc”,“int”,“mg”,“noneu”,“oli”,“opc”)
pcts = c(“1percent”,“5percent”,“20percent”,“30percent”,“10percent”)
de_grid <- do.call(rbind, lapply(pcts, function(p) {
data.frame(
V1 = rep(paste0(“pseudobulk_”, p, “.RDS”), length(cts)),
V2 = rep(p, length(cts)),
V3 = rep(paste0(“pb_”, p), length(cts)),
V4 = if (p == “5percent”) c(“Ast_FALSE”, paste0(cts[-1], “_TRUE”)) else paste0(cts, “_TRUE”),
V5 = if (p == “5percent”) c(“Ast_TRUE”, paste0(cts[-1], “_FALSE”)) else paste0(cts, “_FALSE”),
V6 = ctsl,
V7 = rep(0.4, length(cts)),
stringsAsFactors = FALSE
)
}))
-
17.
For each row of de_grid, check whether the corresponding edgeR differential expression (DE) result file already exists and skip recomputation if it does. The input is the analysis grid containing threshold labels, cell types, and comparison definitions; the output is one DE result file per threshold and cell type combination.
-
18.
Load the threshold-specific pseudobulk object specified by de_grid[i,1]. This object contains aggregated raw count profiles for each sample and senescence group under the current threshold condition.
-
19.
Extract the number of cells contributing to each pseudobulk profile and format this information as a per-sample covariate table (ncells2). This provides sample-level cell count metadata for downstream modeling.
-
20.
Add subject identifiers and the ncells covariate to metadata(pb)$aggr_means to retain sample-level information required for differential expression analysis. The resulting metadata links each pseudobulk sample to its corresponding individual and contributing cell count.
-
21.
Define the senescent versus non-senescent comparison for the current cell type using ct.pairs <- c(de_grid[i,4], de_grid[i,5]). These labels specify the two pseudobulk groups to be compared in the DE model.
-
22.
Run differential expression analysis using dreamletCompareClusters_edgeR() with a fixed-effects model and min.prop = 0.4. The input consists of the annotated pseudobulk object and comparison labels, and the output is an edgeR-based DE fit for the current threshold and cell type. As differential expression analyses are performed using paired cell type specific comparisons within each sample, each comparison contrasts senescent versus non-senescent cells derived from the same sample and therefore cannot by design be impacted by any covariate that capture variation between samples such as age, sex, PMI, or batch.
-
23.
Extract the complete DE results table using topTags(fit, n = Inf) and save the output. The resulting table contains differential expression statistics, including log fold-changes and p-values for all tested genes.
foreach(i = 1:nrow(de_grid)) %do% {
# Step 17. Check whether the DE result for this threshold/cell #type combination already exists; skip if it does.
if(length(which(paste0(“correct_edger_”, de_grid[i,6],“_”, de_grid[i,2], “_fit.RDS”)==list.files(“path”,recursive=TRUE)))==0){
# Step 18. Load the threshold-specific pseudobulk object.
pb = readRDS(paste0(folder, de_grid[i,1]))
# Step 19. Extract the number of cells contributing to each #pseudobulk profile.
ncells = as.data.frame(int_colData(pb)$n_cells)
ncells$celltype = rownames(ncells)
ncells2 = ncells %>%
pivot_longer(!celltype,names_to = “id”, values_to = “count”)
ncells2 = as.data.frame(ncells2)
ncells2$id = gsub(“\\.”, “_”, ncells2$id)
colnames(ncells2) = c(“sen_auc”, “Sample_ID”, “ncell”)
# Step 20. Add subject identifiers and cell-count
#covariates to pseudobulk metadata.
metadata(pb)$aggr_means$Indv_ID = metadata(pb)$aggr_means$Sample_ID
metadata(pb)$aggr_means$Indv_ID=gsub(“R|L”, “”, metadata(pb)$aggr_means$Indv_ID)
metadata(pb)$aggr_means$ncells = as.numeric(ncells2$ncell)
# Step 21. Define the senescent versus non-senescent #comparison for the current cell type.
ct.pairs <- c(de_grid[i,4], de_grid[i,5])
# Step 22. Run differential expression using edgeR via #dreamlet.
try(fit <- dreamletCompareClusters_edgeR(pb, ct.pairs, method = “fixed”,min.prop = 0.4,useProcessOneAssay_edgeR = TRUE))
# Step 23. Extract the full DE table and save the result.
fit_top <- as.data.frame(topTags(fit, n = Inf))
print(i)
#save
saveRDS(fit_top,paste0(“path/correct_edger_”, de_grid[i,6],“_”, de_grid[i,2], “_fit.RDS”))
}else{
print(paste(i, “done”))
}}
Select the optimal senescence threshold per cell type using enrichment for canonical user-specific senescence genes
Timing: 10 s
-
24.
For each cell type and threshold condition, test whether threshold-specific DEGs are enriched for the canonical senescence gene set. The input is the combined DE table with gene symbols and threshold labels; the output is a contingency table used for Fisher’s exact test to quantify senescence-gene enrichment.
-
25.
Define significant DE genes as those with FDR ≤ 0.05. This binary DEG label is used with the canonical senescence gene annotation to determine the enrichment statistics for each threshold and cell type.
# Step 24. For each cell type and threshold condition,
# load the corresponding DE results and combine them
# into a cumulative DE table.
>all2 = c()
>for (i in 1:nrow(de_grid)){
df = readRDS(paste0(“./DE/correct_edger_”,de_grid[i,6],“_”, de_grid[i,2], “_fit.RDS”))
df$celltype = de_grid[i,6]
df$test = de_grid[i,2]
df$symbol = rownames(df)
all2 = rbind(df,all2)
# Step 25. Define significant DE genes using
# an FDR threshold of 0.05.
>all2$sig = all2$FDR <= 0.05
-
26.
Format the canonical senescence gene set as a one-column gene annotation table and label these genes as canonical senescence genes (sen_gene = TRUE). The input is the curated senescence gene list (all_genes), and the output is a gene-level annotation table used for downstream enrichment testing.
-
27.
Merge the combined DE table with the canonical senescence gene annotation table to classify each tested gene as canonical (sen_gene = TRUE) or non-canonical (sen_gene = FALSE). The resulting merged table (sc_with_sen) links differential expression statistics with senescence gene annotations for downstream enrichment analysis.
# Step 26. Format the canonical senescence gene set as a
# one-column annotation table and label these genes as #canonical.
>all_genes = as.data.frame(unique(all_genes)) #all_genes from above canonical senescent genes
>colnames(all_genes) = “symbol”
>all_genes$sen_gene = TRUE
# Step 27. Merge the combined DE table with the canonical
# senescence gene annotation table to classify each gene as
# canonical (TRUE) or non-canonical (FALSE).
>sc_with_sen=merge(all2,all_genes, by = “symbol”, all.x = T)
>sc_with_sen$sen_gene[is.na(sc_with_sen$sen_gene)]= FALSE
-
28.For each cell type and threshold condition, perform Fisher’s exact test to evaluate enrichment of canonical senescence genes among significant DEGs. Select the optimal threshold as the one producing the strongest enrichment, defined by:
-
a.the highest odds ratio for significant enrichment and, when odds ratios are equivalent.
-
b.the most significant enrichment p-value. The input is the merged DE and senescence annotation table (sc_with_sen), and the output is a summary table of enrichment statistics across tested thresholds.
-
a.
# Step 28. Test enrichment of canonical senescence genes among
# significant DEGs for each cell type and threshold condition.
> threshold_enrichment_results <-do.call(rbind,lapply(split(sc_with_sen,interaction(sc_with_sen$celltype,sc_with_sen$test,drop = TRUE)),function(d) {
# Create contingency table of canonical senescence
# gene membership versus DEG significance.
tab <- table(d$sen_gene, d$sig)
# Skip comparisons lacking sufficient dimensions.
if (nrow(tab) < 2 || ncol(tab) < 2) return(NULL)
# Perform Fisher's exact test.
ft <- try(fisher.test(tab), silent = TRUE)
if (inherits(ft, “try-error”)) return(NULL)
# Store enrichment statistics for the current
# threshold and cell type.
data.frame(
celltype = unique(d$celltype),
percent = unique(d$test),
p_value = ft$p.value,
odds_ratio = unname(ft$estimate),
total_expressed = nrow(d),
degs = sum(d$sig),
kegg_true = sum(d$sen_gene & d$sig),
total_expressed_kegg = sum(d$sen_gene),
stringsAsFactors = FALSE
)}))
-
29.
Record the optimal threshold selected for each cell type. The resulting best object contains the selected threshold label, odds ratio, and enrichment p-value for each annotated cell type (Table 1).
# Step 29. Select the optimal threshold per cell type using
# the strongest enrichment of canonical senescence genes.
> threshold_enrichment_results$odds_ratio <-
as.numeric(threshold_enrichment_results$odds_ratio)
> threshold_enrichment_results$p_value <-
as.numeric(threshold_enrichment_results$p_value)
> best <- do.call(rbind, lapply(
split(threshold_enrichment_results, threshold_enrichment_results$celltype),
function(d) {
# If no thresholds are significant for this cell type,
# return a placeholder message.
if (all(d$p_value >= 0.05 | is.na(d$p_value))) {
return(data.frame(
elltype = unique(d$celltype),
percent = NA,
odds_ratio = NA,
p_value = NA,
result = “No senescence identified”
))
}
# Keep only significant thresholds.
d <- d[d$p_value < 0.05 & !is.na(d$p_value), ]
# Rank thresholds by highest odds ratio and,
# when tied, by lowest enrichment p-value.
d <- d[order(-d$odds_ratio, d$p_value), ]
# Select the top-ranked threshold.
out <- d[1, c(“celltype”, “percent”, “odds_ratio”, “p_value”), drop = FALSE]
out$result <- “Significant enrichment”
out
}
))
>rownames(best) <- NULL
-
30.Using the selected threshold for each cell type, generate the final outputs of the workflow:
-
a.continuous senescence activity scores per cell (Step 1),
-
b.binary senescence classifications per cell (Step 8),
-
c.cell type-specific differential expression results (Step 23),
-
d.estimated proportions of senescent cells per cell type based on the optimized threshold selection (Step 29).
-
a.
# Step 30. Use the selected threshold for each cell type to
# generate the final workflow outputs.
# Extract AUCell classifications corresponding to the
# selected threshold for each cell type.
> selected_auc <- do.call(rbind,lapply(seq_len(nrow(best)), function(i) {subset(final_df,celltype == best$celltype[i] & percent == best$percent[i])}))
# Load the DE results corresponding to the selected threshold
# for each cell type.
> selected_de <- do.call(rbind,lapply(seq_len(nrow(best)), function(i){readRDS(paste0(“⋅/DE/correct_edger_”,best$celltype[i],“_”,best$percent[i],“_fit.RDS”))}))
# Compute the estimated proportion of senescent cells
# for each cell type.
> estimated_prop <- aggregate(
sen ∼ celltype,
data = selected_auc,
FUN = mean
)
> colnames(estimated_prop)[2] <-
“estimated_proportion_senescent”
# Store final workflow outputs.
> final_outputs <- list(
continuous_scores = AUCX2,
selected_classification = selected_auc,
selected_de = selected_de,
estimated_proportions = estimated_prop,
best_thresholds = best
)
Table 1.
Example of the final “best” output table generated by the pipeline
| celltype | percent | odds_ratio | p_value | result |
|---|---|---|---|---|
| exc | 5percent | 21.31917 | 5.62E-11 | Significant enrichment |
| mg | 30percent | Inf | 4.30E-11 | Significant enrichment |
| nonneu | 10percent | 1 | 1 | No senescence identified |
For each cell type, the table reports the selected optimal threshold (percent) together with the corresponding enrichment statistics, including the odds ratio and enrichment p-value. The result column indicates whether significant senescence enrichment was identified for the cell type based on the evaluated thresholds. exc = Excitatory Neurons, mg = Microglia, nonneu = Non-neuronal cells.
Expected outcomes
Successful implementation of this protocol will yield cell specific senescence labels, estimated proportion of senescent cells, and cell-type-specific senescence DE signatures in scRNA-seq or snRNA-seq transcriptomic datasets. The primary output consists of (i) continuous senescence activity scores per cell, (ii) optimized threshold-based senescence labels within each annotated cell population, (iii) cell type-specific senescence DE signatures, and (iv) estimated proportion of senescence for each cell type. AUCell scoring is expected to produce a continuous distribution of senescence activity scores within each cell type. In most biological datasets, these scores will not be bimodal but instead show a right-skewed distribution reflecting a subset of cells with elevated expression of canonical senescence genes. Thresholding based on predefined percentile cutoffs (e.g., 1%, 5%, 10%, 20%, 30%) should result in a defined fraction of cells per population being labeled as senescent. The exact proportion will depend on the selected threshold and dataset characteristics. DE analysis between senescent and non-senescent cells within each cell type is to yield DE signatures potentially identifying genes that are associated with senescent state. Canonical senescence genes must be significantly enriched for significant DE genes at the optimal threshold. If no significant DEGs or enrichments are found, senescence labels cannot be confidently assigned.
Researchers should expect reproducible senescence-associated transcriptional signatures that can be used for downstream analyses such as pathway enrichment, cross-dataset validation, or integration with phenotypic data. Furthermore, they should also expect senescent labels for each cell that can be used for downstream analysis such as senescence specific network analysis or others. Finally, they will obtain for each sample and cell-type the estimated proportion of senescent cells which allows for characterizing if a change in senescent cell proportion is expected between conditions (e.g., case-control analysis or age). Recommended figures to illustrate expected outcomes include: distribution plots of senescence scores per cell type (Figure 2A), volcano plots of senescence DE results (Figure 2B), and heatmaps summarizing enrichment statistics across tested thresholds (Figure 2C).
Figure 2.
Example output figures
(A) Relationship between age and the proportion of cells classified as senescent in excitatory neurons and microglia across samples. Each point represents an individual sample, and the blue line indicates the fitted trend with shaded confidence intervals.
(B) Differential expression analysis comparing senescent and non-senescent cells within excitatory neurons and microglia. Volcano plots show log2 fold change versus -log10(P-value). Red points indicate significantly upregulated genes, blue points indicate significantly downregulated genes, and gray points indicate non-significant genes.
(C) Optimization of senescent cell proportion thresholds across cell types. Heatmap colors represent log2 odds ratios for enrichment of the input senescence gene set among differentially expressed genes at each evaluated threshold. Values within each tile indicate the odds ratio and corresponding enrichment p-value. The optimal threshold for each cell type selected is in bold.
Limitations
The method relies on a curated canonical senescence gene set. If the gene set is incomplete, not tissue-appropriate, or contains genes with strong baseline cell type-specific expression, classification performance may be reduced or biased. Since senescence is inferred from enrichment of a predefined canonical gene set, subsequent analyses may be skewed toward similar genes, introducing potential circularity and obscuring whether the observed signals reflect genuine senescence biology or are driven by the input features. Threshold selection is percentile-based and data-driven; however, extreme imbalance (e.g., very small senescent fractions) may reduce statistical power in downstream DE analyses. Similarly, datasets with low sequencing depth, high dropout rates, or poor-quality control may produce unstable AUCell score distributions, leading to unreliable thresholding and differential expression results. Strong batch effects, technical confounders, or uneven sample representation across senescent and non-senescent groups can also distort pseudobulk modeling and enrichment statistics. This protocol defines cellular senescence using transcriptional signatures derived from scRNA-seq or snRNA-seq data. As such, it identifies senescence-associated gene expression programs but does not directly measure key functional features of senescence, such as stable cell-cycle arrest, secretion of senescence-associated secretory phenotype (SASP) factors, or activation of the DNA damage response. Transcriptional profiles may also capture cells undergoing stress responses or transient cell cycle exit that overlap with senescence, particularly in heterogeneous in vivo tissues. Therefore, classifications should be interpreted as senescence-associated transcriptional states rather than definitive evidence of functional senescence.
Troubleshooting
Problem 1
Inconsistent results or errors due to software/package version mismatches (related to Step 1 and downstream steps involving AUCell, Seurat, and edgeR).
Differences in R, package versions (e.g., Seurat, AUCell, dreamlet, edgeR), or sourced custom functions can lead to errors, unexpected output formats, or discrepancies in senescence scoring, pseudobulk aggregation, or differential expression results. This is particularly relevant when reproducing the workflow across systems or over time, as several steps rely on version-sensitive data structures (e.g., Seurat object formats, AUCell outputs, and dreamlet wrappers).
Potential solution
-
•Ensure that all software and package versions match those listed in the key resources table.
-
○Use the specified versions of R, Seurat, AUCell, dreamlet, and edgeR whenever possible.
-
○Verify versions using sessionInfo() before running the protocol.
-
○
-
•Confirm compatibility of Seurat object structure and assay slots.
-
○Ensure that the Seurat object uses the expected assay naming (e.g., “RNA”) and slot structure (e.g., “counts”).
-
○If using newer Seurat versions, verify that GetAssayData() returns the correct matrix format.
-
○
-
•Validate external function dependencies.
-
○Confirm that sourced scripts (e.g., dreamletCompareClusters_edgeR.R, aggregateToPseudoBulk_wrapper.R) are up to date and compatible with installed package versions.
-
○If errors occur, check the GitHub repository for updates or version-specific changes.
-
○
-
•Re-run small test subsets before full execution.
-
○Execute the workflow on a subset of samples or a single cell type to confirm compatibility before scaling to the full dataset.
-
○
Problem 2
Selected senescence thresholds do not produce biologically meaningful or robust DE results (related to threshold definition and optimization steps).
In some datasets, predefined percentile-based thresholds (e.g., 1%, 5%, 10%, 20%, 30%) may not be sufficiently precise or fail to yield strong enrichment of canonical senescence genes or may produce unstable or biologically implausible classifications (e.g., too few or too many cells classified as senescent within a cell type). Failure can occur due to dataset-specific differences in sequencing depth, transcriptional noise, cell type composition, or baseline expression of senescence-associated genes.
Potential solution
-
•Customize the threshold grid to better reflect dataset-specific signal.
-
○Expand or refine the set of candidate percentiles (e.g., include intermediate values such as 2%, 15%, or 25%).
-
○Explore narrower ranges if senescence signal is expected to be rare or broad.
-
○
-
•Inspect AUCell score distributions within each cell type.
-
○Visualize score distributions to identify natural inflection points or tails that may guide threshold selection.
-
○Adjust thresholds to capture biologically meaningful subsets rather than relying strictly on fixed percentiles.
-
○
-
•Ensure sufficient cell representation for downstream analyses.
-
○Avoid thresholds that classify too few cells as senescent, which may reduce statistical power in pseudobulk DE analysis.
-
○Conversely, avoid overly permissive thresholds that dilute the senescence signal.
-
○
-
•Adapt thresholds to biological context and dataset characteristics.
-
○For tissues or conditions with expected high senescence burden, consider higher percentile cutoffs.
-
○For sparse or low-depth datasets, consider more permissive thresholds but validate using enrichment results.
-
○
Problem 3
Poor performance or lack of biological signal when applying the protocol to non-brain datasets using the default senescence gene set (related to Preparation One and AUCell scoring step).
The canonical senescence gene set provided in this protocol was curated and validated for human brain tissue. Applying this gene set directly to other tissues or biological systems may result in weak AUCell signal, poor enrichment of senescence-associated genes, or failure to identify meaningful senescent cell populations. This is due to tissue-specific differences in baseline gene expression and context-dependent senescence programs.
Potential solution
-
•Curate a tissue- or context-specific senescence gene set.
-
○Review published literature to identify canonical senescence-associated genes relevant to the tissue or condition of interest.
-
○Prioritize genes consistently reported across multiple studies or experimental systems.
-
○
-
•Incorporate experimentally validated markers when available.
-
○Include genes supported by functional assays (e.g., markers associated with cell-cycle arrest, SASP factors, DNA damage response).
-
○Exclude genes with strong baseline expression unrelated to senescence in the target tissue.
-
○
-
•Compare multiple candidate gene sets if uncertainty exists.
-
○Evaluate alternative gene lists derived from different studies or databases.
-
○Select the gene set that yields the most robust and biologically interpretable enrichment results.
-
○
-
•Maintain consistency across analyses.
-
○Once a gene set is selected, apply it consistently across all samples and conditions to enable valid comparisons.
-
○
Problem 4
Potential circularity in defining senescence using a canonical senescence gene set as input (related to AUCell scoring and threshold optimization steps).
Because this protocol defines senescence based on enrichment of a curated canonical senescence gene set, there is a risk that downstream analyses (e.g., DE and enrichment testing) may appear biased toward recovering the same or closely related genes. This can raise concerns about circularity and whether the identified transcriptional programs reflect true biological senescence rather than input-driven signal.
Potential solution
-
•Leverage orthogonal validation strategies.
-
○Compare identified senescence signatures to external datasets with experimentally validated senescent cells.
-
○
-
•An additional quantitative evaluation without orthogonal validation is to test whether DEGs after exclusion of canonical gene set used for training are enriched for senescent pathways.
-
○Assess whether DEGs include genes not present in the original canonical gene set but still functionally related to senescence.
-
○Prioritize results where broader senescence-associated pathways are recovered.
-
○
Problem 5
The dataset is too large to process efficiently in R due to limitations in handling very large count matrices (e.g., 32-bit indexing constraints), requiring a Python-based workflow that supports larger data representations and enables cell type-wise chunking for AUCell scoring and downstream thresholding.
For very large snRNA-seq/scRNA-seq datasets, the full count matrix may be too large to load or process efficiently in R, especially when running AUCell across all cells at once. In the published workflow, the brainSCOPE dataset was processed in Python rather than R, and senescence scores were computed separately for each cell type because the complete dataset was too large to load in memory.
Potential solution
-
•Switch to a Python-based scoring workflow for very large datasets.
-
○Use the Python implementation described in the manuscript, including aucell() from ctxscore.genesig and pyscenic.aucell.
-
○
-
•Process the dataset in smaller units rather than all at once.
-
○Split the data by cell type and compute senescence scores separately for each population.
-
○Recombine the per-cell-type results after scoring to continue with thresholding and pseudobulk aggregation.
-
○
-
•Use sparse matrices and intermediate files to reduce memory burden.
-
○Store counts in sparse format whenever possible.
-
○Save intermediate outputs after each cell type so the workflow can resume without rerunning completed steps.
-
○
-
•Restrict the analysis to the minimum required inputs.
-
○Load only the raw count matrix and the curated senescence gene list needed for scoring.
-
○Avoid bringing the full Seurat object into memory if only counts and annotations are needed for the scoring step.
-
○
Resource availability
Lead contact
Further requests for data should be directed to and will be fulfilled by the lead contact, Noam D. Beckmann (noam.beckmann@mssm.edu).
Technical contact
Technical questions on executing this protocol should be directed to and will be answered by the technical contact, Anina N. Lund (anina.lund@mssm.edu).
Materials availability
This study did not generate new unique reagents.
Data and code availability
-
•
All datasets (snRNA-seq [syn65929068 and syn65929069]) are freely available at Synapse.org (https://www.synapse.org/Synapse:syn26337520).
-
•
All code required to reproduce the analyses and generate the figures presented in this manuscript are publicly available in our laboratory’s GitHub repository (https://github.com/BeckmannLab/senescenceSTARprotocol); https://doi.org/10.5281/zenodo.20401748.
Acknowledgments
This work would not have been possible without The Living Brain Project participants. We gratefully acknowledge Jill Gregory for her expertise and assistance in designing and creating the graphical abstract. This work was supported by the following Icahn School of Medicine at Mount Sinai entities: Department of Neurosurgery, Department of Medicine Charles Bronfman Institute of Personalized Medicine, Center for Neuromodulation, and Windreich Department of Artificial Intelligence and Human Health. Funding sources included the National Institute of Aging R01AG069976 and the Michael J. Fox Foundation 18232. This research is supported by the NIH Common Fund, through the Office of Strategic Coordination/Office of the NIH Director under award 5U54AG075936-03. This work was supported in part through the computational and data resources and staff expertise provided by Scientific Computing and Data at the Icahn School of Medicine at Mount Sinai and was supported by the Clinical and Translational Science Awards (CTSA) grant UL1TR004419 from the National Center for Advancing Translational Sciences.
Author contributions
N.D.B. and A.W.C. contributed to the overall study design. N.D.B., A.W.C., and A.N.L. performed all analyses and wrote the manuscript. N.D.B., A.W.C., B.H.K., and E.E.S. contributed to establishing the Living Brain Project. N.D.B., A.W.C., and A.N.L. contributed to data processing and analyses. N.D.B., A.W.C., and A.N.L. contributed to writing specific parts of the text and preparing figures and tables for this manuscript. N.D.B., A.W.C., R.C.T., G.N.N., and E.J.N. provided critical feedback to method development and analyses results.
Declaration of interests
B.H.K. is a consultant for Medtronic, Abbott, Turning Medical.
Declaration of generative AI and AI-assisted technologies in the writing process
During the preparation of this work, the authors used chatGPT in order to proofread the manuscript and fix grammatical errors. After using this tool/service, the authors reviewed and edited the content as needed and take full responsibility for the content of the published article.
Contributor Information
Anina N. Lund, Email: anina.lund@mssm.edu.
Alexander W. Charney, Email: alexander.charney@mssm.edu.
Noam D. Beckmann, Email: noam.beckmann@mssm.edu.
References
- 1.Lund A.N., Kopell B.H., Golestani N., Vornholt E., Thompson R.C., Liharska L.E., Luo Y., Cheng E., Park Y.J., Fennessy B., et al. Establishing the relationship between brain cellular senescence and brain structure. Cell. 2026;189:511–527.e17. doi: 10.1016/j.cell.2025.10.014. [DOI] [PMC free article] [PubMed] [Google Scholar]
- 2.Hayek D., Ziegler G., Kleineidam L., Brosseron F., Nemali A., Vockert N., Ravichandran K.A., Betts M.J., Peters O., Schneider L.S., et al. Different inflammatory signatures based on CSF biomarkers relate to preserved or diminished brain structure and cognition. Mol. Psychiatr. 2024;29:992–1004. doi: 10.1038/s41380-023-02387-3. [DOI] [PMC free article] [PubMed] [Google Scholar]
- 3.Huang W., Hickson L.J., Eirin A., Kirkland J.L., Lerman L.O. Cellular senescence: the good, the bad and the unknown. Nat. Rev. Nephrol. 2022;18:611–627. doi: 10.1038/s41581-022-00601-z. [DOI] [PMC free article] [PubMed] [Google Scholar]
- 4.Gorgoulis V., Adams P.D., Alimonti A., Bennett D.C., Bischof O., Bishop C., Campisi J., Collado M., Evangelou K., Ferbeyre G., et al. Cellular Senescence: Defining a Path Forward. Cell. 2019;179:813–827. doi: 10.1016/j.cell.2019.10.005. [DOI] [PubMed] [Google Scholar]
- 5.Storer M., Mas A., Robert-Moreno A., Pecoraro M., Ortells M.C., Di Giacomo V., Yosef R., Pilpel N., Krizhanovsky V., Sharpe J., Keyes W.M. Senescence is a developmental mechanism that contributes to embryonic growth and patterning. Cell. 2013;155:1119–1130. doi: 10.1016/j.cell.2013.10.041. [DOI] [PubMed] [Google Scholar]
- 6.Vornholt E., Liharska L.E., Cheng E., Hashemi A., Park Y.J., Ziafat K., Wilkins L., Silk H., Linares L.M., Thompson R.C., et al. Characterizing cell type specific transcriptomic differences between the living and postmortem human brain. medRxiv. 2024 Preprint at. [Google Scholar]
- 7.Hoffman G.E., Lee D., Bendl J., Prashant N.M., Hong A., Casey C., Alvia M., Shao Z., Argyriou S., Therrien K., et al. Efficient differential expression analysis of large-scale single cell transcriptomics data using dreamlet. bioRxiv. 2021 Preprint at. [Google Scholar]
- 8.Robinson M.D., McCarthy D.J., Smyth G.K. edgeR: a Bioconductor package for differential expression analysis of digital expression data. Bioinformatics. 2010;26:139–140. doi: 10.1093/bioinformatics/btp616. [DOI] [PMC free article] [PubMed] [Google Scholar]
- 9.Aibar S., González-Blas C.B., Moerman T., Huynh-Thu V.A., Imrichova H., Hulselmans G., Rambow F., Marine J.C., Geurts P., Aerts J., et al. SCENIC: single-cell regulatory network inference and clustering. Nat Methods. 2017;14:1083–1086. doi: 10.1038/nmeth.4463. [DOI] [PMC free article] [PubMed] [Google Scholar]
- 10.Hao Y., Hao S., Andersen-Nissen E., Mauck W.M., Zheng S., Butler A., Lee M.J., Wilk A.J., Darby C., Zager M., et al. Integrated analysis of multimodal single-cell data. Cell. 2021;184:3573–3587.e29. doi: 10.1016/j.cell.2021.04.048. [DOI] [PMC free article] [PubMed] [Google Scholar]
- 11.Langaas M., Lindqvist B., Ferkingstad E. Estimating the Proportion of True Null Hypotheses, with application to DNA Microarray Data. J. R. Stat. Soc. B: Stat. Methodol. 2005;67:555–572. [Google Scholar]
- 12.Wickham, H., François, R., Henry, L., Müller, K., and Vaughan, D. (2023). Dplyr: A Grammar of Data Manipulation.
- 13.Wickham, H., Vaughan, D., and Girlich, M. (2026). Tidyr: Tidy Messy Data.
- 14.Wickham, H. (2025). Stringr: Simple, Consistent Wrappers for Common String Operations.
- 15.Weston, S. (2025). Foreach: Provides Foreach Looping Construct.
- 16.Weston, S. (2025). doParallel: Foreach Parallel Adaptor for the 'parallel' Package.
- 17.Wang, J., Morgan, M., Obenchain, V., Lang, M., Thompson, R., and Turaga, N. (2025). BiocParallel: Bioconductor Facilities for Parallel Evaluation.
- 18.Morgan, M., Falcon, S., and Gentleman, R. (2025). GSEABase: Gene Set Enrichment Data Structures and Methods.
Associated Data
This section collects any data citations, data availability statements, or supplementary materials included in this article.
Data Availability Statement
-
•
All datasets (snRNA-seq [syn65929068 and syn65929069]) are freely available at Synapse.org (https://www.synapse.org/Synapse:syn26337520).
-
•
All code required to reproduce the analyses and generate the figures presented in this manuscript are publicly available in our laboratory’s GitHub repository (https://github.com/BeckmannLab/senescenceSTARprotocol); https://doi.org/10.5281/zenodo.20401748.


Timing: 24 s