|
| 1 | +""" |
| 2 | +=========================== |
| 3 | +On PAGs and their validity |
| 4 | +=========================== |
| 5 | +
|
| 6 | +A PAG or a Partial Ancestral Graph is a type of mixed edge |
| 7 | +graph that can represent, in a single graph, the causal relationship |
| 8 | +between several nodes as defined by an equivalence class of MAGs. |
| 9 | +
|
| 10 | +PAGs account for possible unobserved confounding and selection bias |
| 11 | +in the underlying equivalence class of SCMs. |
| 12 | +
|
| 13 | +Another way to understand this is that PAGs encode conditional independence |
| 14 | +constraints stemming from Causal Graphs. Since these constraints do not lead to a |
| 15 | +unique graph, a PAG, in essence, represents a class of graphs that encode |
| 16 | +the same conditional independence constraints. |
| 17 | +
|
| 18 | +PAGs model this relationship by displaying all common edge marks (tail and arrowhead) shared |
| 19 | +by all members in the equivalence class and displaying circle endpoints for those marks |
| 20 | +that are not common. That is, a circular endpoint (``*-o``) can represent both a directed |
| 21 | +(``*->``) and tail (``*—``) endpoint in causal graphs within the equivalence class. |
| 22 | +
|
| 23 | +More details on PAGs can be found at :footcite:`Zhang2008`. |
| 24 | +
|
| 25 | +""" |
| 26 | + |
| 27 | +import pywhy_graphs |
| 28 | +from pywhy_graphs.viz import draw |
| 29 | +from pywhy_graphs import PAG |
| 30 | + |
| 31 | +try: |
| 32 | + from dodiscover import FCI, make_context |
| 33 | + from dodiscover.ci import Oracle |
| 34 | + from dodiscover.constraint.utils import dummy_sample |
| 35 | +except ImportError as e: |
| 36 | + raise ImportError("The 'dodiscover' package is required to convert a MAG to a PAG.") |
| 37 | + |
| 38 | + |
| 39 | +# %% |
| 40 | +# PAGs in pywhy-graphs |
| 41 | +# --------------------------- |
| 42 | +# Constructing a PAG in pywhy-graphs is an easy task since |
| 43 | +# the library provides a separate class for this purpose. |
| 44 | +# True to the definition of PAGs, the class can contain |
| 45 | +# directed edges, bidirected edges, undirected edges and |
| 46 | +# cicle edges. To illustrate this, we construct an example PAG |
| 47 | +# as described in :footcite:`Zhang2008`, figure 4: |
| 48 | + |
| 49 | +pag = PAG() |
| 50 | +pag.add_edge("I", "S", pag.directed_edge_name) |
| 51 | +pag.add_edge("G", "S", pag.directed_edge_name) |
| 52 | +pag.add_edge("G", "L", pag.directed_edge_name) |
| 53 | +pag.add_edge("S", "L", pag.directed_edge_name) |
| 54 | +pag.add_edge("PSH", "S", pag.directed_edge_name) |
| 55 | +pag.add_edge("S", "PSH", pag.circle_edge_name) |
| 56 | +pag.add_edge("S", "G", pag.circle_edge_name) |
| 57 | +pag.add_edge("S", "I", pag.circle_edge_name) |
| 58 | + |
| 59 | + |
| 60 | +# Finally, the graph looks like this: |
| 61 | +dot_graph = draw(pag) |
| 62 | +dot_graph.render(outfile="valid_pag.png", view=True) |
| 63 | + |
| 64 | + |
| 65 | +# %% |
| 66 | +# Validity of a PAG |
| 67 | +# --------------------------- |
| 68 | +# For a PAG to be valid, it must represent a valid |
| 69 | +# equivalent class of MAGs. This can be verified by |
| 70 | +# turning the PAG into an MAG and then checking the |
| 71 | +# validity of the MAG. |
| 72 | +# Theorem 2 in :footcite:`Zhang2008` provides a method for checking the validity of a PAG. |
| 73 | +# To check if the constructed PAG is a valid one in pywhy-graphs, we can simply do: |
| 74 | + |
| 75 | + |
| 76 | +# returns True |
| 77 | +print(pywhy_graphs.valid_pag(pag)) |
| 78 | + |
| 79 | +# %% |
| 80 | +# If we want to test whether this algorithm is working correctly or not, we can change |
| 81 | +# a single mark in the graph such that the PAG. By removing a circle edge, we are removing |
| 82 | +# the representation of multiple marks as encoded by the different MAGs this PAG represents. |
| 83 | +# In this specific case, by removing the circle endpoint ``S *-o I``, we are saying that ``S`` |
| 84 | +# directly causes ``I``. However, there is no way of determining this using the FCI logical rules. |
| 85 | +# One would not be able to determine that the adjacency is due to a direct |
| 86 | +# causal relationship (directed edge), confounded relationship (bidirected edge), or an inducing path |
| 87 | +# relationship. As such, the resulting graph is no longer a valid PAG. |
| 88 | + |
| 89 | +pag.remove_edge("S", "I", pag.circle_edge_name) |
| 90 | + |
| 91 | +# returns False |
| 92 | +print(pywhy_graphs.valid_pag(pag)) |
| 93 | + |
| 94 | +# %% |
| 95 | +# References |
| 96 | +# ---------- |
| 97 | +# .. footbibliography:: |
0 commit comments