Graph Convolutional Matrix Factorization - #442
Draft
PascalIversen wants to merge 16 commits into
Draft
Conversation
PascalIversen
marked this pull request as draft
June 23, 2026 14:09
Graph-convolutional matrix factorization for drug-response prediction. Predicts the
cell-line x drug response matrix as R = U V^T (as in ordinary matrix factorization),
but the latent factors are learned end-to-end by graph convolutions over
feature-similarity or prior-knowledge graphs (k-NN gene-expression graph for cell
lines, Morgan-fingerprint Tanimoto graph for drugs). Each convolution smooths a node's
embedding over its graph neighbours; the smoothed factors are read out by the dot
product, plus per-cell/per-drug biases and an optional MLP head.
Four selectable models, all registered in MULTI_DRUG_MODEL_FACTORY:
* GCMF - base single-graph model.
* RGCMF - relational/multi-graph variant (multi-omics cell graphs; pathway and
bioassay drug relations) fused by a relational graph convolution.
Works best on leave-cell-out in our experiments.
* PGCMF / PRGCMF - probabilistic variants with a heteroscedastic Gaussian-NLL head
emitting calibrated per-prediction aleatoric uncertainty.
Includes hyperparameters.yaml for all four, bundled gzipped drug-relation resources
for RGCMF/PRGCMF, smoke tests (train/predict/save-load round-trip), docs (model page
+ toctree + usage table), and a check-added-large-files exclude for the bundled
resources.
Lint/type clean: flake8 0, mypy 0, black/isort clean, pre-commit green.
PascalIversen
force-pushed
the
feat/gcmf-geometric-mf
branch
from
June 23, 2026 14:11
60e0898 to
9ec282e
Compare
…tion fallback - Un-bundle the drug-relation tables: remove drevalpy/models/GCMF/resources/ and revert the check-added-large-files whitelist. Resources now load only from <data_path>/meta/gcmf_drug_relations/ (downloaded with the meta bundle). - Rename gcmf_nd_drug_similarity -> gcmf_drug_relations and gcmf_nd_cache -> gcmf_cache; remove provenance/port framing from docstrings and comments. - RGCMF now falls back to the base GCMF graph when a tower resolves to zero relations, instead of crashing; update tests accordingly.
PascalIversen
force-pushed
the
feat/gcmf-geometric-mf
branch
from
July 16, 2026 09:04
0b50db5 to
ac84dbc
Compare
…etric-mf # Conflicts: # drevalpy/models/__init__.py # tests/test_drp_model.py
GCMF._build_*_adj is declared to return Tensor | list[Tensor]; the RGCMF fallback list needs a plain Tensor item, so cast the (always single-tensor) base result.
RGCMF no longer silently substitutes the base graph when a relation is absent: a configured drug-relation resource that is missing now raises FileNotFoundError, and an empty relation set raises ValueError. Tests provide a synthetic drug_pathways relation so RGCMF/PRGCMF train relationally, plus a test for the missing-resource error.
Drop the synthetic drug_pathways fixture and the drug_relation_views override, so RGCMF/PRGCMF train on their real defaults (drug_pathways, drug_bioassay) loaded from <data_path>/meta/gcmf_drug_relations/. The suite now genuinely depends on the shipped resources instead of masking them with a toy stand-in. Add test_default_drug_relations_ship_with_meta_bundle, which asserts every default relation resolves in the downloaded bundle, so a relation dropped from the bundle fails with a clear message rather than as a downstream training error. Verified: 8 passed with the relations present; with them removed the bundle guard and both relational models fail, while GCMF/PGCMF stay green.
The relational tests pinned cell_relation_views to a single gene_expression graph and forced gene_list=None, so the multi-omics cell graphs and the gene lists they reduce with were never exercised. RGCMF/PRGCMF now run their real defaults on both sides: all four cell relations (gene_expression, methylation, mutations, copy_number_variation_gistic) and their real gene_expression_intersection gene list. Only the base GCMF/PGCMF still relax gene_list, because they default to landmark_genes, which the toy bundle does not ship. Cost is negligible: 0.41s vs 0.01s to build the similarities on toy data. Extend the bundle guard (now test_default_relations_ship_with_meta_bundle) to also assert the per-omics gene lists behind the cell relations are present, not just the drug relations. Verified with a cold cache: 8 passed with the bundle intact; removing a cell gene list fails the guard plus both relational models. Note the guard is load-bearing - with a warm <dataset>/gcmf_cache/ the training tests still pass on a missing gene list, since the cache key covers the gene-list name but not its contents.
The drug relations were mapped onto a dataset's drugs by normalizing drug names (lowercase, strip spaces/hyphens/underscores) and matching those. That is fragile, and it ignored pubchem_id, the identifier the datasets actually key drugs on. Relations are now joined on pubchem_id directly. The tables carry a pubchem_id column and a table without one raises a clear error instead of falling back to name matching, so no fuzzy matching remains anywhere in the model. This also drops the drug_names.csv read from load_drug_features and removes _norm_name entirely. Two further fixes while here: - _build_target_jaccard addressed the table positionally (iloc[:, 0] / iloc[:, 1]); it now resolves the feature column by name, so a schema change cannot silently shift the join. - _map_named_similarity is now _map_indexed_similarity and expects a matrix indexed by pubchem_id on both axes, matching the "targets" path. Coverage is unchanged against name matching: CTRPv2 265/545 drug_pathways and 382/545 drug_bioassay, identical to before, now via an exact join. Note a drug name can map to different pubchem_ids in different datasets (4 such names across the shipped datasets), so the tables list one row per identifier and every dataset joins against the id it uses.
The kendall kernel rescaled tau as (tau - sim.min()) / (1 - sim.min()), where sim.min() is the least similar pair in the current cohort. A pair's similarity therefore depended on which other cell lines were present, so the same two cell lines scored differently in different CV splits. On CTRPv2 one pair with tau = 0.0990 scored 0.4089 in a 70-cell cohort and 0.3753 in a 45-cell one. The comment on that line already described the fixed [-1, 1] -> [0, 1] map, which is what the code now does: (tau + 1) / 2. That pair now scores 0.5495 in both, and the shared block of the two cohorts is identical. k-NN topology is unaffected, since the old transform was monotonic and neighbour ranking was already correct. Edge weights do change (mean 0.011 after the GCN normalization) because all four models run with use_edge_weights, so RGCMF/PRGCMF numbers shift slightly. Add _SIM_KERNEL_VERSION to the cell-similarity cache key. Without it the cached matrices under <dataset>/gcmf_cache/ still hold the old scaling and would be reused, so the fix would silently not apply on any machine with a warm cache. Also correct the cache docstring, which claimed the key covers more than it does: it does not cover the contents of the omics table or the gene list, only their names.
…ture column _build_target_jaccard raises when the table carries nothing besides the pubchem_id join key and drug_name, but the docstring had no :raises: entry, which darglint flags as DAR401.
PascalIversen
force-pushed
the
feat/gcmf-geometric-mf
branch
from
August 5, 2026 15:04
8e42e69 to
5633025
Compare
…m targets The train/predict test drew its targets from np.random.default_rng(0).normal(), so despite the docstring saying it trained on TOYv1 only the features were real. It asserted that predictions were finite and survived a save/load round-trip, which a model that learned nothing would also satisfy. It now uses the sample_dataset fixture, the 1776 measured TOYv1 responses, the same source every other model test already uses. Assert the predictions correlate with the responses (> 0.2). Measured across the family after the two training epochs the test runs: GCMF 0.697, PGCMF 0.706, RGCMF 0.716, PRGCMF 0.709, so the threshold has ample headroom for cross-platform numerics. An untrained model scores -0.041 and fails it, which is the case the previous version could not detect. Also drop the invented rows from the missing-pubchem_id test: the join key is validated before any row is read, so the header alone is what that test exercises.
…omization tests cover them
PascalIversen
marked this pull request as ready for review
August 7, 2026 08:45
PascalIversen
marked this pull request as draft
August 7, 2026 09:16
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.
Graph convolutional matrix factorization for drug-response prediction. Predicts the cell-line x drug response matrix as R = U V^T (as in normal matrix fact.) But the latent factors are learned end2end by graph convolutions over feature-similarity or prior knowledge graphs. One can use mutiple graphs for drugs and cell lines in the relational gcmf version.
The P-versions also produce aleatoric uncertainty estimates.
The main idea is to combine learning in primal and dual space (one can use a large-dimensional feature space and transform it to a similarity, and exploit it without blowing up the model complexity). Also, the graph convolution smooths the embeddings with those of the neighboring cells, which I hope prevents overfitting/helps generalization.
The variants are
bioassay drug relations) fused by a relational graph convolution. (works best on LCO per my experiments)
head emitting calibrated per-prediction uncertainty.
Per my experiments this is chocolate worthy but will rerun on the leaderboard scripts.