| name | pseudobulkdeg |
| description | Performs pseudo-bulk differential gene expression analysis using DESeq2 or edgeR. Aggregates single-cell counts to sample-level pseudo-bulk data, then identifies differentially expressed genes between conditions while accounting for biological replicates. Supports complex experimental designs including batch effects, paired samples, and interaction terms. |
PseudoBulkDEG Process Configuration
Purpose
Performs pseudo-bulk differential gene expression analysis using DESeq2 or edgeR. Aggregates single-cell counts to sample-level pseudo-bulk data, then identifies differentially expressed genes between conditions while accounting for biological replicates. Supports complex experimental designs including batch effects, paired samples, and interaction terms. Automatically runs pathway enrichment analysis on significant markers.
When to Use
- Robust differential expression: When you need statistical rigor with biological replicates
- Sample-level variation: To account for sample-to-sample variability (not just cell-level)
- Multiple comparisons: Compare conditions across multiple samples, time points, or treatments
- Batch correction: Model and adjust for batch effects in differential expression
- Paired samples: When samples have natural pairing (e.g., same subject across time points)
- Per-cell-type analysis: Perform DEG separately for each cell type
- Publication-quality results: DESeq2 is the gold standard for bulk RNA-seq analysis
Contrast with ClusterMarkers:
ClusterMarkers: Cell-level comparison (cluster vs cluster), no biological replicates
PseudoBulkDEG: Sample-level comparison (condition vs condition), requires biological replicates
Configuration Structure
Process Enablement
[PseudoBulkDEG]
cache = true
[PseudoBulkDEG.in]
srtobj = ["SeuratClustering"]
Environment Variables - Core Parameters
[PseudoBulkDEG.envs]
ncores = 1
mutaters = {}
each = ""
cache = "/tmp"
subset = ""
aggregate_by = ""
layer = "counts"
assay = ""
error = false
group_by = ""
ident_1 = ""
ident_2 = ""
paired_by = ""
tool = "DESeq2"
dbs = ["KEGG_2021_Human", "MSigDB_Hallmark_2020"]
sigmarkers = "p_val_adj < 0.05"
enrich_style = "enrichr"
DESeq2 Design Formulas
Formula Syntax
DESeq2 uses R's formula syntax with the tilde (~) operator:
~ [main effects] [+ interactions]
Key concepts:
+: Add main effects (terms modeled independently)
*: Full factorial (main effects + interaction)
:: Interaction term only
- Term order matters (last term is the primary comparison)
Formula Patterns
1. Simple Two-Group Comparison
[PseudoBulkDEG.envs]
group_by = "Condition"
ident_1 = "Control"
ident_2 = "Treatment"
Internal design formula: ~ Condition
Interpretation: Direct comparison between Control and Treatment
2. Batch Correction
[PseudoBulkDEG.envs]
group_by = "Treatment"
ident_1 = "Vehicle"
ident_2 = "Drug"
Internal design formula: ~ batch + Treatment
Interpretation: Treatment effect adjusted for batch differences
3. Paired Samples
[PseudoBulkDEG.envs]
group_by = "Timepoint"
ident_1 = "Day0"
ident_2 = "Day7"
paired_by = "SubjectID"
Internal design formula: ~ SubjectID + Timepoint
Interpretation: Timepoint effect adjusted for subject-specific baseline
4. Interaction Design (Treatment × Cell Type)
[PseudoBulkDEG.envs]
group_by = "Treatment_CellType"
ident_1 = "Control_Tcell"
ident_2 = "Treatment_Tcell"
Using mutaters to create interaction:
[PseudoBulkDEG.envs]
mutaters = {
Treatment_CellType = "paste(Treatment, CellType, sep = '_')"
}
group_by = "Treatment_CellType"
5. Multi-Factor Design (Batch + Condition)
[PseudoBulkDEG.envs]
group_by = "Condition"
ident_1 = "WildType"
ident_2 = "Mutant"
Internal design: ~ Batch + Gender + Condition
Interpretation: Condition effect adjusted for batch and gender
Contrast Specification
DESeq2 compares specific group combinations via ident_1 and ident_2:
ident_1 = "Control"
ident_2 = "Treatment"
ident_1 = "Control"
ident_2 = ""
Configuration Examples
Example 1: Minimal Configuration
Scenario: Simple treatment vs control with biological replicates
[PseudoBulkDEG]
cache = true
[PseudoBulkDEG.in]
srtobj = ["SeuratClustering"]
[PseudoBulkDEG.envs]
group_by = "Treatment"
ident_1 = "Vehicle"
ident_2 = "Drug"
Example 2: Disease vs Healthy with Batch Correction
Scenario: Compare disease samples to healthy controls from multiple batches
[PseudoBulkDEG]
cache = true
[PseudoBulkDEG.in]
srtobj = ["SeuratClustering"]
[PseudoBulkDEG.envs]
group_by = "Diagnosis"
ident_1 = "Healthy"
ident_2 = "Disease"
tool = "DESeq2"
sigmarkers = "p_val_adj < 0.01 & abs(log2FC) > 1"
Example 3: Per Cell Type Analysis
Scenario: Find DEGs separately for each major cell type
[PseudoBulkDEG]
cache = true
[PseudoBulkDEG.in]
srtobj = ["SeuratClustering"]
[PseudoBulkDEG.envs]
each = "CellType"
group_by = "Condition"
ident_1 = "Control"
ident_2 = "Disease"
tool = "DESeq2"
sigmarkers = "p_val_adj < 0.05"
enrich_style = "clusterProfiler"
dbs = ["KEGG_2021_Human", "MSigDB_Hallmark_2020"]
Example 4: Time Course with Paired Samples
Scenario: Same subjects measured at multiple time points
[PseudoBulkDEG]
cache = true
[PseudoBulkDEG.in]
srtobj = ["SeuratClustering"]
[PseudoBulkDEG.envs]
group_by = "Timepoint"
ident_1 = "Baseline"
ident_2 = "PostTreatment"
paired_by = "SubjectID"
sigmarkers = "p_val_adj < 0.1"
dbs = ["MSigDB_Hallmark_2020", "Reactome_2024_Human"]
Example 5: Multiple Cases with Custom Grouping
Scenario: Compare multiple disease subtypes to healthy controls
[PseudoBulkDEG]
cache = true
[PseudoBulkDEG.in]
srtobj = ["SeuratClustering"]
[PseudoBulkDEG.envs]
[PseudoBulkDEG.envs.cases."Disease vs Healthy"]
group_by = "Diagnosis"
ident_1 = "Healthy"
ident_2 = "Disease"
[PseudoBulkDEG.envs.cases."Subtype A vs Healthy"]
group_by = "Diagnosis"
ident_1 = "Healthy"
ident_2 = "SubtypeA"
[PseudoBulkDEG.envs.cases."Subtype B vs Healthy"]
group_by = "Diagnosis"
ident_1 = "Healthy"
ident_2 = "SubtypeB"
[PseudoBulkDEG.envs.cases."Subtype A vs B"]
group_by = "Diagnosis"
ident_1 = "SubtypeA"
ident_2 = "SubtypeB"
[PseudoBulkDEG.envs.cases."Subtype A vs Healthy"]
sigmarkers = "p_val_adj < 0.01"
[PseudoBulkDEG.envs.cases."Subtype B vs Healthy"]
sigmarkers = "p_val_adj < 0.05"
Common Patterns
Pattern 1: Disease vs Healthy with Age Adjustment
[PseudoBulkDEG.envs]
mutaters = {
AgeGroup = "ifelse(Age > 65, 'Elderly', 'Adult')"
}
group_by = "Diagnosis"
ident_1 = "Healthy"
ident_2 = "Disease"
Pattern 2: Time Series Analysis
[PseudoBulkDEG.envs]
[PseudoBulkDEG.envs.cases."Day0 vs Day3"]
group_by = "Timepoint"
ident_1 = "Day0"
ident_2 = "Day3"
[PseudoBulkDEG.envs.cases."Day3 vs Day7"]
group_by = "Timepoint"
ident_1 = "Day3"
ident_2 = "Day7"
[PseudoBulkDEG.envs.cases."Day0 vs Day7"]
group_by = "Timepoint"
ident_1 = "Day0"
ident_2 = "Day7"
Pattern 3: Multi-Factor Design (Genotype × Treatment)
[PseudoBulkDEG.envs]
mutaters = {
Combined = "paste(Genotype, Treatment, sep = '_')"
}
[PseudoBulkDEG.envs.cases."WT_Vehicle vs WT_Drug"]
group_by = "Combined"
ident_1 = "WT_Vehicle"
ident_2 = "WT_Drug"
[PseudoBulkDEG.envs.cases."KO_Vehicle vs KO_Drug"]
group_by = "Combined"
ident_1 = "KO_Vehicle"
ident_2 = "KO_Drug"
[PseudoBulkDEG.envs.cases."WT_Drug vs KO_Drug"]
group_by = "Combined"
ident_1 = "WT_Drug"
ident_2 = "KO_Drug"
Pattern 4: Subset to Specific Cell Types
[PseudoBulkDEG.envs]
subset = "CellType %in% c('T_cell', 'B_cell')"
each = "CellType"
group_by = "Condition"
ident_1 = "Control"
ident_2 = "Disease"
Plotting Configuration
Marker Plots (Volcano, Dot, Heatmap)
[PseudoBulkDEG.envs.marker_plots."Volcano Plot"]
plot_type = "volcano"
[PseudoBulkDEG.envs.marker_plots."Dot Plot of Top Genes"]
plot_type = "dot"
genes = 20
[PseudoBulkDEG.envs.marker_plots."Heatmap of Top Markers"]
plot_type = "heatmap"
order_by = "desc(abs(log2FC))"
devpars = {width = 800, height = 1000}
[PseudoBulkDEG.envs.marker_plots."Volcano with Percent Difference"]
plot_type = "volcano_pct"
Enrichment Plots (Bar, Dot, Network, Word Cloud)
[PseudoBulkDEG.envs.enrich_plots."Bar Plot"]
plot_type = "bar"
ncol = 1
top_term = 10
[PseudoBulkDEG.envs.enrich_plots."Dot Plot"]
plot_type = "dot"
top_term = 15
[PseudoBulkDEG.envs.enrich_plots."Network"]
plot_type = "network"
top_term = 20
[PseudoBulkDEG.envs.enrich_plots."Word Cloud"]
plot_type = "wordcloud"
[PseudoBulkDEG.envs.enrich_plots."Enrichmap"]
plot_type = "enrichmap"
Overlap Analysis (Venn/UpSet Plots)
[PseudoBulkDEG.envs.overlaps."Overlapping DEGs"]
plot_type = "venn"
sigmarkers = "p_val_adj < 0.01"
[PseudoBulkDEG.envs.overlaps."UpSet of DEGs"]
plot_type = "upset"
sigmarkers = "p_val_adj < 0.01 & abs(log2FC) > 1"
Dependencies
Upstream Processes
- Required:
CombinedInput (requires SeuratClustering or similar)
- SeuratClustering: Provides clustered Seurat object with metadata
Downstream Uses
- Pathway analysis: Results feed into ScFGSEA (additional enrichment)
- Visualization: Volcano plots, heatmaps, enrichment plots
- Publication: Tables of significant genes, pathways
Validation Rules
Design Formula Validation
- Replicates required: Minimum 2-3 biological replicates per condition
- Degrees of freedom: Must have more samples than coefficients in model
- Factor levels: Cannot have single-factor level for all samples
Metadata Requirements
group_by column: Must exist in Seurat metadata
ident_1/ident_2 values: Must be levels in group_by column
aggregate_by column: Must uniquely identify samples
paired_by column: If specified, each subject must have measurements for all conditions
Sample Size Guidelines
- Simple comparison: ≥ 3 replicates per condition (minimum)
- Batch correction: ≥ 3 replicates per batch per condition
- Paired design: ≥ 5 subject pairs recommended
- Multi-factor: Increase replicates to maintain power
Troubleshooting
Issue: "Coefficients equal to number of samples"
Cause: Model matrix has same number of coefficients as samples
Solution: Add more samples or simplify design formula
Issue: No significant markers found
Cause: Low effect size, high variance, or insufficient replicates
Solutions:
sigmarkers = "p_val_adj < 0.1"
sigmarkers = "p_val_adj < 0.05 & abs(log2FC) > 0.5"
tool = "edgeR"
Issue: Batch effects not corrected
Cause: Batch column not in metadata or improperly named
Solution: Ensure Batch column exists in Seurat metadata
Issue: Paired design not working
Cause: Subjects don't have all time points or unbalanced design
Solution: Ensure each subject has data for all conditions
Issue: Interaction effects not interpretable
Cause: Missing main effects or wrong formula syntax
Solution: Use full factorial syntax or create combined groups
mutaters = {
Combined = "paste(Treatment, CellType, sep = '_')"
}
group_by = "Combined"
Best Practices
1. Always Check Metadata
Before running PseudoBulkDEG, verify metadata columns:
colnames(seurat_obj@meta.data)
table(seurat_obj$Condition)
table(seurat_obj$Batch)
2. Start Simple
Begin with simple comparison, then add complexity:
[PseudoBulkDEG.envs]
group_by = "Condition"
ident_1 = "Control"
ident_2 = "Treatment"
mutaters = {Combined = "paste(Var1, Var2, sep = '_')"}
3. Use Appropriate Thresholds
Choose thresholds based on biological question:
- Stringent:
p_val_adj < 0.01 & abs(log2FC) > 1 (high-confidence genes)
- Standard:
p_val_adj < 0.05 & abs(log2FC) > 0.5 (balanced)
- Exploratory:
p_val_adj < 0.1 (sensitive, more false positives)
4. Validate Results
Check DEG results for sanity:
[PseudoBulkDEG.envs.marker_plots."Top 20 Genes"]
plot_type = "dot"
genes = 20
[PseudoBulkDEG.envs.enrich_plots."Pathway Bar"]
plot_type = "bar"
top_term = 15
5. Cache Strategy
Use caching for efficient iteration:
[PseudoBulkDEG]
cache = true
[PseudoBulkDEG.envs]
cache = "/tmp"
External References
DESeq2 Documentation
edgeR Documentation
Enrichment Databases
Plotthis/scplotter Documentation
Quick Reference: Tool Comparison
| Feature | DESeq2 | edgeR |
|---|
| Default tool | ✅ | ❌ |
| Statistical model | Negative binomial GLM | Negative binomial GLM |
| Shrinkage estimators | apeglm, ashr, lfcShrink | cpm, aveLogCPM |
| Recommended for | Publication-quality, robust results | Small sample sizes, exploratory |
| Batch correction | Built-in via design formula | Built-in via design formula |
| Paired designs | Supported | Supported |
| Computation speed | Moderate | Fast |
| Output columns | baseMean, log2FC, lfcSE, stat, p_val, p_val_adj | logCPM, log2FC, LR, p_val, p_val_adj |
Workflow Integration
Typical Pipeline Position
SampleInfo → SeuratPreparing → SeuratClustering → PseudoBulkDEG → [Visualization/Downstream]
Input Requirements
- Seurat object with:
- Count data (
"counts" layer)
- Metadata columns for grouping (
group_by, aggregate_by)
- Clustering results (optional, for subset analysis)
Output Structure
outdir/
├── pseudo_bulk_counts/ # Aggregated counts per sample
├── deseq2_results/ # DESeq2/edgeR results
│ ├── case1/
│ │ ├── markers.tsv # Full DEG table
│ │ ├── significant.tsv # Filtered by sigmarkers
│ │ ├── volcano/ # Volcano plots
│ │ ├── marker_plots/ # Gene expression plots
│ │ └── enrichment/ # Pathway enrichment results
│ └── case2/
└── overlaps/ # Overlap analysis (if applicable)
Documentation: /docs/processes/PseudoBulkDEG.md
Biopipen Reference: biopipen.ns.scrna.PseudoBulkDEG