def test_asia_graph_dsep(asia_graph): """Example-based test of d-separation for asia_graph.""" assert nx.d_separated( asia_graph, {"asia", "smoking"}, {"dyspnea", "xray"}, {"bronchitis", "either"} ) assert nx.d_separated( asia_graph, {"tuberculosis", "cancer"}, {"bronchitis"}, {"smoking", "xray"} )
def test_cyclic_graphs_raise_error(): """ Test that cycle graphs should cause erroring. This is because PGMs assume a directed acyclic graph. """ with pytest.raises(nx.NetworkXError): g = nx.cycle_graph(3, nx.DiGraph) nx.d_separated(g, {0}, {1}, {2})
def test_undirected_graphs_are_not_supported(): """ Test that undirected graphs are not supported. d-separation does not apply in the case of undirected graphs. """ with pytest.raises(nx.NetworkXNotImplemented): g = nx.path_graph(3, nx.Graph) nx.d_separated(g, {0}, {1}, {2})
def test_markov_condition(graph): """Test that the Markov condition holds for each PGM graph.""" for node in graph.nodes: parents = set(graph.predecessors(node)) non_descendants = graph.nodes - nx.descendants(graph, node) - {node} - parents assert nx.d_separated(graph, {node}, non_descendants, parents)
def d_separated(self, x: Sequence[str], y: Sequence[str], s: Optional[Sequence[str]] = None): """ Checks if all variables in X are d-separated from all variables in Y by the variables in S. Parameters ---------- x: Sequence, First set of nodes in DAG. y: Sequence, Second set of nodes in DAG. s: Sequence (optional), Set of conditioning nodes in DAG. Returns ------- bool, A boolean indicating whether x is d-separated from y by s. """ return nx.d_separated(self.dag, set(x), set(y), set(s) if s is not None else set())
def is_d_separated(self, X: Iterable[str], Y: Iterable[str], Z: Optional[Iterable[str]] = None) -> bool: X, Y, Z = _as_set(X), _as_set(Y), _as_set(Z) if X & Z or Y & Z: return True return nx.d_separated(self._G, X, Y, Z)
def computeDependencies(self, order): deps = [] nodes = list(self.g.nodes()) nodes.sort() #print('nodes = ', nodes, ', order = ', order) cNodes = self.getCombinations(nodes, order) for i in range(len(nodes)): node1 = nodes[i] if not self.rvDict[node1].isObserved: continue for j in range(i, len(nodes)): node2 = nodes[j] if node1 == node2 or not self.rvDict[node2].isObserved: continue isAdjacent = self.isAdjacent(node1, node2) isSeparated = not isAdjacent and networkx.d_separated( self.g, {node1}, {node2}, {}) dep = self.makeDependency(node1, node2, None, not isSeparated) deps.append(dep) for c in cNodes: #print('cNodes = ', cNodes) if node1 in c or node2 in c: continue # Verify that every member of c is observed. If not, we skip this combo. allObserved = True for m in c: if not self.rvDict[m].isObserved: allObserved = False break if not allObserved: continue isSeparated = not isAdjacent and networkx.d_separated( self.g, {node1}, {node2}, set(c)) dep = self.makeDependency(node1, node2, list(c), not isSeparated) deps.append(dep) #print('deps = ', deps) return deps
def test_fork_graph_dsep(fork_graph): """Example-based test of d-separation for fork_graph.""" assert nx.d_separated(fork_graph, {1}, {2}, {0}) assert not nx.d_separated(fork_graph, {1}, {2}, {})
def test_path_graph_dsep(path_graph): """Example-based test of d-separation for path_graph.""" assert nx.d_separated(path_graph, {0}, {2}, {1}) assert not nx.d_separated(path_graph, {0}, {2}, {})
def test_invalid_nodes_raise_error(asia_graph): """ Test that graphs that have invalid nodes passed in raise errors. """ with pytest.raises(nx.NodeNotFound): nx.d_separated(asia_graph, {0}, {1}, {2})
def test_asia_graph_dsep(asia_graph): """Example-based test of d-separation for asia_graph.""" assert nx.d_separated(asia_graph, {'asia', 'smoking'}, {'dyspnea', 'xray'}, {'bronchitis', 'either'}) assert nx.d_separated(asia_graph, {'tuberculosis', 'cancer'}, {'bronchitis'}, {'smoking', 'xray'})
def test_naive_bayes_dsep(naive_bayes_graph): """Example-based test of d-separation for naive_bayes_graph.""" for u, v in combinations(range(1, 5), 2): assert nx.d_separated(naive_bayes_graph, {u}, {v}, {0}) assert not nx.d_separated(naive_bayes_graph, {u}, {v}, {})
def test_collider_graph_dsep(collider_graph): """Example-based test of d-separation for collider_graph.""" assert nx.d_separated(collider_graph, {0}, {1}, {}) assert not nx.d_separated(collider_graph, {0}, {1}, {2})
def findFrontdoorBlockingSet(self, source, target): cacheKey = (source, target) if cacheKey in self.fdCache.keys(): return self.fdCache[cacheKey] backdoorSet = self.findBackdoorBlockingSet(source, target) maxBlocking = 2 bSet = [] # Create a graph view that removes the direct link from the source to the destination def includeEdge(s, d): #print('source, dest = ', s, d) if s == source and d == target: return False return True pathNodes = {} vg = networkx.subgraph_view(self.g, filter_edge=includeEdge) # Use that view to find all indirect paths from source to dest paths0 = networkx.all_simple_paths(vg, source, target) paths = [path for path in paths0] #print('paths = ', paths) if len(paths) == 0: # No indirect paths return [] for path in paths: #print('path = ', path) # Remove the first and last node of the path -- always the source and target intermediates = path[1:-1] #print('int = ', intermediates) for i in intermediates: if i not in pathNodes: pathNodes[i] = 1 else: pathNodes[i] += 1 pathTups = [] # First look for single node solutions for node in pathNodes.keys(): cnt = pathNodes[node] outTup = (cnt, node) pathTups.append(outTup) # Sort the nodes in descending order of the number of paths containing it pathTups.sort() pathTups.reverse() combos = [(tup[1], ) for tup in pathTups] #print('pathNodes = ', pathNodes.keys()) # Now add any multiple field combinations. Order is not significant here. multiCombos = self.getCombinations(pathNodes.keys(), maxBlocking, minOrder=2) combos += multiCombos #print('combos = ', combos) for nodeSet in combos: testSet = set(list(nodeSet) + list(backdoorSet)) #print('testSet = ', list(testSet)) if networkx.d_separated(vg, {source}, {target}, testSet): bSet = list(nodeSet) break print('FDblocking = ', bSet) self.fdCache[cacheKey] = bSet return bSet
def findBackdoorBlockingSet(self, source, target): """ Find the minimal set of nodes that block all backdoor paths from source to target. """ cacheKey = (source, target) if cacheKey in self.bdCache.keys(): return self.bdCache[cacheKey] maxBlocking = 3 bSet = [] # find all paths from parents of source to target. parents = self.getParents(source) #print('parents = ', parents) # Create a graph view that removes the links from the source to its parents def includeEdge(s, d): #print('source, dest = ', s, d) if d == source: return False return True pathNodes = {} vg = networkx.subgraph_view(self.g, filter_edge=includeEdge) for parent in parents: paths = networkx.all_simple_paths(vg, parent, target) #print('paths = ', [path for path in paths]) for path in paths: #print('path = ', path) # Remove the last node of the path -- always the target intermediates = path[:-1] #print('int = ', intermediates) for i in intermediates: if i not in pathNodes: pathNodes[i] = 1 else: pathNodes[i] += 1 pathTups = [] # First look for single node solutions for node in pathNodes.keys(): cnt = pathNodes[node] outTup = (cnt, node) pathTups.append(outTup) # Sort the nodes in descending order of the number of paths containing it pathTups.sort() pathTups.reverse() combos = [(tup[1], ) for tup in pathTups] #print('pathNodes = ', pathNodes.keys()) # Now add any multiple field combinations. Order is not significant here. multiCombos = self.getCombinations(pathNodes.keys(), maxBlocking, minOrder=2) combos += multiCombos #print('combos = ', combos) for nodeSet in combos: testSet = set(nodeSet) #print('testSet = ', list(testSet)) if networkx.d_separated(self.g, set(parents), {target}, testSet): bSet = list(testSet) break print('BDblocking = ', bSet) self.bdCache[cacheKey] = bSet return bSet
def IDC(y, x, z, P, G, orderlist, hiddvar, **kwargs): ''' Function implement the ID algorihtm as presented in Pearl 2006b "Identification of Conditional Interventional Distributions" (https://ftp.cs.ucla.edu/pub/stat_ser/r329-uai.pdf) Parameters ---------- y : array of viarables on which measures the effect of intervention x : array of variables on which the intervention (do) is done z : array containing condition set P : probability structure defined as the output of function probabilitystr G : netwrokx object orderlist : topological order hiddvar : set of hidden (confonuders) nodes **kwargs : dictionary with following jey debug : if True message are print Raises ------ ValueError: if the causal effect is not identifiable then the function raise an error Returns ------- probability structure that define the identification of causal effect ''' debug = kwargs["debug"] #Transform array y, z and z in array y_set = set(list(y)) if all(x) != "": x_set = set(list(x)) #Transform z in set z_set = set(list(z)) #Get graph without incoming edge on x and outgoing edge to z G_under_x_bar_z = incomingedgedel(x, outcomeedgedel(z, G)) for zi in z: c_set = x_set.copy() c_set | (z_set - {zi}) if nx.d_separated(G_under_x_bar_z, y_set, {zi}, c_set): if debug: print("> Iterate over IDC: Z variable is " + str(zi)) P_numerator = IDC(y, np.union1d(x, np.array([zi])), np.setdiff1d(z, np.array([zi])), P, G, orderlist, hiddvar, **kwargs) return P_numerator if debug: print("> Iterate over ID") P_numerator = ID(np.union1d(y, z), x, P, G, orderlist, hiddvar, **kwargs) return P_numerator