comparison env/lib/python3.9/site-packages/networkx/algorithms/d_separation.py @ 0:4f3585e2f14b draft default tip

"planemo upload commit 60cee0fc7c0cda8592644e1aad72851dec82c959"
author shellac
date Mon, 22 Mar 2021 18:12:50 +0000
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:4f3585e2f14b
1 """
2 Algorithm for testing d-separation in DAGs.
3
4 *d-separation* is a test for conditional independence in probability
5 distributions that can be factorized using DAGs. It is a purely
6 graphical test that uses the underlying graph and makes no reference
7 to the actual distribution parameters. See [1]_ for a formal
8 definition.
9
10 The implementation is based on the conceptually simple linear time
11 algorithm presented in [2]_. Refer to [3]_, [4]_ for a couple of
12 alternative algorithms.
13
14
15 Examples
16 --------
17
18 >>>
19 >>> # HMM graph with five states and observation nodes
20 ... g = nx.DiGraph()
21 >>> g.add_edges_from(
22 ... [
23 ... ("S1", "S2"),
24 ... ("S2", "S3"),
25 ... ("S3", "S4"),
26 ... ("S4", "S5"),
27 ... ("S1", "O1"),
28 ... ("S2", "O2"),
29 ... ("S3", "O3"),
30 ... ("S4", "O4"),
31 ... ("S5", "O5"),
32 ... ]
33 ... )
34 >>>
35 >>> # states/obs before 'S3' are d-separated from states/obs after 'S3'
36 ... nx.d_separated(g, {"S1", "S2", "O1", "O2"}, {"S4", "S5", "O4", "O5"}, {"S3"})
37 True
38
39
40 References
41 ----------
42
43 .. [1] Pearl, J. (2009). Causality. Cambridge: Cambridge University Press.
44
45 .. [2] Darwiche, A. (2009). Modeling and reasoning with Bayesian networks. Cambridge: Cambridge University Press.
46
47 .. [3] Shachter, R. D. (1998). Bayes-ball: rational pastime (for determining irrelevance and requisite information in belief networks and influence diagrams). In , Proceedings of the Fourteenth Conference on Uncertainty in Artificial Intelligence (pp. 480–487). San Francisco, CA, USA: Morgan Kaufmann Publishers Inc.
48
49 .. [4] Koller, D., & Friedman, N. (2009). Probabilistic graphical models: principles and techniques. The MIT Press.
50
51 """
52
53 from collections import deque
54 from typing import AbstractSet
55
56 import networkx as nx
57 from networkx.utils import not_implemented_for, UnionFind
58
59 __all__ = ["d_separated"]
60
61
62 @not_implemented_for("undirected")
63 def d_separated(G: nx.DiGraph, x: AbstractSet, y: AbstractSet, z: AbstractSet) -> bool:
64 """
65 Return whether node sets ``x`` and ``y`` are d-separated by ``z``.
66
67 Parameters
68 ----------
69 G : graph
70 A NetworkX DAG.
71
72 x : set
73 First set of nodes in ``G``.
74
75 y : set
76 Second set of nodes in ``G``.
77
78 z : set
79 Set of conditioning nodes in ``G``. Can be empty set.
80
81 Returns
82 -------
83 b : bool
84 A boolean that is true if ``x`` is d-separated from ``y`` given ``z`` in ``G``.
85
86 Raises
87 ------
88 NetworkXError
89 The *d-separation* test is commonly used with directed
90 graphical models which are acyclic. Accordingly, the algorithm
91 raises a :exc:`NetworkXError` if the input graph is not a DAG.
92
93 NodeNotFound
94 If any of the input nodes are not found in the graph,
95 a :exc:`NodeNotFound` exception is raised.
96
97 """
98
99 if not nx.is_directed_acyclic_graph(G):
100 raise nx.NetworkXError("graph should be directed acyclic")
101
102 union_xyz = x.union(y).union(z)
103
104 if any(n not in G.nodes for n in union_xyz):
105 raise nx.NodeNotFound("one or more specified nodes not found in the graph")
106
107 G_copy = G.copy()
108
109 # transform the graph by removing leaves that are not in x | y | z
110 # until no more leaves can be removed.
111 leaves = deque([n for n in G_copy.nodes if G_copy.out_degree[n] == 0])
112 while len(leaves) > 0:
113 leaf = leaves.popleft()
114 if leaf not in union_xyz:
115 for p in G_copy.predecessors(leaf):
116 if G_copy.out_degree[p] == 1:
117 leaves.append(p)
118 G_copy.remove_node(leaf)
119
120 # transform the graph by removing outgoing edges from the
121 # conditioning set.
122 edges_to_remove = list(G_copy.out_edges(z))
123 G_copy.remove_edges_from(edges_to_remove)
124
125 # use disjoint-set data structure to check if any node in `x`
126 # occurs in the same weakly connected component as a node in `y`.
127 disjoint_set = UnionFind(G_copy.nodes())
128 for component in nx.weakly_connected_components(G_copy):
129 disjoint_set.union(*component)
130 disjoint_set.union(*x)
131 disjoint_set.union(*y)
132
133 if x and y and disjoint_set[next(iter(x))] == disjoint_set[next(iter(y))]:
134 return False
135 else:
136 return True