ergodicDocs
Products: switch from ergodic to another product
Get started

GuidesCheck whether an effect is identifiable

Check whether an effect is identifiable

Ask whether a causal graph lets you recover an effect from observational data, and get the formula to estimate if it does. You'll check five graphs, including two from discovery.

You'll need

  • ergodic, installed with pip install "ergodic".
  • A graph: drawn by you with dag, or returned by discovery.

Run the program#

Identification needs no data. It reads only the graph. U marks a common cause you never measured: pass it in latent=.

identify_effect.pyDownload
from ergodic import identify_effect
from ergodic.graph import cpdag, dag, mixed

# 1. Four single graphs in which X may cause Y.
worlds = {
    "measured confounder": dag(["Z -> X", "Z -> Y", "X -> Y"]),
    "hidden confounder, mediator": dag(["U -> X", "U -> Y", "X -> M", "M -> Y"], latent=["U"]),
    "hidden confounder, instrument": dag(["Z -> X", "U -> X", "U -> Y", "X -> Y"], latent=["U"]),
    "hidden confounder only": dag(["U -> X", "U -> Y", "X -> Y"], latent=["U"]),
}
for name, graph in worlds.items():
    result = identify_effect(graph, "X", "Y")
    print(f"{name}: {result}")

# 2. The hedge explains why the last one fails.
print(result.hedge)

# 3. Two equivalence classes, as discovery returns them.
for graph in [cpdag(["Z --- X", "X --> Y", "W --> Y"]), cpdag(["Z --- X", "X --- Y"])]:
    result = identify_effect(graph, "X", "Y")
    print(bool(result), result.strategy, sorted(result.adjustment_set or []))

# 4. A graph without a verified face is refused, with advice.
try:
    identify_effect(mixed(["Z -> X", "Z -> Y", "X -> Y"]), "X", "Y")
except ValueError as error:
    print(error)
Recorded output
measured confounder: Identification(adjustment: P(Y | do(X)) = sum_z P(Y | X, z) P(z), with z = {Z})
hidden confounder, mediator: Identification(frontdoor: P(Y | do(X)) = sum_m P(m | X) sum_x' P(Y | m, x') P(x'), with m = {M})
hidden confounder, instrument: Identification(iv: identified through instrument(s) Z under a linear (parametric) model; needs an IV estimator)
hidden confounder only: Identification(not identifiable, ['X'] -> ['Y'])
not identifiable: a hedge formed by ['X', 'Y'] and the c-component ['Y']
True adjustment ['W', 'Z']
False none []
identification needs a verified graph face (dag, admg, mag, cpdag, or pag), not 'mixed'; every edge is directed and there is no directed cycle. If it came from a search that assumes no hidden confounding (PC or GES with domain knowledge orienting every edge, say) and you read it as one causal world, re-face it with graph.as_kind('dag'); if it came from FCI or RFCI (or otherwise means ancestry, where a directed edge can hide confounding), re-face it with graph.as_kind('mag') instead, which identification reads as an equivalence class

Read the result#

identify_effect(graph, treatment, outcome) returns an Identification. It is truthy when the effect is identifiable.

Field What it holds
identifiable True or False.
strategy "null", "adjustment", "frontdoor", "iv", "id", or "none".
adjustment_set The set to adjust for, with "adjustment".
mediators The front-door set, with "frontdoor".
instruments (instrument, conditioning set) pairs, with "iv".
estimand The formula as text, or the reason it failed.
functional The typed formula, with "id".
hedge The witness of failure, when the general algorithm proves none exists.

The strategies are tried in order. First, if no directed path runs from treatment to outcome, the effect is zero: "null". Then adjustment, the front-door and instruments. On a single graph, the general do-calculus algorithm (Shpitser and Pearl, 2006) runs last. It returns a formula or a hedge. When it recognizes the formula as adjustment or the front door, the strategy says so; otherwise it is "id".

What each graph kind allows#

Kind Where it comes from What can identify
dag, admg Your drawing; LiNGAM Every strategy, unless it marks selection= nodes
cpdag PC, GES, BOSS, GRaSP Null and adjustment, valid in every member
pag, mag FCI, RFCI Null and adjustment, valid in every member
mixed Discovery with orienting knowledge, screens Nothing until you re-face it

A* and HillClimb also return a dag, but it is one member of an equivalence class, picked by tie-breaking among DAGs with the same score. Identify on cpdag_of_dag(graph) instead, so the answer doesn't rest on an arbitrary direction.

A graph that marks selection nodes (selection=) raises on every kind. The criteria assume the data were not selected on any node, and an effect generally isn't identifiable from a selected sample: with X -> S <- Y and S selected, X and Y are dependent in the sample even though X has no effect on Y.

On a class, the front-door and instruments need a single graph. To see each member's answer, use ida, described in Find adjustment sets, front-door sets and instruments.

An instrument identifies only under a linear model

strategy="iv" says so in its estimand. The estimate assumes the effect is linear and the same for everyone. Graphs where the instrument rule fires first, such as the napkin graph, can also be identified without that assumption: call identify_distribution(graph, "Y", "X") to get that formula.

What does an `"id"` result look like?

The Verma graph A -> B -> C -> D, with hidden causes shared by A and C and by B and D, has no adjustment set, front-door set or instrument for A -> D. The general algorithm still identifies it. result.functional holds the formula as a typed object, and result.estimand is its text. estimate_effect can evaluate it on discrete data.

Next steps#

Documentation

Search ergodic 0.2.1

Search ergodic by task, function or error.