formative-ds 2.2.4__tar.gz → 2.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (299) hide show
  1. {formative_ds-2.2.4 → formative_ds-2.3.0}/.github/workflows/ci.yml +1 -1
  2. {formative_ds-2.2.4 → formative_ds-2.3.0}/CLAUDE.md +3 -3
  3. {formative_ds-2.2.4 → formative_ds-2.3.0}/Makefile +3 -3
  4. {formative_ds-2.2.4 → formative_ds-2.3.0}/PKG-INFO +1 -1
  5. {formative_ds-2.2.4 → formative_ds-2.3.0}/docs/design/policy_learning.md +3 -4
  6. {formative_ds-2.2.4 → formative_ds-2.3.0}/docs/index.rst +0 -1
  7. {formative_ds-2.2.4 → formative_ds-2.3.0}/formative/causal/estimators/_base.py +32 -0
  8. {formative_ds-2.2.4 → formative_ds-2.3.0}/formative/causal/estimators/_cate.py +2 -0
  9. {formative_ds-2.2.4 → formative_ds-2.3.0}/formative/causal/estimators/did.py +2 -1
  10. {formative_ds-2.2.4 → formative_ds-2.3.0}/formative/causal/estimators/iv.py +3 -9
  11. {formative_ds-2.2.4 → formative_ds-2.3.0}/formative/causal/estimators/matching.py +4 -20
  12. {formative_ds-2.2.4 → formative_ds-2.3.0}/formative/causal/estimators/ols.py +4 -21
  13. {formative_ds-2.2.4 → formative_ds-2.3.0}/formative/causal/estimators/policy.py +5 -29
  14. {formative_ds-2.2.4 → formative_ds-2.3.0}/formative/causal/estimators/rct.py +2 -1
  15. {formative_ds-2.2.4 → formative_ds-2.3.0}/formative/causal/estimators/rdd.py +7 -1
  16. {formative_ds-2.2.4 → formative_ds-2.3.0}/pyproject.toml +2 -3
  17. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/documentation_options.js +1 -1
  18. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/causal/benchmarks.html +2 -2
  19. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/causal/dag.html +2 -2
  20. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/causal/decisions.html +2 -2
  21. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/causal/estimands.html +2 -2
  22. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/causal/estimators.html +2 -2
  23. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/causal/introduction.html +2 -2
  24. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/causal/motivation.html +2 -2
  25. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/causal/refutations.html +2 -2
  26. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/causal/wizard.html +2 -2
  27. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/game/decision_rules.html +2 -2
  28. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/game/introduction.html +2 -2
  29. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/game/single_player.html +2 -2
  30. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/genindex.html +2 -2
  31. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/index.html +2 -2
  32. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/search.html +2 -2
  33. formative_ds-2.3.0/tests/test_missing_values.py +86 -0
  34. {formative_ds-2.2.4 → formative_ds-2.3.0}/tests/test_policy.py +2 -1
  35. {formative_ds-2.2.4 → formative_ds-2.3.0}/tests/test_rdd_refutation.py +26 -0
  36. {formative_ds-2.2.4 → formative_ds-2.3.0}/uv.lock +98 -1722
  37. formative_ds-2.2.4/docs/causal/benchmarks.rst +0 -431
  38. formative_ds-2.2.4/notebooks/card_krueger_benchmark.ipynb +0 -452
  39. formative_ds-2.2.4/notebooks/lalonde_benchmark.ipynb +0 -538
  40. {formative_ds-2.2.4 → formative_ds-2.3.0}/.gitattributes +0 -0
  41. {formative_ds-2.2.4 → formative_ds-2.3.0}/.github/workflows/publish.yml +0 -0
  42. {formative_ds-2.2.4 → formative_ds-2.3.0}/.gitignore +0 -0
  43. {formative_ds-2.2.4 → formative_ds-2.3.0}/LICENSE +0 -0
  44. {formative_ds-2.2.4 → formative_ds-2.3.0}/README.md +0 -0
  45. {formative_ds-2.2.4 → formative_ds-2.3.0}/TODO.md +0 -0
  46. {formative_ds-2.2.4 → formative_ds-2.3.0}/docs/_static/version_switcher.css +0 -0
  47. {formative_ds-2.2.4 → formative_ds-2.3.0}/docs/_static/version_switcher.js +0 -0
  48. {formative_ds-2.2.4 → formative_ds-2.3.0}/docs/causal/dag.rst +0 -0
  49. {formative_ds-2.2.4 → formative_ds-2.3.0}/docs/causal/decisions.rst +0 -0
  50. {formative_ds-2.2.4 → formative_ds-2.3.0}/docs/causal/estimands.rst +0 -0
  51. {formative_ds-2.2.4 → formative_ds-2.3.0}/docs/causal/estimators.rst +0 -0
  52. {formative_ds-2.2.4 → formative_ds-2.3.0}/docs/causal/introduction.rst +0 -0
  53. {formative_ds-2.2.4 → formative_ds-2.3.0}/docs/causal/motivation.rst +0 -0
  54. {formative_ds-2.2.4 → formative_ds-2.3.0}/docs/causal/refutations.rst +0 -0
  55. {formative_ds-2.2.4 → formative_ds-2.3.0}/docs/causal/wizard.rst +0 -0
  56. {formative_ds-2.2.4 → formative_ds-2.3.0}/docs/conf.py +0 -0
  57. {formative_ds-2.2.4 → formative_ds-2.3.0}/docs/game/decision_rules.rst +0 -0
  58. {formative_ds-2.2.4 → formative_ds-2.3.0}/docs/game/introduction.rst +0 -0
  59. {formative_ds-2.2.4 → formative_ds-2.3.0}/docs/game/single_player.rst +0 -0
  60. {formative_ds-2.2.4 → formative_ds-2.3.0}/examples/decision/01_decision_basic.py +0 -0
  61. {formative_ds-2.2.4 → formative_ds-2.3.0}/examples/did/01_did_basic.py +0 -0
  62. {formative_ds-2.2.4 → formative_ds-2.3.0}/examples/iv/01_iv_basic.py +0 -0
  63. {formative_ds-2.2.4 → formative_ds-2.3.0}/examples/iv/02_iv_vs_ols_bias.py +0 -0
  64. {formative_ds-2.2.4 → formative_ds-2.3.0}/examples/iv/03_iv_with_observed_confounder.py +0 -0
  65. {formative_ds-2.2.4 → formative_ds-2.3.0}/examples/iv/04_iv_refutation.py +0 -0
  66. {formative_ds-2.2.4 → formative_ds-2.3.0}/examples/matching/01_basic_matching.py +0 -0
  67. {formative_ds-2.2.4 → formative_ds-2.3.0}/examples/matching/02_matching_vs_naive.py +0 -0
  68. {formative_ds-2.2.4 → formative_ds-2.3.0}/examples/matching/03_matching_refutation.py +0 -0
  69. {formative_ds-2.2.4 → formative_ds-2.3.0}/examples/ols/01_ols_basic.py +0 -0
  70. {formative_ds-2.2.4 → formative_ds-2.3.0}/examples/ols/02_ols_unobserved_confounder.py +0 -0
  71. {formative_ds-2.2.4 → formative_ds-2.3.0}/examples/ols/03_ols_multiple_confounders.py +0 -0
  72. {formative_ds-2.2.4 → formative_ds-2.3.0}/examples/rct/01_rct_basic.py +0 -0
  73. {formative_ds-2.2.4 → formative_ds-2.3.0}/examples/rdd/01_rdd_basic.py +0 -0
  74. {formative_ds-2.2.4 → formative_ds-2.3.0}/examples/rdd/02_rdd_positive_running_var.py +0 -0
  75. {formative_ds-2.2.4 → formative_ds-2.3.0}/formative/__init__.py +0 -0
  76. {formative_ds-2.2.4 → formative_ds-2.3.0}/formative/causal/__init__.py +0 -0
  77. {formative_ds-2.2.4 → formative_ds-2.3.0}/formative/causal/_assumptions.py +0 -0
  78. {formative_ds-2.2.4 → formative_ds-2.3.0}/formative/causal/_exceptions.py +0 -0
  79. {formative_ds-2.2.4 → formative_ds-2.3.0}/formative/causal/_explain.py +0 -0
  80. {formative_ds-2.2.4 → formative_ds-2.3.0}/formative/causal/dag.py +0 -0
  81. {formative_ds-2.2.4 → formative_ds-2.3.0}/formative/causal/decision.py +0 -0
  82. {formative_ds-2.2.4 → formative_ds-2.3.0}/formative/causal/estimators/__init__.py +0 -0
  83. {formative_ds-2.2.4 → formative_ds-2.3.0}/formative/causal/refutations/__init__.py +0 -0
  84. {formative_ds-2.2.4 → formative_ds-2.3.0}/formative/causal/refutations/_check.py +0 -0
  85. {formative_ds-2.2.4 → formative_ds-2.3.0}/formative/causal/refutations/cate.py +0 -0
  86. {formative_ds-2.2.4 → formative_ds-2.3.0}/formative/causal/refutations/did.py +0 -0
  87. {formative_ds-2.2.4 → formative_ds-2.3.0}/formative/causal/refutations/iv.py +0 -0
  88. {formative_ds-2.2.4 → formative_ds-2.3.0}/formative/causal/refutations/matching.py +0 -0
  89. {formative_ds-2.2.4 → formative_ds-2.3.0}/formative/causal/refutations/ols.py +0 -0
  90. {formative_ds-2.2.4 → formative_ds-2.3.0}/formative/causal/refutations/policy.py +0 -0
  91. {formative_ds-2.2.4 → formative_ds-2.3.0}/formative/causal/refutations/rct.py +0 -0
  92. {formative_ds-2.2.4 → formative_ds-2.3.0}/formative/causal/refutations/rdd.py +0 -0
  93. {formative_ds-2.2.4 → formative_ds-2.3.0}/formative/game/__init__.py +0 -0
  94. {formative_ds-2.2.4 → formative_ds-2.3.0}/formative/game/expected_value.py +0 -0
  95. {formative_ds-2.2.4 → formative_ds-2.3.0}/formative/game/hurwicz.py +0 -0
  96. {formative_ds-2.2.4 → formative_ds-2.3.0}/formative/game/laplace.py +0 -0
  97. {formative_ds-2.2.4 → formative_ds-2.3.0}/formative/game/maximax.py +0 -0
  98. {formative_ds-2.2.4 → formative_ds-2.3.0}/formative/game/maximin.py +0 -0
  99. {formative_ds-2.2.4 → formative_ds-2.3.0}/formative/game/minimax.py +0 -0
  100. {formative_ds-2.2.4 → formative_ds-2.3.0}/scripts/snapshot_docs.py +0 -0
  101. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_sources/causal/benchmarks.rst.txt +0 -0
  102. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_sources/causal/dag.rst.txt +0 -0
  103. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_sources/causal/decisions.rst.txt +0 -0
  104. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_sources/causal/estimands.rst.txt +0 -0
  105. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_sources/causal/estimators.rst.txt +0 -0
  106. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_sources/causal/introduction.rst.txt +0 -0
  107. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_sources/causal/motivation.rst.txt +0 -0
  108. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_sources/causal/refutations.rst.txt +0 -0
  109. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_sources/causal/wizard.rst.txt +0 -0
  110. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_sources/game/decision_rules.rst.txt +0 -0
  111. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_sources/game/introduction.rst.txt +0 -0
  112. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_sources/game/single_player.rst.txt +0 -0
  113. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_sources/index.rst.txt +0 -0
  114. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/_sphinx_javascript_frameworks_compat.js +0 -0
  115. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/base-stemmer.js +0 -0
  116. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/basic.css +0 -0
  117. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/check-solid.svg +0 -0
  118. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/clipboard.min.js +0 -0
  119. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/copy-button.svg +0 -0
  120. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/copybutton.css +0 -0
  121. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/copybutton.js +0 -0
  122. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/copybutton_funcs.js +0 -0
  123. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/css/badge_only.css +0 -0
  124. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/css/fonts/Roboto-Slab-Bold.woff +0 -0
  125. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/css/fonts/Roboto-Slab-Bold.woff2 +0 -0
  126. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/css/fonts/Roboto-Slab-Regular.woff +0 -0
  127. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/css/fonts/Roboto-Slab-Regular.woff2 +0 -0
  128. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/css/fonts/fontawesome-webfont.eot +0 -0
  129. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/css/fonts/fontawesome-webfont.svg +0 -0
  130. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/css/fonts/fontawesome-webfont.ttf +0 -0
  131. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/css/fonts/fontawesome-webfont.woff +0 -0
  132. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/css/fonts/fontawesome-webfont.woff2 +0 -0
  133. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/css/fonts/lato-bold-italic.woff +0 -0
  134. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/css/fonts/lato-bold-italic.woff2 +0 -0
  135. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/css/fonts/lato-bold.woff +0 -0
  136. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/css/fonts/lato-bold.woff2 +0 -0
  137. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/css/fonts/lato-normal-italic.woff +0 -0
  138. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/css/fonts/lato-normal-italic.woff2 +0 -0
  139. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/css/fonts/lato-normal.woff +0 -0
  140. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/css/fonts/lato-normal.woff2 +0 -0
  141. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/css/theme.css +0 -0
  142. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/doctools.js +0 -0
  143. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/documentation_options.js +0 -0
  144. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/english-stemmer.js +0 -0
  145. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/file.png +0 -0
  146. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/fonts/Lato/lato-bold.eot +0 -0
  147. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/fonts/Lato/lato-bold.ttf +0 -0
  148. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/fonts/Lato/lato-bold.woff +0 -0
  149. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/fonts/Lato/lato-bold.woff2 +0 -0
  150. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/fonts/Lato/lato-bolditalic.eot +0 -0
  151. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/fonts/Lato/lato-bolditalic.ttf +0 -0
  152. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/fonts/Lato/lato-bolditalic.woff +0 -0
  153. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/fonts/Lato/lato-bolditalic.woff2 +0 -0
  154. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/fonts/Lato/lato-italic.eot +0 -0
  155. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/fonts/Lato/lato-italic.ttf +0 -0
  156. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/fonts/Lato/lato-italic.woff +0 -0
  157. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/fonts/Lato/lato-italic.woff2 +0 -0
  158. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/fonts/Lato/lato-regular.eot +0 -0
  159. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/fonts/Lato/lato-regular.ttf +0 -0
  160. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/fonts/Lato/lato-regular.woff +0 -0
  161. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/fonts/Lato/lato-regular.woff2 +0 -0
  162. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot +0 -0
  163. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf +0 -0
  164. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff +0 -0
  165. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 +0 -0
  166. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot +0 -0
  167. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf +0 -0
  168. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff +0 -0
  169. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 +0 -0
  170. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/jquery.js +0 -0
  171. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/js/badge_only.js +0 -0
  172. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/js/theme.js +0 -0
  173. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/js/versions.js +0 -0
  174. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/language_data.js +0 -0
  175. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/minus.png +0 -0
  176. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/plus.png +0 -0
  177. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/pygments.css +0 -0
  178. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/searchtools.js +0 -0
  179. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/sphinx_highlight.js +0 -0
  180. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/version_switcher.css +0 -0
  181. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/_static/version_switcher.js +0 -0
  182. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/causal/benchmarks.html +0 -0
  183. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/causal/dag.html +0 -0
  184. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/causal/decisions.html +0 -0
  185. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/causal/estimands.html +0 -0
  186. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/causal/estimators.html +0 -0
  187. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/causal/introduction.html +0 -0
  188. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/causal/motivation.html +0 -0
  189. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/causal/refutations.html +0 -0
  190. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/causal/wizard.html +0 -0
  191. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/game/decision_rules.html +0 -0
  192. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/game/introduction.html +0 -0
  193. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/game/single_player.html +0 -0
  194. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/genindex.html +0 -0
  195. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/index.html +0 -0
  196. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/objects.inv +0 -0
  197. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/search.html +0 -0
  198. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.1/searchindex.js +0 -0
  199. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_sources/causal/benchmarks.rst.txt +0 -0
  200. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_sources/causal/dag.rst.txt +0 -0
  201. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_sources/causal/decisions.rst.txt +0 -0
  202. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_sources/causal/estimands.rst.txt +0 -0
  203. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_sources/causal/estimators.rst.txt +0 -0
  204. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_sources/causal/introduction.rst.txt +0 -0
  205. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_sources/causal/motivation.rst.txt +0 -0
  206. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_sources/causal/refutations.rst.txt +0 -0
  207. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_sources/causal/wizard.rst.txt +0 -0
  208. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_sources/game/decision_rules.rst.txt +0 -0
  209. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_sources/game/introduction.rst.txt +0 -0
  210. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_sources/game/single_player.rst.txt +0 -0
  211. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_sources/index.rst.txt +0 -0
  212. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/_sphinx_javascript_frameworks_compat.js +0 -0
  213. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/base-stemmer.js +0 -0
  214. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/basic.css +0 -0
  215. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/check-solid.svg +0 -0
  216. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/clipboard.min.js +0 -0
  217. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/copy-button.svg +0 -0
  218. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/copybutton.css +0 -0
  219. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/copybutton.js +0 -0
  220. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/copybutton_funcs.js +0 -0
  221. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/css/badge_only.css +0 -0
  222. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/css/fonts/Roboto-Slab-Bold.woff +0 -0
  223. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/css/fonts/Roboto-Slab-Bold.woff2 +0 -0
  224. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/css/fonts/Roboto-Slab-Regular.woff +0 -0
  225. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/css/fonts/Roboto-Slab-Regular.woff2 +0 -0
  226. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/css/fonts/fontawesome-webfont.eot +0 -0
  227. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/css/fonts/fontawesome-webfont.svg +0 -0
  228. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/css/fonts/fontawesome-webfont.ttf +0 -0
  229. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/css/fonts/fontawesome-webfont.woff +0 -0
  230. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/css/fonts/fontawesome-webfont.woff2 +0 -0
  231. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/css/fonts/lato-bold-italic.woff +0 -0
  232. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/css/fonts/lato-bold-italic.woff2 +0 -0
  233. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/css/fonts/lato-bold.woff +0 -0
  234. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/css/fonts/lato-bold.woff2 +0 -0
  235. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/css/fonts/lato-normal-italic.woff +0 -0
  236. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/css/fonts/lato-normal-italic.woff2 +0 -0
  237. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/css/fonts/lato-normal.woff +0 -0
  238. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/css/fonts/lato-normal.woff2 +0 -0
  239. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/css/theme.css +0 -0
  240. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/doctools.js +0 -0
  241. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/english-stemmer.js +0 -0
  242. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/file.png +0 -0
  243. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/fonts/Lato/lato-bold.eot +0 -0
  244. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/fonts/Lato/lato-bold.ttf +0 -0
  245. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/fonts/Lato/lato-bold.woff +0 -0
  246. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/fonts/Lato/lato-bold.woff2 +0 -0
  247. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/fonts/Lato/lato-bolditalic.eot +0 -0
  248. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/fonts/Lato/lato-bolditalic.ttf +0 -0
  249. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/fonts/Lato/lato-bolditalic.woff +0 -0
  250. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/fonts/Lato/lato-bolditalic.woff2 +0 -0
  251. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/fonts/Lato/lato-italic.eot +0 -0
  252. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/fonts/Lato/lato-italic.ttf +0 -0
  253. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/fonts/Lato/lato-italic.woff +0 -0
  254. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/fonts/Lato/lato-italic.woff2 +0 -0
  255. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/fonts/Lato/lato-regular.eot +0 -0
  256. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/fonts/Lato/lato-regular.ttf +0 -0
  257. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/fonts/Lato/lato-regular.woff +0 -0
  258. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/fonts/Lato/lato-regular.woff2 +0 -0
  259. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot +0 -0
  260. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf +0 -0
  261. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff +0 -0
  262. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 +0 -0
  263. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot +0 -0
  264. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf +0 -0
  265. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff +0 -0
  266. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 +0 -0
  267. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/jquery.js +0 -0
  268. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/js/badge_only.js +0 -0
  269. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/js/theme.js +0 -0
  270. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/js/versions.js +0 -0
  271. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/language_data.js +0 -0
  272. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/minus.png +0 -0
  273. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/plus.png +0 -0
  274. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/pygments.css +0 -0
  275. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/searchtools.js +0 -0
  276. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/sphinx_highlight.js +0 -0
  277. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/version_switcher.css +0 -0
  278. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/_static/version_switcher.js +0 -0
  279. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/objects.inv +0 -0
  280. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/2.2/searchindex.js +0 -0
  281. {formative_ds-2.2.4 → formative_ds-2.3.0}/site/versions.json +0 -0
  282. {formative_ds-2.2.4 → formative_ds-2.3.0}/tests/test_cate.py +0 -0
  283. {formative_ds-2.2.4 → formative_ds-2.3.0}/tests/test_cate_refutation.py +0 -0
  284. {formative_ds-2.2.4 → formative_ds-2.3.0}/tests/test_dag.py +0 -0
  285. {formative_ds-2.2.4 → formative_ds-2.3.0}/tests/test_decision.py +0 -0
  286. {formative_ds-2.2.4 → formative_ds-2.3.0}/tests/test_did.py +0 -0
  287. {formative_ds-2.2.4 → formative_ds-2.3.0}/tests/test_did_refutation.py +0 -0
  288. {formative_ds-2.2.4 → formative_ds-2.3.0}/tests/test_game.py +0 -0
  289. {formative_ds-2.2.4 → formative_ds-2.3.0}/tests/test_iv.py +0 -0
  290. {formative_ds-2.2.4 → formative_ds-2.3.0}/tests/test_iv_refutation.py +0 -0
  291. {formative_ds-2.2.4 → formative_ds-2.3.0}/tests/test_matching.py +0 -0
  292. {formative_ds-2.2.4 → formative_ds-2.3.0}/tests/test_matching_refutation.py +0 -0
  293. {formative_ds-2.2.4 → formative_ds-2.3.0}/tests/test_ols.py +0 -0
  294. {formative_ds-2.2.4 → formative_ds-2.3.0}/tests/test_ols_refutation.py +0 -0
  295. {formative_ds-2.2.4 → formative_ds-2.3.0}/tests/test_policy_refutation.py +0 -0
  296. {formative_ds-2.2.4 → formative_ds-2.3.0}/tests/test_rct.py +0 -0
  297. {formative_ds-2.2.4 → formative_ds-2.3.0}/tests/test_rct_refutation.py +0 -0
  298. {formative_ds-2.2.4 → formative_ds-2.3.0}/tests/test_rdd.py +0 -0
  299. {formative_ds-2.2.4 → formative_ds-2.3.0}/vercel.json +0 -0
@@ -16,7 +16,7 @@ jobs:
16
16
  run: uv sync --dev --group docs
17
17
 
18
18
  - name: Lint
19
- run: uv run ruff check --exclude notebooks . && uv run ruff format --check --exclude notebooks .
19
+ run: uv run ruff check . && uv run ruff format --check --exclude notebooks .
20
20
 
21
21
  - name: Run tests
22
22
  run: uv run pytest --cov=formative --cov-report=term-missing --cov-fail-under=80
@@ -9,7 +9,7 @@ uv sync --dev # install dependencies
9
9
  uv run pytest # run all tests
10
10
  uv run pytest tests/test_iv.py # run one file
11
11
  uv run pytest tests/test_iv.py::TestIV2SLSEstimation::test_iv_recovers_true_effect # run one test
12
- make lint # ruff check (excludes notebooks/)
12
+ make lint # ruff check
13
13
  make fmt # ruff format
14
14
  make check # lint + format check + tests with coverage (CI requires ≥80%)
15
15
  make build-docs # sphinx with -W: warnings are errors in CI
@@ -66,7 +66,7 @@ Do not change `_RCC_SEED` or `_PLACEBO_SEED` to 42 — the generated noise colli
66
66
 
67
67
  **Heterogeneous effects (CATE)** — `OLSObservational` and `RCT` accept `effect_modifier=` (a discrete, pre-treatment column). `fit()` then returns `OLSCATEResult`/`RCTCATEResult` with `effect_by_group`, a homogeneity F-test, and `decide_by_group()`. The headline `effect` is the sample-share-weighted average of group effects (the raw treatment coefficient under interaction coding is only the reference group's effect). Shared machinery lives in `estimators/_cate.py`; DAG validation rejects modifiers that are descendants of the treatment (mediators) or not ancestors of the outcome.
68
68
 
69
- **Policy learning** — `RCTResult.learn_policy(df, modifiers=[...], cost=, benefit=, max_depth=1|2)` (design note: `docs/design/policy_learning.md`) does Athey–Wager doubly robust policy learning: cross-fitted AIPW scores, exhaustive shallow-tree search over discrete features, honest (out-of-fold) value reported as the advantage over the best constant policy. Returns `PolicyResult` (`rules`, `value`, `assign()`, `refute()`). Refutation checks test the honest *value*, not tree structure — in-sample search happily splits on noise within its depth budget, so structural checks would be flaky. Trees are simplified post-hoc by dropping splits that don't change the assignment (equal-value trees with redundant conditions can win the search on candidate order).
69
+ **Policy learning** — `RCTResult.learn_policy(df, modifiers=[...], cost=, benefit=, max_depth=1|2)` (design note: `docs/design/policy_learning.md`) does Athey–Wager doubly robust policy learning: cross-fitted AIPW scores, exhaustive shallow-tree search over discrete features, honest (out-of-fold) value reported as the advantage over the best constant policy. Returns `PolicyResult` (`rules`, `value`, `assign()`, `refute()`). Refutation checks test the honest *value*, not tree structure — in-sample search happily splits on noise within its depth budget, so structural checks would be flaky. The depth-2 search starts from the best depth-1 tree, so a deeper tree must strictly beat it — otherwise equal-value trees with redundant conditions can win on candidate order.
70
70
 
71
71
  **Package layout:**
72
72
  - `formative/causal/dag.py` — `DAG`, `_Node`
@@ -98,7 +98,7 @@ Do not change `_RCC_SEED` or `_PLACEBO_SEED` to 42 — the generated noise colli
98
98
 
99
99
  **Bootstrap-heavy tests:** matching tests use `setup_class` (not `setup_method`) so `.fit()` runs once per class. Use N=1_000 — larger N widens bootstrap SE faster than NN noise shrinks, causing refutation checks to fail even on well-specified data.
100
100
 
101
- **Benchmarks:** `notebooks/` holds Jupyter notebooks (LaLonde, Card–Krueger) that validate estimators against published results from famous studies; `docs/causal/benchmarks.rst` renders them as a docs page. Notebooks are excluded from ruff. Runnable per-estimator example scripts live in `examples/`.
101
+ **Examples:** runnable per-estimator example scripts live in `examples/`.
102
102
 
103
103
  **Planned estimators:** the goal is to cover all methods in the causal inference decision tree at https://www.maxpagels.com/prototypes/causal-wizard. Remaining: **Synthetic Control**.
104
104
 
@@ -8,13 +8,13 @@ test:
8
8
  uv run pytest --cov=formative --cov-report=term-missing --cov-fail-under=80
9
9
 
10
10
  lint:
11
- uv run ruff check --exclude notebooks .
11
+ uv run ruff check .
12
12
 
13
13
  fmt:
14
- uv run ruff format --exclude notebooks .
14
+ uv run ruff format .
15
15
 
16
16
  check: lint
17
- uv run ruff format --check --exclude notebooks .
17
+ uv run ruff format --check .
18
18
  uv run pytest --cov=formative --cov-report=term-missing --cov-fail-under=80
19
19
 
20
20
  release: # usage: make release BUMP=patch|minor|major
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: formative-ds
3
- Version: 2.2.4
3
+ Version: 2.3.0
4
4
  Summary: A Python library for quantitative reasoning.
5
5
  Project-URL: Homepage, https://getformative.dev
6
6
  Project-URL: Documentation, https://docs.getformative.dev
@@ -2,8 +2,8 @@
2
2
 
3
3
  **Status:** implemented (see `formative/causal/estimators/policy.py`); deviations from this
4
4
  note: refutation checks test the honest value rather than tree structure (in-sample search
5
- splits on noise within its depth budget, so structural checks are flaky), and final trees get
6
- a simplification pass that drops splits which don't change the assignment.
5
+ splits on noise within its depth budget, so structural checks are flaky), and a deeper tree
6
+ must strictly beat the best shallower one, so equal-value trees with redundant splits never win.
7
7
  **Date:** 2026-07-11
8
8
 
9
9
  ## Motivation
@@ -115,8 +115,7 @@ print(policy.summary())
115
115
  - Narrative rendering in `_explain.py`.
116
116
  - Tests in `tests/test_policy.py` + `tests/test_policy_refutation.py`; cross-fitting
117
117
  makes fitting expensive, so use `setup_class` like the matching tests.
118
- - Docs page under `docs/causal/` with a worked example; consider a benchmark notebook
119
- once a public dataset with a known-good policy exists.
118
+ - Docs page under `docs/causal/` with a worked example.
120
119
 
121
120
  ## Open questions
122
121
 
@@ -29,7 +29,6 @@ under uncertainty or in strategic situations. :doc:`Get started <game/introducti
29
29
  causal/estimators
30
30
  causal/refutations
31
31
  causal/decisions
32
- causal/benchmarks
33
32
 
34
33
  .. toctree::
35
34
  :maxdepth: 2
@@ -1,6 +1,38 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from .._assumptions import Assumption
4
+ from .._exceptions import IdentificationError
5
+
6
+
7
+ def _backdoor_confounders(dag, treatment: str, outcome: str) -> set[str]:
8
+ """
9
+ Confounders under the backdoor criterion: common ancestors of treatment and
10
+ outcome that are not descendants of treatment (mediators or post-treatment
11
+ variables).
12
+ """
13
+ return (dag.ancestors(treatment) & dag.ancestors(outcome)) - dag.descendants(treatment)
14
+
15
+
16
+ def _missing_confounders_error(missing: set[str], treatment: str, outcome: str) -> IdentificationError:
17
+ """``IdentificationError`` for DAG-declared confounders absent from the dataframe."""
18
+ return IdentificationError(
19
+ f"\nDAG confounders not found in dataframe: {sorted(missing)}\n\n"
20
+ f"Your DAG declares these variables as confounders of '{treatment}' and\n"
21
+ f"'{outcome}', but they are absent from the dataframe and cannot be\n"
22
+ f"controlled for. Note: there may also be confounders not modelled in your\n"
23
+ f"DAG at all — formative cannot detect those.\n\n"
24
+ f"Consider:\n"
25
+ f" - Collecting data on {sorted(missing)} and adding it to the dataframe\n"
26
+ f" - IV estimation if you have a valid instrument for '{treatment}'\n"
27
+ f" - DiD or RD if a natural experiment is available"
28
+ )
29
+
30
+
31
+ def _reject_missing(data, columns, action: str = "fitting") -> None:
32
+ """Raise if any used column has NaNs: statsmodels formulas drop those rows silently, our own code doesn't."""
33
+ with_nan = [c for c in dict.fromkeys(columns) if data[c].isna().any()]
34
+ if with_nan:
35
+ raise ValueError(f"Columns {with_nan} contain missing values — drop or impute them before {action}.")
4
36
 
5
37
 
6
38
  class _BaseResult:
@@ -18,6 +18,7 @@ import statsmodels.formula.api as smf
18
18
 
19
19
  from .._assumptions import Assumption
20
20
  from ..dag import DAG
21
+ from ._base import _reject_missing
21
22
 
22
23
  CATE_ASSUMPTIONS: list[Assumption] = [
23
24
  Assumption("Correct specification of effect modification (linear interaction form)", testable=False),
@@ -86,6 +87,7 @@ def _validate_modifier_data(data: pd.DataFrame, treatment: str, modifier: str) -
86
87
  """Data checks on the effect modifier column. Raises ``ValueError`` on violation."""
87
88
  if modifier not in data.columns:
88
89
  raise ValueError(f"Effect modifier column '{modifier}' not found in dataframe.")
90
+ _reject_missing(data, [modifier])
89
91
  levels = data[modifier].dropna().unique()
90
92
  if len(levels) < 2:
91
93
  raise ValueError(f"Effect modifier '{modifier}' must have at least 2 levels. Found: {list(levels)}")
@@ -5,7 +5,7 @@ import statsmodels.formula.api as smf
5
5
 
6
6
  from .._assumptions import Assumption
7
7
  from ..dag import DAG
8
- from ._base import _StatsmodelsResult
8
+ from ._base import _reject_missing, _StatsmodelsResult
9
9
 
10
10
  DID_ASSUMPTIONS: list[Assumption] = [
11
11
  Assumption(
@@ -227,6 +227,7 @@ class DiD:
227
227
  ]:
228
228
  if var not in data.columns:
229
229
  raise ValueError(f"{label} column '{var}' not found in dataframe.")
230
+ _reject_missing(data, [self._group, self._time, self._outcome])
230
231
 
231
232
  for label, var in [("Group", self._group), ("Time", self._time)]:
232
233
  vals = set(data[var].dropna().unique())
@@ -7,7 +7,7 @@ from statsmodels.sandbox.regression.gmm import IV2SLS as _IV2SLS
7
7
 
8
8
  from .._assumptions import Assumption
9
9
  from ..dag import DAG
10
- from ._base import _StatsmodelsResult
10
+ from ._base import _backdoor_confounders, _reject_missing, _StatsmodelsResult
11
11
 
12
12
  IV_ASSUMPTIONS: list[Assumption] = [
13
13
  Assumption("Relevance: the instrument strongly affects treatment", testable=True),
@@ -228,14 +228,7 @@ class IV2SLS:
228
228
  Unobserved confounders are handled by the instrument and are not flagged
229
229
  as errors — this is the primary use case for IV estimation.
230
230
  """
231
- dag = self._dag
232
- T, Y = self._treatment, self._outcome
233
-
234
- treatment_ancestors = dag.ancestors(T)
235
- outcome_ancestors = dag.ancestors(Y)
236
- treatment_descendants = dag.descendants(T)
237
-
238
- confounders = (treatment_ancestors & outcome_ancestors) - treatment_descendants
231
+ confounders = _backdoor_confounders(self._dag, self._treatment, self._outcome)
239
232
  confounders.discard(self._instrument)
240
233
 
241
234
  return {c for c in confounders if c in data_columns}
@@ -274,6 +267,7 @@ class IV2SLS:
274
267
  controls = sorted(adjustment_set)
275
268
 
276
269
  T, Y, Z = self._treatment, self._outcome, self._instrument
270
+ _reject_missing(data, [T, Y, Z, *controls])
277
271
 
278
272
  # Build design matrices for 2SLS:
279
273
  # exog: [const, T, controls] — includes endogenous treatment
@@ -6,9 +6,8 @@ import statsmodels.api as sm
6
6
  import statsmodels.formula.api as smf
7
7
 
8
8
  from .._assumptions import Assumption
9
- from .._exceptions import IdentificationError
10
9
  from ..dag import DAG
11
- from ._base import _BaseResult
10
+ from ._base import _backdoor_confounders, _BaseResult, _missing_confounders_error, _reject_missing
12
11
 
13
12
  MATCHING_ASSUMPTIONS: list[Assumption] = [
14
13
  Assumption("Conditional independence: no unobserved confounders given matched variables", testable=False),
@@ -283,14 +282,7 @@ class PropensityScoreMatching:
283
282
  raise ValueError("Treatment and outcome must be different variables.")
284
283
 
285
284
  def _identify(self, data_columns: set[str]) -> tuple[set[str], set[str]]:
286
- dag = self._dag
287
- T, Y = self._treatment, self._outcome
288
-
289
- treatment_ancestors = dag.ancestors(T)
290
- outcome_ancestors = dag.ancestors(Y)
291
- treatment_descendants = dag.descendants(T)
292
-
293
- confounders = (treatment_ancestors & outcome_ancestors) - treatment_descendants
285
+ confounders = _backdoor_confounders(self._dag, self._treatment, self._outcome)
294
286
  observed = {c for c in confounders if c in data_columns}
295
287
  missing = {c for c in confounders if c not in data_columns}
296
288
  return observed, missing
@@ -328,18 +320,10 @@ class PropensityScoreMatching:
328
320
  adjustment_set, missing = self._identify(data_columns)
329
321
 
330
322
  if missing:
331
- raise IdentificationError(
332
- f"\nDAG confounders not found in dataframe: {sorted(missing)}\n\n"
333
- f"Your DAG declares these variables as confounders of "
334
- f"'{self._treatment}' and '{self._outcome}', but they are "
335
- f"absent from the dataframe and cannot be controlled for.\n\n"
336
- f"Consider:\n"
337
- f" - Collecting data on {sorted(missing)} and adding it to the dataframe\n"
338
- f" - IV estimation if you have a valid instrument for '{self._treatment}'\n"
339
- f" - DiD or RD if a natural experiment is available"
340
- )
323
+ raise _missing_confounders_error(missing, self._treatment, self._outcome)
341
324
 
342
325
  T, Y = self._treatment, self._outcome
326
+ _reject_missing(data, [T, Y, *sorted(adjustment_set)])
343
327
 
344
328
  # Unadjusted estimate: naive mean difference, no matching.
345
329
  unadjusted = data.loc[data[T] == 1, Y].mean() - data.loc[data[T] == 0, Y].mean()
@@ -4,9 +4,8 @@ import pandas as pd
4
4
  import statsmodels.formula.api as smf
5
5
 
6
6
  from .._assumptions import Assumption
7
- from .._exceptions import IdentificationError
8
7
  from ..dag import DAG
9
- from ._base import _StatsmodelsResult
8
+ from ._base import _backdoor_confounders, _missing_confounders_error, _reject_missing, _StatsmodelsResult
10
9
  from ._cate import (
11
10
  CATE_ASSUMPTIONS,
12
11
  _CATEResultMixin,
@@ -282,14 +281,7 @@ class OLSObservational:
282
281
  absent from the data. There may be additional unobserved confounders
283
282
  not represented in the DAG at all.
284
283
  """
285
- dag = self._dag
286
- T, Y = self._treatment, self._outcome
287
-
288
- treatment_ancestors = dag.ancestors(T)
289
- outcome_ancestors = dag.ancestors(Y)
290
- treatment_descendants = dag.descendants(T)
291
-
292
- confounders = (treatment_ancestors & outcome_ancestors) - treatment_descendants
284
+ confounders = _backdoor_confounders(self._dag, self._treatment, self._outcome)
293
285
 
294
286
  observed_confounders = {c for c in confounders if c in data_columns}
295
287
  dag_confounders_not_in_data = {c for c in confounders if c not in data_columns}
@@ -335,18 +327,9 @@ class OLSObservational:
335
327
  adjustment_set, dag_confounders_not_in_data = self._identify(data_columns)
336
328
 
337
329
  if dag_confounders_not_in_data:
338
- raise IdentificationError(
339
- f"\nDAG confounders not found in dataframe: {sorted(dag_confounders_not_in_data)}\n\n"
340
- f"Your DAG declares these variables as confounders of '{self._treatment}' and\n"
341
- f"'{self._outcome}', but they are absent from the dataframe and cannot be\n"
342
- f"controlled for. Note: there may also be confounders not modelled in your\n"
343
- f"DAG at all — formative cannot detect those.\n\n"
344
- f"Consider:\n"
345
- f" - Collecting data on {sorted(dag_confounders_not_in_data)} and adding it to the dataframe\n"
346
- f" - IV estimation if you have a valid instrument for '{self._treatment}'\n"
347
- f" - DiD or RD if a natural experiment is available"
348
- )
330
+ raise _missing_confounders_error(dag_confounders_not_in_data, self._treatment, self._outcome)
349
331
 
332
+ _reject_missing(data, [self._treatment, self._outcome, *sorted(adjustment_set)])
350
333
  unadjusted_result = smf.ols(f"{self._outcome} ~ {self._treatment}", data=data).fit()
351
334
 
352
335
  if self._modifier is not None:
@@ -23,6 +23,7 @@ import numpy as np
23
23
  import pandas as pd
24
24
 
25
25
  from .._assumptions import Assumption
26
+ from ._base import _reject_missing
26
27
  from ._cate import _validate_modifier_dag
27
28
 
28
29
  _FOLD_SEED = 13579
@@ -101,30 +102,6 @@ def _rules_lines(tree: PolicyNode | bool) -> list[str]:
101
102
  return [f"treat if {' and '.join(conds)}" for conds in paths] + ["otherwise don't treat"]
102
103
 
103
104
 
104
- def _simplify_tree(tree: PolicyNode | bool, data: pd.DataFrame) -> PolicyNode | bool:
105
- """
106
- Drop splits that don't change the assignment on *data*.
107
-
108
- The exhaustive search keeps the first of several equal-value trees, which
109
- can carry redundant conditions (e.g. a root split whose subtree already
110
- implies it). Replacing a node by one of its children is safe whenever the
111
- assignment is identical over the sample — same assignment, same value.
112
- """
113
- if isinstance(tree, bool):
114
- return tree
115
- cond = data[tree.feature].to_numpy() == tree.level
116
- left = _simplify_tree(tree.if_true, data.loc[cond])
117
- right = _simplify_tree(tree.if_false, data.loc[~cond])
118
- if isinstance(left, bool) and isinstance(right, bool) and left == right:
119
- return left
120
- tree = PolicyNode(tree.feature, tree.level, left, right)
121
- full = _assign_tree(tree, data)
122
- for child in (left, right):
123
- if np.array_equal(_assign_tree(child, data), full):
124
- return child
125
- return tree
126
-
127
-
128
105
  def _dr_scores(
129
106
  W: np.ndarray,
130
107
  Y: np.ndarray,
@@ -188,6 +165,9 @@ def _learn_tree(
188
165
  best_val = float(vals[i])
189
166
  return best_tree, best_val
190
167
 
168
+ # A deeper tree must strictly beat the best shallower one, or an equal-value
169
+ # tree with redundant splits can win on candidate order.
170
+ best_tree, best_val = _learn_tree(gamma, M, candidates, max_depth - 1)
191
171
  for i, (feature, level) in enumerate(candidates):
192
172
  mask = M[i].astype(bool)
193
173
  left_tree, left_val = _learn_tree(gamma[mask], M[:, mask], candidates, max_depth - 1)
@@ -260,7 +240,6 @@ def _fit_policy(
260
240
  value_ci = (value - 1.96 * value_se, value + 1.96 * value_se)
261
241
 
262
242
  tree, _ = _learn_tree(gamma, M, candidates, max_depth)
263
- tree = _simplify_tree(tree, data)
264
243
  coverage = float(_assign_tree(tree, data).mean())
265
244
 
266
245
  return _PolicyFit(tree=tree, value=value, value_se=value_se, value_ci=value_ci, coverage=coverage)
@@ -458,10 +437,7 @@ def _validate_policy_inputs(
458
437
  f"Policy trees need discrete features — bin continuous or high-cardinality columns first."
459
438
  )
460
439
 
461
- used = [treatment, outcome, *modifiers]
462
- with_nan = [c for c in used if data[c].isna().any()]
463
- if with_nan:
464
- raise ValueError(f"Columns {with_nan} contain missing values — drop or impute them before learning a policy.")
440
+ _reject_missing(data, [treatment, outcome, *modifiers], action="learning a policy")
465
441
 
466
442
  w = set(data[treatment].unique())
467
443
  if w != {0, 1}:
@@ -5,7 +5,7 @@ import statsmodels.formula.api as smf
5
5
 
6
6
  from .._assumptions import Assumption
7
7
  from ..dag import DAG
8
- from ._base import _StatsmodelsResult
8
+ from ._base import _reject_missing, _StatsmodelsResult
9
9
  from ._cate import (
10
10
  CATE_ASSUMPTIONS,
11
11
  _CATEResultMixin,
@@ -305,6 +305,7 @@ class RCT:
305
305
  for label, var in [("Treatment", self._treatment), ("Outcome", self._outcome)]:
306
306
  if var not in data.columns:
307
307
  raise ValueError(f"{label} column '{var}' not found in dataframe.")
308
+ _reject_missing(data, [self._treatment, self._outcome])
308
309
 
309
310
  if self._modifier is not None:
310
311
  _validate_modifier_data(data, self._treatment, self._modifier)
@@ -5,7 +5,7 @@ import statsmodels.formula.api as smf
5
5
 
6
6
  from .._assumptions import Assumption
7
7
  from ..dag import DAG
8
- from ._base import _StatsmodelsResult
8
+ from ._base import _reject_missing, _StatsmodelsResult
9
9
 
10
10
  RDD_ASSUMPTIONS: list[Assumption] = [
11
11
  Assumption(
@@ -131,6 +131,10 @@ class RDDResult(_StatsmodelsResult):
131
131
  _check_random_common_cause,
132
132
  )
133
133
 
134
+ # Refute on the same rows fit() used, not the whole dataframe.
135
+ if self._bandwidth is not None:
136
+ data = data.loc[(data[self._running_var] - self._cutoff).abs() <= self._bandwidth]
137
+
134
138
  checks = [
135
139
  _check_placebo_cutoff(
136
140
  data,
@@ -256,6 +260,8 @@ class RDD:
256
260
  filtered = data.loc[mask]
257
261
  else:
258
262
  filtered = data
263
+ # Rows outside the bandwidth are never used, so their gaps don't matter.
264
+ _reject_missing(filtered, [self._running_var, self._outcome])
259
265
 
260
266
  # Unadjusted effect: naive mean difference above vs below cutoff
261
267
  above = filtered.loc[filtered[self._running_var] >= self._cutoff, self._outcome].mean()
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "formative-ds"
7
- version = "2.2.4"
7
+ version = "2.3.0"
8
8
  description = "A Python library for quantitative reasoning."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -37,7 +37,6 @@ Repository = "https://github.com/maxpagels/formative"
37
37
 
38
38
  [dependency-groups]
39
39
  dev = [
40
- "jupyter>=1.1.1",
41
40
  "pytest>=9,<10",
42
41
  "pytest-cov",
43
42
  "ruff>=0.9",
@@ -48,7 +47,7 @@ docs = ["sphinx>=7,<10", "sphinx-rtd-theme>=2,<4", "sphinx-copybutton>=0.5,<0.6"
48
47
  packages = ["formative"]
49
48
 
50
49
  [tool.bumpversion]
51
- current_version = "2.2.4"
50
+ current_version = "2.3.0"
52
51
  commit = true
53
52
  tag = true
54
53
  tag_name = "v{new_version}"
@@ -1,5 +1,5 @@
1
1
  const DOCUMENTATION_OPTIONS = {
2
- VERSION: '2.2.3',
2
+ VERSION: '2.2.5',
3
3
  LANGUAGE: 'en',
4
4
  COLLAPSE_INDEX: false,
5
5
  BUILDER: 'html',
@@ -6,7 +6,7 @@
6
6
  <meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" />
7
7
 
8
8
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
9
- <title>Benchmarks &mdash; formative 2.2.3 documentation</title>
9
+ <title>Benchmarks &mdash; formative 2.2.5 documentation</title>
10
10
  <link rel="stylesheet" type="text/css" href="../_static/pygments.css?v=b86133f3" />
11
11
  <link rel="stylesheet" type="text/css" href="../_static/css/theme.css?v=9edc463e" />
12
12
  <link rel="stylesheet" type="text/css" href="../_static/copybutton.css?v=76b2166b" />
@@ -16,7 +16,7 @@
16
16
  <link rel="canonical" href="https://docs.getformative.dev/causal/benchmarks.html" />
17
17
  <script src="../_static/jquery.js?v=5d32c60e"></script>
18
18
  <script src="../_static/_sphinx_javascript_frameworks_compat.js?v=2cd50e6c"></script>
19
- <script src="../_static/documentation_options.js?v=85e56e6b"></script>
19
+ <script src="../_static/documentation_options.js?v=ea147abf"></script>
20
20
  <script src="../_static/doctools.js?v=fd6eb6e6"></script>
21
21
  <script src="../_static/sphinx_highlight.js?v=6ffebe34"></script>
22
22
  <script src="../_static/clipboard.min.js?v=a7894cd8"></script>
@@ -6,7 +6,7 @@
6
6
  <meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" />
7
7
 
8
8
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
9
- <title>DAG &mdash; formative 2.2.3 documentation</title>
9
+ <title>DAG &mdash; formative 2.2.5 documentation</title>
10
10
  <link rel="stylesheet" type="text/css" href="../_static/pygments.css?v=b86133f3" />
11
11
  <link rel="stylesheet" type="text/css" href="../_static/css/theme.css?v=9edc463e" />
12
12
  <link rel="stylesheet" type="text/css" href="../_static/copybutton.css?v=76b2166b" />
@@ -16,7 +16,7 @@
16
16
  <link rel="canonical" href="https://docs.getformative.dev/causal/dag.html" />
17
17
  <script src="../_static/jquery.js?v=5d32c60e"></script>
18
18
  <script src="../_static/_sphinx_javascript_frameworks_compat.js?v=2cd50e6c"></script>
19
- <script src="../_static/documentation_options.js?v=85e56e6b"></script>
19
+ <script src="../_static/documentation_options.js?v=ea147abf"></script>
20
20
  <script src="../_static/doctools.js?v=fd6eb6e6"></script>
21
21
  <script src="../_static/sphinx_highlight.js?v=6ffebe34"></script>
22
22
  <script src="../_static/clipboard.min.js?v=a7894cd8"></script>
@@ -6,7 +6,7 @@
6
6
  <meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" />
7
7
 
8
8
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
9
- <title>Decisions &mdash; formative 2.2.3 documentation</title>
9
+ <title>Decisions &mdash; formative 2.2.5 documentation</title>
10
10
  <link rel="stylesheet" type="text/css" href="../_static/pygments.css?v=b86133f3" />
11
11
  <link rel="stylesheet" type="text/css" href="../_static/css/theme.css?v=9edc463e" />
12
12
  <link rel="stylesheet" type="text/css" href="../_static/copybutton.css?v=76b2166b" />
@@ -16,7 +16,7 @@
16
16
  <link rel="canonical" href="https://docs.getformative.dev/causal/decisions.html" />
17
17
  <script src="../_static/jquery.js?v=5d32c60e"></script>
18
18
  <script src="../_static/_sphinx_javascript_frameworks_compat.js?v=2cd50e6c"></script>
19
- <script src="../_static/documentation_options.js?v=85e56e6b"></script>
19
+ <script src="../_static/documentation_options.js?v=ea147abf"></script>
20
20
  <script src="../_static/doctools.js?v=fd6eb6e6"></script>
21
21
  <script src="../_static/sphinx_highlight.js?v=6ffebe34"></script>
22
22
  <script src="../_static/clipboard.min.js?v=a7894cd8"></script>
@@ -6,7 +6,7 @@
6
6
  <meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" />
7
7
 
8
8
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
9
- <title>Estimands &mdash; formative 2.2.3 documentation</title>
9
+ <title>Estimands &mdash; formative 2.2.5 documentation</title>
10
10
  <link rel="stylesheet" type="text/css" href="../_static/pygments.css?v=b86133f3" />
11
11
  <link rel="stylesheet" type="text/css" href="../_static/css/theme.css?v=9edc463e" />
12
12
  <link rel="stylesheet" type="text/css" href="../_static/copybutton.css?v=76b2166b" />
@@ -16,7 +16,7 @@
16
16
  <link rel="canonical" href="https://docs.getformative.dev/causal/estimands.html" />
17
17
  <script src="../_static/jquery.js?v=5d32c60e"></script>
18
18
  <script src="../_static/_sphinx_javascript_frameworks_compat.js?v=2cd50e6c"></script>
19
- <script src="../_static/documentation_options.js?v=85e56e6b"></script>
19
+ <script src="../_static/documentation_options.js?v=ea147abf"></script>
20
20
  <script src="../_static/doctools.js?v=fd6eb6e6"></script>
21
21
  <script src="../_static/sphinx_highlight.js?v=6ffebe34"></script>
22
22
  <script src="../_static/clipboard.min.js?v=a7894cd8"></script>
@@ -6,7 +6,7 @@
6
6
  <meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" />
7
7
 
8
8
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
9
- <title>Estimators &mdash; formative 2.2.3 documentation</title>
9
+ <title>Estimators &mdash; formative 2.2.5 documentation</title>
10
10
  <link rel="stylesheet" type="text/css" href="../_static/pygments.css?v=b86133f3" />
11
11
  <link rel="stylesheet" type="text/css" href="../_static/css/theme.css?v=9edc463e" />
12
12
  <link rel="stylesheet" type="text/css" href="../_static/copybutton.css?v=76b2166b" />
@@ -16,7 +16,7 @@
16
16
  <link rel="canonical" href="https://docs.getformative.dev/causal/estimators.html" />
17
17
  <script src="../_static/jquery.js?v=5d32c60e"></script>
18
18
  <script src="../_static/_sphinx_javascript_frameworks_compat.js?v=2cd50e6c"></script>
19
- <script src="../_static/documentation_options.js?v=85e56e6b"></script>
19
+ <script src="../_static/documentation_options.js?v=ea147abf"></script>
20
20
  <script src="../_static/doctools.js?v=fd6eb6e6"></script>
21
21
  <script src="../_static/sphinx_highlight.js?v=6ffebe34"></script>
22
22
  <script src="../_static/clipboard.min.js?v=a7894cd8"></script>
@@ -6,7 +6,7 @@
6
6
  <meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" />
7
7
 
8
8
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
9
- <title>Introduction &mdash; formative 2.2.3 documentation</title>
9
+ <title>Introduction &mdash; formative 2.2.5 documentation</title>
10
10
  <link rel="stylesheet" type="text/css" href="../_static/pygments.css?v=b86133f3" />
11
11
  <link rel="stylesheet" type="text/css" href="../_static/css/theme.css?v=9edc463e" />
12
12
  <link rel="stylesheet" type="text/css" href="../_static/copybutton.css?v=76b2166b" />
@@ -16,7 +16,7 @@
16
16
  <link rel="canonical" href="https://docs.getformative.dev/causal/introduction.html" />
17
17
  <script src="../_static/jquery.js?v=5d32c60e"></script>
18
18
  <script src="../_static/_sphinx_javascript_frameworks_compat.js?v=2cd50e6c"></script>
19
- <script src="../_static/documentation_options.js?v=85e56e6b"></script>
19
+ <script src="../_static/documentation_options.js?v=ea147abf"></script>
20
20
  <script src="../_static/doctools.js?v=fd6eb6e6"></script>
21
21
  <script src="../_static/sphinx_highlight.js?v=6ffebe34"></script>
22
22
  <script src="../_static/clipboard.min.js?v=a7894cd8"></script>
@@ -6,7 +6,7 @@
6
6
  <meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" />
7
7
 
8
8
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
9
- <title>Motivation &mdash; formative 2.2.3 documentation</title>
9
+ <title>Motivation &mdash; formative 2.2.5 documentation</title>
10
10
  <link rel="stylesheet" type="text/css" href="../_static/pygments.css?v=b86133f3" />
11
11
  <link rel="stylesheet" type="text/css" href="../_static/css/theme.css?v=9edc463e" />
12
12
  <link rel="stylesheet" type="text/css" href="../_static/copybutton.css?v=76b2166b" />
@@ -16,7 +16,7 @@
16
16
  <link rel="canonical" href="https://docs.getformative.dev/causal/motivation.html" />
17
17
  <script src="../_static/jquery.js?v=5d32c60e"></script>
18
18
  <script src="../_static/_sphinx_javascript_frameworks_compat.js?v=2cd50e6c"></script>
19
- <script src="../_static/documentation_options.js?v=85e56e6b"></script>
19
+ <script src="../_static/documentation_options.js?v=ea147abf"></script>
20
20
  <script src="../_static/doctools.js?v=fd6eb6e6"></script>
21
21
  <script src="../_static/sphinx_highlight.js?v=6ffebe34"></script>
22
22
  <script src="../_static/clipboard.min.js?v=a7894cd8"></script>
@@ -6,7 +6,7 @@
6
6
  <meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" />
7
7
 
8
8
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
9
- <title>Refutations &mdash; formative 2.2.3 documentation</title>
9
+ <title>Refutations &mdash; formative 2.2.5 documentation</title>
10
10
  <link rel="stylesheet" type="text/css" href="../_static/pygments.css?v=b86133f3" />
11
11
  <link rel="stylesheet" type="text/css" href="../_static/css/theme.css?v=9edc463e" />
12
12
  <link rel="stylesheet" type="text/css" href="../_static/copybutton.css?v=76b2166b" />
@@ -16,7 +16,7 @@
16
16
  <link rel="canonical" href="https://docs.getformative.dev/causal/refutations.html" />
17
17
  <script src="../_static/jquery.js?v=5d32c60e"></script>
18
18
  <script src="../_static/_sphinx_javascript_frameworks_compat.js?v=2cd50e6c"></script>
19
- <script src="../_static/documentation_options.js?v=85e56e6b"></script>
19
+ <script src="../_static/documentation_options.js?v=ea147abf"></script>
20
20
  <script src="../_static/doctools.js?v=fd6eb6e6"></script>
21
21
  <script src="../_static/sphinx_highlight.js?v=6ffebe34"></script>
22
22
  <script src="../_static/clipboard.min.js?v=a7894cd8"></script>
@@ -6,7 +6,7 @@
6
6
  <meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" />
7
7
 
8
8
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
9
- <title>Choosing an estimator &mdash; formative 2.2.3 documentation</title>
9
+ <title>Choosing an estimator &mdash; formative 2.2.5 documentation</title>
10
10
  <link rel="stylesheet" type="text/css" href="../_static/pygments.css?v=b86133f3" />
11
11
  <link rel="stylesheet" type="text/css" href="../_static/css/theme.css?v=9edc463e" />
12
12
  <link rel="stylesheet" type="text/css" href="../_static/copybutton.css?v=76b2166b" />
@@ -16,7 +16,7 @@
16
16
  <link rel="canonical" href="https://docs.getformative.dev/causal/wizard.html" />
17
17
  <script src="../_static/jquery.js?v=5d32c60e"></script>
18
18
  <script src="../_static/_sphinx_javascript_frameworks_compat.js?v=2cd50e6c"></script>
19
- <script src="../_static/documentation_options.js?v=85e56e6b"></script>
19
+ <script src="../_static/documentation_options.js?v=ea147abf"></script>
20
20
  <script src="../_static/doctools.js?v=fd6eb6e6"></script>
21
21
  <script src="../_static/sphinx_highlight.js?v=6ffebe34"></script>
22
22
  <script src="../_static/clipboard.min.js?v=a7894cd8"></script>