formative-ds 2.2.3__tar.gz → 2.2.5__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.3 → formative_ds-2.2.5}/PKG-INFO +2 -2
  2. {formative_ds-2.2.3 → formative_ds-2.2.5}/formative/causal/estimators/_base.py +25 -0
  3. {formative_ds-2.2.3 → formative_ds-2.2.5}/formative/causal/estimators/iv.py +2 -9
  4. {formative_ds-2.2.3 → formative_ds-2.2.5}/formative/causal/estimators/matching.py +3 -20
  5. {formative_ds-2.2.3 → formative_ds-2.2.5}/formative/causal/estimators/ols.py +3 -21
  6. {formative_ds-2.2.3 → formative_ds-2.2.5}/pyproject.toml +3 -3
  7. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_sources/causal/benchmarks.rst.txt +22 -5
  8. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_sources/causal/decisions.rst.txt +69 -24
  9. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_sources/causal/introduction.rst.txt +10 -5
  10. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/documentation_options.js +1 -1
  11. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/causal/benchmarks.html +25 -8
  12. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/causal/dag.html +6 -6
  13. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/causal/decisions.html +74 -29
  14. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/causal/estimands.html +3 -3
  15. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/causal/estimators.html +3 -3
  16. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/causal/introduction.html +16 -12
  17. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/causal/motivation.html +7 -7
  18. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/causal/refutations.html +3 -3
  19. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/causal/wizard.html +3 -3
  20. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/game/decision_rules.html +3 -3
  21. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/game/introduction.html +3 -3
  22. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/game/single_player.html +3 -3
  23. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/genindex.html +3 -3
  24. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/index.html +5 -5
  25. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/search.html +3 -3
  26. formative_ds-2.2.5/site/2.2/searchindex.js +1 -0
  27. {formative_ds-2.2.3 → formative_ds-2.2.5}/uv.lock +1 -1
  28. formative_ds-2.2.3/site/2.2/searchindex.js +0 -1
  29. {formative_ds-2.2.3 → formative_ds-2.2.5}/.gitattributes +0 -0
  30. {formative_ds-2.2.3 → formative_ds-2.2.5}/.github/workflows/ci.yml +0 -0
  31. {formative_ds-2.2.3 → formative_ds-2.2.5}/.github/workflows/publish.yml +0 -0
  32. {formative_ds-2.2.3 → formative_ds-2.2.5}/.gitignore +0 -0
  33. {formative_ds-2.2.3 → formative_ds-2.2.5}/CLAUDE.md +0 -0
  34. {formative_ds-2.2.3 → formative_ds-2.2.5}/LICENSE +0 -0
  35. {formative_ds-2.2.3 → formative_ds-2.2.5}/Makefile +0 -0
  36. {formative_ds-2.2.3 → formative_ds-2.2.5}/README.md +0 -0
  37. {formative_ds-2.2.3 → formative_ds-2.2.5}/TODO.md +0 -0
  38. {formative_ds-2.2.3 → formative_ds-2.2.5}/docs/_static/version_switcher.css +0 -0
  39. {formative_ds-2.2.3 → formative_ds-2.2.5}/docs/_static/version_switcher.js +0 -0
  40. {formative_ds-2.2.3 → formative_ds-2.2.5}/docs/causal/benchmarks.rst +0 -0
  41. {formative_ds-2.2.3 → formative_ds-2.2.5}/docs/causal/dag.rst +0 -0
  42. {formative_ds-2.2.3 → formative_ds-2.2.5}/docs/causal/decisions.rst +0 -0
  43. {formative_ds-2.2.3 → formative_ds-2.2.5}/docs/causal/estimands.rst +0 -0
  44. {formative_ds-2.2.3 → formative_ds-2.2.5}/docs/causal/estimators.rst +0 -0
  45. {formative_ds-2.2.3 → formative_ds-2.2.5}/docs/causal/introduction.rst +0 -0
  46. {formative_ds-2.2.3 → formative_ds-2.2.5}/docs/causal/motivation.rst +0 -0
  47. {formative_ds-2.2.3 → formative_ds-2.2.5}/docs/causal/refutations.rst +0 -0
  48. {formative_ds-2.2.3 → formative_ds-2.2.5}/docs/causal/wizard.rst +0 -0
  49. {formative_ds-2.2.3 → formative_ds-2.2.5}/docs/conf.py +0 -0
  50. {formative_ds-2.2.3 → formative_ds-2.2.5}/docs/design/policy_learning.md +0 -0
  51. {formative_ds-2.2.3 → formative_ds-2.2.5}/docs/game/decision_rules.rst +0 -0
  52. {formative_ds-2.2.3 → formative_ds-2.2.5}/docs/game/introduction.rst +0 -0
  53. {formative_ds-2.2.3 → formative_ds-2.2.5}/docs/game/single_player.rst +0 -0
  54. {formative_ds-2.2.3 → formative_ds-2.2.5}/docs/index.rst +0 -0
  55. {formative_ds-2.2.3 → formative_ds-2.2.5}/examples/decision/01_decision_basic.py +0 -0
  56. {formative_ds-2.2.3 → formative_ds-2.2.5}/examples/did/01_did_basic.py +0 -0
  57. {formative_ds-2.2.3 → formative_ds-2.2.5}/examples/iv/01_iv_basic.py +0 -0
  58. {formative_ds-2.2.3 → formative_ds-2.2.5}/examples/iv/02_iv_vs_ols_bias.py +0 -0
  59. {formative_ds-2.2.3 → formative_ds-2.2.5}/examples/iv/03_iv_with_observed_confounder.py +0 -0
  60. {formative_ds-2.2.3 → formative_ds-2.2.5}/examples/iv/04_iv_refutation.py +0 -0
  61. {formative_ds-2.2.3 → formative_ds-2.2.5}/examples/matching/01_basic_matching.py +0 -0
  62. {formative_ds-2.2.3 → formative_ds-2.2.5}/examples/matching/02_matching_vs_naive.py +0 -0
  63. {formative_ds-2.2.3 → formative_ds-2.2.5}/examples/matching/03_matching_refutation.py +0 -0
  64. {formative_ds-2.2.3 → formative_ds-2.2.5}/examples/ols/01_ols_basic.py +0 -0
  65. {formative_ds-2.2.3 → formative_ds-2.2.5}/examples/ols/02_ols_unobserved_confounder.py +0 -0
  66. {formative_ds-2.2.3 → formative_ds-2.2.5}/examples/ols/03_ols_multiple_confounders.py +0 -0
  67. {formative_ds-2.2.3 → formative_ds-2.2.5}/examples/rct/01_rct_basic.py +0 -0
  68. {formative_ds-2.2.3 → formative_ds-2.2.5}/examples/rdd/01_rdd_basic.py +0 -0
  69. {formative_ds-2.2.3 → formative_ds-2.2.5}/examples/rdd/02_rdd_positive_running_var.py +0 -0
  70. {formative_ds-2.2.3 → formative_ds-2.2.5}/formative/__init__.py +0 -0
  71. {formative_ds-2.2.3 → formative_ds-2.2.5}/formative/causal/__init__.py +0 -0
  72. {formative_ds-2.2.3 → formative_ds-2.2.5}/formative/causal/_assumptions.py +0 -0
  73. {formative_ds-2.2.3 → formative_ds-2.2.5}/formative/causal/_exceptions.py +0 -0
  74. {formative_ds-2.2.3 → formative_ds-2.2.5}/formative/causal/_explain.py +0 -0
  75. {formative_ds-2.2.3 → formative_ds-2.2.5}/formative/causal/dag.py +0 -0
  76. {formative_ds-2.2.3 → formative_ds-2.2.5}/formative/causal/decision.py +0 -0
  77. {formative_ds-2.2.3 → formative_ds-2.2.5}/formative/causal/estimators/__init__.py +0 -0
  78. {formative_ds-2.2.3 → formative_ds-2.2.5}/formative/causal/estimators/_cate.py +0 -0
  79. {formative_ds-2.2.3 → formative_ds-2.2.5}/formative/causal/estimators/did.py +0 -0
  80. {formative_ds-2.2.3 → formative_ds-2.2.5}/formative/causal/estimators/policy.py +0 -0
  81. {formative_ds-2.2.3 → formative_ds-2.2.5}/formative/causal/estimators/rct.py +0 -0
  82. {formative_ds-2.2.3 → formative_ds-2.2.5}/formative/causal/estimators/rdd.py +0 -0
  83. {formative_ds-2.2.3 → formative_ds-2.2.5}/formative/causal/refutations/__init__.py +0 -0
  84. {formative_ds-2.2.3 → formative_ds-2.2.5}/formative/causal/refutations/_check.py +0 -0
  85. {formative_ds-2.2.3 → formative_ds-2.2.5}/formative/causal/refutations/cate.py +0 -0
  86. {formative_ds-2.2.3 → formative_ds-2.2.5}/formative/causal/refutations/did.py +0 -0
  87. {formative_ds-2.2.3 → formative_ds-2.2.5}/formative/causal/refutations/iv.py +0 -0
  88. {formative_ds-2.2.3 → formative_ds-2.2.5}/formative/causal/refutations/matching.py +0 -0
  89. {formative_ds-2.2.3 → formative_ds-2.2.5}/formative/causal/refutations/ols.py +0 -0
  90. {formative_ds-2.2.3 → formative_ds-2.2.5}/formative/causal/refutations/policy.py +0 -0
  91. {formative_ds-2.2.3 → formative_ds-2.2.5}/formative/causal/refutations/rct.py +0 -0
  92. {formative_ds-2.2.3 → formative_ds-2.2.5}/formative/causal/refutations/rdd.py +0 -0
  93. {formative_ds-2.2.3 → formative_ds-2.2.5}/formative/game/__init__.py +0 -0
  94. {formative_ds-2.2.3 → formative_ds-2.2.5}/formative/game/expected_value.py +0 -0
  95. {formative_ds-2.2.3 → formative_ds-2.2.5}/formative/game/hurwicz.py +0 -0
  96. {formative_ds-2.2.3 → formative_ds-2.2.5}/formative/game/laplace.py +0 -0
  97. {formative_ds-2.2.3 → formative_ds-2.2.5}/formative/game/maximax.py +0 -0
  98. {formative_ds-2.2.3 → formative_ds-2.2.5}/formative/game/maximin.py +0 -0
  99. {formative_ds-2.2.3 → formative_ds-2.2.5}/formative/game/minimax.py +0 -0
  100. {formative_ds-2.2.3 → formative_ds-2.2.5}/notebooks/card_krueger_benchmark.ipynb +0 -0
  101. {formative_ds-2.2.3 → formative_ds-2.2.5}/notebooks/lalonde_benchmark.ipynb +0 -0
  102. {formative_ds-2.2.3 → formative_ds-2.2.5}/scripts/snapshot_docs.py +0 -0
  103. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_sources/causal/benchmarks.rst.txt +0 -0
  104. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_sources/causal/dag.rst.txt +0 -0
  105. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_sources/causal/decisions.rst.txt +0 -0
  106. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_sources/causal/estimands.rst.txt +0 -0
  107. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_sources/causal/estimators.rst.txt +0 -0
  108. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_sources/causal/introduction.rst.txt +0 -0
  109. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_sources/causal/motivation.rst.txt +0 -0
  110. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_sources/causal/refutations.rst.txt +0 -0
  111. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_sources/causal/wizard.rst.txt +0 -0
  112. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_sources/game/decision_rules.rst.txt +0 -0
  113. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_sources/game/introduction.rst.txt +0 -0
  114. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_sources/game/single_player.rst.txt +0 -0
  115. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_sources/index.rst.txt +0 -0
  116. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/_sphinx_javascript_frameworks_compat.js +0 -0
  117. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/base-stemmer.js +0 -0
  118. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/basic.css +0 -0
  119. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/check-solid.svg +0 -0
  120. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/clipboard.min.js +0 -0
  121. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/copy-button.svg +0 -0
  122. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/copybutton.css +0 -0
  123. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/copybutton.js +0 -0
  124. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/copybutton_funcs.js +0 -0
  125. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/css/badge_only.css +0 -0
  126. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/css/fonts/Roboto-Slab-Bold.woff +0 -0
  127. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/css/fonts/Roboto-Slab-Bold.woff2 +0 -0
  128. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/css/fonts/Roboto-Slab-Regular.woff +0 -0
  129. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/css/fonts/Roboto-Slab-Regular.woff2 +0 -0
  130. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/css/fonts/fontawesome-webfont.eot +0 -0
  131. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/css/fonts/fontawesome-webfont.svg +0 -0
  132. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/css/fonts/fontawesome-webfont.ttf +0 -0
  133. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/css/fonts/fontawesome-webfont.woff +0 -0
  134. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/css/fonts/fontawesome-webfont.woff2 +0 -0
  135. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/css/fonts/lato-bold-italic.woff +0 -0
  136. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/css/fonts/lato-bold-italic.woff2 +0 -0
  137. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/css/fonts/lato-bold.woff +0 -0
  138. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/css/fonts/lato-bold.woff2 +0 -0
  139. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/css/fonts/lato-normal-italic.woff +0 -0
  140. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/css/fonts/lato-normal-italic.woff2 +0 -0
  141. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/css/fonts/lato-normal.woff +0 -0
  142. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/css/fonts/lato-normal.woff2 +0 -0
  143. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/css/theme.css +0 -0
  144. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/doctools.js +0 -0
  145. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/documentation_options.js +0 -0
  146. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/english-stemmer.js +0 -0
  147. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/file.png +0 -0
  148. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/fonts/Lato/lato-bold.eot +0 -0
  149. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/fonts/Lato/lato-bold.ttf +0 -0
  150. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/fonts/Lato/lato-bold.woff +0 -0
  151. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/fonts/Lato/lato-bold.woff2 +0 -0
  152. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/fonts/Lato/lato-bolditalic.eot +0 -0
  153. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/fonts/Lato/lato-bolditalic.ttf +0 -0
  154. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/fonts/Lato/lato-bolditalic.woff +0 -0
  155. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/fonts/Lato/lato-bolditalic.woff2 +0 -0
  156. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/fonts/Lato/lato-italic.eot +0 -0
  157. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/fonts/Lato/lato-italic.ttf +0 -0
  158. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/fonts/Lato/lato-italic.woff +0 -0
  159. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/fonts/Lato/lato-italic.woff2 +0 -0
  160. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/fonts/Lato/lato-regular.eot +0 -0
  161. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/fonts/Lato/lato-regular.ttf +0 -0
  162. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/fonts/Lato/lato-regular.woff +0 -0
  163. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/fonts/Lato/lato-regular.woff2 +0 -0
  164. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot +0 -0
  165. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf +0 -0
  166. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff +0 -0
  167. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 +0 -0
  168. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot +0 -0
  169. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf +0 -0
  170. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff +0 -0
  171. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 +0 -0
  172. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/jquery.js +0 -0
  173. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/js/badge_only.js +0 -0
  174. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/js/theme.js +0 -0
  175. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/js/versions.js +0 -0
  176. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/language_data.js +0 -0
  177. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/minus.png +0 -0
  178. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/plus.png +0 -0
  179. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/pygments.css +0 -0
  180. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/searchtools.js +0 -0
  181. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/sphinx_highlight.js +0 -0
  182. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/version_switcher.css +0 -0
  183. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/_static/version_switcher.js +0 -0
  184. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/causal/benchmarks.html +0 -0
  185. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/causal/dag.html +0 -0
  186. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/causal/decisions.html +0 -0
  187. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/causal/estimands.html +0 -0
  188. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/causal/estimators.html +0 -0
  189. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/causal/introduction.html +0 -0
  190. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/causal/motivation.html +0 -0
  191. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/causal/refutations.html +0 -0
  192. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/causal/wizard.html +0 -0
  193. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/game/decision_rules.html +0 -0
  194. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/game/introduction.html +0 -0
  195. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/game/single_player.html +0 -0
  196. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/genindex.html +0 -0
  197. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/index.html +0 -0
  198. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/objects.inv +0 -0
  199. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/search.html +0 -0
  200. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.1/searchindex.js +0 -0
  201. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_sources/causal/dag.rst.txt +0 -0
  202. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_sources/causal/estimands.rst.txt +0 -0
  203. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_sources/causal/estimators.rst.txt +0 -0
  204. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_sources/causal/motivation.rst.txt +0 -0
  205. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_sources/causal/refutations.rst.txt +0 -0
  206. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_sources/causal/wizard.rst.txt +0 -0
  207. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_sources/game/decision_rules.rst.txt +0 -0
  208. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_sources/game/introduction.rst.txt +0 -0
  209. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_sources/game/single_player.rst.txt +0 -0
  210. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_sources/index.rst.txt +1 -1
  211. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/_sphinx_javascript_frameworks_compat.js +0 -0
  212. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/base-stemmer.js +0 -0
  213. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/basic.css +0 -0
  214. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/check-solid.svg +0 -0
  215. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/clipboard.min.js +0 -0
  216. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/copy-button.svg +0 -0
  217. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/copybutton.css +0 -0
  218. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/copybutton.js +0 -0
  219. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/copybutton_funcs.js +0 -0
  220. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/css/badge_only.css +0 -0
  221. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/css/fonts/Roboto-Slab-Bold.woff +0 -0
  222. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/css/fonts/Roboto-Slab-Bold.woff2 +0 -0
  223. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/css/fonts/Roboto-Slab-Regular.woff +0 -0
  224. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/css/fonts/Roboto-Slab-Regular.woff2 +0 -0
  225. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/css/fonts/fontawesome-webfont.eot +0 -0
  226. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/css/fonts/fontawesome-webfont.svg +0 -0
  227. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/css/fonts/fontawesome-webfont.ttf +0 -0
  228. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/css/fonts/fontawesome-webfont.woff +0 -0
  229. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/css/fonts/fontawesome-webfont.woff2 +0 -0
  230. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/css/fonts/lato-bold-italic.woff +0 -0
  231. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/css/fonts/lato-bold-italic.woff2 +0 -0
  232. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/css/fonts/lato-bold.woff +0 -0
  233. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/css/fonts/lato-bold.woff2 +0 -0
  234. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/css/fonts/lato-normal-italic.woff +0 -0
  235. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/css/fonts/lato-normal-italic.woff2 +0 -0
  236. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/css/fonts/lato-normal.woff +0 -0
  237. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/css/fonts/lato-normal.woff2 +0 -0
  238. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/css/theme.css +0 -0
  239. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/doctools.js +0 -0
  240. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/english-stemmer.js +0 -0
  241. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/file.png +0 -0
  242. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/fonts/Lato/lato-bold.eot +0 -0
  243. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/fonts/Lato/lato-bold.ttf +0 -0
  244. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/fonts/Lato/lato-bold.woff +0 -0
  245. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/fonts/Lato/lato-bold.woff2 +0 -0
  246. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/fonts/Lato/lato-bolditalic.eot +0 -0
  247. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/fonts/Lato/lato-bolditalic.ttf +0 -0
  248. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/fonts/Lato/lato-bolditalic.woff +0 -0
  249. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/fonts/Lato/lato-bolditalic.woff2 +0 -0
  250. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/fonts/Lato/lato-italic.eot +0 -0
  251. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/fonts/Lato/lato-italic.ttf +0 -0
  252. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/fonts/Lato/lato-italic.woff +0 -0
  253. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/fonts/Lato/lato-italic.woff2 +0 -0
  254. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/fonts/Lato/lato-regular.eot +0 -0
  255. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/fonts/Lato/lato-regular.ttf +0 -0
  256. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/fonts/Lato/lato-regular.woff +0 -0
  257. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/fonts/Lato/lato-regular.woff2 +0 -0
  258. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot +0 -0
  259. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf +0 -0
  260. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff +0 -0
  261. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 +0 -0
  262. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot +0 -0
  263. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf +0 -0
  264. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff +0 -0
  265. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 +0 -0
  266. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/jquery.js +0 -0
  267. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/js/badge_only.js +0 -0
  268. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/js/theme.js +0 -0
  269. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/js/versions.js +0 -0
  270. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/language_data.js +0 -0
  271. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/minus.png +0 -0
  272. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/plus.png +0 -0
  273. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/pygments.css +0 -0
  274. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/searchtools.js +0 -0
  275. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/sphinx_highlight.js +0 -0
  276. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/version_switcher.css +0 -0
  277. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/_static/version_switcher.js +0 -0
  278. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/2.2/objects.inv +0 -0
  279. {formative_ds-2.2.3 → formative_ds-2.2.5}/site/versions.json +0 -0
  280. {formative_ds-2.2.3 → formative_ds-2.2.5}/tests/test_cate.py +0 -0
  281. {formative_ds-2.2.3 → formative_ds-2.2.5}/tests/test_cate_refutation.py +0 -0
  282. {formative_ds-2.2.3 → formative_ds-2.2.5}/tests/test_dag.py +0 -0
  283. {formative_ds-2.2.3 → formative_ds-2.2.5}/tests/test_decision.py +0 -0
  284. {formative_ds-2.2.3 → formative_ds-2.2.5}/tests/test_did.py +0 -0
  285. {formative_ds-2.2.3 → formative_ds-2.2.5}/tests/test_did_refutation.py +0 -0
  286. {formative_ds-2.2.3 → formative_ds-2.2.5}/tests/test_game.py +0 -0
  287. {formative_ds-2.2.3 → formative_ds-2.2.5}/tests/test_iv.py +0 -0
  288. {formative_ds-2.2.3 → formative_ds-2.2.5}/tests/test_iv_refutation.py +0 -0
  289. {formative_ds-2.2.3 → formative_ds-2.2.5}/tests/test_matching.py +0 -0
  290. {formative_ds-2.2.3 → formative_ds-2.2.5}/tests/test_matching_refutation.py +0 -0
  291. {formative_ds-2.2.3 → formative_ds-2.2.5}/tests/test_ols.py +0 -0
  292. {formative_ds-2.2.3 → formative_ds-2.2.5}/tests/test_ols_refutation.py +0 -0
  293. {formative_ds-2.2.3 → formative_ds-2.2.5}/tests/test_policy.py +0 -0
  294. {formative_ds-2.2.3 → formative_ds-2.2.5}/tests/test_policy_refutation.py +0 -0
  295. {formative_ds-2.2.3 → formative_ds-2.2.5}/tests/test_rct.py +0 -0
  296. {formative_ds-2.2.3 → formative_ds-2.2.5}/tests/test_rct_refutation.py +0 -0
  297. {formative_ds-2.2.3 → formative_ds-2.2.5}/tests/test_rdd.py +0 -0
  298. {formative_ds-2.2.3 → formative_ds-2.2.5}/tests/test_rdd_refutation.py +0 -0
  299. {formative_ds-2.2.3 → formative_ds-2.2.5}/vercel.json +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: formative-ds
3
- Version: 2.2.3
3
+ Version: 2.2.5
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
@@ -9,7 +9,7 @@ Author: Max Pagels
9
9
  License: MIT
10
10
  License-File: LICENSE
11
11
  Keywords: DAG,causal estimation,causal inference,econometrics,statistics
12
- Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Development Status :: 5 - Production/Stable
13
13
  Classifier: Intended Audience :: Science/Research
14
14
  Classifier: License :: OSI Approved :: MIT License
15
15
  Classifier: Programming Language :: Python :: 3
@@ -1,6 +1,31 @@
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
+ )
4
29
 
5
30
 
6
31
  class _BaseResult:
@@ -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, _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}
@@ -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
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,16 +320,7 @@ 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
343
326
 
@@ -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, _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,17 +327,7 @@ 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
 
350
332
  unadjusted_result = smf.ols(f"{self._outcome} ~ {self._treatment}", data=data).fit()
351
333
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "formative-ds"
7
- version = "2.2.3"
7
+ version = "2.2.5"
8
8
  description = "A Python library for quantitative reasoning."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -12,7 +12,7 @@ license = {text = "MIT"}
12
12
  authors = [{name = "Max Pagels"}]
13
13
  keywords = ["causal inference", "causal estimation", "econometrics", "statistics", "DAG"]
14
14
  classifiers = [
15
- "Development Status :: 3 - Alpha",
15
+ "Development Status :: 5 - Production/Stable",
16
16
  "Intended Audience :: Science/Research",
17
17
  "Topic :: Scientific/Engineering :: Mathematics",
18
18
  "License :: OSI Approved :: MIT License",
@@ -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.3"
51
+ current_version = "2.2.5"
52
52
  commit = true
53
53
  tag = true
54
54
  tag_name = "v{new_version}"
@@ -118,7 +118,9 @@ treatment/outcome relationship. The observational DAG declares this:
118
118
  obs_dag.assume(c).causes("treat", "re78")
119
119
  obs_dag.assume("treat").causes("re78")
120
120
 
121
- result = OLSObservational(obs_dag, treatment="treat", outcome="re78").fit(observational)
121
+ result = OLSObservational(
122
+ obs_dag, treatment="treat", outcome="re78"
123
+ ).fit(observational)
122
124
  print(result.summary())
123
125
 
124
126
  .. code-block:: text
@@ -156,7 +158,9 @@ each treated person only with the most similar controls:
156
158
 
157
159
  from formative.causal import PropensityScoreMatching
158
160
 
159
- result = PropensityScoreMatching(obs_dag, treatment="treat", outcome="re78").fit(observational)
161
+ result = PropensityScoreMatching(
162
+ obs_dag, treatment="treat", outcome="re78"
163
+ ).fit(observational)
160
164
  print(result.summary())
161
165
 
162
166
  .. code-block:: text
@@ -293,7 +297,10 @@ Table 3 reports four cell means; formative's DiD will be built from exactly thes
293
297
 
294
298
  for state, name in [(1, "NJ"), (0, "PA")]:
295
299
  sub = raw[raw.STATE == state]
296
- print(f"{name}: before {sub.fte_before.mean():.2f}, after {sub.fte_after.mean():.2f}")
300
+ print(
301
+ f"{name}: before {sub.fte_before.mean():.2f}, "
302
+ f"after {sub.fte_after.mean():.2f}"
303
+ )
297
304
 
298
305
  .. code-block:: text
299
306
 
@@ -317,8 +324,18 @@ binary group and time indicators:
317
324
 
318
325
  long = pd.concat(
319
326
  [
320
- pd.DataFrame({"store": raw.SHEET, "nj": raw.STATE, "after": 0, "fte": raw.fte_before}),
321
- pd.DataFrame({"store": raw.SHEET, "nj": raw.STATE, "after": 1, "fte": raw.fte_after}),
327
+ pd.DataFrame({
328
+ "store": raw.SHEET,
329
+ "nj": raw.STATE,
330
+ "after": 0,
331
+ "fte": raw.fte_before,
332
+ }),
333
+ pd.DataFrame({
334
+ "store": raw.SHEET,
335
+ "nj": raw.STATE,
336
+ "after": 1,
337
+ "fte": raw.fte_after,
338
+ }),
322
339
  ],
323
340
  ignore_index=True,
324
341
  ).dropna(subset=["fte"])
@@ -52,7 +52,11 @@ earnings using OLS with a DAG that encodes family background as a confounder:
52
52
  training = 0.6 * background + rng.normal(size=N)
53
53
  earnings = 3.0 * training + 1.2 * background + rng.normal(size=N)
54
54
 
55
- df = pd.DataFrame({"background": background, "training": training, "earnings": earnings})
55
+ df = pd.DataFrame({
56
+ "background": background,
57
+ "training": training,
58
+ "earnings": earnings,
59
+ })
56
60
  dag = DAG()
57
61
  dag.assume("background").causes("training", "earnings")
58
62
  dag.assume("training").causes("earnings")
@@ -85,7 +89,7 @@ But how confident are we in that estimate? And is it robust to estimation error?
85
89
  Decision confidence : 100.0%
86
90
  Robust to estimation error : Yes — decision is stable across 95% CI
87
91
 
88
- Game-theoretic robustness
92
+ Robustness
89
93
  ~~~~~~~~~~~~~~~~~~~~~~~~~
90
94
 
91
95
  ``decide()`` uses expected value maximisation: it picks "treat" when
@@ -96,7 +100,7 @@ treats a certain $37 gain and a 50/50 gamble between $0 and $74 identically.
96
100
  The ``robust`` flag is a first step toward robustness: it checks whether
97
101
  the decision flips anywhere inside the 95% confidence interval. But it
98
102
  only returns ``True`` or ``False``, and it is implicitly using the most
99
- conservative possible standard (the CI bounds).
103
+ conservative possible standard (the confidence interval bounds).
100
104
 
101
105
  For finer control, call ``to_outcomes()`` on the report and pass the result
102
106
  to any rule in ``formative.game``:
@@ -118,8 +122,8 @@ to any rule in ``formative.game``:
118
122
 
119
123
  By default, the three scenarios correspond to the 10th, 50th, and 90th
120
124
  percentiles of the net-benefit sampling distribution (assumed normal with
121
- the se derived from the 95% CI). The ``"don't treat"`` payoff is 0 in
122
- every scenario — the status quo baseline.
125
+ the standard error derived from the 95% confidence interval). The ``"don't treat"`` payoff is 0 in
126
+ every scenario.
123
127
 
124
128
  You can supply your own scenario names and quantiles:
125
129
 
@@ -129,17 +133,19 @@ You can supply your own scenario names and quantiles:
129
133
  scenarios={"bear": 0.05, "base": 0.50, "bull": 0.95}
130
134
  )
131
135
 
132
- **Relationship to** ``robust``. ``robust=True`` is equivalent to
133
- ``maximin`` returning the same choice as ``optimal`` when the scenarios are
134
- set to the CI bounds (quantiles 0.025 and 0.975). ``to_outcomes()``
136
+ How does this differ from using ``robust``? ``robust=True`` is equivalent to
137
+ ``maximin``, returning the same choice when the scenarios are
138
+ set to the confidence interval bounds (quantiles 0.025 and 0.975). ``to_outcomes()``
135
139
  generalises that check: different rules express different risk attitudes, and
136
140
  you can dial in your own pessimism level via ``hurwicz(alpha=...)``.
137
141
 
138
142
  Should we treat each segment?
139
143
  -----------------------------
140
144
 
141
- An average effect can hide groups where the treatment is worthless — or where
142
- it pays for itself several times over. When an estimator was fitted with an
145
+ An average effect can hide groups where the treatment is worthless, or where
146
+ it pays for itself several times over. It will also cause you to scale up a treatment
147
+ that is not worth it for everyone.
148
+ When an estimator is fitted with an
143
149
  ``effect_modifier`` (see :doc:`estimands`), ``decide_by_group(cost, benefit)``
144
150
  runs the same cost-benefit analysis within each level of the modifier and
145
151
  returns a mapping of level → :class:`~formative.DecisionReport`, so different
@@ -161,19 +167,33 @@ and everyone else not at all:
161
167
  tenure = rng.choice(["<2y", "2-5y", ">5y"], size=N)
162
168
  region = rng.choice(["north", "south"], size=N)
163
169
  coaching = rng.integers(0, 2, size=N)
164
- retention = 6.0 * coaching * (tenure == "<2y") + 2.0 * (tenure == ">5y") + rng.normal(size=N)
170
+ retention = (
171
+ 6.0 * coaching * (tenure == "<2y")
172
+ + 2.0 * (tenure == ">5y")
173
+ + rng.normal(size=N)
174
+ )
165
175
 
166
- df = pd.DataFrame({"tenure": tenure, "region": region, "coaching": coaching, "retention": retention})
176
+ df = pd.DataFrame({
177
+ "tenure": tenure,
178
+ "region": region,
179
+ "coaching": coaching,
180
+ "retention": retention,
181
+ })
167
182
 
168
183
  dag = DAG()
169
184
  dag.assume("coaching").causes("retention")
170
185
  dag.assume("tenure").causes("retention")
171
186
  dag.assume("region").causes("retention")
172
187
 
173
- result = RCT(dag, treatment="coaching", outcome="retention", effect_modifier="tenure").fit(df)
188
+ result = RCT(
189
+ dag, treatment="coaching", outcome="retention", effect_modifier="tenure"
190
+ ).fit(df)
174
191
  for level, decision in result.decide_by_group(cost=2.5, benefit=1.0).items():
175
192
  lo, hi = decision.net_benefit_ci
176
- print(f"{level:>5}: {decision.optimal:<12} net benefit {decision.net_benefit:+.2f} [{lo:+.2f}, {hi:+.2f}]")
193
+ print(
194
+ f"{level:>5}: {decision.optimal:<12} "
195
+ f"net benefit {decision.net_benefit:+.2f} [{lo:+.2f}, {hi:+.2f}]"
196
+ )
177
197
 
178
198
  .. code-block:: text
179
199
 
@@ -203,10 +223,9 @@ one less thing to get wrong.
203
223
  How it works
204
224
  ~~~~~~~~~~~~
205
225
 
206
- ``learn_policy()`` implements doubly robust policy learning in the style of
207
- Athey & Wager (2021):
226
+ ``learn_policy()`` implements doubly robust policy learning:
208
227
 
209
- 1. **Score every unit.** Each unit gets a cross-fitted AIPW score — an
228
+ 1. **Score every unit.** Each unit gets a cross-fitted score — an
210
229
  unbiased estimate of its individual treatment effect. Outcome models are
211
230
  fit by OLS on the candidate features over four folds and evaluated on the
212
231
  fifth, so no unit is scored by a model that saw it.
@@ -225,7 +244,7 @@ Athey & Wager (2021):
225
244
 
226
245
  Because the tree may split on any level of any candidate feature, features
227
246
  must be discrete: bin continuous columns before passing them in. Candidate
228
- features face the same DAG validation as effect modifiers — each must cause
247
+ features face the same DAG validation as effect modifiers; each must cause
229
248
  the outcome and must not be a descendant of the treatment (targeting on a
230
249
  mediator is not actionable at assignment time).
231
250
 
@@ -238,7 +257,13 @@ features instead of choosing a segmentation ourselves:
238
257
  .. code-block:: python
239
258
 
240
259
  result = RCT(dag, treatment="coaching", outcome="retention").fit(df)
241
- policy = result.learn_policy(df, modifiers=["tenure", "region"], cost=2.5, benefit=1.0, max_depth=2)
260
+ policy = result.learn_policy(
261
+ df,
262
+ modifiers=["tenure", "region"],
263
+ cost=2.5,
264
+ benefit=1.0,
265
+ max_depth=2
266
+ )
242
267
  print(policy.summary())
243
268
 
244
269
  .. code-block:: text
@@ -286,10 +311,17 @@ it only helps recent hires in the north:
286
311
  coaching = rng.integers(0, 2, size=N)
287
312
  helped = (tenure == "<2y") & (region == "north")
288
313
  retention = 7.0 * coaching * helped + 2.0 * (tenure == ">5y") + rng.normal(size=N)
289
- df = pd.DataFrame({"tenure": tenure, "region": region, "coaching": coaching, "retention": retention})
314
+ df = pd.DataFrame({
315
+ "tenure": tenure,
316
+ "region": region,
317
+ "coaching": coaching,
318
+ "retention": retention,
319
+ })
290
320
 
291
321
  result = RCT(dag, treatment="coaching", outcome="retention").fit(df)
292
- policy = result.learn_policy(df, modifiers=["tenure", "region"], cost=2.5, benefit=1.0, max_depth=2)
322
+ policy = result.learn_policy(
323
+ df, modifiers=["tenure", "region"], cost=2.5, benefit=1.0, max_depth=2
324
+ )
293
325
  print(policy.summary())
294
326
 
295
327
  .. code-block:: text
@@ -316,7 +348,9 @@ back to treating all recent hires and burns money on the southern half:
316
348
 
317
349
  .. code-block:: python
318
350
 
319
- result.learn_policy(df, modifiers=["tenure", "region"], cost=2.5, benefit=1.0, max_depth=1).value
351
+ result.learn_policy(
352
+ df, modifiers=["tenure", "region"], cost=2.5, benefit=1.0, max_depth=1
353
+ ).value
320
354
  # +0.34 per unit — versus +0.75 at depth 2
321
355
 
322
356
  When the deeper policy's value is clearly higher (as here), ship it; when the
@@ -339,7 +373,12 @@ segment-2 employees in the north:
339
373
  training = rng.integers(0, 2, size=N)
340
374
  effect = 4.0 * (segment == 0) + 5.0 * ((segment == 2) & (region == "north"))
341
375
  earnings = effect * training + 0.5 * segment + rng.normal(size=N)
342
- df = pd.DataFrame({"segment": segment, "region": region, "training": training, "earnings": earnings})
376
+ df = pd.DataFrame({
377
+ "segment": segment,
378
+ "region": region,
379
+ "training": training,
380
+ "earnings": earnings,
381
+ })
343
382
 
344
383
  dag = DAG()
345
384
  dag.assume("training").causes("earnings")
@@ -347,7 +386,13 @@ segment-2 employees in the north:
347
386
  dag.assume("region").causes("earnings")
348
387
 
349
388
  result = RCT(dag, treatment="training", outcome="earnings").fit(df)
350
- policy = result.learn_policy(df, modifiers=["segment", "region"], cost=1.0, benefit=1.0, max_depth=2)
389
+ policy = result.learn_policy(
390
+ df,
391
+ modifiers=["segment", "region"],
392
+ cost=1.0,
393
+ benefit=1.0,
394
+ max_depth=2
395
+ )
351
396
  print(policy.summary())
352
397
 
353
398
  .. code-block:: text
@@ -49,7 +49,7 @@ you choose.
49
49
 
50
50
  After you have obtained the result object, you can print a summary of the estimate and
51
51
  its assumptions. The assumptions are marked as testable or untestable depending on whether
52
- formative can check them. Most assumptions in causal inference are untestable by nature —
52
+ formative can check them. Most assumptions in causal inference are untestable by nature, i.e.
53
53
  things you must argue for based on domain knowledge and theory.
54
54
 
55
55
  .. code-block:: python
@@ -83,7 +83,7 @@ communicating with non-technical audiences.
83
83
  **3. Refute**
84
84
 
85
85
  Once you have a result, run statistical checks that probe whether its assumptions hold
86
- in the data. Each check returns a clear pass or fail — but no set of tests can guarantee
86
+ in the data. Each check returns a clear pass or fail, but no set of tests can guarantee
87
87
  validity. Use them as diagnostics, not proof. Causal inference is a judgment call, not a
88
88
  mathematical certainty.
89
89
 
@@ -104,10 +104,10 @@ mathematical certainty.
104
104
 
105
105
  **4. Decide**
106
106
 
107
- An estimate is rarely the end goal — the question is whether to act on it. Pass the
107
+ An estimate is rarely the end goal; the question is whether to act on it. Pass the
108
108
  cost per unit of treatment applied and the value of one unit of improvement in the
109
109
  outcome, and formative turns the estimate and its uncertainty into a treat/don't-treat
110
- call. Suppose a unit of education costs $8 and a unit of income is worth $15:
110
+ decision. Suppose a unit of education costs $8 and a unit of income is worth $15:
111
111
 
112
112
  .. code-block:: python
113
113
 
@@ -128,7 +128,12 @@ call. Suppose a unit of education costs $8 and a unit of income is worth $15:
128
128
  Decision confidence : 100.0%
129
129
  Robust to estimation error : Yes — decision is stable across 95% CI
130
130
 
131
- Depending on the estimator, decisions can go beyond a single call for everyone:
131
+ You may ask why a cost must be passed; is it not enough if the estimate is positive?
132
+ The answer is that a positive estimate does not guarantee that the treatment is worth it.
133
+ If the estimate gives an effect of +1.0, but the treatment costs $10 and the outcome is worth $1,
134
+ then the treatment is not worth it. The decision layer makes this explicit.
135
+
136
+ Depending on the estimator you choose, decisions can go beyond a single call for everyone:
132
137
  per-group decisions via an effect modifier, or a learned treatment rule that
133
138
  decides *whom* to treat. See :doc:`decisions` for the full decision layer.
134
139
 
@@ -1,5 +1,5 @@
1
1
  const DOCUMENTATION_OPTIONS = {
2
- VERSION: '2.2.2',
2
+ VERSION: '2.2.4',
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.2 documentation</title>
9
+ <title>Benchmarks &mdash; formative 2.2.4 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=219dea72"></script>
19
+ <script src="../_static/documentation_options.js?v=4e6cfea6"></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>
@@ -50,8 +50,8 @@
50
50
  </div><div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="Navigation menu">
51
51
  <p class="caption" role="heading"><span class="caption-text">Causal Estimation</span></p>
52
52
  <ul class="current">
53
- <li class="toctree-l1"><a class="reference internal" href="introduction.html">Introduction</a></li>
54
53
  <li class="toctree-l1"><a class="reference internal" href="motivation.html">Motivation</a></li>
54
+ <li class="toctree-l1"><a class="reference internal" href="introduction.html">Introduction</a></li>
55
55
  <li class="toctree-l1"><a class="reference internal" href="dag.html">DAG</a></li>
56
56
  <li class="toctree-l1"><a class="reference internal" href="estimands.html">Estimands</a></li>
57
57
  <li class="toctree-l1"><a class="reference internal" href="wizard.html">Choosing an estimator</a></li>
@@ -210,7 +210,9 @@ treatment/outcome relationship. The observational DAG declares this:</p>
210
210
  <span class="n">obs_dag</span><span class="o">.</span><span class="n">assume</span><span class="p">(</span><span class="n">c</span><span class="p">)</span><span class="o">.</span><span class="n">causes</span><span class="p">(</span><span class="s2">&quot;treat&quot;</span><span class="p">,</span> <span class="s2">&quot;re78&quot;</span><span class="p">)</span>
211
211
  <span class="n">obs_dag</span><span class="o">.</span><span class="n">assume</span><span class="p">(</span><span class="s2">&quot;treat&quot;</span><span class="p">)</span><span class="o">.</span><span class="n">causes</span><span class="p">(</span><span class="s2">&quot;re78&quot;</span><span class="p">)</span>
212
212
 
213
- <span class="n">result</span> <span class="o">=</span> <span class="n">OLSObservational</span><span class="p">(</span><span class="n">obs_dag</span><span class="p">,</span> <span class="n">treatment</span><span class="o">=</span><span class="s2">&quot;treat&quot;</span><span class="p">,</span> <span class="n">outcome</span><span class="o">=</span><span class="s2">&quot;re78&quot;</span><span class="p">)</span><span class="o">.</span><span class="n">fit</span><span class="p">(</span><span class="n">observational</span><span class="p">)</span>
213
+ <span class="n">result</span> <span class="o">=</span> <span class="n">OLSObservational</span><span class="p">(</span>
214
+ <span class="n">obs_dag</span><span class="p">,</span> <span class="n">treatment</span><span class="o">=</span><span class="s2">&quot;treat&quot;</span><span class="p">,</span> <span class="n">outcome</span><span class="o">=</span><span class="s2">&quot;re78&quot;</span>
215
+ <span class="p">)</span><span class="o">.</span><span class="n">fit</span><span class="p">(</span><span class="n">observational</span><span class="p">)</span>
214
216
  <span class="nb">print</span><span class="p">(</span><span class="n">result</span><span class="o">.</span><span class="n">summary</span><span class="p">())</span>
215
217
  </pre></div>
216
218
  </div>
@@ -244,7 +246,9 @@ linear model extrapolates across incomparable people. Propensity score matching
244
246
  each treated person only with the most similar controls:</p>
245
247
  <div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">formative.causal</span><span class="w"> </span><span class="kn">import</span> <span class="n">PropensityScoreMatching</span>
246
248
 
247
- <span class="n">result</span> <span class="o">=</span> <span class="n">PropensityScoreMatching</span><span class="p">(</span><span class="n">obs_dag</span><span class="p">,</span> <span class="n">treatment</span><span class="o">=</span><span class="s2">&quot;treat&quot;</span><span class="p">,</span> <span class="n">outcome</span><span class="o">=</span><span class="s2">&quot;re78&quot;</span><span class="p">)</span><span class="o">.</span><span class="n">fit</span><span class="p">(</span><span class="n">observational</span><span class="p">)</span>
249
+ <span class="n">result</span> <span class="o">=</span> <span class="n">PropensityScoreMatching</span><span class="p">(</span>
250
+ <span class="n">obs_dag</span><span class="p">,</span> <span class="n">treatment</span><span class="o">=</span><span class="s2">&quot;treat&quot;</span><span class="p">,</span> <span class="n">outcome</span><span class="o">=</span><span class="s2">&quot;re78&quot;</span>
251
+ <span class="p">)</span><span class="o">.</span><span class="n">fit</span><span class="p">(</span><span class="n">observational</span><span class="p">)</span>
248
252
  <span class="nb">print</span><span class="p">(</span><span class="n">result</span><span class="o">.</span><span class="n">summary</span><span class="p">())</span>
249
253
  </pre></div>
250
254
  </div>
@@ -386,7 +390,10 @@ staff plus managers plus half the part-time staff.</p>
386
390
  Table 3 reports four cell means; formative’s DiD will be built from exactly these:</p>
387
391
  <div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">for</span> <span class="n">state</span><span class="p">,</span> <span class="n">name</span> <span class="ow">in</span> <span class="p">[(</span><span class="mi">1</span><span class="p">,</span> <span class="s2">&quot;NJ&quot;</span><span class="p">),</span> <span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="s2">&quot;PA&quot;</span><span class="p">)]:</span>
388
392
  <span class="n">sub</span> <span class="o">=</span> <span class="n">raw</span><span class="p">[</span><span class="n">raw</span><span class="o">.</span><span class="n">STATE</span> <span class="o">==</span> <span class="n">state</span><span class="p">]</span>
389
- <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2">: before </span><span class="si">{</span><span class="n">sub</span><span class="o">.</span><span class="n">fte_before</span><span class="o">.</span><span class="n">mean</span><span class="p">()</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2">, after </span><span class="si">{</span><span class="n">sub</span><span class="o">.</span><span class="n">fte_after</span><span class="o">.</span><span class="n">mean</span><span class="p">()</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2">&quot;</span><span class="p">)</span>
393
+ <span class="nb">print</span><span class="p">(</span>
394
+ <span class="sa">f</span><span class="s2">&quot;</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2">: before </span><span class="si">{</span><span class="n">sub</span><span class="o">.</span><span class="n">fte_before</span><span class="o">.</span><span class="n">mean</span><span class="p">()</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2">, &quot;</span>
395
+ <span class="sa">f</span><span class="s2">&quot;after </span><span class="si">{</span><span class="n">sub</span><span class="o">.</span><span class="n">fte_after</span><span class="o">.</span><span class="n">mean</span><span class="p">()</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2">&quot;</span>
396
+ <span class="p">)</span>
390
397
  </pre></div>
391
398
  </div>
392
399
  <div class="highlight-text notranslate"><div class="highlight"><pre><span></span>NJ: before 20.44, after 21.03
@@ -406,8 +413,18 @@ binary group and time indicators:</p>
406
413
 
407
414
  <span class="n">long</span> <span class="o">=</span> <span class="n">pd</span><span class="o">.</span><span class="n">concat</span><span class="p">(</span>
408
415
  <span class="p">[</span>
409
- <span class="n">pd</span><span class="o">.</span><span class="n">DataFrame</span><span class="p">({</span><span class="s2">&quot;store&quot;</span><span class="p">:</span> <span class="n">raw</span><span class="o">.</span><span class="n">SHEET</span><span class="p">,</span> <span class="s2">&quot;nj&quot;</span><span class="p">:</span> <span class="n">raw</span><span class="o">.</span><span class="n">STATE</span><span class="p">,</span> <span class="s2">&quot;after&quot;</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> <span class="s2">&quot;fte&quot;</span><span class="p">:</span> <span class="n">raw</span><span class="o">.</span><span class="n">fte_before</span><span class="p">}),</span>
410
- <span class="n">pd</span><span class="o">.</span><span class="n">DataFrame</span><span class="p">({</span><span class="s2">&quot;store&quot;</span><span class="p">:</span> <span class="n">raw</span><span class="o">.</span><span class="n">SHEET</span><span class="p">,</span> <span class="s2">&quot;nj&quot;</span><span class="p">:</span> <span class="n">raw</span><span class="o">.</span><span class="n">STATE</span><span class="p">,</span> <span class="s2">&quot;after&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="s2">&quot;fte&quot;</span><span class="p">:</span> <span class="n">raw</span><span class="o">.</span><span class="n">fte_after</span><span class="p">}),</span>
416
+ <span class="n">pd</span><span class="o">.</span><span class="n">DataFrame</span><span class="p">({</span>
417
+ <span class="s2">&quot;store&quot;</span><span class="p">:</span> <span class="n">raw</span><span class="o">.</span><span class="n">SHEET</span><span class="p">,</span>
418
+ <span class="s2">&quot;nj&quot;</span><span class="p">:</span> <span class="n">raw</span><span class="o">.</span><span class="n">STATE</span><span class="p">,</span>
419
+ <span class="s2">&quot;after&quot;</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
420
+ <span class="s2">&quot;fte&quot;</span><span class="p">:</span> <span class="n">raw</span><span class="o">.</span><span class="n">fte_before</span><span class="p">,</span>
421
+ <span class="p">}),</span>
422
+ <span class="n">pd</span><span class="o">.</span><span class="n">DataFrame</span><span class="p">({</span>
423
+ <span class="s2">&quot;store&quot;</span><span class="p">:</span> <span class="n">raw</span><span class="o">.</span><span class="n">SHEET</span><span class="p">,</span>
424
+ <span class="s2">&quot;nj&quot;</span><span class="p">:</span> <span class="n">raw</span><span class="o">.</span><span class="n">STATE</span><span class="p">,</span>
425
+ <span class="s2">&quot;after&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
426
+ <span class="s2">&quot;fte&quot;</span><span class="p">:</span> <span class="n">raw</span><span class="o">.</span><span class="n">fte_after</span><span class="p">,</span>
427
+ <span class="p">}),</span>
411
428
  <span class="p">],</span>
412
429
  <span class="n">ignore_index</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
413
430
  <span class="p">)</span><span class="o">.</span><span class="n">dropna</span><span class="p">(</span><span class="n">subset</span><span class="o">=</span><span class="p">[</span><span class="s2">&quot;fte&quot;</span><span class="p">])</span>