Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
124 commits
Select commit Hold shift + click to select a range
3c27650
functions for creating and querying HCMs with pygraphviz
Sep 5, 2024
3975ba4
functionality to collapse phygraphviz HCMs to nxmixedgraphs
Sep 5, 2024
134202c
added __all__
Sep 5, 2024
e643395
added test_hierarchical
Sep 5, 2024
4079a0d
pygraphviz dep and lint exception for hierarchical in pyproject
Sep 6, 2024
e8fb669
auto lint hierarchical
Sep 6, 2024
c76a6f9
auto lint test_hierarchical
Sep 6, 2024
9134417
added docstrings to hierarchical.py
Sep 6, 2024
833ed32
added docstrings to test_hierarchical.py
Sep 6, 2024
6828af3
more ignores in pyproject.toml
Sep 6, 2024
06796ea
imperative docstrings
Sep 6, 2024
f7b6da8
cleaned whitespace
Sep 6, 2024
61fcfc1
lint HCM_from_lists
Sep 6, 2024
63070c0
more linting
Sep 6, 2024
7fa1594
added typing to hierarchical.py
Sep 6, 2024
f36ad10
added typing to test_hierarchical.py
Sep 6, 2024
0709527
HCM notebook with Fig 2 up to collapsed models
Sep 11, 2024
cfed46d
lint fixes
Sep 11, 2024
5e132c9
docstring lint
Sep 11, 2024
7390162
test fixtures for Fig 2 augmented models
Sep 12, 2024
609cd42
added augment_collapsed_model function that implements Algorithm 2
Sep 12, 2024
eb1bdda
tox formatting
Sep 12, 2024
69d40ff
tests for augmentation and formatting
Sep 12, 2024
1a8fdf3
added augmented models to notebook
Sep 12, 2024
64dbada
add marginalize function implementing Algorithm 3
Sep 12, 2024
cce08cd
Instrument test for algorithm 3
Sep 12, 2024
07e1ef6
Clean up literals
cthoyt Sep 13, 2024
dc6124b
update collapse_HCM to preserve unit-level edges
Sep 13, 2024
57f975d
update augment and marginalize functions to return new objects instea…
Sep 13, 2024
5613ddf
added copy_HCM
Sep 17, 2024
dee780e
Fig 2 fixture tests for copy_HCM
Sep 17, 2024
cf96bed
added convert_to_HCGM
Sep 17, 2024
bfc5872
tests for convert to HCGM
Sep 18, 2024
82f899b
minor lint
Sep 18, 2024
6393862
update HCGM styles for consistency with observed vars
Sep 18, 2024
fb0148e
update convert_to_HCGM to handle unobserved Q vars
Sep 18, 2024
a92f76d
add functions to parse semiMarkov edges from HCMs with latent variables
Sep 18, 2024
5b43cdb
updated collapse_HCM with intermediate convert_to_HCGM; follows paper…
Sep 18, 2024
21dc386
lint and fix bug on direct_unit_descendents
Sep 19, 2024
018e949
bug fix augment_collapsed_model
Sep 25, 2024
64ba48f
added sorting to create_Qvar
Sep 25, 2024
d31d86d
update test with sorted Qvar
Sep 25, 2024
3df16be
Update hierarchical.py
cthoyt Oct 1, 2024
f70a9c0
Add type checking for tests
cthoyt Oct 1, 2024
0110442
Update test_hierarchical.py
cthoyt Oct 1, 2024
1ec56b5
Update tox.ini
cthoyt Oct 2, 2024
2258732
function and unit tests for getting ancestors
Nov 12, 2024
d77ef55
linted
Nov 12, 2024
1ec241e
mid update of augmentation mechanism, previous tests modified and pas…
Nov 12, 2024
88c99b8
tests for augmentation_mechanism
Nov 12, 2024
91bd0f4
update augmentation_mechanism to use direct subunit descendents rathe…
Nov 13, 2024
ca5de3c
test aug mech for complicated subunit graph HCM
Nov 13, 2024
f0be28f
update augment_collapsed_model
Nov 14, 2024
9f6aca8
commit before rebase
Nov 14, 2024
e78c18e
added subunit_graph and fixed bug in collapse_HCM
Nov 22, 2024
b6c65e6
fixed bug in marginalize
Nov 22, 2024
c0e399f
add demote_Qvar
Dec 5, 2024
ddb6158
update HCM notebook with all Figs from paper
Dec 5, 2024
0b440b0
update HCM images
Dec 5, 2024
1778c81
add identification to HCM nb
Dec 6, 2024
65e6469
add parse_Qvar
Jan 8, 2025
c242cda
generalize augmentation code
Jan 9, 2025
a1d54fc
update augmentation tests
Jan 9, 2025
72b566e
add tests for parse_Qvar
Jan 9, 2025
ac347e1
add more tests for subunit_graph and parse_Qvar
Jan 23, 2025
8f5005a
add convert_to_HSCM
Jan 23, 2025
a3550cd
Refactor
cthoyt Feb 3, 2025
e0a945f
Switch fully to nx from pygraphviz
cthoyt Feb 3, 2025
529dfc7
Refactor while loops
cthoyt Feb 3, 2025
1c18daf
Cleanup typing
cthoyt Feb 3, 2025
1d96ea6
Remove warnings
cthoyt Feb 3, 2025
73659a7
Clean up variable names
cthoyt Feb 3, 2025
3483304
Reuse some example graphs
cthoyt Feb 3, 2025
6827b85
Update Hierarchical Causal Models.ipynb
cthoyt Feb 4, 2025
a6da4e4
Keep track of stochastic variables
cthoyt Feb 7, 2025
6990df3
minor commit before pull
Feb 5, 2025
cf08dfa
merge nb
Feb 19, 2025
036c531
finish nb merge
Feb 19, 2025
d72ae02
change HCM attribute name stochastic to deterministic
Feb 19, 2025
c0c1b30
fix to_hscm and update to_pygraphviz for exogenous variables
Feb 19, 2025
91bcf5f
add collapse_hcm_function
Feb 21, 2025
26de0c0
refactor with HSCM subclass
Feb 27, 2025
369c960
add HSCM examples
Feb 27, 2025
37b433c
add tests for HCM.to_hscm and HSCM.to_hcm
Feb 27, 2025
9d9214e
lint
Feb 27, 2025
8e5f2ce
minor lint and bug fixes
Feb 27, 2025
5be8134
minor bug fix for collapse_hcm
Feb 27, 2025
7c400c7
trying fix for mypy bug
Feb 28, 2025
bbd7e8f
lint
Feb 28, 2025
2a16fb4
try again
Feb 28, 2025
6319378
minor clean
Feb 28, 2025
cd95f2b
Merge branch 'main' into HCM-fig2
cthoyt Mar 1, 2025
3d2a39a
Update tox.ini
cthoyt Mar 1, 2025
7006267
raise exception if collapsing HCM with unobserved subunits
Mar 5, 2025
ee87ec5
allow strings for marginal_parents in marginalize_augmented_model
Mar 5, 2025
4ccd6ff
minor clean to HCM nb
Mar 5, 2025
9d9563f
add tests for augment_collapsed_model and lint
Mar 5, 2025
b25a8a8
switch Q variables to upper case to match paper
Mar 13, 2025
b9a8918
Update index.rst
cthoyt Mar 14, 2025
4fe51fb
Merge branch 'main' into HCM-fig2
cthoyt Mar 14, 2025
60fb658
Ruff
cthoyt Mar 14, 2025
0633c6f
Update tests.yml
cthoyt Mar 14, 2025
c59a7f9
rename current HCM nb
Mar 17, 2025
a80607d
add HCM nb
Mar 17, 2025
4dafe73
lint
Mar 17, 2025
478d8fc
update HCM Figs nb with new Q variable format
Mar 18, 2025
b9756ff
Update Hierarchical Causal Models.ipynb
cthoyt Mar 18, 2025
e7ed6fd
Cleanup unused code
cthoyt Mar 18, 2025
8811ed0
Rerun
cthoyt Mar 18, 2025
ae91b8b
Started the process of converting unit-level and subunit-level variab…
djinnome Jun 11, 2025
3864813
Merge branch 'main' into HCM-fig2
cthoyt Jun 20, 2025
4c3926a
Update notebook
cthoyt Jun 20, 2025
428be21
Renames
cthoyt Jun 20, 2025
f485b00
Update README.md
cthoyt Jun 21, 2025
efa354c
Focusing the narrative on test scores in schools
djinnome Sep 18, 2025
93d855d
Added narrative to Hierarchical Causal Models
Sep 24, 2025
539eb8d
more small Narrative changes
Sep 24, 2025
a0e5c1b
Show Identifiability before technical discussion
Sep 24, 2025
8517e3b
Cleanup markdown code statements. Make descriptions less technical
Sep 24, 2025
50b205f
Update Counterfactual Transportability edited.ipynb
cthoyt Nov 14, 2025
17e5ed8
Merge branch 'main' into HCM-fig2
cthoyt Nov 14, 2025
17d1323
Changed explanation of Collapse
Nov 19, 2025
edaa0c7
Resolved merge conflict in Hierarchical Causal Models.ipynb
Nov 19, 2025
a9753a7
Rewrite the Augment section to be more readable
Dec 8, 2025
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
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ jobs:

- name: Install dependencies
run: |
sudo apt-get install graphviz
sudo apt-get install graphviz libgraphviz-dev
- name: Test with pytest and generate coverage file
run:
uvx -p ${{ matrix.python-version }} --with tox-uv tox -e py
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ unimplemented for the last 15 years of publications including:
| IDC Star | [Shpitser and Pearl, 2012](https://arxiv.org/abs/1206.5294) |
| Surrogate Outcomes | [Tikka and Karvanen, 2018](https://arxiv.org/abs/1806.07172) |
| Counterfactual Transportability | [Correia, Lee, Bareinboim, 2022](https://proceedings.mlr.press/v162/correa22a.html) |
| Hierarchical Causal Models | [Weinstein and Blei, 2024](https://arxiv.org/abs/2401.05330) |

Apply an algorithm to an Acyclic Directed Mixed Graph (ADMG) and a causal query
to generate an estimand represented in the DSL like:
Expand Down
3 changes: 3 additions & 0 deletions docs/source/hierarchical.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Hierarchical Graphs
===================
.. automodapi:: y0.hierarchical
1 change: 1 addition & 0 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ y0 |release| Documentation
tian_id
transport
lvdags
hierarchical
struct
cli

Expand Down
1,585 changes: 1,585 additions & 0 deletions notebooks/Counterfactual Transportability edited.ipynb

Large diffs are not rendered by default.

10,229 changes: 10,229 additions & 0 deletions notebooks/HCM Manuscript Figures.ipynb

Large diffs are not rendered by default.

1,503 changes: 1,503 additions & 0 deletions notebooks/Hierarchical Causal Models.ipynb

Large diffs are not rendered by default.

227 changes: 227 additions & 0 deletions src/y0/examples/hierarchical.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
"""An example directory of hierarchical causal models."""

from y0.dsl import A, B, C, D, U, Y, Z
from y0.graph import NxMixedGraph
from y0.hierarchical import HierarchicalCausalModel, HierarchicalStructuralCausalModel, QVariable

__all__ = [
"confounder_hcm",
]

Q_A = QVariable(name="A")
Q_Y = QVariable(name="Y")
Q_Y_A = QVariable(name="Y", parents=frozenset([A]))
Q_A_Z = QVariable(name="A", parents=frozenset([Z]))
Q_Z = QVariable(name="Z")
Q_Y_A_Z = QVariable(name="Y", parents=frozenset([A, Z]))


def _make_confounder_hcm() -> HierarchicalCausalModel:
"""Pytest fixture for the Confounder HCM in Figure 2 (a)."""
graph = HierarchicalCausalModel()
graph.add_observed_node(A)
graph.add_observed_node(Y)
graph.add_edge(U, A)
graph.add_edge(A, Y)
graph.add_edge(U, Y)
graph.add_subunits([A, Y])
return graph


confounder_hcm = _make_confounder_hcm()


def get_confounder_hcgm() -> HierarchicalCausalModel:
"""Pytest fixture for the Confounder HCGM in Figure 2 (b)."""
hcm = HierarchicalCausalModel()
hcm.add_observed_node(A)
hcm.add_observed_node(Y)
hcm.add_observed_node(Q_A)
hcm.add_observed_node(Q_Y_A)
hcm.add_edge(U, Q_A)
hcm.add_edge(Q_A, A)
hcm.add_edge(A, Y)
hcm.add_edge(U, Q_Y_A)
hcm.add_edge(Q_Y_A, Y)
hcm.add_subunits([A, Y])
return hcm


def get_confounder_hscm() -> HierarchicalStructuralCausalModel:
"""Pytest fixture for Confounder HSCM in Section 2.3 Eqn. (8)."""
hscm = HierarchicalStructuralCausalModel()
hscm.add_observed_node(A)
hscm.add_observed_node(Y)
hscm.add_unobserved_node(U)
hscm.add_edge(U, A)
hscm.add_edge(A, Y)
hscm.add_edge(U, Y)
hscm.add_subunits([A, Y])
return hscm


def get_school_confounder_interference_hcm() -> HierarchicalCausalModel:
"""Pytest fixture for the Confounder Interference HCM in Figure 2 (e)."""
hcm = HierarchicalCausalModel()
hcm.add_observed_node(A)
hcm.add_observed_node(Y)
hcm.add_observed_node(Z)
hcm.add_edge(U, A)
hcm.add_edge(A, Y)
hcm.add_edge(U, Y)
hcm.add_edge(A, Z)
hcm.add_edge(Z, Y)
hcm.add_subunits([A, Y])
return hcm


def get_school_confounder_interference_hcgm() -> HierarchicalCausalModel:
"""Pytest School fixture for the Confounder Interference HCGM in Hierarchical Causal Models notebook."""
hcm = HierarchicalCausalModel()
hcm.add_observed_node(A)
hcm.add_observed_node(Y)
hcm.add_observed_node(Z)
hcm.add_observed_node(Q_A)
hcm.add_observed_node(Q_Y_A)
hcm.add_edge(U, Q_A)
hcm.add_edge(Q_A, A)
hcm.add_edge(A, Y)
hcm.add_edge(U, Q_Y_A)
hcm.add_edge(Q_Y_A, Y)
hcm.add_edge(A, Z)
hcm.add_edge(Z, Q_Y_A)
hcm.add_subunits([A, Y])
return hcm


def get_school_confounder_interference_hscm() -> HierarchicalStructuralCausalModel:
"""Pytest fixture for the Confounder Interference HSCM in Sec. 2.3 Eqn. (9)."""
student_tutoring, student_test_score, school_budget = A, Y, U
hscm = HierarchicalStructuralCausalModel()
hscm.add_observed_node(student_tutoring)
hscm.add_observed_node(student_tutoring)
hscm.add_observed_node(student_test_score)
hscm.add_unobserved_node(school_budget)
hscm.add_edge(school_budget, student_tutoring)
hscm.add_edge(student_tutoring, student_tutoring)
hscm.add_edge(school_budget, student_tutoring)
hscm.add_edge(student_tutoring, student_test_score)
hscm.add_edge(student_test_score, student_tutoring)
hscm.add_subunits([student_tutoring, student_tutoring])
return hscm


def get_instrument_hcm() -> HierarchicalCausalModel:
"""Pytest fixture for the Instrument HCM in Figure 2 (i)."""
hcm = HierarchicalCausalModel()
hcm.add_observed_node(A)
hcm.add_observed_node(Y)
hcm.add_observed_node(Z)
hcm.add_edge(U, A)
hcm.add_edge(A, Y)
hcm.add_edge(U, Y)
hcm.add_edge(Z, A)
hcm.add_subunits([A, Z])
return hcm


def get_instrument_subunit_graph() -> HierarchicalCausalModel:
"""Pytest fixture for the Instrument HCM subunit graph."""
subg = HierarchicalCausalModel()
subg.add_observed_node(A)
subg.add_observed_node(Z)
subg.add_edge(Z, A)
return subg


def get_instrument_hcgm() -> HierarchicalCausalModel:
"""Pytest fixture for the Instrument HCGM in Figure 2(j)."""
hcm = HierarchicalCausalModel()
hcm.add_observed_node(A)
hcm.add_observed_node(Y)
hcm.add_observed_node(Z)
hcm.add_observed_node(Q_Z)
hcm.add_observed_node(Q_A_Z)
hcm.add_edge(U, Q_A_Z)
hcm.add_edge(Q_A_Z, A)
hcm.add_edge(Q_Z, Z)
hcm.add_edge(A, Y)
hcm.add_edge(U, Y)
hcm.add_edge(Z, A)
hcm.add_subunits([A, Z])
return hcm


def get_instrument_hscm() -> HierarchicalStructuralCausalModel:
"""Pytest fixture for the Instrument HSCM in Section 2.3 Eqn. (10)."""
hscm = HierarchicalStructuralCausalModel()
hscm.add_observed_node(A)
hscm.add_observed_node(Y)
hscm.add_observed_node(Z)
hscm.add_unobserved_node(U)
hscm.add_edge(U, A)
hscm.add_edge(A, Y)
hscm.add_edge(U, Y)
hscm.add_edge(Z, A)
hscm.add_subunits([A, Z])
return hscm


def get_compl_subgraph_hcm() -> HierarchicalCausalModel:
"""Pytest fixture for HCM with complicated subgraph structure."""
hcm = HierarchicalCausalModel.from_lists(
observed_subunits=[A, B, C, Y],
observed_units=["D"],
unobserved_units=[U],
edges=[
(U, A),
(U, B),
(U, C),
(A, B),
(B, Y),
(C, Y),
(A, D),
(D, C),
],
)
return hcm


confounder_augmented_admg = NxMixedGraph.from_edges(
undirected=[(Q_A, Q_Y_A)], directed=[(Q_A, Q_Y), (Q_Y_A, Q_Y)]
)
"""Pytest fixture for augmented Confounder HCM in Figure 2 (d)."""


confounder_interference_augmented_admg = NxMixedGraph.from_edges(
undirected=[(Q_A, Q_Y_A)], directed=[(Q_A, Q_Y), (Q_Y_A, Q_Y), (Q_A, Z), (Z, Q_Y_A)]
)
"""Pytest fixture for augmented Confounder Interference HCM in Figure 2 (h)."""


instrument_augmented_admg = NxMixedGraph.from_edges(
undirected=[(Q_A_Z, Y)], directed=[(Q_A_Z, Q_A), (Q_Z, Q_A), (Q_A, Y)]
)
"""Pytest fixture for augmented Instrument HCM in Figure A2."""


instrument_marginalized_admg = NxMixedGraph.from_edges(
undirected=[(Q_A_Z, Y)], directed=[(Q_A_Z, Q_A), (Q_A, Y)]
)
"""Pytest fixture for augmented Instrument HCM in Figure 2 (l)."""


confounder_collapsed_admg = NxMixedGraph.from_edges(undirected=[(Q_A, Q_Y_A)])
"""Pytest fixture for collapsed Confounder HCM in Figure 2 (c)."""


confounder_interference_collapsed_admg = NxMixedGraph.from_edges(
undirected=[(Q_A, Q_Y_A)], directed=[(Q_A, Z), (Z, Q_Y_A)]
)
"""Pytest fixture for collapsed Confounder Interference HCM in Figure 2 (g)."""


instrument_collapsed_admg = NxMixedGraph.from_edges(
undirected=[(Q_A_Z, Y)], directed=[(Q_A_Z, Y), (Q_Z, Y)]
)
"""Pytest fixture for collapsed Instrument HCM in Figure 2 (k)."""
Loading
Loading