.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "auto_examples/intro/inducing_path.py" .. LINE NUMBERS ARE GIVEN BELOW. .. only:: html .. note:: :class: sphx-glr-download-link-note :ref:`Go to the end ` to download the full example code. .. rst-class:: sphx-glr-example-title .. _sphx_glr_auto_examples_intro_inducing_path.py: ====================================================== An introduction to Inducing Paths and how to find them ====================================================== A path p is called an ``inducing path`` relative to on an mixed-edge graph with directed and bidirected edges, where every non-endpoint vertex on p is either in L or a collider, and every collider on p is an ancestor of either X , Y or a member of S. In other words, it is a path between two nodes that cannot be d-seperated, making it active regardless of what variables we condition on. More details on inducing paths can be found at :footcite:`Zhang2008`. .. GENERATED FROM PYTHON SOURCE LINES 19-24 .. code-block:: Python import pywhy_graphs from pywhy_graphs import ADMG from pywhy_graphs.viz import draw .. GENERATED FROM PYTHON SOURCE LINES 25-29 Construct an example graph --------------------------- To illustrate the workings of the inducing path algorithm, we will construct the causal graph from figure 2 of :footcite:`Colombo2012`. .. GENERATED FROM PYTHON SOURCE LINES 29-51 .. code-block:: Python G = ADMG() G.add_edge("X4", "X1", G.directed_edge_name) G.add_edge("X2", "X5", G.directed_edge_name) G.add_edge("X2", "X6", G.directed_edge_name) G.add_edge("X4", "X6", G.directed_edge_name) G.add_edge("X3", "X6", G.directed_edge_name) G.add_edge("X3", "X4", G.directed_edge_name) G.add_edge("X3", "X2", G.directed_edge_name) G.add_edge("X5", "X6", G.directed_edge_name) G.add_edge("L2", "X4", G.directed_edge_name) G.add_edge("L2", "X5", G.directed_edge_name) G.add_edge("L1", "X1", G.directed_edge_name) G.add_edge("L1", "X2", G.directed_edge_name) # this is the Figure 2(a) in the paper as we see. dot_graph = draw(G) dot_graph.render(outfile="graph.png", view=True) .. image-sg:: /auto_examples/intro/images/sphx_glr_inducing_path_001.png :alt: inducing path :srcset: /auto_examples/intro/images/sphx_glr_inducing_path_001.png :class: sphx-glr-single-img .. rst-class:: sphx-glr-script-out .. code-block:: none 'graph.png' .. GENERATED FROM PYTHON SOURCE LINES 52-56 Adjacent nodes trivially have an inducing path ----------------------------------------------- By definition, all adjacent nodes have a trivial inducing path between them, that path only consists of one edge, which is the edge between those two nodes. .. GENERATED FROM PYTHON SOURCE LINES 56-64 .. code-block:: Python L = {} S = {} # All such tests will return True. print(pywhy_graphs.inducing_path(G, "X1", "X4", L, S)) print(pywhy_graphs.inducing_path(G, "X3", "X2", L, S)) .. rst-class:: sphx-glr-script-out .. code-block:: none (True, ['X1', 'X4']) (True, ['X3', 'X2']) .. GENERATED FROM PYTHON SOURCE LINES 65-77 Inducing paths between non-adjacent nodes ------------------------------------------ Given the definition of an inducing path, we need to satisfy all requirements for the function to return True. Adding the latent variables to L is not enough for the pair [X1,X5]. As we see in Figure 2(c) in :footcite:`Colombo2012`, (X1, X5) are not adjacent in the final skeleton of the equivalence class, which makes sense because a MAG is an equivalence class of a DAG and contains an edge among two nodes if i) the two nodes are adjacent in the DAG, or ii) the two nodes have a primitive inducing path between them. Since there is no adjacency among (X1, X5) in the final skeleton, there is no primitive inducing path between them. .. GENERATED FROM PYTHON SOURCE LINES 77-97 .. code-block:: Python L = {"L1", "L2"} S = {} # returns False print(pywhy_graphs.inducing_path(G, "X1", "X5", L, S)) # However, if we add X3, a non-collider on the path # from X1 to X5 to L, we make a valid inducing path. L = {"L1", "L2", "X3"} S = {} # now it returns True print(pywhy_graphs.inducing_path(G, "X1", "X5", L, S)) .. rst-class:: sphx-glr-script-out .. code-block:: none (False, []) (True, ['X1', 'L1', 'X2', 'X3', 'X4', 'L2', 'X5']) .. GENERATED FROM PYTHON SOURCE LINES 98-104 The role of colliders ---------------------- Adding colliders to the set S has a downstream effect. Conditioning on a collider, or descendant of a collider opens up that collider path. For example, we will add the node 'X6' to the set ``S``. This will make the path ``(X1, X2, X3)`` a valid inducing path, since 'X2' is an ancestor of 'X6'. .. GENERATED FROM PYTHON SOURCE LINES 104-122 .. code-block:: Python # Some node pairs still do not have a valid inducing path between them. # For example, the path between X1 and X3 is not available. # this returns False print(pywhy_graphs.inducing_path(G, "X1", "X3", L, S)) # If we add X6 to ``S``, paths containing all the collider ancestors of X6 # will be valid inducing paths. # Since X2 is a collider and an ancestor of X6, there should be a valid inducing # path from X1 to X3 now. L = {"L1", "L2", "X3"} S = {"X6"} # now it returns True print(pywhy_graphs.inducing_path(G, "X1", "X3", L, S)) .. rst-class:: sphx-glr-script-out .. code-block:: none (False, []) (False, []) .. GENERATED FROM PYTHON SOURCE LINES 123-126 References ---------- .. footbibliography:: .. rst-class:: sphx-glr-timing **Total running time of the script:** (0 minutes 1.035 seconds) **Estimated memory usage:** 166 MB .. _sphx_glr_download_auto_examples_intro_inducing_path.py: .. only:: html .. container:: sphx-glr-footer sphx-glr-footer-example .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: inducing_path.ipynb ` .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: inducing_path.py ` .. container:: sphx-glr-download sphx-glr-download-zip :download:`Download zipped: inducing_path.zip ` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_