Source code for pywhy_graphs.classes.admg

from typing import Iterator, Mapping, Set

import networkx as nx

import pywhy_graphs.networkx as pywhy_nx

from .base import AncestralMixin


[docs] class ADMG(pywhy_nx.MixedEdgeGraph, AncestralMixin): """Acyclic directed mixed graph (ADMG). A causal graph with two different edge types: bidirected and traditional directed edges. Directed edges constitute causal relations as a causal DAG did, while bidirected edges constitute the presence of a latent confounder. Parameters ---------- incoming_directed_edges : input directed edges (optional, default: None) Data to initialize directed edges. All arguments that are accepted by `networkx.DiGraph` are accepted. incoming_bidirected_edges : input bidirected edges (optional, default: None) Data to initialize bidirected edges. All arguments that are accepted by `networkx.Graph` are accepted. incoming_undirected_edges : input undirected edges (optional, default: None) Data to initialize undirected edges. All arguments that are accepted by `networkx.Graph` are accepted. directed_edge_name : str The name for the directed edges. By default 'directed'. bidirected_edge_name : str The name for the bidirected edges. By default 'bidirected'. undirected_edge_name : str The name for the directed edges. By default 'undirected'. attr : keyword arguments, optional (default= no attributes) Attributes to add to graph as key=value pairs. See Also -------- networkx.DiGraph networkx.Graph pywhy_graphs.networkx.MixedEdgeGraph Notes ----- **Edge Type Subgraphs** The data structure underneath the hood is stored in two networkx graphs: ``networkx.Graph`` and ``networkx.DiGraph`` to represent the non-directed edges and directed edges. Non-directed edges in an ADMG can be present as bidirected edges standing for latent confounders, or undirected edges standing for selection bias. - Directed edges (<-, ->, indicating causal relationship) = `networkx.DiGraph` The subgraph of directed edges may be accessed by the `ADMG.sub_directed_graph`. Their edges in networkx format can be accessed by `ADMG.directed_edges` and the corresponding name of the edge type by `ADMG.directed_edge_name`. - Bidirected edges (<->, indicating latent confounder) = `networkx.Graph` The subgraph of bidirected edges may be accessed by the `ADMG.sub_bidirected_graph`. Their edges in networkx format can be accessed by `ADMG.bidirected_edges` and the corresponding name of the edge type by `ADMG.bidirected_edge_name`. - Undirected edges (--, indicating selection bias) = `networkx.Graph` The subgraph of undirected edges may be accessed by the `ADMG.sub_undirected_graph`. Their edges in networkx format can be accessed by `ADMG.undirected_edges` and the corresponding name of the edge type by `ADMG.undirected_edge_name`. By definition, no cycles may exist due to the directed edges. However, beyond that multiple types of edges between the same pairs of nodes are possible. """ def __init__( self, incoming_directed_edges=None, incoming_bidirected_edges=None, incoming_undirected_edges=None, directed_edge_name: str = "directed", bidirected_edge_name: str = "bidirected", undirected_edge_name: str = "undirected", **attr, ): super().__init__(**attr) self.add_edge_type(nx.DiGraph(incoming_directed_edges), directed_edge_name) self.add_edge_type(nx.Graph(incoming_bidirected_edges), bidirected_edge_name) self.add_edge_type(nx.Graph(incoming_undirected_edges), undirected_edge_name) self._directed_name = directed_edge_name self._bidirected_name = bidirected_edge_name self._undirected_name = undirected_edge_name if not nx.is_directed_acyclic_graph(self.sub_directed_graph()): raise RuntimeError(f"{self} is not a DAG, which it should be.") @property def undirected_edge_name(self) -> str: """Name of the undirected edge internal graph.""" return self._undirected_name @property def directed_edge_name(self) -> str: """Name of the directed edge internal graph.""" return self._directed_name @property def bidirected_edge_name(self) -> str: """Name of the bidirected edge internal graph.""" return self._bidirected_name
[docs] def c_components(self) -> Iterator[Set]: """Generate confounded components of the graph. Note the trivial c-component of a node without bidirected edges is the node themself. Returns ------- comp : iterator of sets The c-components. """ return nx.connected_components(self.sub_bidirected_graph())
@property def bidirected_edges(self) -> Mapping: """``EdgeView`` of the bidirected edges.""" return self.get_graphs(self._bidirected_name).edges @property def undirected_edges(self) -> Mapping: """``EdgeView`` of the undirected edges.""" return self.get_graphs(self._undirected_name).edges @property def directed_edges(self) -> Mapping: """``EdgeView`` of the directed edges.""" return self.get_graphs(self._directed_name).edges
[docs] def sub_directed_graph(self) -> nx.DiGraph: """Sub-graph of just the directed edges.""" return self._get_internal_graph(self._directed_name)
[docs] def sub_bidirected_graph(self) -> nx.Graph: """Sub-graph of just the bidirected edges.""" return self._get_internal_graph(self._bidirected_name)
[docs] def sub_undirected_graph(self) -> nx.Graph: """Sub-graph of just the undirected edges.""" return self._get_internal_graph(self._undirected_name)