Skip to content

Network-backend API refactor: Phases 0–4 #127

Closed

Description

@tonywu1999

Network-backend API refactor: Phases 0–4

This issue tracks the refactor of MSstatsBioNet toward a generic network-backend API. The goals are:

  • Decouple the package from INDRA, so other databases (OmniPath next) can be added as backends.
  • Support entity types other than proteins (PTM sites, metabolites, ...).
  • Tell apart nodes that are in the user's input from nodes the database adds.

Phases 0–4 are merged to devel (#112–#126). Phases 5–9 are listed at the end.

Each phase PR passed devtools::check() with 0 errors, 0 warnings and 1 NOTE (the existing "MSstats in Depends is not imported" NOTE). Each was also tested against MSstatsShiny's test_network_visualization.R (52 pass).

Summary

Phase PR What it did
0 #112 Deprecated unused arguments: the paper-count filter and the correlation filter
1a #113 Edge and node contract, plus validate_network()
1b #115 Edge columns renamed and added to match the contract
1c #116 Node columns renamed to match the contract, contract enforced, golden output pinned
— #114 roxygen 8.1.0 update (chore)
2 #117 INDRA moved behind an internal IndraBackend object and get_network()
3 (node logFC) #119 Node fold-change column kept as logFC (not log2FC)
3a #118 Entity table: prepare_entities(), select_entities()
3b #120 convert_ids() and get_entity_properties() for INDRA
3c #121 Node status: measured, included_in_query, node_role, latent nodes
3d #122 New API exported, plus backend_capabilities(), min_confidence, ?network_queries
4a #123 Cytoscape display: latent-node styling, shapes by entity type, directed edges
4b #124 get_evidence(), with the backend chosen from backend_database
4c #125 get_curations() and filter_by_curation()
4d #126 One node per input row when rows share a grounding

Paired MSstatsShiny change: Vitek-Lab/MSstatsShiny#234 (displayLabelType = "entity_name"). It is still open and can merge now, because BioNet 1c is merged.


Phase 0: Deprecate unused arguments (#112)

  • Merged 2026-10-02

  • paper_count_cutoff is ignored and warns; the paper filter was removed. correlation_cutoff and protein_level_data warn as deprecated.

  • paper_count_cutoff >= 2 no longer errors.

  • The deprecated arguments follow the Bioconductor policy: defunct in release N+1, removed in N+2.

  • Internal analysis scripts that used the deprecated arguments were updated. No callers of them remain.

Phase 1: Edge and node contract, snake_case column names

Phase 1 was written as one large commit, then split into three PRs for review.

1a. Contract module (#113)

  • Merged 2026-10-05

  • New R/contract.R: the vocabularies (INTERACTION_TYPES, ENTITY_TYPES, NODE_ROLES) and the exported validate_network(). It checks required columns and types, vocabularies and value ranges, NA statistics on latent nodes, and that every edge endpoint is a node. It reports every problem in one error.

  • Changes made in review:

    • Symmetric statement types (Complex, Association) must have directed = FALSE.
    • evidence_count rejects NA and Inf.
    • Malformed columns are reported instead of erroring.
    • Renames: query_type value "induced" → "subnetwork"; source_db → backend_database; provenance_url → evidence_url; selected → included_in_query.
    • Final NODE_ROLES: passed_cutoffs, user_added, mediator, upstream_regulator, downstream_target, path_start, path_end, path_intermediate.

1b. Edge columns (#115)

  • Merged 2026-10-05

  • New edge columns: directed, confidence (from INDRA belief), backend_database, query_type.

  • Renamed edge columns:

    • stmt_hash → statement_id
    • evidenceLink → evidence_url, now a link to this statement instead of every statement between the two agents
    • evidenceCount → evidence_count (integer)
    • sourceCounts → evidence_sources
  • Code that reads these columns was updated: filterSubnetworkByContext(), the topic functions, the Cytoscape edge elements, the JS click handler and the _edge_clicked Shiny input, the curation filter, and the Filter-By-Context vignette.

  • MSstatsShiny: highlightEdgeInTable() reads only source, target, interaction and edge_type, so the rename is safe there. Its edge table now shows the new column names.

1c. Node columns and enforcement (#116)

  • Merged 2026-10-05

  • Renamed node columns: Site → site, entityName → entity_name, entityId → entity_id. Added namespace. Node IDs are character.

  • cytoscapeNetwork(displayLabelType = "entityName") is deprecated in favor of "entity_name". Values other than "id" and "entity_name" now error; they used to fall back to "id" silently.

  • getSubnetworkFromIndra() calls validate_network() on its output.

  • Golden output pinned in tests/testthat/_fixtures/golden_subnetwork.rds (8 nodes, 20 edges). _fixtures/make_golden_subnetwork.R regenerates it.

  • Removed inst/extdata/subnetwork.rds, which nothing referenced and which had the old columns.

  • Follow-up: chore: Switch entityName to entity_name for displayLabelType cytoscapeNetwork MSstatsShiny#234 changes the "Gene Name" radio button to "entity_name" (still open).

Fixtures for future INDRA query types stay on the refactor-api branch and will merge with the Phase 7 parsers.

Phase 2: INDRA behind a backend object (#117)

  • Merged 2026-10-05

  • New S4 classes: NetworkBackend (virtual), IndraBackend, NetworkQuery (virtual), SubnetworkQuery.

  • New generic get_network(backend, entities, query = subnetwork_query(), ...) with shared arguments:

    • interaction_types (renamed from statement_types in review)
    • min_evidence
    • evidence_sources (renamed from sources in review)
    • include_entities
  • CoGEx HTTP code moved to backend-indra-cogex.R. getSubnetworkFromIndra() now calls get_network(), and the golden output is unchanged.

  • Decisions:

    • Curation filtering is not part of get_network(). It uses a different service and runs after the network is built. It became get_curations() and filter_by_curation() in Phase 4.
    • INDRA calls stay on httr.
  • User-visible changes:

    • A non-character sources_filter errors with "evidence_sources must be a character vector".
    • When protein_level_data and sources_filter are both invalid, the sources_filter error is shown first.

Phase 3: Entity model, and measured vs. latent nodes

Node fold-change column stays logFC (#119)

  • Merged 2026-10-06

  • MSstats writes log2FC or log10FC depending on logTrans. prepare_entities() therefore copies whichever column the input has to logFC, with the values unchanged. Compared with RELEASE_3_23, the node column keeps its name.

3a. Entity table (#118)

  • Merged 2026-10-06

  • prepare_entities() builds one row per analyte with these columns: id, entity_type, id_type, namespace, entity_id, entity_name, included_in_query, site, parent_id, organism, logFC, adj.pvalue.

    • It errors if the input has several comparisons in Label and label = doesn't pick one. This replaces silently keeping the first.
  • select_entities() flags rows that pass the cutoffs and drops none, so nodes in the input are recognized even when they fail the cutoffs.

    • force_include takes id values or "NS:ID" groundings.
    • An equivalence test shows it selects the same rows as the old input filter over a 48-combination argument grid.
  • parse_ptm_sites() uses an anchored pattern on each ;-separated member. Results that differ from the old parsing:

    • CLH1_HUMAN_S148 → site S148 (was HUMAN_S148)
    • P1_S148;P2_T5 → parent P1;P2, site S148;T5
  • build_grounding_table() returns one row per grounding (long form). Only this function splits the ;-joined grounding strings.

3b. convert_ids() and get_entity_properties() (#120)

  • Merged 2026-10-06

  • convert_ids() grounds the entity table:

    • UniProt IDs and mnemonics through CoGEx
    • HGNC gene symbols through Gilda, using the table's organism
    • chemical names through Gilda
    • Unsupported entity type / ID type pairs, and proteins from a non-human organism, error with the supported options.
  • get_entity_properties() adds is_transcription_factor, is_kinase and is_phosphatase (also for PTM rows, from the parent gene).

  • IndraBackend gained a grounding_url slot.

  • annotateProteinInfoFromIndra() now uses convert_ids() and get_entity_properties(). Its output is identical() to 1.5.3 in live calls for all four proteinIdType values.

  • Renamed in review: get_annotations() → get_entity_properties(), because MSstats uses "annotation" for the experimental design file.

3c. Node status (#121)

  • Merged 2026-10-06

  • get_network() takes the entity table and sends only the included_in_query rows. Each returned node is matched back to all rows by namespace and ID.

  • New node columns: entity_type, measured, included_in_query, node_role, has_measured_sites. The required node columns in validate_network() now follow the contract.

  • Nodes not in the input (latent nodes) get measured = FALSE, NA statistics, and an entity_type from their namespace (e.g. FPLX → family). This removes the made-up logFC = 0, adj.pvalue = 1.

  • The edge dedup key now includes the namespace, which fixes the collision between IDs like HGNC:2 and CHEBI:2.

  • PTM rule: site rows are measured = TRUE with their own statistics. Every row of a protein that has site rows gets has_measured_sites = TRUE.

  • getSubnetworkFromIndra() builds its entities with prepare_entities() and select_entities(). The golden output was re-pinned; this was an intended change.

3d. Export the new API (#122)

  • Merged 2026-10-06

  • Exported: prepare_entities(), select_entities(), indra_backend(), convert_ids(), get_entity_properties(), subnetwork_query(), get_network(), backend_capabilities(), and the S4 classes, so other packages can add backends.

  • backend_capabilities() returns query_types, id_conversions, entity_properties, interaction_types and max_nodes. It is not called capabilities(), which would mask base::capabilities().

  • min_confidence drops edges below the cutoff, plus edges with no score, and a message gives the count.

  • get_network() prints the question it asks, e.g. "INDRA subnetwork: how are 42 selected proteins connected to each other, with no other nodes added?".

  • New help pages: ?network_queries (with a glossary), ?NetworkBackend-class, ?NetworkQuery-class.

Phase 4: Downstream functions use the contract

4a. Display (#123)

  • Merged 2026-10-06

  • Node status styling:

    • Latent nodes have no fill and a dashed grey border; the legend says "not in input data".
    • Nodes in the input but not in the query are faded.
    • NA logFC gives no fill. It used to get the zero-fold-change grey.
  • A PTM protein node takes its color from its protein-level row. If there is none, it gets a neutral container style ("no protein-level row in input").

  • Node shape by entity_type: hexagon for metabolites and lipids, diamond for drugs, octagon for complexes, barrel for families.

  • Edges use the directed column. Undirected edges have no arrow, and the reverse duplicate is dropped.

  • .relProps() covers the whole interaction vocabulary, with a new modification category.

  • The legend is built from what is drawn.

  • node_type values are unchanged, so MSstatsShiny's click handlers still work.

4b. get_evidence() (#124)

  • Merged 2026-10-06

  • Exported generic get_evidence(backend, edges) with an INDRA method. Output is identical() to the old internal function on live data.

  • filterSubnetworkByContext(), decomposeSubnetworkByTopic(), decomposeSubnetworkIntoHierarchicalTopics(), compareTopicModels() and bootstrapTopicModels() take backend = NULL. By default each edge goes to the backend named in its backend_database; an explicit backend takes every edge.

  • A missing or NA backend_database errors. There is no silent fallback to INDRA.

4c. Curation (#125)

  • Merged 2026-10-07

  • get_curations(backend, edges) makes one request per unique statement_id, where it used to make one per edge row. IndraBackend gained a curation_url slot.

  • filter_by_curation(network, min_evidence = 1, backend = NULL):

    • subtracts incorrect curations from evidence_count
    • drops edges below min_evidence, and always drops edges left below 1
    • drops nodes that lost all their edges
  • The output of get_curations() is validated; for example, a numeric statement_id that would lose precision errors.

  • Fixed: curation source_hash values were parsed as doubles, so two hashes could count once. They are now parsed with bigint_as_char.

  • getSubnetworkFromIndra(filter_by_curation = TRUE) now calls filter_by_curation().

4d. One node per input row (#126)

  • Merged 2026-10-07

  • When several input rows share a grounding (isoforms such as P04637 and P04637-2, or a protein group and its member), each row is its own node with its own statistics. The edge is copied once per (source node, target node) pair, and every copy keeps the statement's columns.

  • source and target stay node IDs, so no code that reads edges had to change.

  • A statement from a grounding to itself gives each matching node a self-loop, with no edges between isoforms.

  • statement_id is not unique per edge row. The statement-level key is backend_database + statement_id.

  • Replaces the 3c fallback for these cases, which kept INDRA's name as the node ID with NA statistics. Compared with 1.5.3, this is a breaking change: 1.5.3 merged these into one node with logFC = 0, adj.pvalue = 1.


Remaining phases

  • Phase 5: second backend plus merge_networks(), as two PRs:
    • 5a: merge_networks() and a shared backend contract test helper
    • 5b: omnipath_backend() through OmnipathR (in Suggests)
    • Each backend's convert_ids() will keep the groundings of other backends, so one entity table serves both.
  • Phase 6: switch over. Deprecate getSubnetworkFromIndra() and annotateProteinInfoFromIndra(); update MSstatsShiny and the vignettes in the same devel cycle.
  • Phase 7: more query types: upstream regulators (graph and enrichment), downstream targets, mediated, path search.
  • Phase 8: cleanup and vignettes (incl. batching the curation lookup).
  • Phase 9 (proposed, not scheduled): multi-condition input.
  • Release N+1 / N+2: the deprecated items become defunct, then are removed.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions