Which edges earn an arrow¶
An arrow in a causal diagram is a claim: intervene on the tail and the head responds. Most edges in a knowledge graph were never asserted with that strength. CausalPhylo's first job is to say which ones were.
The rule¶
An edge is causal-admissible when either:
- its
edge_typenames a directed mechanism — someone has asserted that the source acts on the target; or - it is a gene-to-disease association backed by genetic evidence.
>>> from causalphylo import CAUSAL_EDGE_TYPES, GENETIC_EVIDENCE_CLASSES
>>> sorted(CAUSAL_EDGE_TYPES)
['activates', 'causes', 'downregulates', 'inhibits', 'phosphorylates',
'regulates', 'upregulates']
>>> sorted(GENETIC_EVIDENCE_CLASSES)
['genetic_association', 'somatic_mutation']
Everything else is associational.
from causalphylo import is_causal_admissible
is_causal_admissible({"edge_type": "activates", "evidence_class": "regulatory"})
is_causal_admissible({"edge_type": "associated_with", "evidence_class": "genetic_association"})
is_causal_admissible({"edge_type": "associated_with", "evidence_class": "literature"})
is_causal_admissible({"edge_type": "interacts_with", "evidence_class": "regulatory"})
is_causal_admissible({"edge_type": "participates_in", "evidence_class": ""})
| Edge | Admissible |
|---|---|
activates / regulatory |
True |
associated_with / genetic_association |
True |
associated_with / literature |
False |
interacts_with / regulatory (STRING) |
False |
participates_in (pathway membership) |
False |
Why genetic evidence is the exception¶
A germline variant is randomised at conception. A genetic association therefore carries a natural-experiment warrant that a co-mention does not — this is the Mendelian randomisation argument (Davey Smith & Ebrahim, 2003, Int J Epidemiol 32:1–22). That warrant is what earns the arrow.
A literature co-mention earns nothing. Two genes appear in one abstract for reasons that include a shared assay, a shared author, and a shared review.
Why an undirected interaction earns nothing¶
STRING interactions are not arrows
A protein–protein interaction is symmetric. When it reaches you as a directed edge, the direction is an artefact of which protein the API returned first. Admitting it would let the ordering of a response determine a causal conclusion.
Pruning the graph¶
causal_subgraph keeps the admissible edges with their attributes intact, and
drops nodes left with no admissible edge — a node participating in no causal
relation can be neither treatment, outcome, nor confounder.
import networkx as nx
from causalphylo import causal_subgraph
G = nx.DiGraph()
G.add_edge("U", "X", edge_type="activates", evidence_class="regulatory")
G.add_edge("X", "D", edge_type="associated_with", evidence_class="genetic_association")
G.add_edge("P", "D", edge_type="associated_with", evidence_class="literature")
G.add_edge("X", "pathway:1", edge_type="participates_in", evidence_class="")
sub = causal_subgraph(G)
sorted(G.nodes()) # ['D', 'P', 'U', 'X', 'pathway:1']
sorted(sub.nodes()) # ['D', 'U', 'X']
sorted(sub.edges()) # [('U', 'X'), ('X', 'D')]
P and pathway:1 are gone. The association that linked P to D still
exists in your own graph and can still contribute to evidence scoring — it is
simply not an arrow here.
Feedback loops¶
nx.is_d_separator requires a DAG of the whole graph, not merely of the
neighbourhood under test. Biological regulatory networks are full of feedback,
so cyclic nodes have to come out — and acyclic_core names the ones it
removed rather than silently repairing them.
from causalphylo import acyclic_core
G = nx.DiGraph()
G.add_edge("a", "b") # a and b form a 2-cycle
G.add_edge("b", "a")
G.add_edge("b", "c")
G.add_edge("c", "d")
core = acyclic_core(G)
sorted(core.excluded) # ['a', 'b']
sorted(core.dag.nodes()) # ['c', 'd']
nx.is_directed_acyclic_graph(core.dag) # True
Self-loops count as cycles. A node that regulates itself is excluded on the same grounds.
Identification involving an excluded node reports
cyclic_component rather than guessing an acyclic orientation. Choosing a
direction to break the loop would be inventing the mechanism the method is
supposed to test.
Adapting the vocabulary¶
The edge types and evidence classes above are frozensets of plain strings. If your graph speaks a different vocabulary, map it to these names when you build the graph. CausalPhylo deliberately has no configuration layer: the admissibility rule is an argument about evidence, and burying it in a settings file would make it look like a preference.