MAG#
- class pgmpy.base.MAG(*args, backend=None, **kwargs)[source]#
Bases:
AncestralBaseClass for representing Maximal Ancestral Graphs (MAGs).
A MAG is a type of graph used in causal inference to represent conditional independence relations when some variables are latent (unobserved). Unlike simple directed acyclic graphs (DAGs), MAGs allow for special edge types (directed and bidirected) that capture the presence of latent confounding and selection bias. Every pair of nodes in a MAG is connected in such a way that the graph is “maximal,” meaning no additional edges can be added without changing the set of implied conditional independence relations.
- Parameters:
- ebunchiterable of tuples, optional
A list or iterable of edges to add at initialization.
- latentsset, default=set()
Set of latent (unobserved) variables.
- exposuresset, default=set()
Set of exposure variables in the graph. These are the variables that represent the treatment or intervention being studied in a causal analysis. Default is an empty set.
- outcomesset, default=set()
Set of outcome variables in the graph. These are the variables that represent the response or dependent variables being studied in a causal analysis. Default is an empty set.
- rolesdict, optional (default: None)
A dictionary mapping roles to node names. The keys are roles, and the values are role names (strings or iterables of str). If provided, this will automatically assign roles to the nodes in the graph. Passing a key-value pair via
rolesis equivalent to callingwith_role(role, variables)for each key-value pair in the dictionary.
References
[1]Zhang, J. (2008). Causal Reasoning with Ancestral Graphs. Journal of Machine Learning Research, 9(7).
Examples
>>> from pgmpy.base import MAG >>> mag = MAG(ebunch=[("L", "A", "-", ">"), ("B", "C", "-", ">")], latents={"L"}) >>> sorted(mag.nodes()) ['A', 'B', 'C', 'L']
Roles can be assigned to nodes in the graph at construction or using methods.
At construction:
>>> mag = MAG( ... ebunch=[("L", "A", "-", ">"), ("B", "C", "-", ">")], ... latents={"L"}, ... exposures={"A"}, ... outcomes={"B"}, ... )
Roles can also be assigned after creation using
with_rolemethod.>>> mag = mag.with_role("adjustment", {"L", "C"})
Vertices of a specific role can be retrieved using
get_rolemethod.>>> mag.get_role("exposures") ['A'] >>> mag.get_role("adjustment") ['L', 'C']
- has_inducing_path(u, v, W)[source]#
Check if there exists an inducing path between two nodes relative to W.
An inducing path between u and v is a path such that: - The path has length > 2 (at least one intermediate node), - Every intermediate node is a collider on the path, - Every intermediate node is either:
in W, or
an ancestor of u or v.
- Parameters:
- uHashable
Source node.
- vHashable
Target node.
- Wset
Subset of nodes to check inducing paths through (often latents).
- Returns:
- bool
True if there exists an inducing path, False otherwise.
Examples
>>> from pgmpy.base import MAG >>> mag = MAG() >>> mag.add_edge("X", "L", "-", ">") >>> mag.add_edge("Y", "L", "-", ">") >>> mag.latents = {"L"} >>> mag.has_inducing_path("X", "Y", mag.latents) True
- is_visible_edge(u, v) bool[source]#
Check if a directed edge u -> v is visible in the MAG.
A directed edge A → B in a MAG is considered visible if there exists a vertex C not adjacent to B such that either:
C → A exists, or
2. There is a collider path from C to A that is into A, and every vertex on that path is a parent of B.
- Parameters:
- uHashable
Source node (tail of the edge).
- vHashable
Target node (head of the edge).
- Returns:
- bool
True if the edge u -> v is visible, False otherwise.
Examples
>>> edges = [ ... ("A", "D", "-", ">"), ... ("B", "C", "-", ">"), ... ("X", "A", "-", ">"), ... ] >>> mag = MAG(ebunch=edges) >>> mag.is_visible_edge("A", "D") True >>> mag.is_visible_edge("B", "C") False
- lower_manipulation(X, inplace=False)[source]#
Performs lower manipulation.
Removes all edges that are visible and originate from nodes in X. For edges from X that are invisible, adds bidirected edges from the other endpoint to its neighbors outside X to preserve conditional independencies. All other edges remain unchanged.
- Parameters:
- Xset
Set of nodes to perform manipulation on.
- inplacebool, optional
If True, modifies the current graph in place. Defaults to False.
- Returns:
- MAG
A new MAG with outgoing edges from X removed.
Examples
>>> from pgmpy.base import MAG >>> mag = MAG() >>> mag.add_edge("A", "B", "-", ">") >>> mag.add_edge("A", "C", "-", ">") >>> mag.add_edge("C", "B", "-", ">") >>> mag.add_edge("B", "C", ">", ">") >>> new_mag = mag.lower_manipulation({"A"}) >>> edges = list(new_mag.edges(data=True)) >>> len(edges) 1 >>> edges[0][0], edges[0][1] ('B', 'C') >>> edges[0][2]['marks']['B'], edges[0][2]['marks']['C'] ('>', '>')
- upper_manipulation(X, inplace=False)[source]#
Performs upper manipulation.
Deletes all edges (directed or bidirected) that have an arrowhead pointing to any variable in X. The rest of the graph remains unchanged.
- Parameters:
- Xset
Set of nodes to perform manipulation on.
- inplacebool, optional
If True, modifies the current graph in place. Defaults to False.
- Returns:
- MAG
A new MAG with incoming edges to X removed.
Examples
>>> from pgmpy.base import MAG >>> mag = MAG() >>> mag.add_edge("X", "Y", ">", "-") >>> mag.add_edge("Z", "X", ">", "-") >>> mag.add_edge("A", "X", "-", ">") >>> new_mag = mag.upper_manipulation({"X"}) >>> new_mag.has_edge("Z", "X") True >>> new_mag.has_edge("A", "X") False >>> new_mag.has_edge("X", "Y") False