Summary
Proteomics term enrichment is ORA-only today — enrichmentAnalysis calls g:Profiler's
g:GOSt (src/analysis/enrichment.ts) on the thresholded significant-gene hit list, split by
direction. ORA is set-based and threshold-sensitive: a protein counts fully in or fully out once
it crosses the significance cut, so a handful of borderline proteins can move a whole term. Propose
adding preranked GSEA as a complementary, threshold-free enrichment method.
Why (evidence from a real client analysis)
On the Cytokinetics DMD/WT data we compared enrichment built from two DE paths (Spectronaut
"candidates" vs our recomputed "report" path). The two hit lists agree ~90% (κ=0.90), and pathway
conclusions agree — except for four DMD-relevant terms (interstitial matrix, elastic fibres,
growth factor activity; regulation of calcium ion transport) that were significant in one path and
not the other. The flip was driven entirely by ~a handful of boundary proteins crossing the DE cut,
amplified by ORA's all-or-nothing counting.
A one-off preranked GSEA (gseapy, ranking = log2FC) on the same data, run on both paths:
- NES concordance between the two paths: Spearman 0.965 across 4,131 gene sets (vs ORA, where
the four terms flipped). The path-dependence essentially disappears.
- The four flip terms become path-robust: e.g. elastic fibres NES −1.44 vs −1.45, growth factor
activity −1.52 vs −1.53, calcium transport +1.49 vs +1.59 — near-identical either way, directions
consistent with ORA.
- Bonus finding: under GSEA those terms are sub-significant (FDR 0.14–0.42), i.e. ORA's
significance for them in the report path was partly a threshold artifact. GSEA is both
path-robust and less prone to overstating marginal terms.
Repro: ~/Downloads/ck/analysis_scripts/gsea_concordance.py (analysis deliverables, not in-repo).
Proposal
Add preranked GSEA alongside ORA (complement, not replace):
- Engine: server-side script —
fgsea (R, matches the existing limma/DEqMS/VSN R-script +
client-fallback pattern) or gseapy/blitzgsea (Python). g:Profiler's API can't do preranked GSEA.
- Input: the full ranked DE result (all proteins, signed metric — log2FC or sign·−log10p), not a
hit list. Needs tie/missing handling and a gene-set DB (MSigDB / GO / KEGG / Reactome).
- Output schema: Term, ES, NES, NOM p, FDR, leading-edge genes, Direction (sign of NES).
- Viewers — largely already built. The Phase 19 enrichment aids map almost 1:1 onto GSEA output:
signed NES ↔ the directionality bar, and leading-edge is native to GSEA (the current leading-edge
drill-down is an ORA-intersection approximation of exactly this). Much of the UI investment is reusable.
Implications / effort
- Heavier compute than ORA's instant hypergeometric (permutation-based) → server call, not client.
- New dependency (fgsea or gseapy) + gene-set DB handling + result schema + viewer wiring + tests.
- De-risked by the existing R-script infrastructure and the Phase 19 viewers.
- Positioning: keep ORA for fast, interactive "what's in my hit list"; GSEA for robust, threshold-free
conclusions. Offer both; flag when they disagree.
Open questions
- fgsea (R) vs gseapy/blitzgsea (Python) — which fits the deploy story best?
- Ranking metric default (log2FC vs signed −log10p vs moderated-t) and how to expose it.
- Gene-set source/versioning and organism handling (reuse the enrichment organism setting).
Sibling to #3930 (imputer-default rethink) — both are method-quality improvements surfaced by the
Cytokinetics report-path validation.
Summary
Proteomics term enrichment is ORA-only today —
enrichmentAnalysiscalls g:Profiler'sg:GOSt (
src/analysis/enrichment.ts) on the thresholded significant-gene hit list, split bydirection. ORA is set-based and threshold-sensitive: a protein counts fully in or fully out once
it crosses the significance cut, so a handful of borderline proteins can move a whole term. Propose
adding preranked GSEA as a complementary, threshold-free enrichment method.
Why (evidence from a real client analysis)
On the Cytokinetics DMD/WT data we compared enrichment built from two DE paths (Spectronaut
"candidates" vs our recomputed "report" path). The two hit lists agree ~90% (κ=0.90), and pathway
conclusions agree — except for four DMD-relevant terms (interstitial matrix, elastic fibres,
growth factor activity; regulation of calcium ion transport) that were significant in one path and
not the other. The flip was driven entirely by ~a handful of boundary proteins crossing the DE cut,
amplified by ORA's all-or-nothing counting.
A one-off preranked GSEA (gseapy, ranking = log2FC) on the same data, run on both paths:
the four terms flipped). The path-dependence essentially disappears.
activity −1.52 vs −1.53, calcium transport +1.49 vs +1.59 — near-identical either way, directions
consistent with ORA.
significance for them in the report path was partly a threshold artifact. GSEA is both
path-robust and less prone to overstating marginal terms.
Repro:
~/Downloads/ck/analysis_scripts/gsea_concordance.py(analysis deliverables, not in-repo).Proposal
Add preranked GSEA alongside ORA (complement, not replace):
fgsea(R, matches the existing limma/DEqMS/VSN R-script +client-fallback pattern) or
gseapy/blitzgsea(Python). g:Profiler's API can't do preranked GSEA.hit list. Needs tie/missing handling and a gene-set DB (MSigDB / GO / KEGG / Reactome).
signed NES ↔ the directionality bar, and leading-edge is native to GSEA (the current leading-edge
drill-down is an ORA-intersection approximation of exactly this). Much of the UI investment is reusable.
Implications / effort
conclusions. Offer both; flag when they disagree.
Open questions
Sibling to #3930 (imputer-default rethink) — both are method-quality improvements surfaced by the
Cytokinetics report-path validation.