formative-ds 2.2.0__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.
- {formative_ds-2.2.0 → formative_ds-2.2.2}/CLAUDE.md +3 -2
- {formative_ds-2.2.0 → formative_ds-2.2.2}/PKG-INFO +1 -1
- {formative_ds-2.2.0 → formative_ds-2.2.2}/formative/causal/__init__.py +1 -1
- formative_ds-2.2.2/formative/causal/_assumptions.py +25 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/formative/causal/estimators/_base.py +1 -1
- {formative_ds-2.2.0 → formative_ds-2.2.2}/formative/causal/estimators/_cate.py +1 -1
- {formative_ds-2.2.0 → formative_ds-2.2.2}/formative/causal/estimators/did.py +1 -1
- {formative_ds-2.2.0 → formative_ds-2.2.2}/formative/causal/estimators/iv.py +1 -1
- {formative_ds-2.2.0 → formative_ds-2.2.2}/formative/causal/estimators/matching.py +27 -19
- {formative_ds-2.2.0 → formative_ds-2.2.2}/formative/causal/estimators/ols.py +1 -1
- {formative_ds-2.2.0 → formative_ds-2.2.2}/formative/causal/estimators/policy.py +1 -1
- {formative_ds-2.2.0 → formative_ds-2.2.2}/formative/causal/estimators/rct.py +1 -1
- {formative_ds-2.2.0 → formative_ds-2.2.2}/formative/causal/estimators/rdd.py +3 -3
- {formative_ds-2.2.0 → formative_ds-2.2.2}/formative/causal/refutations/_check.py +26 -22
- {formative_ds-2.2.0 → formative_ds-2.2.2}/formative/causal/refutations/cate.py +4 -12
- {formative_ds-2.2.0 → formative_ds-2.2.2}/formative/causal/refutations/did.py +14 -24
- {formative_ds-2.2.0 → formative_ds-2.2.2}/formative/causal/refutations/iv.py +4 -12
- {formative_ds-2.2.0 → formative_ds-2.2.2}/formative/causal/refutations/matching.py +15 -25
- {formative_ds-2.2.0 → formative_ds-2.2.2}/formative/causal/refutations/ols.py +4 -12
- {formative_ds-2.2.0 → formative_ds-2.2.2}/formative/causal/refutations/policy.py +1 -4
- {formative_ds-2.2.0 → formative_ds-2.2.2}/formative/causal/refutations/rct.py +7 -13
- {formative_ds-2.2.0 → formative_ds-2.2.2}/formative/causal/refutations/rdd.py +4 -12
- {formative_ds-2.2.0 → formative_ds-2.2.2}/pyproject.toml +2 -2
- formative_ds-2.2.2/site/2.2/_sources/causal/benchmarks.rst.txt +414 -0
- formative_ds-2.2.2/site/2.2/_sources/causal/dag.rst.txt +9 -0
- formative_ds-2.2.2/site/2.2/_sources/causal/decisions.rst.txt +458 -0
- formative_ds-2.2.2/site/2.2/_sources/causal/estimands.rst.txt +241 -0
- formative_ds-2.2.2/site/2.2/_sources/causal/estimators.rst.txt +100 -0
- formative_ds-2.2.2/site/2.2/_sources/causal/introduction.rst.txt +178 -0
- formative_ds-2.2.2/site/2.2/_sources/causal/motivation.rst.txt +57 -0
- formative_ds-2.2.2/site/2.2/_sources/causal/refutations.rst.txt +54 -0
- formative_ds-2.2.2/site/2.2/_sources/causal/wizard.rst.txt +9 -0
- formative_ds-2.2.2/site/2.2/_sources/game/decision_rules.rst.txt +271 -0
- formative_ds-2.2.2/site/2.2/_sources/game/introduction.rst.txt +39 -0
- formative_ds-2.2.2/site/2.2/_sources/game/single_player.rst.txt +10 -0
- formative_ds-2.2.2/site/2.2/_sources/index.rst.txt +40 -0
- formative_ds-2.2.2/site/2.2/_static/_sphinx_javascript_frameworks_compat.js +123 -0
- formative_ds-2.2.2/site/2.2/_static/base-stemmer.js +476 -0
- formative_ds-2.2.2/site/2.2/_static/basic.css +906 -0
- formative_ds-2.2.2/site/2.2/_static/check-solid.svg +4 -0
- formative_ds-2.2.2/site/2.2/_static/clipboard.min.js +7 -0
- formative_ds-2.2.2/site/2.2/_static/copy-button.svg +5 -0
- formative_ds-2.2.2/site/2.2/_static/copybutton.css +94 -0
- formative_ds-2.2.2/site/2.2/_static/copybutton.js +248 -0
- formative_ds-2.2.2/site/2.2/_static/copybutton_funcs.js +73 -0
- formative_ds-2.2.2/site/2.2/_static/css/badge_only.css +1 -0
- formative_ds-2.2.2/site/2.2/_static/css/fonts/Roboto-Slab-Bold.woff +0 -0
- formative_ds-2.2.2/site/2.2/_static/css/fonts/Roboto-Slab-Bold.woff2 +0 -0
- formative_ds-2.2.2/site/2.2/_static/css/fonts/Roboto-Slab-Regular.woff +0 -0
- formative_ds-2.2.2/site/2.2/_static/css/fonts/Roboto-Slab-Regular.woff2 +0 -0
- formative_ds-2.2.2/site/2.2/_static/css/fonts/fontawesome-webfont.eot +0 -0
- formative_ds-2.2.2/site/2.2/_static/css/fonts/fontawesome-webfont.svg +2671 -0
- formative_ds-2.2.2/site/2.2/_static/css/fonts/fontawesome-webfont.ttf +0 -0
- formative_ds-2.2.2/site/2.2/_static/css/fonts/fontawesome-webfont.woff +0 -0
- formative_ds-2.2.2/site/2.2/_static/css/fonts/fontawesome-webfont.woff2 +0 -0
- formative_ds-2.2.2/site/2.2/_static/css/fonts/lato-bold-italic.woff +0 -0
- formative_ds-2.2.2/site/2.2/_static/css/fonts/lato-bold-italic.woff2 +0 -0
- formative_ds-2.2.2/site/2.2/_static/css/fonts/lato-bold.woff +0 -0
- formative_ds-2.2.2/site/2.2/_static/css/fonts/lato-bold.woff2 +0 -0
- formative_ds-2.2.2/site/2.2/_static/css/fonts/lato-normal-italic.woff +0 -0
- formative_ds-2.2.2/site/2.2/_static/css/fonts/lato-normal-italic.woff2 +0 -0
- formative_ds-2.2.2/site/2.2/_static/css/fonts/lato-normal.woff +0 -0
- formative_ds-2.2.2/site/2.2/_static/css/fonts/lato-normal.woff2 +0 -0
- formative_ds-2.2.2/site/2.2/_static/css/theme.css +4 -0
- formative_ds-2.2.2/site/2.2/_static/doctools.js +150 -0
- formative_ds-2.2.2/site/2.2/_static/documentation_options.js +13 -0
- formative_ds-2.2.2/site/2.2/_static/english-stemmer.js +1066 -0
- formative_ds-2.2.2/site/2.2/_static/file.png +0 -0
- formative_ds-2.2.2/site/2.2/_static/fonts/Lato/lato-bold.eot +0 -0
- formative_ds-2.2.2/site/2.2/_static/fonts/Lato/lato-bold.ttf +0 -0
- formative_ds-2.2.2/site/2.2/_static/fonts/Lato/lato-bold.woff +0 -0
- formative_ds-2.2.2/site/2.2/_static/fonts/Lato/lato-bold.woff2 +0 -0
- formative_ds-2.2.2/site/2.2/_static/fonts/Lato/lato-bolditalic.eot +0 -0
- formative_ds-2.2.2/site/2.2/_static/fonts/Lato/lato-bolditalic.ttf +0 -0
- formative_ds-2.2.2/site/2.2/_static/fonts/Lato/lato-bolditalic.woff +0 -0
- formative_ds-2.2.2/site/2.2/_static/fonts/Lato/lato-bolditalic.woff2 +0 -0
- formative_ds-2.2.2/site/2.2/_static/fonts/Lato/lato-italic.eot +0 -0
- formative_ds-2.2.2/site/2.2/_static/fonts/Lato/lato-italic.ttf +0 -0
- formative_ds-2.2.2/site/2.2/_static/fonts/Lato/lato-italic.woff +0 -0
- formative_ds-2.2.2/site/2.2/_static/fonts/Lato/lato-italic.woff2 +0 -0
- formative_ds-2.2.2/site/2.2/_static/fonts/Lato/lato-regular.eot +0 -0
- formative_ds-2.2.2/site/2.2/_static/fonts/Lato/lato-regular.ttf +0 -0
- formative_ds-2.2.2/site/2.2/_static/fonts/Lato/lato-regular.woff +0 -0
- formative_ds-2.2.2/site/2.2/_static/fonts/Lato/lato-regular.woff2 +0 -0
- formative_ds-2.2.2/site/2.2/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot +0 -0
- formative_ds-2.2.2/site/2.2/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf +0 -0
- formative_ds-2.2.2/site/2.2/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff +0 -0
- formative_ds-2.2.2/site/2.2/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 +0 -0
- formative_ds-2.2.2/site/2.2/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot +0 -0
- formative_ds-2.2.2/site/2.2/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf +0 -0
- formative_ds-2.2.2/site/2.2/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff +0 -0
- formative_ds-2.2.2/site/2.2/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 +0 -0
- formative_ds-2.2.2/site/2.2/_static/jquery.js +2 -0
- formative_ds-2.2.2/site/2.2/_static/js/badge_only.js +1 -0
- formative_ds-2.2.2/site/2.2/_static/js/theme.js +1 -0
- formative_ds-2.2.2/site/2.2/_static/js/versions.js +228 -0
- formative_ds-2.2.2/site/2.2/_static/language_data.js +13 -0
- formative_ds-2.2.2/site/2.2/_static/minus.png +0 -0
- formative_ds-2.2.2/site/2.2/_static/plus.png +0 -0
- formative_ds-2.2.2/site/2.2/_static/pygments.css +75 -0
- formative_ds-2.2.2/site/2.2/_static/searchtools.js +693 -0
- formative_ds-2.2.2/site/2.2/_static/sphinx_highlight.js +159 -0
- formative_ds-2.2.2/site/2.2/_static/version_switcher.css +11 -0
- formative_ds-2.2.2/site/2.2/_static/version_switcher.js +46 -0
- formative_ds-2.2.2/site/2.2/causal/benchmarks.html +547 -0
- formative_ds-2.2.2/site/2.2/causal/dag.html +296 -0
- formative_ds-2.2.2/site/2.2/causal/decisions.html +952 -0
- formative_ds-2.2.2/site/2.2/causal/estimands.html +344 -0
- formative_ds-2.2.2/site/2.2/causal/estimators.html +2021 -0
- formative_ds-2.2.2/site/2.2/causal/introduction.html +297 -0
- formative_ds-2.2.2/site/2.2/causal/motivation.html +175 -0
- formative_ds-2.2.2/site/2.2/causal/refutations.html +396 -0
- formative_ds-2.2.2/site/2.2/causal/wizard.html +133 -0
- formative_ds-2.2.2/site/2.2/game/decision_rules.html +892 -0
- formative_ds-2.2.2/site/2.2/game/introduction.html +156 -0
- formative_ds-2.2.2/site/2.2/game/single_player.html +135 -0
- formative_ds-2.2.2/site/2.2/genindex.html +819 -0
- formative_ds-2.2.2/site/2.2/index.html +140 -0
- formative_ds-2.2.2/site/2.2/objects.inv +0 -0
- formative_ds-2.2.2/site/2.2/search.html +139 -0
- formative_ds-2.2.2/site/2.2/searchindex.js +1 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/versions.json +5 -1
- {formative_ds-2.2.0 → formative_ds-2.2.2}/uv.lock +1 -1
- {formative_ds-2.2.0 → formative_ds-2.2.2}/vercel.json +2 -2
- {formative_ds-2.2.0 → formative_ds-2.2.2}/.gitattributes +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/.github/workflows/ci.yml +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/.github/workflows/publish.yml +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/.gitignore +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/LICENSE +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/Makefile +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/README.md +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/docs/_static/version_switcher.css +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/docs/_static/version_switcher.js +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/docs/causal/benchmarks.rst +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/docs/causal/dag.rst +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/docs/causal/decisions.rst +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/docs/causal/estimands.rst +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/docs/causal/estimators.rst +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/docs/causal/introduction.rst +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/docs/causal/motivation.rst +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/docs/causal/refutations.rst +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/docs/causal/wizard.rst +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/docs/conf.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/docs/design/policy_learning.md +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/docs/game/decision_rules.rst +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/docs/game/introduction.rst +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/docs/game/single_player.rst +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/docs/index.rst +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/examples/decision/01_decision_basic.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/examples/did/01_did_basic.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/examples/iv/01_iv_basic.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/examples/iv/02_iv_vs_ols_bias.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/examples/iv/03_iv_with_observed_confounder.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/examples/iv/04_iv_refutation.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/examples/matching/01_basic_matching.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/examples/matching/02_matching_vs_naive.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/examples/matching/03_matching_refutation.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/examples/ols/01_ols_basic.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/examples/ols/02_ols_unobserved_confounder.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/examples/ols/03_ols_multiple_confounders.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/examples/rct/01_rct_basic.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/examples/rdd/01_rdd_basic.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/examples/rdd/02_rdd_positive_running_var.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/formative/__init__.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/formative/causal/_exceptions.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/formative/causal/_explain.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/formative/causal/dag.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/formative/causal/decision.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/formative/causal/estimators/__init__.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/formative/causal/refutations/__init__.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/formative/game/__init__.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/formative/game/expected_value.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/formative/game/hurwicz.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/formative/game/laplace.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/formative/game/maximax.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/formative/game/maximin.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/formative/game/minimax.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/notebooks/card_krueger_benchmark.ipynb +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/notebooks/lalonde_benchmark.ipynb +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/scripts/snapshot_docs.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_sources/causal/benchmarks.rst.txt +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_sources/causal/dag.rst.txt +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_sources/causal/decisions.rst.txt +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_sources/causal/estimands.rst.txt +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_sources/causal/estimators.rst.txt +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_sources/causal/introduction.rst.txt +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_sources/causal/motivation.rst.txt +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_sources/causal/refutations.rst.txt +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_sources/causal/wizard.rst.txt +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_sources/game/decision_rules.rst.txt +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_sources/game/introduction.rst.txt +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_sources/game/single_player.rst.txt +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_sources/index.rst.txt +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/_sphinx_javascript_frameworks_compat.js +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/base-stemmer.js +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/basic.css +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/check-solid.svg +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/clipboard.min.js +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/copy-button.svg +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/copybutton.css +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/copybutton.js +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/copybutton_funcs.js +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/css/badge_only.css +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/css/fonts/Roboto-Slab-Bold.woff +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/css/fonts/Roboto-Slab-Bold.woff2 +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/css/fonts/Roboto-Slab-Regular.woff +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/css/fonts/Roboto-Slab-Regular.woff2 +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/css/fonts/fontawesome-webfont.eot +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/css/fonts/fontawesome-webfont.svg +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/css/fonts/fontawesome-webfont.ttf +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/css/fonts/fontawesome-webfont.woff +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/css/fonts/fontawesome-webfont.woff2 +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/css/fonts/lato-bold-italic.woff +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/css/fonts/lato-bold-italic.woff2 +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/css/fonts/lato-bold.woff +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/css/fonts/lato-bold.woff2 +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/css/fonts/lato-normal-italic.woff +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/css/fonts/lato-normal-italic.woff2 +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/css/fonts/lato-normal.woff +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/css/fonts/lato-normal.woff2 +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/css/theme.css +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/doctools.js +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/documentation_options.js +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/english-stemmer.js +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/file.png +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/fonts/Lato/lato-bold.eot +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/fonts/Lato/lato-bold.ttf +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/fonts/Lato/lato-bold.woff +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/fonts/Lato/lato-bold.woff2 +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/fonts/Lato/lato-bolditalic.eot +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/fonts/Lato/lato-bolditalic.ttf +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/fonts/Lato/lato-bolditalic.woff +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/fonts/Lato/lato-bolditalic.woff2 +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/fonts/Lato/lato-italic.eot +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/fonts/Lato/lato-italic.ttf +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/fonts/Lato/lato-italic.woff +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/fonts/Lato/lato-italic.woff2 +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/fonts/Lato/lato-regular.eot +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/fonts/Lato/lato-regular.ttf +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/fonts/Lato/lato-regular.woff +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/fonts/Lato/lato-regular.woff2 +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/jquery.js +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/js/badge_only.js +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/js/theme.js +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/js/versions.js +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/language_data.js +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/minus.png +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/plus.png +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/pygments.css +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/searchtools.js +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/sphinx_highlight.js +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/version_switcher.css +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/_static/version_switcher.js +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/causal/benchmarks.html +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/causal/dag.html +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/causal/decisions.html +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/causal/estimands.html +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/causal/estimators.html +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/causal/introduction.html +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/causal/motivation.html +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/causal/refutations.html +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/causal/wizard.html +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/game/decision_rules.html +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/game/introduction.html +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/game/single_player.html +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/genindex.html +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/index.html +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/objects.inv +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/search.html +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/site/2.1/searchindex.js +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/tests/test_cate.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/tests/test_cate_refutation.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/tests/test_dag.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/tests/test_decision.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/tests/test_did.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/tests/test_did_refutation.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/tests/test_game.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/tests/test_iv.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/tests/test_iv_refutation.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/tests/test_matching.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/tests/test_matching_refutation.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/tests/test_ols.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/tests/test_ols_refutation.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/tests/test_policy.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/tests/test_policy_refutation.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/tests/test_rct.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/tests/test_rct_refutation.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/tests/test_rdd.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.2}/tests/test_rdd_refutation.py +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` — `
|
|
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`
|
|
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,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 ]"
|
|
@@ -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
|
-
|
|
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
|
-
|
|
61
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
348
|
-
#
|
|
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 =
|
|
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,
|
|
@@ -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]
|
|
256
|
+
filtered = data.loc[mask]
|
|
257
257
|
else:
|
|
258
|
-
filtered = data
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
46
|
-
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
-
|
|
72
|
-
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
95
|
-
|
|
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
|
-
|
|
31
|
-
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
90
|
-
|
|
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.
|
|
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.
|
|
51
|
+
current_version = "2.2.2"
|
|
52
52
|
commit = true
|
|
53
53
|
tag = true
|
|
54
54
|
tag_name = "v{new_version}"
|