
Pseudobulk GSEA with limma contrasts and per-patient ssGSEA
RunGSEA_pseudobulk.RdThe statistically rigorous alternative to RunGSEA(). Instead of treating
individual cells as replicates, this function aggregates counts to the
donor/sample level (pseudobulk), fits a limma-voom linear model, and ranks
genes by the moderated t-statistic before running fgsea. This respects the
true unit of replication and avoids the inflated degrees of freedom that
arise from cell-level comparisons.
Optionally also computes per-patient pathway activity scores (ssGSEA via GSVA) and tests them across groups, producing boxplots and heatmaps alongside the GSEA results.
Usage
RunGSEA_pseudobulk(
seurat_object = NULL,
group.by = NULL,
sample.by = NULL,
contrast.by = NULL,
split.by = NULL,
subset.by = NULL,
run_label = NULL,
label.by = NULL,
pseudobulk = NULL,
covariates = NULL,
contrasts = NULL,
gene_sets = NULL,
deg_df = NULL,
deg_gene_column = "feature",
deg_group_column = "group",
deg_fc_column = "logFC",
deg_padj_column = "padj",
deg_padj_cutoff = 0.05,
deg_top_n = 20L,
identity_column = NULL,
sample_column = NULL,
group_column = NULL,
pathway_sets = list(Hallmark = list(category = "H"), KEGG = list(category = "C2",
subcategory = "CP:KEGG"), Reactome = list(category = "C2", subcategory =
"CP:REACTOME"), WP = list(category = "C2", subcategory = "CP:WIKIPATHWAYS")),
search_terms = NULL,
species = "human",
output_dir,
assay = NULL,
min.cells = 10L,
min.samples.per.group = 3L,
run.ssgsea = TRUE,
ssgsea.use.padj = TRUE,
gsea.use.padj = TRUE,
top_n_heatmap = 30L,
top_n_ssgsea = 30L,
nes.cutoff = 1,
group_colors = NULL,
heatmap_params = list(row_names_side = "left", show_row_dend = FALSE,
row_names_max_width = grid::unit(15, "cm")),
heatmap_colors = NULL,
width = NULL,
height = NULL,
resume = FALSE,
resume_folder = NULL,
timestamp = TRUE,
save.rds = TRUE,
redo_plots = FALSE,
caffeinate = FALSE
)Arguments
- seurat_object
A Seurat object. Either
seurat_objector a precomputedpseudobulklist must be supplied.- group.by
Character or
NULL. Metadata column holding cell-type labels. One independent GSEA is run per level (e.g."Assignment"). PassNULLwhen the object already contains only one cell type (e.g. a Microglia-only object); all cells are treated as a single group called"All"and a single GSEA is run.- sample.by
Character. Metadata column identifying the biological replicate (donor/patient ID). This is the statistical unit; each unique value becomes one pseudobulk sample in the limma-voom model (e.g.
"DonorID").- contrast.by
Character. Metadata column holding the comparison group label (e.g.
"Disease"with levels"AD"and"Control"). Cells withNAin this column are excluded before aggregation.- split.by
Character or
NULL. Optional metadata column used to create interaction groups before fitting the model. When supplied, the contrast groups become<contrast.by>.<split.by>combinations (e.g.split.by = "Sex"turns"AD"and"Control"into"AD.Female","AD.Male","Control.Female","Control.Male"), allowing sex-stratified or condition-stratified contrasts. DefaultNULL.- subset.by
Character or
NULL. Optional metadata column used to run the full pipeline separately for each level. Each level gets its own subdirectory underoutput_dir. Use this to produce independent analyses per broad cell class, brain region, or any other grouping variable without calling the function multiple times. DefaultNULL.- run_label
Character or
NULL. Short label appended to output filenames, plot titles, AND (whentimestamp = TRUE) the run's subfolder name, to distinguish this run from others saved to the sameoutput_dir(e.g.run_label = "with_age_covariate"). Set automatically whensubset.byis used; otherwise leaveNULLunless you need a custom tag.- pseudobulk
Optional precomputed pseudobulk. A named list with elements
$counts(genes x samples matrix) and$samples(sample-level metadata data frame). Supply the value returned by a previousRunGSEA_pseudobulk()call to skip the aggregation step on reruns. DefaultNULL(aggregation is performed fromseurat_object).- covariates
Character vector of sample-level metadata columns to include as covariates in the limma-voom model (e.g.
c("Age", "PMI")). Numeric covariates are median-imputed forNA; factor/character covariates receive an"unknown"level. DefaultNULL.- contrasts
Named list of limma contrast expressions. Every element must be a single string built from model coefficient names of the form
make.names(paste0("group", level)). A small helper makes this concise:cf <- function(l) make.names(paste0("group", l)) contrasts <- list( AD_vs_Ctrl = paste(cf("AD"), "-", cf("Control")), Interaction = paste0("(", cf("AD.F"), "-", cf("Ctrl.F"), ")", " - ", "(", cf("AD.M"), "-", cf("Ctrl.M"), ")") )Positive NES = enrichment in the first term. When
NULLa standard set is generated automatically from the levels ofcontrast.by. Whenredo_plots = TRUE, a user-supplied list takes priority over the list saved in the RDS. DefaultNULL.- gene_sets
Named list of character vectors. Each element is one gene set (e.g.
list(MySet = c("GENE1", "GENE2", ...))). When supplied,pathway_setsanddeg_dfare ignored andmsigdbris not required. DefaultNULL.- deg_df
Data frame of differential expression results. Top DEGs per group are extracted and used as gene sets. When supplied,
pathway_setsis ignored andmsigdbris not required. Use thedeg_*parameters below to map your data frame's column names. DefaultNULL.- deg_gene_column
Character. Column in
deg_dfcontaining gene names. Default"feature".- deg_group_column
Character (length 1 or 2). Column(s) in
deg_dfdefining the group each row belongs to. Two columns create a two-level label used for heatmap row splitting. Default"group".- deg_fc_column
Character. Column in
deg_dfcontaining the fold change used to rank genes within each group. Default"logFC".- deg_padj_column
Character or
NULL. Column indeg_dfcontaining adjusted p-values for pre-filtering. Set toNULLto skip filtering. Default"padj".- deg_padj_cutoff
Numeric. Adjusted p-value threshold applied to
deg_dfbefore selecting top genes. Default0.05.- deg_top_n
Integer. Number of top genes per group (ranked by
deg_fc_column, descending) to include in each gene set. Default20.- pathway_sets
Named list of MSigDB collections to test. Each element must have a
categoryfield and an optionalsubcategoryfield (seemsigdbr::msigdbr_collections()). The element name becomes the label used in output filenames. Default: Hallmark, KEGG, Reactome, WikiPathways. Ignored whengene_setsordeg_dfis supplied.- search_terms
Optional keyword filter for gene-set names (case- insensitive, regex), applied after fetching each database in
pathway_sets(or to the names of a customgene_setslist). Two forms: a character vector is an OR search where any set matching at least one term is kept (e.g.c("APOPTOSIS", "CELL_DEATH")); a list of character vectors is a union of AND-groups where all terms in an element must appear in the same name (e.g.list(c("T_CELL", "ACTIVATION"), "APOPTOSIS")). Ignored whendeg_dfis supplied.NULL(default) tests all gene sets.- species
Character. Species name passed to
msigdbr::msigdbr(). Use"human"or"mouse"(shorthand accepted) or any full name returned bymsigdbr::msigdbr_species(). Default"human".- output_dir
Character. Root directory for all CSV, PDF, and RDS output. Per-contrast and per-database subdirectories are created automatically.
- assay
Character or
NULL. Seurat assay containing raw counts to aggregate.NULLuses the default assay. For BPCells sketch workflows, always pass"RNA"explicitly since pseudobulk aggregation requires raw counts from all cells. DefaultNULL.- min.cells
Integer. Minimum number of cells a sample must contribute to a given cell type to be included in the pseudobulk for that cell type. Samples below this threshold are excluded to avoid noisy pseudo-replicates. Default
10.- min.samples.per.group
Integer. Minimum number of samples required per group level within a cell type. Cell types where any group has fewer than this many samples are skipped entirely. Default
3.- run.ssgsea
Logical. Also compute per-patient pathway activity via single-sample GSEA (ssGSEA, using
GSVA::gsva())? Produces score heatmaps and contrast boxplots per cell type per database. Requires theGSVABioconductor package. DefaultTRUE.- ssgsea.use.padj
Logical. When
TRUE(default), pathway significance in ssGSEA is judged by BH-adjusted p-value (padj < 0.05). Set toFALSEto use the raw p-value instead – useful when many contrasts make BH correction too aggressive and real signals drop out. Affects pathway selection for heatmaps, boxplot panel selection, and significance bracket thresholds. DefaultTRUE.- gsea.use.padj
Logical. Same choice as
ssgsea.use.padj, but for the main fgsea NES heatmaps: which column (padjvs. rawpval) drives the significance stars (*/**/***) drawn in each pathway x cell-type (or pathway x contrast, for the summary heatmap) cell. DefaultTRUE.- top_n_heatmap
Integer. Number of pathways shown in the NES heatmap per contrast/database combination, selected by maximum |NES| across cell types. Default
30.- top_n_ssgsea
Integer. Number of pathways shown in the ssGSEA score heatmap per cell type, selected by significance or variance. Default
30.- nes.cutoff
Numeric. Minimum absolute NES required for a pathway to appear in the summary NES heatmap (pathways x contrasts). The candidate row set is first built as the union of the top
top_n_heatmappathways from each individual per-contrast heatmap;nes.cutoffthen removes rows whose maximum |NES| across all contrasts falls below this threshold. Default1.0. Set to0to skip the cutoff and show all union rows.- group_colors
Named character vector mapping
contrast.bylevels to colors. Used consistently across all boxplots and heatmap annotations.NULLauto-generates colors from the Nour palette. DefaultNULL.- heatmap_params
Named list of additional arguments forwarded to
ComplexHeatmap::Heatmap(). Any default set internally can be overridden here (e.g.list(clustering_distance_rows = "pearson")).- heatmap_colors
A
circlize::colorRamp2color function for all NES and ssGSEA score heatmaps.NULL(default) uses a 5-stop blue-white-red diverging palette centered at 0. Breaks are fixed atc(-2, -1, 0, 1, 2)for ssGSEA row-z-score heatmaps and auto-scaled to the data for NES heatmaps. Supply anycolorRamp2object to override all heatmaps at once. Examples:Classic RdBu:
circlize::colorRamp2(c(-3, 0, 3), c("#2166ac", "white", "#b2182b"))Purple-green (PRGn):
circlize::colorRamp2(c(-3, 0, 3), RColorBrewer::brewer.pal(3, "PRGn"))Viridis plasma (for one-sided scores):
circlize::colorRamp2(seq(0, 3, length.out = 9), viridis::plasma(9))Tighter scale for subtle signals:
circlize::colorRamp2(c(-1.5, -0.75, 0, 0.75, 1.5), c("#007dd1", "#b3d9f5", "white", "#f5c08a", "#ab3000"))
- width, height
Numeric or
NULL. Override the auto-calculated PDF dimensions (in inches) applied to every NES and ssGSEA heatmap this run saves.NULL(default) auto-sizes each heatmap from its own row/column count and label lengths.- resume
Logical or
"last". Controls both WHICH subfolder this call uses and whether cached computation is reused:FALSE(default)Always start a fresh, new timestamped subfolder (see
timestamp).TRUEScan
output_dirfor previousRunGSEA_pseudobulk_*subfolders whose call-time parameters (group.by,sample.by,contrast.by,covariates, gene sets, etc. - NOT purely cosmetic ones likeheatmap_colors) match this call exactly. Zero matches falls back to a fresh subfolder; one match resumes it (skipping recomputation the same waypseudobulkreuse already does); several matches prompt interactively (utils::menu()) when the session is interactive, orstop()with instructions otherwise."last"Same scan, but silently resumes the most recent match instead of asking.
Within a resumed subfolder, cell types whose GSEA CSV files already exist are additionally skipped (as before) so a crashed run can continue.
- resume_folder
Character or
NULL. Explicitly resume this exact subfolder (a bare folder name underoutput_dir, or a full path), skipping the scan/match described underresume. Useful in scripts where you already know which previous run to continue.- timestamp
Logical. When
TRUE(default), every fresh call gets its own subfolder namedRunGSEA_pseudobulk[_run_label]_<YYYYMMDD-HHMMSS>underoutput_dir, so re-running with different settings (e.g. with vs. withoutcovariates) into the sameoutput_dirnever overwrites a previous run's files or methods JSON. SetFALSEto useoutput_dirdirectly (legacy behavior) - in that mode you are responsible for giving different configurations differentoutput_dirvalues yourself.- save.rds
Logical. Save the pseudobulk counts, sample metadata, GSEA results, and ssGSEA results as
RunGSEA_pseudobulk_results.rdsinoutput_dir. Required forredo_plots = TRUE. DefaultTRUE.- redo_plots
Logical. Reload a previously saved RDS and regenerate all plots without rerunning the full pipeline. Default
FALSE. WhenTRUE:RunGSEA_pseudobulk_results.rdsis loaded fromoutput_dir.NES heatmaps and lollipop plots are regenerated from the saved GSEA data frame.
ssGSEA score CSVs on disk are reread and used to regenerate score heatmaps and boxplots.
seurat_object,group.by,sample.by, andcontrast.byare not needed and can be omitted.
Useful after changing
group_colors,heatmap_params, or any plot parameter without rerunning the hours-long pipeline.- caffeinate
Logical. Prevent the system from sleeping during the run (macOS only, uses the built-in
caffeinateutility). Recommended for overnight runs. DefaultFALSE.
Value
Invisibly returns a named list:
pseudobulkGenes x samples aggregated count matrix.
samplesSample-level metadata data frame used in the model.
gseaLong-format data frame of all fgsea results with columns
pathway,NES,padj,cell_type,contrast, anddb.ssgseassGSEA contrast results (one row per pathway/cell type/ contrast), or
NULLifrun.ssgsea = FALSE.contrastsThe named contrast list actually used in the model (useful when contrasts were auto-generated).
Three required columns
Three metadata columns drive the analysis:
group.byCell type column. GSEA is run independently for each level (e.g.
"Assignment"). Cells are aggregated within each level.sample.byDonor/sample ID column. This is the statistical unit. Each unique value becomes one pseudobulk replicate in the limma model (e.g.
"DonorID","SampleID").contrast.byComparison group column. Levels define the groups being contrasted (e.g.
"Disease"with levels"AD"and"Control"). Cells withNAin this column are dropped before aggregation.
Gene set input modes
Supply gene sets using exactly one of the following (priority order):
gene_sets- a named list of character vectors, one vector per gene set.deg_df- a differential expression data frame; top DEGs per group are extracted and used as gene sets (seedeg_*parameters).pathway_sets(default) - one or more MSigDB collections fetched viamsigdbr. Does not requiregene_setsordeg_df.
See also
RunGSEA() for cell-level GSEA, RunSCssGSEA() for single-cell
ssGSEA scoring, PlotGSEAEnrichment() to visualize enrichment plots from
the output directory.