feat(l2g): pathway enrichment features - #1282
Open
addramir wants to merge 1 commit into
Open
Conversation
Add two L2G features that score a gene by how much of its pathway membership is relevant to the diseases behind a credible set: - pathwayEnrichment500kb: the fraction of the pathways a gene belongs to that are enriched among the genes associated with the study's diseases - pathwayEnrichment500kbNeighbourhood: the same score relative to the best-scoring gene at the locus Two new datasets feed them. PathwayIndex holds the gene sets of a pathway library, parsed from a GMT file. PathwayEnrichment holds the enriched pathways per disease; the enrichment results it reads can arrive with the FDR missing for a whole disease at once, so it recomputes a Benjamini-Hochberg value from the p-values wherever the FDR is null, which would otherwise drop those diseases out of any FDR filter. Studies are grouped by their set of diseases rather than handled one by one, so that a pathway flagged by several of a study's diseases counts once and the pathway-level join stays bounded by the number of distinct disease sets. The features are opt-in: they are registered in the feature factory and wired into LocusToGeneFeatureMatrixStep, but not part of the default features list. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
addramir
marked this pull request as ready for review
August 28, 2026 23:59
Contributor
There was a problem hiding this comment.
Pull request overview
Adds two opt-in L2G features that score genes near a GWAS locus by the fraction of their pathway memberships that are enriched for the study’s disease(s), backed by new pathway library + enrichment datasets (including BH-based FDR repair).
Changes:
- Introduces
PathwayIndex(GMT-parsed pathway→gene set membership) andPathwayEnrichment(disease→enriched pathways, with FDR correction). - Adds
pathwayEnrichment500kbandpathwayEnrichment500kbNeighbourhoodfeatures to the L2G feature factory and wiring inl2g.py/config. - Adds unit + feature-level tests and API docs for the new datasets/features.
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/gentropy/dataset/test_pathway.py | Adds unit tests for GMT parsing, BH correction, and enriched-pathway filtering. |
| tests/gentropy/dataset/test_l2g_feature.py | Adds feature tests for pathway enrichment scoring and neighbourhood normalization; updates factory return-type test. |
| tests/gentropy/conftest.py | Adds fixtures for mock pathway index and enrichment datasets. |
| src/gentropy/method/l2g/feature_factory.py | Registers the two new pathway features in the factory mapping. |
| src/gentropy/l2g.py | Loads optional pathway inputs and enforces required inputs when pathway features are requested. |
| src/gentropy/dataset/pathway_index.py | New dataset for pathway library membership with GMT parsing and gene-membership explosion. |
| src/gentropy/dataset/pathway_enrichment.py | New dataset for disease-pathway enrichment with corrected-FDR logic and enriched-pathway extraction. |
| src/gentropy/dataset/l2g_features/pathway.py | Implements pathway enrichment feature logic + neighbourhood normalization. |
| src/gentropy/config.py | Adds config paths for pathway library and enrichment inputs. |
| src/gentropy/assets/schemas/pathway_index.json | Adds schema for PathwayIndex. |
| src/gentropy/assets/schemas/pathway_enrichment.json | Adds schema for PathwayEnrichment. |
| docs/python_api/datasets/pathway_index.md | Adds API docs page for PathwayIndex. |
| docs/python_api/datasets/pathway_enrichment.md | Adds API docs page for PathwayEnrichment. |
| docs/python_api/datasets/l2g_features/pathway.md | Adds API docs page for the pathway enrichment features and shared logic. |
| docs/assets/schemas/pathway_index.md | Adds rendered schema doc snippet for PathwayIndex. |
| docs/assets/schemas/pathway_enrichment.md | Adds rendered schema doc snippet for PathwayEnrichment. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+63
to
+70
| membership = ( | ||
| pathway_index.gene_membership() | ||
| .join(pathway_enrichment.df.select("pathway").distinct(), "pathway", "inner") | ||
| .join( | ||
| target_index.symbols_lut().select("geneSymbol", "geneId"), | ||
| "geneSymbol", | ||
| "inner", | ||
| ) |
Comment on lines
+55
to
+69
| columns = f.split(f.col("value"), "\t") | ||
| pathway = columns.getItem(0) | ||
| return cls( | ||
| _df=( | ||
| session.spark.read.text(path) | ||
| .filter(f.trim(f.col("value")) != "") | ||
| .select( | ||
| pathway.alias("pathway"), | ||
| f.regexp_extract(pathway, cls.SOURCE_PATTERN, 1).alias("source"), | ||
| f.array_distinct( | ||
| f.filter( | ||
| f.slice(columns, 3, f.size(columns)), lambda gene: gene != "" | ||
| ) | ||
| ).alias("geneSymbols"), | ||
| ) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds two opt-in L2G features built on a catalogue of pathways enriched among the genetically associated genes of each Open Targets phenotype.
What the feature is
For a credible set, take every gene whose TSS is within 500 kb of the study locus. Resolve the study to its disease identifiers, collect the pathways enriched for those diseases, and score each gene by
The value is 0 for a gene no enriched pathway contains, 1 for a gene whose every pathway is enriched, and it is normalised so that promiscuous genes are not favoured just for belonging to many gene sets.
pathwayEnrichment500kb— the score abovepathwayEnrichment500kbNeighbourhood— the same score divided by the largest score at the locusThe second one exists because the raw score is dense rather than sparse: for a typical disease around half of the library's genes belong to at least one enriched pathway, so nearly every gene in a window is non-zero and what separates genes is how they compare with their neighbours.
Neither feature is in the default
features_list. They have to be asked for explicitly, and the step raises if the inputs they need are missing.New datasets
PathwayIndex— the gene sets of a pathway library, one row per pathway.from_gmtparses a GMT file, so the library the enrichment was computed against can be passed straight through.PathwayEnrichment— the enriched pathways of each disease. Read with an explicit schema, so extra columns in the source (effect sizes, gene set sizes, provenance) are simply not projected.with_corrected_fdrfills in a missing FDR with a Benjamini-Hochberg step-up value recomputed from the p-values of that disease. This is not cosmetic: in the catalogue this was developed against, 249 of 3,766 diseases have a null FDR on every single row, and those are the gene-rich ones — body mass index, systemic lupus erythematosus, psoriasis, ulcerative colitis, haematocrit among them. Roughly 15% of GWAS studies point at one. Their p-values are intact, so recomputing recovers them; without it an FDR filter deletes them silently. Where the published FDR is present it is kept as is.Implementation notes
Studies are grouped by their set of diseases rather than handled one at a time. A pathway enriched for more than one of a study's diseases has to count once, which cannot be done from per-disease gene counts, and there are far fewer distinct disease sets than studies (94% of GWAS studies carry exactly one disease). That keeps the pathway-level explosion bounded by the number of distinct sets instead of the number of studies.
Gene symbols in the library are mapped onto Ensembl identifiers through
TargetIndex.symbols_lut, which covers approved and obsolete symbols. On the library used here that resolves 16,733 of 17,246 symbols; the remainder are tRNA and immunoglobulin segment names.The enrichment dataset is read without
recursiveFileLookup, since it is partitioned bydiseaseIdand recursing would stop Spark from reading that partition column back.Testing
For review
The catalogue ranks genes by the Open Targets
genetic_associationdatatype score, which is itself built largely on L2G output. A feature derived from it therefore feeds L2G predictions back into L2G input: gold standard performance will overstate what the feature adds, and it couples each model to its predecessor. That is why the features are opt-in here, and it is worth settling before either reaches a released model.🤖 Generated with Claude Code