Skip to content

Include PLAID #167

Closed
Closed

Description

@ivokwee

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.

Activity

  1. ncborcherding commented on Jun 16, 2025

    @ncborcherding
    Member

    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

  2. ivokwee commented on Jun 16, 2025

    @ivokwee
    Author

    Hi. You can easily install it using
    devtools::install_github('bigomics/plaid')

  3. ncborcherding commented on Jun 16, 2025

    @ncborcherding
    Member

    @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

  4. ivokwee commented on Aug 2, 2026

    @ivokwee
    Author
  5. ncborcherding commented on Aug 3, 2026

    @ncborcherding
    Member

    @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.

  6. ncborcherding commented on Aug 5, 2026

    @ncborcherding
    Member

    Status

    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:

    1. New method. method = "PLAID" — average log-intensity of gene set members per cell.
    2. 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 method and a value of backend is confusing; the native PLAID score should be method = "PLAID" with backend ignored.

    Relevant v1.1.0 API

    plaid(X, matG, stats = c("mean","sum"), chunk = NULL, normalize = TRUE,
          nsmooth = 3, assay = "logcounts", min.genes = 5, max.genes = 500)
    
    replaid.ssgsea(X, matG, alpha = 0, assay = "logcounts", min.genes = 5, max.genes = 500)
    replaid.gsva(X, matG, tau = 0, rowtf = c("z","ecdf"), assay = "logcounts", ...)
    replaid.aucell(X, matG, aucMaxRank = NULL, assay = "logcounts", ...)
    replaid.ucell(X, matG, rmax = 1500, assay = "logcounts", ...)
    replaid.sing(X, matG, assay = "logcounts", ...)
    replaid.scse(X, matG, removeLog2 = NULL, scoreMean = FALSE, assay = "logcounts", ...)
    
    gmt2mat(gmt, max.genes = -1, ntop = -1, sparse = TRUE, bg = NULL, use.multicore = TRUE)

    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".
    • Add method = "PLAID" as a first-class method.
    • Add method = "singscore" and method = "scSE" (plaid-only backends initially).
    • 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.
    • NEWS.md entry.
    • Link bigomics/plaid and https://bigomics.github.io/plaid/ from the README method list.

    Acceptance criteria

    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.

  7. self-assigned this
    on Aug 5, 2026
  8. ivokwee commented on Aug 12, 2026

    @ivokwee
    Author

    Amazing. Very meticulously done.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions