Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ENH] Add ability to determine whether an inducing path exists between two nodes #78

Merged
merged 36 commits into from
Jun 21, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
8b7a1b6
Add function definition for inducing path algorithm
aryan26roy May 22, 2023
317c0a2
Changed function definition and handled multiple graph types as inputs
aryan26roy May 24, 2023
827f770
Add helper functions and test
aryan26roy May 25, 2023
6c3b2f9
Add some more unit tests
aryan26roy May 26, 2023
9d74925
Add implementation
aryan26roy May 26, 2023
cd5cb51
Fixed bugs in tests and implementation
aryan26roy May 26, 2023
e0f1e47
Update Changelog
aryan26roy May 26, 2023
a6ab296
Merge branch 'main' into inducing_path
aryan26roy May 26, 2023
3eec132
Fixed docstring
aryan26roy May 26, 2023
8a413f1
Fixed default arguments
aryan26roy May 26, 2023
ae6cad4
Added unit tests covering corner cases
aryan26roy May 26, 2023
641f06d
Update pywhy_graphs/algorithms/generic.py
aryan26roy Jun 1, 2023
c5255e1
Update pywhy_graphs/algorithms/generic.py
aryan26roy Jun 1, 2023
1aa0c59
Update pywhy_graphs/algorithms/generic.py
aryan26roy Jun 1, 2023
13fe758
Added some tests and incoporated review suggestions
aryan26roy Jun 1, 2023
efa2837
changed docstrings
aryan26roy Jun 1, 2023
30f7a91
Clarified return type
aryan26roy Jun 1, 2023
799bfa3
Corrected implementation
aryan26roy Jun 6, 2023
2282aa7
linting
aryan26roy Jun 6, 2023
f71670a
Changed ancestor check in implementation
aryan26roy Jun 6, 2023
bd658b8
Patched is_collider bug and updated function names
aryan26roy Jun 10, 2023
f3ed973
Updated docstring
aryan26roy Jun 10, 2023
3b7a0de
Patched ancestors bug
aryan26roy Jun 11, 2023
529a2a6
Fixed docstring
aryan26roy Jun 11, 2023
1f9ddac
Linting
aryan26roy Jun 11, 2023
433fd22
Fix docstring
aryan26roy Jun 11, 2023
a4c34d9
Update pywhy_graphs/algorithms/generic.py
aryan26roy Jun 13, 2023
cb8d57a
Linting
aryan26roy Jun 13, 2023
b23a6bd
Fixed typing
aryan26roy Jun 17, 2023
2cabbfc
Added a minimal working example
aryan26roy Jun 17, 2023
0568b4c
Linting
aryan26roy Jun 17, 2023
2919d13
Added refrences section in docstring
aryan26roy Jun 18, 2023
f8eef02
Fix circleCI issues
adam2392 Jun 20, 2023
ead96b0
Merge branch 'main' into inducing_path
adam2392 Jun 20, 2023
81dbd69
Compelte merge
adam2392 Jun 20, 2023
83852da
Removed inducing path example
aryan26roy Jun 21, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions pywhy_graphs/algorithms/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"is_node_common_cause",
"set_nodes_as_latent_confounders",
"is_valid_mec_graph",
"inducing_path",
]


Expand Down Expand Up @@ -333,3 +334,84 @@ def _single_shortest_path_early_stop(G, firstlevel, paths, cutoff, join, valid_p
nextlevel[w] = 1
level += 1
return paths


def _find_directed_parents(G, node):
"""Finds the parents of a node in the directed and the bidirected subgraphs.
aryan26roy marked this conversation as resolved.
Show resolved Hide resolved

Args:
G : Graph
The graph.
node : node label
The node for which we have to find the parents.

Returns:
out : set
The parents of the provided node.
"""
bidirected_parents = set(G.sub_bidirected_graph().neighbors(node))
directed_parents = set(G.sub_directed_graph().predecessors(node))

out = bidirected_parents.union(directed_parents)

return out


def _find_directed_children(G, node):
aryan26roy marked this conversation as resolved.
Show resolved Hide resolved
"""Finds the children of a node in the directed and the bidirected subgraphs.

Args:
G : Graph
The graph.
node : node label
The node for which we have to find the children.

Returns:
out : set
The children of the provided node.
"""
bidirected_children = set(G.sub_bidirected_graph().neighbors(node))
directed_children = set(G.sub_directed_graph().successors(node))

out = bidirected_children.union(directed_children)

return out


def inducing_path(G, node_x, node_y, L=None, S=None):
"""Checks if an inducing path exists between node_x and node_y and if it does returns it.

aryan26roy marked this conversation as resolved.
Show resolved Hide resolved
Args:
G : Graph
The graph.
node_x : node
The source node.
node_y : node
The destination node.
L : set
The set containing every non-collider on the path.
S: set
The set containing every collider on the path that is not an ancestor of the endpoints.


Returns:
path : Tuple[bool, path]
A tuple containing a bool and a path if the bool is true.
aryan26roy marked this conversation as resolved.
Show resolved Hide resolved
"""
aryan26roy marked this conversation as resolved.
Show resolved Hide resolved

graph = ADMG()

graph.add_edges_from(G.sub_directed_graph())

if not isinstance(G, CPDAG):
graph.add_edges_from(G.sub_bidirected_graph())

nodes = graph.nodes

if node_x not in nodes or node_y not in nodes:
raise ValueError("The provided nodes are not in the graph.")

path = [] # this will contain the path.
path.append(node_x)

return (False, [])
51 changes: 51 additions & 0 deletions pywhy_graphs/algorithms/tests/test_generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import pytest

import pywhy_graphs
from pywhy_graphs import ADMG


def test_convert_to_latent_confounder_errors():
Expand Down Expand Up @@ -39,3 +40,53 @@ def test_convert_to_latent_confounder(graph_func):
G.remove_edge(3, 1, G.bidirected_edge_name)
G.add_edge(3, 1, G.directed_edge_name)
assert pywhy_graphs.is_node_common_cause(G, 3)


def test_inducing_path():
aryan26roy marked this conversation as resolved.
Show resolved Hide resolved

admg = ADMG()

admg.add_edge("X", "Y", admg.directed_edge_name)
admg.add_edge("z", "Y", admg.bidirected_edge_name)
admg.add_edge("z", "H", admg.bidirected_edge_name)

# X -> Y <-> z <-> H

S = {"Y", "Z"}
L = {}
assert pywhy_graphs.inducing_path(admg, "X", "H", L, S)[0]

admg.add_edge("H", "J", admg.directed_edge_name)

# X -> Y <-> z <-> H -> J

S = {"Y", "Z"}
L = {"H"}

assert pywhy_graphs.inducing_path(admg, "X", "J", L, S)[0]

admg.add_edge("K", "J", admg.directed_edge_name)

# X -> Y <-> z <-> H -> J <- K
S = {"Y", "Z", "J"}
L = {"H"}

assert not pywhy_graphs.inducing_path(admg, "X", "K", L, S)[0] # no directed path exists

admg.add_edge("J", "K", admg.directed_edge_name)

# X -> Y <-> z <-> H -> J <-> K

S = {"Y", "Z"}
L = {"H"}

assert not pywhy_graphs.inducing_path(admg, "X", "K", L, S)[
0
] # A collider on the path is not in S

S = {"Y", "Z"}
L = {}

assert not pywhy_graphs.inducing_path(admg, "X", "K", L, S)[
0
] # A non-collider on the path is not in S