Skip to content

Commit efeeeb0

Browse files
committed
Add implementation of is_outcome_ancestor
1 parent a51345a commit efeeeb0

File tree

8 files changed

+71
-1
lines changed

8 files changed

+71
-1
lines changed

.idea/.gitignore

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/inspectionProfiles/profiles_settings.xml

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/misc.xml

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/modules.xml

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/vcs.xml

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/y0.iml

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/y0/controls.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
"""Predicates for good, bad, and neutral controls."""
44

5+
from .algorithm.conditional_independencies import are_d_separated
56
from .dsl import Probability, Variable
67
from .graph import NxMixedGraph
78

@@ -44,3 +45,27 @@ def is_bad_control(graph: NxMixedGraph, query: Probability, variable: Variable)
4445
"""
4546
_control_precondition(graph, query, variable)
4647
raise NotImplementedError
48+
49+
50+
def is_outcome_ancestor(
51+
graph: NxMixedGraph, cause: Variable, effect: Variable, variable: Variable
52+
) -> bool:
53+
"""Check if the variable is an outcome ancestor given a causal query and graph.
54+
55+
> In Model 8, Z is not a confounder nor does it block any back-door paths. Likewise,
56+
controlling for Z does not open any back-door paths from X to Y . Thus, in terms of
57+
asymptotic bias, Z is a “neutral control.” Analysis shows, however, that controlling for
58+
Z reduces the variation of the outcome variable Y , and helps to improve the precision
59+
of the ACE estimate in finite samples (Hahn, 2004; White and Lu, 2011; Henckel et al.,
60+
2019; Rotnitzky and Smucler, 2019).
61+
62+
:param graph: An ADMG
63+
:param cause: The intervention in the causal query
64+
:param effect: The outcome of the causal query
65+
:param variable: The variable to check
66+
:return: If the variable is a bad control
67+
"""
68+
if variable == cause:
69+
return False
70+
judgement = are_d_separated(graph, cause, variable)
71+
return judgement.separated and variable in graph.ancestors_inclusive(effect)

tests/test_controls.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
import unittest
66

7-
from y0.controls import is_bad_control, is_good_control
7+
from y0.controls import is_bad_control, is_good_control, is_outcome_ancestor
88
from y0.dsl import U1, U2, A, M, P, U, W, X, Y, Z
99
from y0.graph import NxMixedGraph
1010

@@ -95,3 +95,7 @@ def test_bad_controls(self):
9595
for model in bad_test_models:
9696
with self.subTest():
9797
self.assertTrue(is_bad_control(model, P(Y @ X), Z))
98+
99+
def test_neutral_controls(self):
100+
"""Test neutral controls."""
101+
self.assertTrue(is_outcome_ancestor(model_8, X, Y, Z))

0 commit comments

Comments
 (0)