axiom

Making a model travel · step 2 of 6

Ask the graph about the population

Is a difference between two populations a difference that matters?

Populations always differ somehow. The question is whether they differ in a way that changes the effect. That is a claim about mechanisms, so it is answered on a graph — a **selection diagram**, in which every node whose mechanism differs between the two populations gets an extra parent S[node] standing for 'this one is not the same here'.

The verdict is not 'yes'. It is a formula, a named assumption, the set that licenses it, and the thing that would refute it. The source's age-specific effects reweighted by district B's age mix — which is why the strata weights on Population were not decoration.

step 2
from axiom.identify import CausalGraph, selection_diagram, transport_verdict

# What we believe about how illness comes about, and what differs
# between the districts: the age composition, and nothing else.
world = CausalGraph.from_edges(
    "filters -> illness, age -> illness, age -> filters,"
    " sanitation -> illness, sanitation -> filters",
    selection=["age"],
    name="two_districts",
)

print("the selection diagram makes the difference a node:")
for edge in sorted(selection_diagram(world).edges):
    print("   ", " -> ".join(edge))
print()

verdict = transport_verdict(world, "filters", "illness")
print("status  :", verdict.status)
print("route   :", verdict.verdict.route)
print("set     :", verdict.s_admissible_set)
print("formula :", verdict.formula)
print()
for a in verdict.verdict.assumptions:
    print(f"assumption   {a.name}  [{a.state}]")
    print(f"  says       {a.statement}")
    print(f"  refuted by {a.challenged_by}")
what it printed
the selection diagram makes the difference a node:
    S[age] -> age
    age -> filters
    age -> illness
    filters -> illness
    sanitation -> filters
    sanitation -> illness

status  : identified
route   : s_admissible_adjustment
set     : ('age',)
formula : P*(illness|do(filters)) = Σ_{age} P(illness|do(filters),age) P*(age)

assumption   s_admissibility  [satisfied]
  says       Z=['age'] is S-admissible for the effect of filters on illness: (illness ⊥ S | filters, Z) in G_bar(filters) of the selection diagram, so P*(illness|do(filters)) = Σ_{age} P(illness|do(filters),age) P*(age)
  refuted by a mechanism difference between the populations the diagram omits

Every line above was captured by running this code at build time. The steps share one namespace, so the code on this page is the code you would type after the steps before it.