formative-ds 2.2.1__tar.gz → 2.2.2__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 (297) hide show
  1. {formative_ds-2.2.1 → formative_ds-2.2.2}/CLAUDE.md +3 -2
  2. {formative_ds-2.2.1 → formative_ds-2.2.2}/PKG-INFO +1 -1
  3. {formative_ds-2.2.1 → formative_ds-2.2.2}/formative/causal/__init__.py +1 -1
  4. formative_ds-2.2.2/formative/causal/_assumptions.py +25 -0
  5. {formative_ds-2.2.1 → formative_ds-2.2.2}/formative/causal/estimators/_base.py +1 -1
  6. {formative_ds-2.2.1 → formative_ds-2.2.2}/formative/causal/estimators/_cate.py +1 -1
  7. {formative_ds-2.2.1 → formative_ds-2.2.2}/formative/causal/estimators/did.py +1 -1
  8. {formative_ds-2.2.1 → formative_ds-2.2.2}/formative/causal/estimators/iv.py +1 -1
  9. {formative_ds-2.2.1 → formative_ds-2.2.2}/formative/causal/estimators/matching.py +27 -19
  10. {formative_ds-2.2.1 → formative_ds-2.2.2}/formative/causal/estimators/ols.py +1 -1
  11. {formative_ds-2.2.1 → formative_ds-2.2.2}/formative/causal/estimators/policy.py +1 -1
  12. {formative_ds-2.2.1 → formative_ds-2.2.2}/formative/causal/estimators/rct.py +1 -1
  13. {formative_ds-2.2.1 → formative_ds-2.2.2}/formative/causal/estimators/rdd.py +3 -3
  14. {formative_ds-2.2.1 → formative_ds-2.2.2}/formative/causal/refutations/_check.py +26 -22
  15. {formative_ds-2.2.1 → formative_ds-2.2.2}/formative/causal/refutations/cate.py +4 -12
  16. {formative_ds-2.2.1 → formative_ds-2.2.2}/formative/causal/refutations/did.py +14 -24
  17. {formative_ds-2.2.1 → formative_ds-2.2.2}/formative/causal/refutations/iv.py +4 -12
  18. {formative_ds-2.2.1 → formative_ds-2.2.2}/formative/causal/refutations/matching.py +15 -25
  19. {formative_ds-2.2.1 → formative_ds-2.2.2}/formative/causal/refutations/ols.py +4 -12
  20. {formative_ds-2.2.1 → formative_ds-2.2.2}/formative/causal/refutations/policy.py +1 -4
  21. {formative_ds-2.2.1 → formative_ds-2.2.2}/formative/causal/refutations/rct.py +7 -13
  22. {formative_ds-2.2.1 → formative_ds-2.2.2}/formative/causal/refutations/rdd.py +4 -12
  23. {formative_ds-2.2.1 → formative_ds-2.2.2}/pyproject.toml +2 -2
  24. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/documentation_options.js +1 -1
  25. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/causal/benchmarks.html +2 -2
  26. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/causal/dag.html +2 -2
  27. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/causal/decisions.html +2 -2
  28. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/causal/estimands.html +2 -2
  29. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/causal/estimators.html +2 -2
  30. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/causal/introduction.html +2 -2
  31. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/causal/motivation.html +2 -2
  32. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/causal/refutations.html +2 -2
  33. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/causal/wizard.html +2 -2
  34. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/game/decision_rules.html +2 -2
  35. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/game/introduction.html +2 -2
  36. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/game/single_player.html +2 -2
  37. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/genindex.html +2 -2
  38. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/index.html +2 -2
  39. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/search.html +2 -2
  40. {formative_ds-2.2.1 → formative_ds-2.2.2}/uv.lock +1 -1
  41. {formative_ds-2.2.1 → formative_ds-2.2.2}/.gitattributes +0 -0
  42. {formative_ds-2.2.1 → formative_ds-2.2.2}/.github/workflows/ci.yml +0 -0
  43. {formative_ds-2.2.1 → formative_ds-2.2.2}/.github/workflows/publish.yml +0 -0
  44. {formative_ds-2.2.1 → formative_ds-2.2.2}/.gitignore +0 -0
  45. {formative_ds-2.2.1 → formative_ds-2.2.2}/LICENSE +0 -0
  46. {formative_ds-2.2.1 → formative_ds-2.2.2}/Makefile +0 -0
  47. {formative_ds-2.2.1 → formative_ds-2.2.2}/README.md +0 -0
  48. {formative_ds-2.2.1 → formative_ds-2.2.2}/docs/_static/version_switcher.css +0 -0
  49. {formative_ds-2.2.1 → formative_ds-2.2.2}/docs/_static/version_switcher.js +0 -0
  50. {formative_ds-2.2.1 → formative_ds-2.2.2}/docs/causal/benchmarks.rst +0 -0
  51. {formative_ds-2.2.1 → formative_ds-2.2.2}/docs/causal/dag.rst +0 -0
  52. {formative_ds-2.2.1 → formative_ds-2.2.2}/docs/causal/decisions.rst +0 -0
  53. {formative_ds-2.2.1 → formative_ds-2.2.2}/docs/causal/estimands.rst +0 -0
  54. {formative_ds-2.2.1 → formative_ds-2.2.2}/docs/causal/estimators.rst +0 -0
  55. {formative_ds-2.2.1 → formative_ds-2.2.2}/docs/causal/introduction.rst +0 -0
  56. {formative_ds-2.2.1 → formative_ds-2.2.2}/docs/causal/motivation.rst +0 -0
  57. {formative_ds-2.2.1 → formative_ds-2.2.2}/docs/causal/refutations.rst +0 -0
  58. {formative_ds-2.2.1 → formative_ds-2.2.2}/docs/causal/wizard.rst +0 -0
  59. {formative_ds-2.2.1 → formative_ds-2.2.2}/docs/conf.py +0 -0
  60. {formative_ds-2.2.1 → formative_ds-2.2.2}/docs/design/policy_learning.md +0 -0
  61. {formative_ds-2.2.1 → formative_ds-2.2.2}/docs/game/decision_rules.rst +0 -0
  62. {formative_ds-2.2.1 → formative_ds-2.2.2}/docs/game/introduction.rst +0 -0
  63. {formative_ds-2.2.1 → formative_ds-2.2.2}/docs/game/single_player.rst +0 -0
  64. {formative_ds-2.2.1 → formative_ds-2.2.2}/docs/index.rst +0 -0
  65. {formative_ds-2.2.1 → formative_ds-2.2.2}/examples/decision/01_decision_basic.py +0 -0
  66. {formative_ds-2.2.1 → formative_ds-2.2.2}/examples/did/01_did_basic.py +0 -0
  67. {formative_ds-2.2.1 → formative_ds-2.2.2}/examples/iv/01_iv_basic.py +0 -0
  68. {formative_ds-2.2.1 → formative_ds-2.2.2}/examples/iv/02_iv_vs_ols_bias.py +0 -0
  69. {formative_ds-2.2.1 → formative_ds-2.2.2}/examples/iv/03_iv_with_observed_confounder.py +0 -0
  70. {formative_ds-2.2.1 → formative_ds-2.2.2}/examples/iv/04_iv_refutation.py +0 -0
  71. {formative_ds-2.2.1 → formative_ds-2.2.2}/examples/matching/01_basic_matching.py +0 -0
  72. {formative_ds-2.2.1 → formative_ds-2.2.2}/examples/matching/02_matching_vs_naive.py +0 -0
  73. {formative_ds-2.2.1 → formative_ds-2.2.2}/examples/matching/03_matching_refutation.py +0 -0
  74. {formative_ds-2.2.1 → formative_ds-2.2.2}/examples/ols/01_ols_basic.py +0 -0
  75. {formative_ds-2.2.1 → formative_ds-2.2.2}/examples/ols/02_ols_unobserved_confounder.py +0 -0
  76. {formative_ds-2.2.1 → formative_ds-2.2.2}/examples/ols/03_ols_multiple_confounders.py +0 -0
  77. {formative_ds-2.2.1 → formative_ds-2.2.2}/examples/rct/01_rct_basic.py +0 -0
  78. {formative_ds-2.2.1 → formative_ds-2.2.2}/examples/rdd/01_rdd_basic.py +0 -0
  79. {formative_ds-2.2.1 → formative_ds-2.2.2}/examples/rdd/02_rdd_positive_running_var.py +0 -0
  80. {formative_ds-2.2.1 → formative_ds-2.2.2}/formative/__init__.py +0 -0
  81. {formative_ds-2.2.1 → formative_ds-2.2.2}/formative/causal/_exceptions.py +0 -0
  82. {formative_ds-2.2.1 → formative_ds-2.2.2}/formative/causal/_explain.py +0 -0
  83. {formative_ds-2.2.1 → formative_ds-2.2.2}/formative/causal/dag.py +0 -0
  84. {formative_ds-2.2.1 → formative_ds-2.2.2}/formative/causal/decision.py +0 -0
  85. {formative_ds-2.2.1 → formative_ds-2.2.2}/formative/causal/estimators/__init__.py +0 -0
  86. {formative_ds-2.2.1 → formative_ds-2.2.2}/formative/causal/refutations/__init__.py +0 -0
  87. {formative_ds-2.2.1 → formative_ds-2.2.2}/formative/game/__init__.py +0 -0
  88. {formative_ds-2.2.1 → formative_ds-2.2.2}/formative/game/expected_value.py +0 -0
  89. {formative_ds-2.2.1 → formative_ds-2.2.2}/formative/game/hurwicz.py +0 -0
  90. {formative_ds-2.2.1 → formative_ds-2.2.2}/formative/game/laplace.py +0 -0
  91. {formative_ds-2.2.1 → formative_ds-2.2.2}/formative/game/maximax.py +0 -0
  92. {formative_ds-2.2.1 → formative_ds-2.2.2}/formative/game/maximin.py +0 -0
  93. {formative_ds-2.2.1 → formative_ds-2.2.2}/formative/game/minimax.py +0 -0
  94. {formative_ds-2.2.1 → formative_ds-2.2.2}/notebooks/card_krueger_benchmark.ipynb +0 -0
  95. {formative_ds-2.2.1 → formative_ds-2.2.2}/notebooks/lalonde_benchmark.ipynb +0 -0
  96. {formative_ds-2.2.1 → formative_ds-2.2.2}/scripts/snapshot_docs.py +0 -0
  97. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_sources/causal/benchmarks.rst.txt +0 -0
  98. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_sources/causal/dag.rst.txt +0 -0
  99. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_sources/causal/decisions.rst.txt +0 -0
  100. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_sources/causal/estimands.rst.txt +0 -0
  101. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_sources/causal/estimators.rst.txt +0 -0
  102. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_sources/causal/introduction.rst.txt +0 -0
  103. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_sources/causal/motivation.rst.txt +0 -0
  104. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_sources/causal/refutations.rst.txt +0 -0
  105. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_sources/causal/wizard.rst.txt +0 -0
  106. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_sources/game/decision_rules.rst.txt +0 -0
  107. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_sources/game/introduction.rst.txt +0 -0
  108. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_sources/game/single_player.rst.txt +0 -0
  109. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_sources/index.rst.txt +0 -0
  110. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/_sphinx_javascript_frameworks_compat.js +0 -0
  111. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/base-stemmer.js +0 -0
  112. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/basic.css +0 -0
  113. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/check-solid.svg +0 -0
  114. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/clipboard.min.js +0 -0
  115. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/copy-button.svg +0 -0
  116. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/copybutton.css +0 -0
  117. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/copybutton.js +0 -0
  118. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/copybutton_funcs.js +0 -0
  119. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/css/badge_only.css +0 -0
  120. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/css/fonts/Roboto-Slab-Bold.woff +0 -0
  121. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/css/fonts/Roboto-Slab-Bold.woff2 +0 -0
  122. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/css/fonts/Roboto-Slab-Regular.woff +0 -0
  123. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/css/fonts/Roboto-Slab-Regular.woff2 +0 -0
  124. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/css/fonts/fontawesome-webfont.eot +0 -0
  125. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/css/fonts/fontawesome-webfont.svg +0 -0
  126. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/css/fonts/fontawesome-webfont.ttf +0 -0
  127. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/css/fonts/fontawesome-webfont.woff +0 -0
  128. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/css/fonts/fontawesome-webfont.woff2 +0 -0
  129. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/css/fonts/lato-bold-italic.woff +0 -0
  130. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/css/fonts/lato-bold-italic.woff2 +0 -0
  131. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/css/fonts/lato-bold.woff +0 -0
  132. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/css/fonts/lato-bold.woff2 +0 -0
  133. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/css/fonts/lato-normal-italic.woff +0 -0
  134. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/css/fonts/lato-normal-italic.woff2 +0 -0
  135. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/css/fonts/lato-normal.woff +0 -0
  136. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/css/fonts/lato-normal.woff2 +0 -0
  137. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/css/theme.css +0 -0
  138. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/doctools.js +0 -0
  139. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/documentation_options.js +0 -0
  140. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/english-stemmer.js +0 -0
  141. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/file.png +0 -0
  142. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/fonts/Lato/lato-bold.eot +0 -0
  143. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/fonts/Lato/lato-bold.ttf +0 -0
  144. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/fonts/Lato/lato-bold.woff +0 -0
  145. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/fonts/Lato/lato-bold.woff2 +0 -0
  146. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/fonts/Lato/lato-bolditalic.eot +0 -0
  147. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/fonts/Lato/lato-bolditalic.ttf +0 -0
  148. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/fonts/Lato/lato-bolditalic.woff +0 -0
  149. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/fonts/Lato/lato-bolditalic.woff2 +0 -0
  150. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/fonts/Lato/lato-italic.eot +0 -0
  151. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/fonts/Lato/lato-italic.ttf +0 -0
  152. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/fonts/Lato/lato-italic.woff +0 -0
  153. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/fonts/Lato/lato-italic.woff2 +0 -0
  154. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/fonts/Lato/lato-regular.eot +0 -0
  155. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/fonts/Lato/lato-regular.ttf +0 -0
  156. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/fonts/Lato/lato-regular.woff +0 -0
  157. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/fonts/Lato/lato-regular.woff2 +0 -0
  158. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot +0 -0
  159. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf +0 -0
  160. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff +0 -0
  161. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 +0 -0
  162. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot +0 -0
  163. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf +0 -0
  164. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff +0 -0
  165. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 +0 -0
  166. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/jquery.js +0 -0
  167. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/js/badge_only.js +0 -0
  168. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/js/theme.js +0 -0
  169. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/js/versions.js +0 -0
  170. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/language_data.js +0 -0
  171. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/minus.png +0 -0
  172. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/plus.png +0 -0
  173. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/pygments.css +0 -0
  174. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/searchtools.js +0 -0
  175. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/sphinx_highlight.js +0 -0
  176. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/version_switcher.css +0 -0
  177. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/_static/version_switcher.js +0 -0
  178. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/causal/benchmarks.html +0 -0
  179. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/causal/dag.html +0 -0
  180. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/causal/decisions.html +0 -0
  181. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/causal/estimands.html +0 -0
  182. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/causal/estimators.html +0 -0
  183. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/causal/introduction.html +0 -0
  184. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/causal/motivation.html +0 -0
  185. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/causal/refutations.html +0 -0
  186. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/causal/wizard.html +0 -0
  187. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/game/decision_rules.html +0 -0
  188. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/game/introduction.html +0 -0
  189. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/game/single_player.html +0 -0
  190. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/genindex.html +0 -0
  191. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/index.html +0 -0
  192. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/objects.inv +0 -0
  193. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/search.html +0 -0
  194. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.1/searchindex.js +0 -0
  195. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_sources/causal/benchmarks.rst.txt +0 -0
  196. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_sources/causal/dag.rst.txt +0 -0
  197. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_sources/causal/decisions.rst.txt +0 -0
  198. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_sources/causal/estimands.rst.txt +0 -0
  199. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_sources/causal/estimators.rst.txt +0 -0
  200. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_sources/causal/introduction.rst.txt +0 -0
  201. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_sources/causal/motivation.rst.txt +0 -0
  202. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_sources/causal/refutations.rst.txt +0 -0
  203. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_sources/causal/wizard.rst.txt +0 -0
  204. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_sources/game/decision_rules.rst.txt +0 -0
  205. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_sources/game/introduction.rst.txt +0 -0
  206. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_sources/game/single_player.rst.txt +0 -0
  207. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_sources/index.rst.txt +0 -0
  208. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/_sphinx_javascript_frameworks_compat.js +0 -0
  209. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/base-stemmer.js +0 -0
  210. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/basic.css +0 -0
  211. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/check-solid.svg +0 -0
  212. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/clipboard.min.js +0 -0
  213. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/copy-button.svg +0 -0
  214. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/copybutton.css +0 -0
  215. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/copybutton.js +0 -0
  216. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/copybutton_funcs.js +0 -0
  217. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/css/badge_only.css +0 -0
  218. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/css/fonts/Roboto-Slab-Bold.woff +0 -0
  219. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/css/fonts/Roboto-Slab-Bold.woff2 +0 -0
  220. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/css/fonts/Roboto-Slab-Regular.woff +0 -0
  221. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/css/fonts/Roboto-Slab-Regular.woff2 +0 -0
  222. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/css/fonts/fontawesome-webfont.eot +0 -0
  223. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/css/fonts/fontawesome-webfont.svg +0 -0
  224. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/css/fonts/fontawesome-webfont.ttf +0 -0
  225. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/css/fonts/fontawesome-webfont.woff +0 -0
  226. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/css/fonts/fontawesome-webfont.woff2 +0 -0
  227. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/css/fonts/lato-bold-italic.woff +0 -0
  228. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/css/fonts/lato-bold-italic.woff2 +0 -0
  229. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/css/fonts/lato-bold.woff +0 -0
  230. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/css/fonts/lato-bold.woff2 +0 -0
  231. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/css/fonts/lato-normal-italic.woff +0 -0
  232. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/css/fonts/lato-normal-italic.woff2 +0 -0
  233. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/css/fonts/lato-normal.woff +0 -0
  234. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/css/fonts/lato-normal.woff2 +0 -0
  235. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/css/theme.css +0 -0
  236. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/doctools.js +0 -0
  237. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/english-stemmer.js +0 -0
  238. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/file.png +0 -0
  239. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/fonts/Lato/lato-bold.eot +0 -0
  240. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/fonts/Lato/lato-bold.ttf +0 -0
  241. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/fonts/Lato/lato-bold.woff +0 -0
  242. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/fonts/Lato/lato-bold.woff2 +0 -0
  243. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/fonts/Lato/lato-bolditalic.eot +0 -0
  244. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/fonts/Lato/lato-bolditalic.ttf +0 -0
  245. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/fonts/Lato/lato-bolditalic.woff +0 -0
  246. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/fonts/Lato/lato-bolditalic.woff2 +0 -0
  247. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/fonts/Lato/lato-italic.eot +0 -0
  248. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/fonts/Lato/lato-italic.ttf +0 -0
  249. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/fonts/Lato/lato-italic.woff +0 -0
  250. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/fonts/Lato/lato-italic.woff2 +0 -0
  251. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/fonts/Lato/lato-regular.eot +0 -0
  252. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/fonts/Lato/lato-regular.ttf +0 -0
  253. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/fonts/Lato/lato-regular.woff +0 -0
  254. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/fonts/Lato/lato-regular.woff2 +0 -0
  255. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot +0 -0
  256. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf +0 -0
  257. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff +0 -0
  258. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 +0 -0
  259. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot +0 -0
  260. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf +0 -0
  261. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff +0 -0
  262. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 +0 -0
  263. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/jquery.js +0 -0
  264. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/js/badge_only.js +0 -0
  265. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/js/theme.js +0 -0
  266. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/js/versions.js +0 -0
  267. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/language_data.js +0 -0
  268. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/minus.png +0 -0
  269. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/plus.png +0 -0
  270. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/pygments.css +0 -0
  271. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/searchtools.js +0 -0
  272. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/sphinx_highlight.js +0 -0
  273. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/version_switcher.css +0 -0
  274. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/_static/version_switcher.js +0 -0
  275. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/objects.inv +0 -0
  276. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/2.2/searchindex.js +0 -0
  277. {formative_ds-2.2.1 → formative_ds-2.2.2}/site/versions.json +0 -0
  278. {formative_ds-2.2.1 → formative_ds-2.2.2}/tests/test_cate.py +0 -0
  279. {formative_ds-2.2.1 → formative_ds-2.2.2}/tests/test_cate_refutation.py +0 -0
  280. {formative_ds-2.2.1 → formative_ds-2.2.2}/tests/test_dag.py +0 -0
  281. {formative_ds-2.2.1 → formative_ds-2.2.2}/tests/test_decision.py +0 -0
  282. {formative_ds-2.2.1 → formative_ds-2.2.2}/tests/test_did.py +0 -0
  283. {formative_ds-2.2.1 → formative_ds-2.2.2}/tests/test_did_refutation.py +0 -0
  284. {formative_ds-2.2.1 → formative_ds-2.2.2}/tests/test_game.py +0 -0
  285. {formative_ds-2.2.1 → formative_ds-2.2.2}/tests/test_iv.py +0 -0
  286. {formative_ds-2.2.1 → formative_ds-2.2.2}/tests/test_iv_refutation.py +0 -0
  287. {formative_ds-2.2.1 → formative_ds-2.2.2}/tests/test_matching.py +0 -0
  288. {formative_ds-2.2.1 → formative_ds-2.2.2}/tests/test_matching_refutation.py +0 -0
  289. {formative_ds-2.2.1 → formative_ds-2.2.2}/tests/test_ols.py +0 -0
  290. {formative_ds-2.2.1 → formative_ds-2.2.2}/tests/test_ols_refutation.py +0 -0
  291. {formative_ds-2.2.1 → formative_ds-2.2.2}/tests/test_policy.py +0 -0
  292. {formative_ds-2.2.1 → formative_ds-2.2.2}/tests/test_policy_refutation.py +0 -0
  293. {formative_ds-2.2.1 → formative_ds-2.2.2}/tests/test_rct.py +0 -0
  294. {formative_ds-2.2.1 → formative_ds-2.2.2}/tests/test_rct_refutation.py +0 -0
  295. {formative_ds-2.2.1 → formative_ds-2.2.2}/tests/test_rdd.py +0 -0
  296. {formative_ds-2.2.1 → formative_ds-2.2.2}/tests/test_rdd_refutation.py +0 -0
  297. {formative_ds-2.2.1 → formative_ds-2.2.2}/vercel.json +0 -0
@@ -71,6 +71,7 @@ Do not change `_RCC_SEED` or `_PLACEBO_SEED` to 42 — the generated noise colli
71
71
  **Package layout:**
72
72
  - `formative/causal/dag.py` — `DAG`, `_Node`
73
73
  - `formative/causal/_exceptions.py` — `IdentificationError`, `GraphError`
74
+ - `formative/causal/_assumptions.py` — `Assumption` (estimators must not import from `refutations/` at module level — keeping this one-directional is what lets refutation modules import estimator internals without import cycles)
74
75
  - `formative/causal/estimators/_base.py` — `_BaseResult`, `_StatsmodelsResult` (shared result plumbing: effect stats via `_param`, `decide()`, assumptions footer)
75
76
  - `formative/causal/estimators/_cate.py` — `GroupEffect`, `_fit_cate`, `_CATEResultMixin`, modifier validation (shared by OLS/RCT and the CATE refutations)
76
77
  - `formative/causal/estimators/ols.py` — `OLSObservational`, `OLSResult`, `OLSCATEResult`
@@ -80,12 +81,12 @@ Do not change `_RCC_SEED` or `_PLACEBO_SEED` to 42 — the generated noise colli
80
81
  - `formative/causal/estimators/rct.py` — `RCT`, `RCTResult`, `RCTCATEResult`
81
82
  - `formative/causal/estimators/did.py` — `DiD`, `DiDResult`
82
83
  - `formative/causal/estimators/rdd.py` — `RDD`, `RDDResult`
83
- - `formative/causal/refutations/_check.py` — `Assumption`, `RefutationCheck`, `RefutationReport`
84
+ - `formative/causal/refutations/_check.py` — `RefutationCheck`, `RefutationReport`, shared verdict helpers (`_shift_check`, `_placebo_check`)
84
85
  - `formative/causal/refutations/ols.py` — `OLSRefutationReport`, `_check_random_common_cause`
85
86
  - `formative/causal/refutations/iv.py` — `IVRefutationReport`, `_check_first_stage_f`, `_check_random_common_cause`
86
87
  - `formative/causal/refutations/matching.py` — `MatchingRefutationReport`, `_check_placebo_treatment`, `_check_random_common_cause`
87
88
  - `formative/causal/refutations/rct.py` — `RCTRefutationReport`, `_check_random_common_cause`
88
- - `formative/causal/refutations/policy.py` — `PolicyRefutationReport`, `_check_placebo_modifiers`, `_check_random_modifier` (import `_fit_policy` lazily — a module-level import is circular via `refutations/__init__`)
89
+ - `formative/causal/refutations/policy.py` — `PolicyRefutationReport`, `_check_placebo_modifiers`, `_check_random_modifier`
89
90
  - `formative/causal/refutations/cate.py` — `_check_placebo_modifier`, `_check_random_modifier`, `_check_random_common_cause` (used by `OLSCATEResult`/`RCTCATEResult`; reports reuse the OLS/RCT report classes)
90
91
  - `formative/causal/refutations/did.py` — `DiDRefutationReport`, `_check_placebo_group`, `_check_random_common_cause`
91
92
  - `formative/causal/refutations/rdd.py` — `RDDRefutationReport`, `_check_placebo_cutoff`, `_check_random_common_cause`
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: formative-ds
3
- Version: 2.2.1
3
+ Version: 2.2.2
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
@@ -1,3 +1,4 @@
1
+ from ._assumptions import Assumption
1
2
  from .dag import DAG
2
3
  from .decision import DecisionReport
3
4
  from .estimators._cate import GroupEffect
@@ -19,7 +20,6 @@ from .refutations import (
19
20
  RefutationCheck,
20
21
  RefutationReport,
21
22
  )
22
- from .refutations._check import Assumption
23
23
 
24
24
  __all__ = [
25
25
  "DAG",
@@ -0,0 +1,25 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+
5
+
6
+ @dataclass(frozen=True)
7
+ class Assumption:
8
+ """
9
+ A single modelling assumption required for causal identification.
10
+
11
+ Every estimator exposes its assumptions via ``result.assumptions``,
12
+ a list of ``Assumption`` objects. Each assumption has a human-readable
13
+ name and a ``testable`` flag indicating whether it can be empirically
14
+ checked in the data or must be justified on substantive grounds.
15
+ """
16
+
17
+ name: str
18
+ """Human-readable description of the assumption."""
19
+
20
+ testable: bool
21
+ """``True`` if the assumption can be empirically checked; ``False`` if it rests on domain knowledge."""
22
+
23
+ def fmt_tag(self) -> str:
24
+ """Return a fixed-width bracketed testability label for use in summary output."""
25
+ return "[ testable ]" if self.testable else "[ untestable ]"
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from ..refutations._check import Assumption
3
+ from .._assumptions import Assumption
4
4
 
5
5
 
6
6
  class _BaseResult:
@@ -16,8 +16,8 @@ import numpy as np
16
16
  import pandas as pd
17
17
  import statsmodels.formula.api as smf
18
18
 
19
+ from .._assumptions import Assumption
19
20
  from ..dag import DAG
20
- from ..refutations._check import Assumption
21
21
 
22
22
  CATE_ASSUMPTIONS: list[Assumption] = [
23
23
  Assumption("Correct specification of effect modification (linear interaction form)", testable=False),
@@ -3,8 +3,8 @@ from __future__ import annotations
3
3
  import pandas as pd
4
4
  import statsmodels.formula.api as smf
5
5
 
6
+ from .._assumptions import Assumption
6
7
  from ..dag import DAG
7
- from ..refutations._check import Assumption
8
8
  from ._base import _StatsmodelsResult
9
9
 
10
10
  DID_ASSUMPTIONS: list[Assumption] = [
@@ -5,8 +5,8 @@ import statsmodels.api as sm
5
5
  import statsmodels.formula.api as smf
6
6
  from statsmodels.sandbox.regression.gmm import IV2SLS as _IV2SLS
7
7
 
8
+ from .._assumptions import Assumption
8
9
  from ..dag import DAG
9
- from ..refutations._check import Assumption
10
10
  from ._base import _StatsmodelsResult
11
11
 
12
12
  IV_ASSUMPTIONS: list[Assumption] = [
@@ -2,11 +2,12 @@ from __future__ import annotations
2
2
 
3
3
  import numpy as np
4
4
  import pandas as pd
5
+ import statsmodels.api as sm
5
6
  import statsmodels.formula.api as smf
6
7
 
8
+ from .._assumptions import Assumption
7
9
  from .._exceptions import IdentificationError
8
10
  from ..dag import DAG
9
- from ..refutations._check import Assumption
10
11
  from ._base import _BaseResult
11
12
 
12
13
  MATCHING_ASSUMPTIONS: list[Assumption] = [
@@ -23,19 +24,19 @@ _BOOTSTRAP_SEED = 42
23
24
  # ── Private helpers (also imported by formative/refutations/matching.py) ──────
24
25
 
25
26
 
27
+ def _ps_model(data: pd.DataFrame, treatment: str, adjustment_set: set[str]):
28
+ """Unfitted logit model of treatment on the adjustment set (intercept-only when empty)."""
29
+ rhs = " + ".join(sorted(adjustment_set)) if adjustment_set else "1"
30
+ return smf.logit(f"{treatment} ~ {rhs}", data=data)
31
+
32
+
26
33
  def _propensity_scores(
27
34
  data: pd.DataFrame,
28
35
  treatment: str,
29
36
  adjustment_set: set[str],
30
37
  ) -> np.ndarray:
31
38
  """Fit logistic regression and return propensity scores as a 1-D array."""
32
- if adjustment_set:
33
- rhs = " + ".join(sorted(adjustment_set))
34
- ps = smf.logit(f"{treatment} ~ {rhs}", data=data).fit(disp=0).predict()
35
- else:
36
- # Intercept-only: all units get the same PS (treatment base rate).
37
- ps = smf.logit(f"{treatment} ~ 1", data=data).fit(disp=0).predict()
38
- return np.asarray(ps)
39
+ return np.asarray(_ps_model(data, treatment, adjustment_set).fit(disp=0).predict())
39
40
 
40
41
 
41
42
  def _att_from_ps(
@@ -54,11 +55,19 @@ def _att_from_ps(
54
55
  raise ValueError("Sample must contain both treated and control units.")
55
56
 
56
57
  ps_treated = ps[treated_idx]
57
- ps_control = ps[control_idx]
58
58
 
59
- # For each treated unit find the nearest control unit by PS distance.
60
- dists = np.abs(ps_treated[:, None] - ps_control[None, :])
61
- matched = control_idx[np.argmin(dists, axis=1)]
59
+ # For each treated unit find the nearest control unit by PS distance,
60
+ # by binary search over the sorted control scores rather than a full
61
+ # treated × control distance matrix.
62
+ order = np.argsort(ps[control_idx], kind="stable")
63
+ sorted_controls = control_idx[order]
64
+ sorted_ps = ps[sorted_controls]
65
+
66
+ pos = np.searchsorted(sorted_ps, ps_treated)
67
+ left = np.clip(pos - 1, 0, len(sorted_ps) - 1)
68
+ right = np.clip(pos, 0, len(sorted_ps) - 1)
69
+ take_right = np.abs(sorted_ps[right] - ps_treated) < np.abs(sorted_ps[left] - ps_treated)
70
+ matched = sorted_controls[np.where(take_right, right, left)]
62
71
 
63
72
  return float(np.mean(outcome[treated_idx] - outcome[matched]))
64
73
 
@@ -340,22 +349,21 @@ class PropensityScoreMatching:
340
349
  Y_arr = data[Y].values.astype(float)
341
350
 
342
351
  # Point estimate ATT.
343
- ps = _propensity_scores(data, T, adjustment_set)
352
+ model = _ps_model(data, T, adjustment_set)
353
+ ps = np.asarray(model.fit(disp=0).predict())
344
354
  att = _att_from_ps(T_arr, Y_arr, ps)
345
355
 
346
356
  # Bootstrap SE and CI.
347
- # Use a minimal DataFrame (treatment + confounders only) to avoid
348
- # copying unneeded columns on every iteration.
357
+ # Resample rows of the design matrix built for the point estimate —
358
+ # re-parsing the formula per replicate dominates the runtime otherwise.
359
+ X = model.exog
349
360
  rng = np.random.default_rng(_BOOTSTRAP_SEED)
350
361
  n = len(data)
351
- ps_cols = sorted({T} | adjustment_set)
352
- ps_data = data[ps_cols]
353
362
  boot = []
354
363
  for _ in range(_BOOTSTRAP_N):
355
364
  idx = rng.integers(0, n, size=n)
356
- bd = ps_data.iloc[idx].reset_index(drop=True)
357
365
  try:
358
- bps = _propensity_scores(bd, T, adjustment_set)
366
+ bps = np.asarray(sm.Logit(T_arr[idx], X[idx]).fit(disp=0).predict())
359
367
  boot.append(_att_from_ps(T_arr[idx], Y_arr[idx], bps))
360
368
  except Exception:
361
369
  # Degenerate sample or logit failure — skip this replicate.
@@ -3,9 +3,9 @@ from __future__ import annotations
3
3
  import pandas as pd
4
4
  import statsmodels.formula.api as smf
5
5
 
6
+ from .._assumptions import Assumption
6
7
  from .._exceptions import IdentificationError
7
8
  from ..dag import DAG
8
- from ..refutations._check import Assumption
9
9
  from ._base import _StatsmodelsResult
10
10
  from ._cate import (
11
11
  CATE_ASSUMPTIONS,
@@ -22,7 +22,7 @@ from dataclasses import dataclass
22
22
  import numpy as np
23
23
  import pandas as pd
24
24
 
25
- from ..refutations._check import Assumption
25
+ from .._assumptions import Assumption
26
26
  from ._cate import _validate_modifier_dag
27
27
 
28
28
  _FOLD_SEED = 13579
@@ -3,8 +3,8 @@ from __future__ import annotations
3
3
  import pandas as pd
4
4
  import statsmodels.formula.api as smf
5
5
 
6
+ from .._assumptions import Assumption
6
7
  from ..dag import DAG
7
- from ..refutations._check import Assumption
8
8
  from ._base import _StatsmodelsResult
9
9
  from ._cate import (
10
10
  CATE_ASSUMPTIONS,
@@ -3,8 +3,8 @@ from __future__ import annotations
3
3
  import pandas as pd
4
4
  import statsmodels.formula.api as smf
5
5
 
6
+ from .._assumptions import Assumption
6
7
  from ..dag import DAG
7
- from ..refutations._check import Assumption
8
8
  from ._base import _StatsmodelsResult
9
9
 
10
10
  RDD_ASSUMPTIONS: list[Assumption] = [
@@ -253,9 +253,9 @@ class RDD:
253
253
  # Apply bandwidth filter
254
254
  if self._bandwidth is not None:
255
255
  mask = (data[self._running_var] - self._cutoff).abs() <= self._bandwidth
256
- filtered = data.loc[mask].copy()
256
+ filtered = data.loc[mask]
257
257
  else:
258
- filtered = data.copy()
258
+ filtered = data
259
259
 
260
260
  # Unadjusted effect: naive mean difference above vs below cutoff
261
261
  above = filtered.loc[filtered[self._running_var] >= self._cutoff, self._outcome].mean()
@@ -1,7 +1,5 @@
1
1
  from __future__ import annotations
2
2
 
3
- from dataclasses import dataclass
4
-
5
3
  import numpy as np
6
4
  import pandas as pd
7
5
 
@@ -17,26 +15,32 @@ def _add_random_column(data: pd.DataFrame, seed: int = _RCC_SEED) -> tuple[pd.Da
17
15
  return data.assign(**{col: rng.normal(size=len(data))}), col
18
16
 
19
17
 
20
- @dataclass(frozen=True)
21
- class Assumption:
22
- """
23
- A single modelling assumption required for causal identification.
24
-
25
- Every estimator exposes its assumptions via ``result.assumptions``,
26
- a list of ``Assumption`` objects. Each assumption has a human-readable
27
- name and a ``testable`` flag indicating whether it can be empirically
28
- checked in the data or must be justified on substantive grounds.
29
- """
30
-
31
- name: str
32
- """Human-readable description of the assumption."""
33
-
34
- testable: bool
35
- """``True`` if the assumption can be empirically checked; ``False`` if it rests on domain knowledge."""
36
-
37
- def fmt_tag(self) -> str:
38
- """Return a fixed-width bracketed testability label for use in summary output."""
39
- return "[ testable ]" if self.testable else "[ untestable ]"
18
+ def _shift_check(new_effect: float, original_effect: float, original_se: float, fail_suffix: str) -> RefutationCheck:
19
+ """Random-common-cause verdict: pass iff the refitted estimate moved by at most one standard error."""
20
+ shift = abs(new_effect - original_effect)
21
+ passed = shift <= original_se
22
+ if passed:
23
+ detail = f"estimate shifted by {shift:.4f} (≤ 1 SE = {original_se:.4f})"
24
+ else:
25
+ detail = f"estimate shifted by {shift:.4f} (> 1 SE = {original_se:.4f}) {fail_suffix}"
26
+ return RefutationCheck(name="Random common cause", passed=passed, detail=detail)
27
+
28
+
29
+ def _placebo_check(
30
+ name: str,
31
+ label: str,
32
+ placebo_effect: float,
33
+ original_se: float,
34
+ pass_suffix: str,
35
+ fail_suffix: str,
36
+ ) -> RefutationCheck:
37
+ """Placebo verdict: pass iff the placebo estimate is within one standard error of zero."""
38
+ passed = abs(placebo_effect) <= original_se
39
+ if passed:
40
+ detail = f"{label} = {placebo_effect:.4f} (≤ 1 SE = {original_se:.4f}) {pass_suffix}"
41
+ else:
42
+ detail = f"{label} = {placebo_effect:.4f} (> 1 SE = {original_se:.4f}) {fail_suffix}"
43
+ return RefutationCheck(name=name, passed=passed, detail=detail)
40
44
 
41
45
 
42
46
  class RefutationCheck:
@@ -13,7 +13,7 @@ import numpy as np
13
13
  import pandas as pd
14
14
 
15
15
  from ..estimators._cate import _fit_cate
16
- from ._check import RefutationCheck, _add_random_column
16
+ from ._check import RefutationCheck, _add_random_column, _shift_check
17
17
 
18
18
  _PLACEBO_MODIFIER_SEED = 77777
19
19
  _RANDOM_MODIFIER_SEED = 31313
@@ -42,17 +42,9 @@ def _check_random_common_cause(
42
42
 
43
43
  fit = _fit_cate(augmented, treatment, outcome, modifier, adjustment_set | {col})
44
44
 
45
- shift = abs(fit.effect - original_effect)
46
- passed = shift <= original_se
47
-
48
- if passed:
49
- detail = f"estimate shifted by {shift:.4f} (≤ 1 SE = {original_se:.4f})"
50
- else:
51
- detail = (
52
- f"estimate shifted by {shift:.4f} (> 1 SE = {original_se:.4f}) "
53
- f"Adding a random common cause destabilised the estimate."
54
- )
55
- return RefutationCheck(name="Random common cause", passed=passed, detail=detail)
45
+ return _shift_check(
46
+ fit.effect, original_effect, original_se, "Adding a random common cause destabilised the estimate."
47
+ )
56
48
 
57
49
 
58
50
  def _check_placebo_modifier(
@@ -4,7 +4,7 @@ import numpy as np
4
4
  import pandas as pd
5
5
  import statsmodels.formula.api as smf
6
6
 
7
- from ._check import RefutationCheck, RefutationReport, _add_random_column
7
+ from ._check import RefutationCheck, RefutationReport, _add_random_column, _placebo_check, _shift_check
8
8
 
9
9
  _PLACEBO_SEED = 99999
10
10
  _PLACEBO_TIME_SEED = 22222
@@ -25,15 +25,14 @@ def _placebo_permutation(
25
25
  rng = np.random.default_rng(seed)
26
26
  augmented = data.assign(**{permute_var: rng.permutation(data[permute_var].values)})
27
27
  placebo_effect = float(smf.ols(f"{outcome} ~ {group} * {time}", data=augmented).fit().params[f"{group}:{time}"])
28
- passed = abs(placebo_effect) <= original_se
29
- if passed:
30
- detail = (
31
- f"placebo estimate = {placebo_effect:.4f} (≤ 1 SE = {original_se:.4f}) "
32
- f"Permuting {axis_label} labels yields near-zero effect, as expected."
33
- )
34
- else:
35
- detail = f"placebo estimate = {placebo_effect:.4f} (> 1 SE = {original_se:.4f}) {fail_suffix}"
36
- return RefutationCheck(name=check_name, passed=passed, detail=detail)
28
+ return _placebo_check(
29
+ name=check_name,
30
+ label="placebo estimate",
31
+ placebo_effect=placebo_effect,
32
+ original_se=original_se,
33
+ pass_suffix=f"Permuting {axis_label} labels yields near-zero effect, as expected.",
34
+ fail_suffix=fail_suffix,
35
+ )
37
36
 
38
37
 
39
38
  def _check_placebo_group(
@@ -120,20 +119,11 @@ def _check_random_common_cause(
120
119
  augmented, col = _add_random_column(data)
121
120
 
122
121
  result = smf.ols(f"{outcome} ~ {group} * {time} + {col}", data=augmented).fit()
123
- interaction = f"{group}:{time}"
124
- new_effect = float(result.params[interaction])
125
-
126
- shift = abs(new_effect - original_effect)
127
- passed = shift <= original_se
128
-
129
- if passed:
130
- detail = f"estimate shifted by {shift:.4f} (≤ 1 SE = {original_se:.4f})"
131
- else:
132
- detail = (
133
- f"estimate shifted by {shift:.4f} (> 1 SE = {original_se:.4f}) "
134
- f"Adding a random common cause destabilised the DiD estimate."
135
- )
136
- return RefutationCheck(name="Random common cause", passed=passed, detail=detail)
122
+ new_effect = float(result.params[f"{group}:{time}"])
123
+
124
+ return _shift_check(
125
+ new_effect, original_effect, original_se, "Adding a random common cause destabilised the DiD estimate."
126
+ )
137
127
 
138
128
 
139
129
  class DiDRefutationReport(RefutationReport):
@@ -5,7 +5,7 @@ import statsmodels.api as sm
5
5
  import statsmodels.formula.api as smf
6
6
  from statsmodels.sandbox.regression.gmm import IV2SLS as _IV2SLS
7
7
 
8
- from ._check import RefutationCheck, RefutationReport, _add_random_column
8
+ from ._check import RefutationCheck, RefutationReport, _add_random_column, _shift_check
9
9
 
10
10
  _FIRST_STAGE_F_THRESHOLD = 10.0
11
11
 
@@ -68,17 +68,9 @@ def _check_random_common_cause(
68
68
 
69
69
  new_effect = float(_IV2SLS(endog=augmented[outcome], exog=X, instrument=Z_mat).fit().params[treatment])
70
70
 
71
- shift = abs(new_effect - original_effect)
72
- passed = shift <= original_se
73
-
74
- if passed:
75
- detail = f"estimate shifted by {shift:.4f} (≤ 1 SE = {original_se:.4f})"
76
- else:
77
- detail = (
78
- f"estimate shifted by {shift:.4f} (> 1 SE = {original_se:.4f}) "
79
- f"Adding a random common cause destabilised the estimate."
80
- )
81
- return RefutationCheck(name="Random common cause", passed=passed, detail=detail)
71
+ return _shift_check(
72
+ new_effect, original_effect, original_se, "Adding a random common cause destabilised the estimate."
73
+ )
82
74
 
83
75
 
84
76
  class IVRefutationReport(RefutationReport):
@@ -4,7 +4,7 @@ import numpy as np
4
4
  import pandas as pd
5
5
 
6
6
  from ..estimators.matching import _att_from_ps, _propensity_scores
7
- from ._check import RefutationCheck, RefutationReport, _add_random_column
7
+ from ._check import RefutationCheck, RefutationReport, _add_random_column, _placebo_check, _shift_check
8
8
 
9
9
  _PLACEBO_SEED = 99999
10
10
 
@@ -42,19 +42,17 @@ def _check_placebo_treatment(
42
42
  detail="Matching failed on permuted treatment — check data quality.",
43
43
  )
44
44
 
45
- passed = abs(placebo_att) <= original_se
46
- if passed:
47
- detail = (
48
- f"placebo ATT = {placebo_att:.4f} (≤ 1 SE = {original_se:.4f}) "
49
- f"Permuting treatment labels yields near-zero effect, as expected."
50
- )
51
- else:
52
- detail = (
53
- f"placebo ATT = {placebo_att:.4f} (> 1 SE = {original_se:.4f}) "
54
- f"A randomly permuted treatment produced a large effect — the original "
55
- f"result may be driven by residual confounding or overfitting."
56
- )
57
- return RefutationCheck(name="Placebo treatment", passed=passed, detail=detail)
45
+ return _placebo_check(
46
+ name="Placebo treatment",
47
+ label="placebo ATT",
48
+ placebo_effect=placebo_att,
49
+ original_se=original_se,
50
+ pass_suffix="Permuting treatment labels yields near-zero effect, as expected.",
51
+ fail_suffix=(
52
+ "A randomly permuted treatment produced a large effect — the original "
53
+ "result may be driven by residual confounding or overfitting."
54
+ ),
55
+ )
58
56
 
59
57
 
60
58
  def _check_random_common_cause(
@@ -91,17 +89,9 @@ def _check_random_common_cause(
91
89
  detail="Matching failed after adding random covariate — check data quality.",
92
90
  )
93
91
 
94
- shift = abs(new_att - original_att)
95
- passed = shift <= original_se
96
-
97
- if passed:
98
- detail = f"estimate shifted by {shift:.4f} (≤ 1 SE = {original_se:.4f})"
99
- else:
100
- detail = (
101
- f"estimate shifted by {shift:.4f} (> 1 SE = {original_se:.4f}) "
102
- f"Adding a random common cause destabilised the ATT estimate."
103
- )
104
- return RefutationCheck(name="Random common cause", passed=passed, detail=detail)
92
+ return _shift_check(
93
+ new_att, original_att, original_se, "Adding a random common cause destabilised the ATT estimate."
94
+ )
105
95
 
106
96
 
107
97
  class MatchingRefutationReport(RefutationReport):
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  import pandas as pd
4
4
  import statsmodels.formula.api as smf
5
5
 
6
- from ._check import RefutationCheck, RefutationReport, _add_random_column
6
+ from ._check import RefutationCheck, RefutationReport, _add_random_column, _shift_check
7
7
 
8
8
 
9
9
  def _check_random_common_cause(
@@ -27,17 +27,9 @@ def _check_random_common_cause(
27
27
  rhs = " + ".join([treatment] + controls)
28
28
  new_effect = float(smf.ols(f"{outcome} ~ {rhs}", data=augmented).fit().params[treatment])
29
29
 
30
- shift = abs(new_effect - original_effect)
31
- passed = shift <= original_se
32
-
33
- if passed:
34
- detail = f"estimate shifted by {shift:.4f} (≤ 1 SE = {original_se:.4f})"
35
- else:
36
- detail = (
37
- f"estimate shifted by {shift:.4f} (> 1 SE = {original_se:.4f}) "
38
- f"Adding a random common cause destabilised the estimate."
39
- )
40
- return RefutationCheck(name="Random common cause", passed=passed, detail=detail)
30
+ return _shift_check(
31
+ new_effect, original_effect, original_se, "Adding a random common cause destabilised the estimate."
32
+ )
41
33
 
42
34
 
43
35
  class OLSRefutationReport(RefutationReport):
@@ -13,6 +13,7 @@ from __future__ import annotations
13
13
  import numpy as np
14
14
  import pandas as pd
15
15
 
16
+ from ..estimators.policy import _fit_policy
16
17
  from ._check import RefutationCheck, RefutationReport
17
18
 
18
19
  _PLACEBO_POLICY_SEED = 88888
@@ -44,8 +45,6 @@ def _check_placebo_modifiers(
44
45
  (z ≤ 2). A significantly positive placebo value means the learner is
45
46
  manufacturing value out of noise.
46
47
  """
47
- from ..estimators.policy import _fit_policy
48
-
49
48
  rng = np.random.default_rng(_PLACEBO_POLICY_SEED)
50
49
  augmented = data.assign(**{m: rng.permutation(data[m].to_numpy()) for m in modifiers})
51
50
 
@@ -87,8 +86,6 @@ def _check_random_modifier(
87
86
  Noise carries no targeting information, so the honest value should not
88
87
  improve by more than one standard error of the original estimate.
89
88
  """
90
- from ..estimators.policy import _fit_policy
91
-
92
89
  rng = np.random.default_rng(_RANDOM_POLICY_MODIFIER_SEED)
93
90
  col = "_rpol"
94
91
  while col in data.columns:
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  import pandas as pd
4
4
  import statsmodels.formula.api as smf
5
5
 
6
- from ._check import RefutationCheck, RefutationReport, _add_random_column
6
+ from ._check import RefutationCheck, RefutationReport, _add_random_column, _shift_check
7
7
 
8
8
 
9
9
  def _check_random_common_cause(
@@ -24,18 +24,12 @@ def _check_random_common_cause(
24
24
 
25
25
  new_effect = float(smf.ols(f"{outcome} ~ {treatment} + {col}", data=augmented).fit().params[treatment])
26
26
 
27
- shift = abs(new_effect - original_effect)
28
- passed = shift <= original_se
29
-
30
- if passed:
31
- detail = f"estimate shifted by {shift:.4f} (≤ 1 SE = {original_se:.4f})"
32
- else:
33
- detail = (
34
- f"estimate shifted by {shift:.4f} (> 1 SE = {original_se:.4f}) "
35
- f"Adding a random covariate destabilised the estimate — "
36
- f"verify that treatment was truly randomised."
37
- )
38
- return RefutationCheck(name="Random common cause", passed=passed, detail=detail)
27
+ return _shift_check(
28
+ new_effect,
29
+ original_effect,
30
+ original_se,
31
+ "Adding a random covariate destabilised the estimate — verify that treatment was truly randomised.",
32
+ )
39
33
 
40
34
 
41
35
  class RCTRefutationReport(RefutationReport):
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  import pandas as pd
4
4
  import statsmodels.formula.api as smf
5
5
 
6
- from ._check import RefutationCheck, RefutationReport, _add_random_column
6
+ from ._check import RefutationCheck, RefutationReport, _add_random_column, _shift_check
7
7
 
8
8
  _PLACEBO_SEED = 99999
9
9
 
@@ -86,17 +86,9 @@ def _check_random_common_cause(
86
86
  ).fit()
87
87
  new_effect = float(result.params[treatment])
88
88
 
89
- shift = abs(new_effect - original_effect)
90
- passed = shift <= original_se
91
-
92
- if passed:
93
- detail = f"estimate shifted by {shift:.4f} (\u2264 1 SE = {original_se:.4f})"
94
- else:
95
- detail = (
96
- f"estimate shifted by {shift:.4f} (> 1 SE = {original_se:.4f}) "
97
- f"Adding a random common cause destabilised the RDD estimate."
98
- )
99
- return RefutationCheck(name="Random common cause", passed=passed, detail=detail)
89
+ return _shift_check(
90
+ new_effect, original_effect, original_se, "Adding a random common cause destabilised the RDD estimate."
91
+ )
100
92
 
101
93
 
102
94
  class RDDRefutationReport(RefutationReport):
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "formative-ds"
7
- version = "2.2.1"
7
+ version = "2.2.2"
8
8
  description = "A Python library for quantitative reasoning."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -48,7 +48,7 @@ docs = ["sphinx>=7,<10", "sphinx-rtd-theme>=2,<4", "sphinx-copybutton>=0.5,<0.6"
48
48
  packages = ["formative"]
49
49
 
50
50
  [tool.bumpversion]
51
- current_version = "2.2.1"
51
+ current_version = "2.2.2"
52
52
  commit = true
53
53
  tag = true
54
54
  tag_name = "v{new_version}"