You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Hi. It would be great if you could integrate our new ultrafast algorithm into Escape. It's really fast and can simulate several scoring methods. Code is here. github.com/bigomics/plaid. We will soon publish on Bioarxiv.
Very interesting approach and great cross calculation support! If you are planning to submit to CRAN or bioconductor, I'd be happy to add it to escape. But in order to manage things on my end, I think it needs to be able to be installed via install.packages() or bioconductor.
No I understand that - for a package like escape - imports/suggests ideally need to be in either CRAN or bioconductor. Happy to revisit if that is in the works.
@ivokwee Thanks for the update and congrats on getting Plaid into Bioconductor - I will reopen this issue and develop a tracking post below in order to get it incorporated into escape.
Reopened. Originally requested by @ivokwee (BigOmics) in June 2025, when PLAID was pre-print and GitHub-only. Since then:
Published: Zito A. et al.PLAID: ultrafast single-sample gene set enrichment scoring.Bioinformatics 2025, btaf621.
Available as a Bioconductor package (plaid, currently 1.1.0), not just bigomics/plaid on GitHub.
The API has stabilized around plaid() plus a family of replaid.* functions.
That removes the two main blockers to acting on this (unstable API, non-Bioc dependency), so this is now actionable.
Motivation
Runtime and memory are the dominant complaints against escape on large objects — the chunked ssGSEA/GSVA path is slow, and AUCell/UCell on 100k+ cell objects is memory-hungry. PLAID reports 10–100× speedups and ~5–10× lower memory via sparse matrix operations, and, more interesting for us, provides replaid.* functions that approximate the methods escape already exposes.
Two distinct wins, worth treating separately:
New method.method = "PLAID" — average log-intensity of gene set members per cell.
New backend for existing methods.replaid.ssgsea, replaid.gsva, replaid.aucell, replaid.ucell as opt-in fast paths for method = "ssGSEA" | "GSVA" | "AUCell" | "UCell", plus two methods we don't currently offer at all (singscore via replaid.sing, scSE via replaid.scse).
Design preference: keep method semantics stable and add a backend argument, so method = "UCell", backend = "plaid" is understood as "UCell scores, computed faster" rather than a different method. Making PLAID a value of methodand a value of backend is confusing; the native PLAID score should be method = "PLAID" with backend ignored.
X accepts a matrix, sparse matrix, SummarizedExperiment, or SingleCellExperiment. matG accepts a sparse gene-set matrix, a BiocSet, or a GMT list.
Integration friction points
These are the things that will silently produce wrong or surprising results if we wrap naively:
Output orientation.plaid() and replaid.* return gene sets × cells; escape.matrix() returns cells × gene sets. The wrapper must transpose, and tests must assert orientation rather than just dimensions (square-ish test cases will hide this).
min.genes / max.genes defaults. PLAID defaults to min.genes = 5, max.genes = 500. Several Hallmark and most GO/Reactome C2 sets exceed 500 genes and would be silently dropped. We must map min.size → min.genes and set max.genes to Inf/nrow(X) by default, and warn loudly if a user-supplied cap drops sets. This is the highest-risk item on the list.
normalize name collision.plaid(normalize = TRUE) performs median normalization of the scores; escape's normalize performs drop-out normalization by expressed genes per cell. Same word, unrelated operations, and both would be in scope in a single call. Needs explicit disambiguation in the argument name and in the docs.
Counts vs logcounts. PLAID explicitly recommends log-transformed input (assay = "logcounts" default) since the average in log space approximates a geometric mean. .cntEval() currently pulls counts. We need a per-method input contract: PLAID/replaid paths take logcounts, and should error informatively — not silently score raw counts — when only counts exists. Note the exception: replaid.scse intentionally power-transforms back out of log space.
Sparsity preservation. The memory benefit evaporates if .cntEval() densifies. Whatever path feeds PLAID must keep dgCMatrix end to end.
Double chunking.escape chunks columns itself; plaid() does its own chunked crossprod (chunk = NULL autodetects). Running both is wasteful and may defeat the sparse path. The plaid backend should probably bypass the escape chunk loop and delegate, with chunk exposed.
Parallelism. PLAID uses the parallel package; escape uses BiocParallel. Decide who owns cores so we don't oversubscribe.
Dependency weight.plaid pulls in GSVA, fgsea, Rfast, qlcMatrix, and requires R >= 4.3.3. Recommend Suggests + requireNamespace() guard with a clear "install plaid to use this backend" error, not Imports.
Fidelity caveats to document
Per the PLAID docs, the replications are not all exact, and users need to know which is which:
replaid.ssgsea: exact at alpha = 0; approximate for alpha > 0.
replaid.gsva: the empirical CDF row transform is approximated by a z-transform by default (rowtf = "z"); "ecdf" is available.
replaid.aucell, replaid.ucell, replaid.sing: reported as near-identical to the originals.
replaid.scse: matches the original with removeLog2 = TRUE, scoreMean = FALSE.
Anything we ship must state that backend = "plaid" is an approximation of the named method, and that scores are not guaranteed interchangeable with previously published escape results.
Tracking checklist
1. Implementation
Add backend = c("native", "plaid") to escape.matrix() and runEscape(); default "native".
Internal .plaid_dispatch() mapping method → replaid.* function, handling transpose, gene-set conversion via gmt2mat(), and size-filter translation.
requireNamespace("plaid") guard with actionable install message.
Explicit input-assay contract per method; informative error when logcounts is unavailable.
Ensure runEscape() attaches plaid-derived scores identically to native scores (same assay/altExp structure, same colname ordering).
Pass through method-specific tuning args (alpha, tau, rowtf, aucMaxRank, rmax, nsmooth, stats) without polluting the top-level signature — likely via a single backend.args list.
2. Tests
Orientation test: output is cells × gene sets for every method/backend combination.
Dimension and dimname integrity against the input object.
Concordance regression test (loose tolerance) native vs plaid on pbmc_small.
No gene sets silently dropped under default settings.
Graceful error when plaid is not installed.
Skip-if-not-installed guards throughout, so CI without plaid still passes.
3. Documentation
Method table in the vignette: method × available backends × exact-or-approximate × recommended input assay.
Explicit note that backend = "plaid" approximates rather than reproduces, with the per-method caveats above.
Citation guidance: users of PLAID backends must cite Zito et al., Bioinformatics 2025, btaf621, in addition to the original method paper (AUCell/UCell/GSVA/ssGSEA/singscore/scSE) and escape.
escape.matrix() and runEscape() accept a plaid backend for the existing methods plus method = "PLAID"; output orientation, dimnames, and downstream compatibility (performNormalization(), all plotting functions, performPCA()) are identical to the native path; no gene sets are dropped without a warning; the default backend remains "native" in this release cycle; and the documented fidelity caveats are in the vignette.
Activity
ncborcherding commentedon Jun 16, 2025
Hey @ivokwee,
Very interesting approach and great cross calculation support! If you are planning to submit to CRAN or bioconductor, I'd be happy to add it to escape. But in order to manage things on my end, I think it needs to be able to be installed via install.packages() or bioconductor.
Let me know and I'll follow up!
Nick
ivokwee commentedon Jun 16, 2025
Hi. You can easily install it using
devtools::install_github('bigomics/plaid')ncborcherding commentedon Jun 16, 2025
@ivokwee
No I understand that - for a package like escape - imports/suggests ideally need to be in either CRAN or bioconductor. Happy to revisit if that is in the works.
Nick
ivokwee commentedon Aug 2, 2026
Plaid is in Bioconductor https://bioconductor.posit.co/packages/3.23/bioc/html/plaid.html
ncborcherding commentedon Aug 3, 2026
@ivokwee Thanks for the update and congrats on getting Plaid into Bioconductor - I will reopen this issue and develop a tracking post below in order to get it incorporated into escape.
ncborcherding commentedon Aug 5, 2026
Status
Reopened. Originally requested by @ivokwee (BigOmics) in June 2025, when PLAID was pre-print and GitHub-only. Since then:
plaid, currently 1.1.0), not justbigomics/plaidon GitHub.plaid()plus a family ofreplaid.*functions.That removes the two main blockers to acting on this (unstable API, non-Bioc dependency), so this is now actionable.
Motivation
Runtime and memory are the dominant complaints against
escapeon large objects — the chunked ssGSEA/GSVA path is slow, and AUCell/UCell on 100k+ cell objects is memory-hungry. PLAID reports 10–100× speedups and ~5–10× lower memory via sparse matrix operations, and, more interesting for us, providesreplaid.*functions that approximate the methodsescapealready exposes.Two distinct wins, worth treating separately:
method = "PLAID"— average log-intensity of gene set members per cell.replaid.ssgsea,replaid.gsva,replaid.aucell,replaid.ucellas opt-in fast paths formethod = "ssGSEA" | "GSVA" | "AUCell" | "UCell", plus two methods we don't currently offer at all (singscoreviareplaid.sing,scSEviareplaid.scse).Design preference: keep
methodsemantics stable and add abackendargument, somethod = "UCell", backend = "plaid"is understood as "UCell scores, computed faster" rather than a different method. Making PLAID a value ofmethodand a value ofbackendis confusing; the native PLAID score should bemethod = "PLAID"withbackendignored.Relevant v1.1.0 API
Xaccepts a matrix, sparse matrix,SummarizedExperiment, orSingleCellExperiment.matGaccepts a sparse gene-set matrix, aBiocSet, or a GMT list.Integration friction points
These are the things that will silently produce wrong or surprising results if we wrap naively:
plaid()andreplaid.*return gene sets × cells;escape.matrix()returns cells × gene sets. The wrapper must transpose, and tests must assert orientation rather than just dimensions (square-ish test cases will hide this).min.genes/max.genesdefaults. PLAID defaults tomin.genes = 5, max.genes = 500. Several Hallmark and most GO/Reactome C2 sets exceed 500 genes and would be silently dropped. We must mapmin.size→min.genesand setmax.genestoInf/nrow(X)by default, and warn loudly if a user-supplied cap drops sets. This is the highest-risk item on the list.normalizename collision.plaid(normalize = TRUE)performs median normalization of the scores;escape'snormalizeperforms drop-out normalization by expressed genes per cell. Same word, unrelated operations, and both would be in scope in a single call. Needs explicit disambiguation in the argument name and in the docs.assay = "logcounts"default) since the average in log space approximates a geometric mean..cntEval()currently pullscounts. We need a per-method input contract: PLAID/replaid paths take logcounts, and should error informatively — not silently score raw counts — when onlycountsexists. Note the exception:replaid.scseintentionally power-transforms back out of log space..cntEval()densifies. Whatever path feeds PLAID must keepdgCMatrixend to end.escapechunks columns itself;plaid()does its own chunked crossprod (chunk = NULLautodetects). Running both is wasteful and may defeat the sparse path. The plaid backend should probably bypass theescapechunk loop and delegate, withchunkexposed.parallelpackage;escapeusesBiocParallel. Decide who owns cores so we don't oversubscribe.plaidpulls in GSVA, fgsea, Rfast, qlcMatrix, and requires R >= 4.3.3. RecommendSuggests+requireNamespace()guard with a clear "installplaidto use this backend" error, notImports.Fidelity caveats to document
Per the PLAID docs, the replications are not all exact, and users need to know which is which:
replaid.ssgsea: exact atalpha = 0; approximate foralpha > 0.replaid.gsva: the empirical CDF row transform is approximated by a z-transform by default (rowtf = "z");"ecdf"is available.replaid.aucell,replaid.ucell,replaid.sing: reported as near-identical to the originals.replaid.scse: matches the original withremoveLog2 = TRUE, scoreMean = FALSE.Anything we ship must state that
backend = "plaid"is an approximation of the named method, and that scores are not guaranteed interchangeable with previously publishedescaperesults.Tracking checklist
1. Implementation
backend = c("native", "plaid")toescape.matrix()andrunEscape(); default"native".method = "PLAID"as a first-class method.method = "singscore"andmethod = "scSE"(plaid-only backends initially)..plaid_dispatch()mappingmethod→replaid.*function, handling transpose, gene-set conversion viagmt2mat(), and size-filter translation.requireNamespace("plaid")guard with actionable install message.logcountsis unavailable.runEscape()attaches plaid-derived scores identically to native scores (same assay/altExp structure, same colname ordering).alpha,tau,rowtf,aucMaxRank,rmax,nsmooth,stats) without polluting the top-level signature — likely via a singlebackend.argslist.2. Tests
pbmc_small.plaidis not installed.plaidstill passes.3. Documentation
backend = "plaid"approximates rather than reproduces, with the per-method caveats above.escape.NEWS.mdentry.bigomics/plaidand https://bigomics.github.io/plaid/ from the README method list.Acceptance criteria
escape.matrix()andrunEscape()accept a plaid backend for the existing methods plusmethod = "PLAID"; output orientation, dimnames, and downstream compatibility (performNormalization(), all plotting functions,performPCA()) are identical to the native path; no gene sets are dropped without a warning; the default backend remains"native"in this release cycle; and the documented fidelity caveats are in the vignette.ivokwee commentedon Aug 12, 2026
Amazing. Very meticulously done.