Source code for causal_networkx.algorithms.convert
import networkx as nx
from causal_networkx import ADMG, CPDAG, DAG, PAG
[docs]def dag2cpdag(graph: DAG) -> CPDAG:
"""Convert a DAG to a completed partially directed acyclic graph.
Runs the PC algorithm with an Oracle to obtain the CPDAG.
This is useful to obtain the best possible CPDAG one can obtain
for a certain graph using data.
Parameters
----------
graph : DAG
The causal DAG.
Returns
-------
cpdag : CPDAG
The oracle CPDAG.
"""
from causal_networkx.ci import Oracle
from causal_networkx.discovery import PC
oracle = Oracle(graph)
dummy_X = graph.dummy_sample()
# run PC algorithm with oracle to obtain the CPDAG
pcalg = PC(oracle)
pcalg.fit(dummy_X)
cpdag = pcalg.graph_
return cpdag # type: ignore
[docs]def admg2pag(graph: ADMG) -> PAG:
"""Convert an ADMG to a PAG.
Runs the FCI algorithm with an Oracle to obtain the PAG.
This is useful to obtain the best possible PAG one can obtain
for a certain graph using data.
Parameters
----------
graph : ADMG
The causal ADMG.
Returns
-------
pag : PAG
The oracle PAG.
"""
from causal_networkx.ci import Oracle
from causal_networkx.discovery import FCI
oracle = Oracle(graph)
dummy_X = graph.dummy_sample()
# run FCI algorithm with oracle to obtain the PAG
fci = FCI(oracle)
fci.fit(dummy_X)
pag = fci.graph_
return pag # type: ignore
[docs]def is_markov_equivalent(graph, other_graph) -> bool:
"""Check markov equivalence of two graphs.
For DAGs, two graphs are Markov equivalent iff. they have the same skeleton
and same v-structures.
For ADMGs, two graphs are Markov equivalent iff. they are MEC in the DAG-sense
and if whenever there is the same discriminating path for some node in both
graphs, the node is a collider on that path in one graph iff. it is a collider
on that path in the other graph.
Parameters
----------
graph : instance of DAG
Causal graph.
other_graph : instance of DAG
Another causal graph to compare to.
Returns
-------
is_mec : bool
If the two graphs are markov equivalent.
"""
from causal_networkx.algorithms.dag import compute_v_structures
# See: https://graphical-models.readthedocs.io/en/latest/_modules/graphical_models/classes/mags/ancestral_graph.html#AncestralGraph.markov_blanket_of # noqa
# first check skeleton
first_skel = graph.to_adjacency_graph()
second_skel = other_graph.to_adjacency_graph()
same_skeleton = nx.is_isomorphic(first_skel, second_skel)
if not same_skeleton:
return False
# second check v-structures
first_vstructs = compute_v_structures(graph)
second_vstructs = compute_v_structures(other_graph)
same_vstructures = first_vstructs == second_vstructs
if not same_vstructures:
return False
# TODO: implement discriminating paths
# third check discriminating triples if needed
# self_discriminating_paths = self.discriminating_paths()
# other_discriminating_paths = other.discriminating_paths()
# shared_disc_paths = set(self_discriminating_paths.keys()) & set(other_discriminating_paths)
# same_discriminating = all(
# self_discriminating_paths[path] == other_discriminating_paths[path]
# for path in shared_disc_paths
# )
# fourth if interventional, then must check that
# interventional distributions are consistent
return True