Skip to content

Add signature reversal scoring to enrichment - #1082

Merged
Zethson merged 10 commits into
scverse:mainfrom
daveringelberg:signature-reversal-enrichment
Aug 30, 2026
Merged

Zethson merged 10 commits into
scverse:mainfrom
daveringelberg:signature-reversal-enrichment

Conversation

@daveringelberg

Copy link
Copy Markdown
Contributor

PR Checklist

  • Referenced issue is linked
  • If you've fixed a bug or added code that should be tested, add tests!
  • Documentation in docs is updated

Description of changes

This adds a first CMap-style signature reversal workflow to pt.tl.Enrichment.

Given perturbation-level AnnData with perturbations as observations and genes as variables, users can provide either up/down query gene sets or a signed query signature. The method scores each perturbation by how strongly it opposes the query signature and stores the results back on the AnnData object.

This addresses the signature reversal part of #1036. It does not address Bliss/Loewe synergy, dose-response, or genetic interaction workflows.

Technical details

  • adds Enrichment.signature_reversal
  • stores reversal score, connectivity score, and rank in adata.obs
  • stores query metadata and result keys in adata.uns
  • supports .X, an optional layer, and optional gene-symbol matching through adata.var
  • documents signature reversal under the Enrichment tools section
  • adds compact tests for up/down gene sets and signed query signatures
  • adds no new dependency

Testing

  • ruff check src\pertpy\tools\_enrichment.py tests\tools\test_enrichment.py
  • ruff format --check src\pertpy\tools\_enrichment.py tests\tools\test_enrichment.py
  • pytest tests\tools\test_enrichment.py -q -p no:cacheprovider

Addresses part of #1036.

@codecov-commenter

codecov-commenter commented Aug 19, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 79.95%. Comparing base (61428d5) to head (79e3714).
⚠️ Report is 6 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1082      +/-   ##
==========================================
+ Coverage   79.47%   79.95%   +0.48%     
==========================================
  Files          53       55       +2     
  Lines        7556     7533      -23     
==========================================
+ Hits         6005     6023      +18     
+ Misses       1551     1510      -41     
Files with missing lines Coverage Δ
src/pertpy/tools/_enrichment.py 83.33% <100.00%> (+16.94%) ⬆️

... and 14 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@daveringelberg

Copy link
Copy Markdown
Contributor Author

It looks like the ReadTheDocs failure is unrelated to this PR. The warnings seem to come from external intersphinx/scvi-lightning references.

@daveringelberg

Copy link
Copy Markdown
Contributor Author

After a further pass, the implementation now uses raw CMap WTCS. The remaining ReadTheDocs failure is caused by the same external issue noted above.

@Zethson

Zethson commented Aug 21, 2026

Copy link
Copy Markdown
Member

Thanks! I'll have a look at the RTD failure soon.

@Zethson

Zethson commented Aug 23, 2026

Copy link
Copy Markdown
Member

I think the RTD failure is related to Lightning-AI/pytorch-lightning#21915.

@Zethson Zethson left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please ensure that your markdown fits the style I used -> single sentences on single lines.
Same for docstrings.

Should we also already add a small section to a tutorial?

>>> from anndata import AnnData
>>> effect_adata = AnnData(np.array([[-2.0, 1.0, 0.5]]))
>>> effect_adata.var_names = ["IL6", "CCR7", "other"]
>>> enr = pt.tl.Enrichment()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to use one of the existing datasets here instead of creating one? Makes the example shorter

Comment thread docs/api/tools_index.md Outdated
For a simple single-context dataset, cell-level effects can be computed before aggregation:

```python
cell_adata = sc.read_h5ad("perturbation_data.h5ad")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not copy and pastable. Every example needs to be trivial to run.

@daveringelberg

Copy link
Copy Markdown
Contributor Author

Thanks for the feedback. I checked the existing datasets but none appears to provide perturbation-level, control-relative signatures directly. distance_example() can be used after pseudobulk aggregation and control subtraction, so I used that for a runnable API example while keeping the shorter synthetic docstring example.

I also noticed that the tutorials live in the separate scverse/pertpy-tutorials repository as a submodule, so I haven't included the tutorial in this PR. Would you prefer a separate PR there, followed by a submodule update here?

@Zethson

Zethson commented Aug 25, 2026

Copy link
Copy Markdown
Member

Yes exactly, that'd be the way to go to update the tutorials. Thank you! Let me know if you need help, please.

@Zethson

Zethson commented Aug 29, 2026

Copy link
Copy Markdown
Member

I'll be traveling next week and might not get to it but I'll get back to you for this PR. Looks pretty good already

@Zethson Zethson left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I had time now. It's pretty defensively programmed with lots of edge cases covered but I think it's okay.
Just one minor comment and then let's get this in! Thanks

Comment thread docs/api/tools_index.md Outdated
@Zethson
Zethson merged commit e4e30b6 into scverse:main Aug 30, 2026
18 of 19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants