formative-ds 2.2.0__tar.gz → 2.2.1__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.1}/PKG-INFO +1 -1
- {formative_ds-2.2.0 → formative_ds-2.2.1}/pyproject.toml +2 -2
- formative_ds-2.2.1/site/2.2/_sources/causal/benchmarks.rst.txt +414 -0
- formative_ds-2.2.1/site/2.2/_sources/causal/dag.rst.txt +9 -0
- formative_ds-2.2.1/site/2.2/_sources/causal/decisions.rst.txt +458 -0
- formative_ds-2.2.1/site/2.2/_sources/causal/estimands.rst.txt +241 -0
- formative_ds-2.2.1/site/2.2/_sources/causal/estimators.rst.txt +100 -0
- formative_ds-2.2.1/site/2.2/_sources/causal/introduction.rst.txt +178 -0
- formative_ds-2.2.1/site/2.2/_sources/causal/motivation.rst.txt +57 -0
- formative_ds-2.2.1/site/2.2/_sources/causal/refutations.rst.txt +54 -0
- formative_ds-2.2.1/site/2.2/_sources/causal/wizard.rst.txt +9 -0
- formative_ds-2.2.1/site/2.2/_sources/game/decision_rules.rst.txt +271 -0
- formative_ds-2.2.1/site/2.2/_sources/game/introduction.rst.txt +39 -0
- formative_ds-2.2.1/site/2.2/_sources/game/single_player.rst.txt +10 -0
- formative_ds-2.2.1/site/2.2/_sources/index.rst.txt +40 -0
- formative_ds-2.2.1/site/2.2/_static/_sphinx_javascript_frameworks_compat.js +123 -0
- formative_ds-2.2.1/site/2.2/_static/base-stemmer.js +476 -0
- formative_ds-2.2.1/site/2.2/_static/basic.css +906 -0
- formative_ds-2.2.1/site/2.2/_static/check-solid.svg +4 -0
- formative_ds-2.2.1/site/2.2/_static/clipboard.min.js +7 -0
- formative_ds-2.2.1/site/2.2/_static/copy-button.svg +5 -0
- formative_ds-2.2.1/site/2.2/_static/copybutton.css +94 -0
- formative_ds-2.2.1/site/2.2/_static/copybutton.js +248 -0
- formative_ds-2.2.1/site/2.2/_static/copybutton_funcs.js +73 -0
- formative_ds-2.2.1/site/2.2/_static/css/badge_only.css +1 -0
- formative_ds-2.2.1/site/2.2/_static/css/fonts/Roboto-Slab-Bold.woff +0 -0
- formative_ds-2.2.1/site/2.2/_static/css/fonts/Roboto-Slab-Bold.woff2 +0 -0
- formative_ds-2.2.1/site/2.2/_static/css/fonts/Roboto-Slab-Regular.woff +0 -0
- formative_ds-2.2.1/site/2.2/_static/css/fonts/Roboto-Slab-Regular.woff2 +0 -0
- formative_ds-2.2.1/site/2.2/_static/css/fonts/fontawesome-webfont.eot +0 -0
- formative_ds-2.2.1/site/2.2/_static/css/fonts/fontawesome-webfont.svg +2671 -0
- formative_ds-2.2.1/site/2.2/_static/css/fonts/fontawesome-webfont.ttf +0 -0
- formative_ds-2.2.1/site/2.2/_static/css/fonts/fontawesome-webfont.woff +0 -0
- formative_ds-2.2.1/site/2.2/_static/css/fonts/fontawesome-webfont.woff2 +0 -0
- formative_ds-2.2.1/site/2.2/_static/css/fonts/lato-bold-italic.woff +0 -0
- formative_ds-2.2.1/site/2.2/_static/css/fonts/lato-bold-italic.woff2 +0 -0
- formative_ds-2.2.1/site/2.2/_static/css/fonts/lato-bold.woff +0 -0
- formative_ds-2.2.1/site/2.2/_static/css/fonts/lato-bold.woff2 +0 -0
- formative_ds-2.2.1/site/2.2/_static/css/fonts/lato-normal-italic.woff +0 -0
- formative_ds-2.2.1/site/2.2/_static/css/fonts/lato-normal-italic.woff2 +0 -0
- formative_ds-2.2.1/site/2.2/_static/css/fonts/lato-normal.woff +0 -0
- formative_ds-2.2.1/site/2.2/_static/css/fonts/lato-normal.woff2 +0 -0
- formative_ds-2.2.1/site/2.2/_static/css/theme.css +4 -0
- formative_ds-2.2.1/site/2.2/_static/doctools.js +150 -0
- formative_ds-2.2.1/site/2.2/_static/documentation_options.js +13 -0
- formative_ds-2.2.1/site/2.2/_static/english-stemmer.js +1066 -0
- formative_ds-2.2.1/site/2.2/_static/file.png +0 -0
- formative_ds-2.2.1/site/2.2/_static/fonts/Lato/lato-bold.eot +0 -0
- formative_ds-2.2.1/site/2.2/_static/fonts/Lato/lato-bold.ttf +0 -0
- formative_ds-2.2.1/site/2.2/_static/fonts/Lato/lato-bold.woff +0 -0
- formative_ds-2.2.1/site/2.2/_static/fonts/Lato/lato-bold.woff2 +0 -0
- formative_ds-2.2.1/site/2.2/_static/fonts/Lato/lato-bolditalic.eot +0 -0
- formative_ds-2.2.1/site/2.2/_static/fonts/Lato/lato-bolditalic.ttf +0 -0
- formative_ds-2.2.1/site/2.2/_static/fonts/Lato/lato-bolditalic.woff +0 -0
- formative_ds-2.2.1/site/2.2/_static/fonts/Lato/lato-bolditalic.woff2 +0 -0
- formative_ds-2.2.1/site/2.2/_static/fonts/Lato/lato-italic.eot +0 -0
- formative_ds-2.2.1/site/2.2/_static/fonts/Lato/lato-italic.ttf +0 -0
- formative_ds-2.2.1/site/2.2/_static/fonts/Lato/lato-italic.woff +0 -0
- formative_ds-2.2.1/site/2.2/_static/fonts/Lato/lato-italic.woff2 +0 -0
- formative_ds-2.2.1/site/2.2/_static/fonts/Lato/lato-regular.eot +0 -0
- formative_ds-2.2.1/site/2.2/_static/fonts/Lato/lato-regular.ttf +0 -0
- formative_ds-2.2.1/site/2.2/_static/fonts/Lato/lato-regular.woff +0 -0
- formative_ds-2.2.1/site/2.2/_static/fonts/Lato/lato-regular.woff2 +0 -0
- formative_ds-2.2.1/site/2.2/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot +0 -0
- formative_ds-2.2.1/site/2.2/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf +0 -0
- formative_ds-2.2.1/site/2.2/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff +0 -0
- formative_ds-2.2.1/site/2.2/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 +0 -0
- formative_ds-2.2.1/site/2.2/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot +0 -0
- formative_ds-2.2.1/site/2.2/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf +0 -0
- formative_ds-2.2.1/site/2.2/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff +0 -0
- formative_ds-2.2.1/site/2.2/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 +0 -0
- formative_ds-2.2.1/site/2.2/_static/jquery.js +2 -0
- formative_ds-2.2.1/site/2.2/_static/js/badge_only.js +1 -0
- formative_ds-2.2.1/site/2.2/_static/js/theme.js +1 -0
- formative_ds-2.2.1/site/2.2/_static/js/versions.js +228 -0
- formative_ds-2.2.1/site/2.2/_static/language_data.js +13 -0
- formative_ds-2.2.1/site/2.2/_static/minus.png +0 -0
- formative_ds-2.2.1/site/2.2/_static/plus.png +0 -0
- formative_ds-2.2.1/site/2.2/_static/pygments.css +75 -0
- formative_ds-2.2.1/site/2.2/_static/searchtools.js +693 -0
- formative_ds-2.2.1/site/2.2/_static/sphinx_highlight.js +159 -0
- formative_ds-2.2.1/site/2.2/_static/version_switcher.css +11 -0
- formative_ds-2.2.1/site/2.2/_static/version_switcher.js +46 -0
- formative_ds-2.2.1/site/2.2/causal/benchmarks.html +547 -0
- formative_ds-2.2.1/site/2.2/causal/dag.html +296 -0
- formative_ds-2.2.1/site/2.2/causal/decisions.html +952 -0
- formative_ds-2.2.1/site/2.2/causal/estimands.html +344 -0
- formative_ds-2.2.1/site/2.2/causal/estimators.html +2021 -0
- formative_ds-2.2.1/site/2.2/causal/introduction.html +297 -0
- formative_ds-2.2.1/site/2.2/causal/motivation.html +175 -0
- formative_ds-2.2.1/site/2.2/causal/refutations.html +396 -0
- formative_ds-2.2.1/site/2.2/causal/wizard.html +133 -0
- formative_ds-2.2.1/site/2.2/game/decision_rules.html +892 -0
- formative_ds-2.2.1/site/2.2/game/introduction.html +156 -0
- formative_ds-2.2.1/site/2.2/game/single_player.html +135 -0
- formative_ds-2.2.1/site/2.2/genindex.html +819 -0
- formative_ds-2.2.1/site/2.2/index.html +140 -0
- formative_ds-2.2.1/site/2.2/objects.inv +0 -0
- formative_ds-2.2.1/site/2.2/search.html +139 -0
- formative_ds-2.2.1/site/2.2/searchindex.js +1 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/versions.json +5 -1
- {formative_ds-2.2.0 → formative_ds-2.2.1}/uv.lock +1 -1
- {formative_ds-2.2.0 → formative_ds-2.2.1}/vercel.json +2 -2
- {formative_ds-2.2.0 → formative_ds-2.2.1}/.gitattributes +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/.github/workflows/ci.yml +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/.github/workflows/publish.yml +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/.gitignore +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/CLAUDE.md +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/LICENSE +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/Makefile +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/README.md +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/docs/_static/version_switcher.css +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/docs/_static/version_switcher.js +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/docs/causal/benchmarks.rst +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/docs/causal/dag.rst +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/docs/causal/decisions.rst +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/docs/causal/estimands.rst +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/docs/causal/estimators.rst +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/docs/causal/introduction.rst +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/docs/causal/motivation.rst +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/docs/causal/refutations.rst +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/docs/causal/wizard.rst +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/docs/conf.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/docs/design/policy_learning.md +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/docs/game/decision_rules.rst +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/docs/game/introduction.rst +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/docs/game/single_player.rst +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/docs/index.rst +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/examples/decision/01_decision_basic.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/examples/did/01_did_basic.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/examples/iv/01_iv_basic.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/examples/iv/02_iv_vs_ols_bias.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/examples/iv/03_iv_with_observed_confounder.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/examples/iv/04_iv_refutation.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/examples/matching/01_basic_matching.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/examples/matching/02_matching_vs_naive.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/examples/matching/03_matching_refutation.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/examples/ols/01_ols_basic.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/examples/ols/02_ols_unobserved_confounder.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/examples/ols/03_ols_multiple_confounders.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/examples/rct/01_rct_basic.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/examples/rdd/01_rdd_basic.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/examples/rdd/02_rdd_positive_running_var.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/formative/__init__.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/formative/causal/__init__.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/formative/causal/_exceptions.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/formative/causal/_explain.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/formative/causal/dag.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/formative/causal/decision.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/formative/causal/estimators/__init__.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/formative/causal/estimators/_base.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/formative/causal/estimators/_cate.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/formative/causal/estimators/did.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/formative/causal/estimators/iv.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/formative/causal/estimators/matching.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/formative/causal/estimators/ols.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/formative/causal/estimators/policy.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/formative/causal/estimators/rct.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/formative/causal/estimators/rdd.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/formative/causal/refutations/__init__.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/formative/causal/refutations/_check.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/formative/causal/refutations/cate.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/formative/causal/refutations/did.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/formative/causal/refutations/iv.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/formative/causal/refutations/matching.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/formative/causal/refutations/ols.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/formative/causal/refutations/policy.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/formative/causal/refutations/rct.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/formative/causal/refutations/rdd.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/formative/game/__init__.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/formative/game/expected_value.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/formative/game/hurwicz.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/formative/game/laplace.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/formative/game/maximax.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/formative/game/maximin.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/formative/game/minimax.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/notebooks/card_krueger_benchmark.ipynb +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/notebooks/lalonde_benchmark.ipynb +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/scripts/snapshot_docs.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_sources/causal/benchmarks.rst.txt +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_sources/causal/dag.rst.txt +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_sources/causal/decisions.rst.txt +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_sources/causal/estimands.rst.txt +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_sources/causal/estimators.rst.txt +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_sources/causal/introduction.rst.txt +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_sources/causal/motivation.rst.txt +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_sources/causal/refutations.rst.txt +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_sources/causal/wizard.rst.txt +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_sources/game/decision_rules.rst.txt +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_sources/game/introduction.rst.txt +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_sources/game/single_player.rst.txt +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_sources/index.rst.txt +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/_sphinx_javascript_frameworks_compat.js +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/base-stemmer.js +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/basic.css +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/check-solid.svg +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/clipboard.min.js +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/copy-button.svg +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/copybutton.css +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/copybutton.js +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/copybutton_funcs.js +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/css/badge_only.css +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/css/fonts/Roboto-Slab-Bold.woff +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/css/fonts/Roboto-Slab-Bold.woff2 +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/css/fonts/Roboto-Slab-Regular.woff +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/css/fonts/Roboto-Slab-Regular.woff2 +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/css/fonts/fontawesome-webfont.eot +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/css/fonts/fontawesome-webfont.svg +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/css/fonts/fontawesome-webfont.ttf +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/css/fonts/fontawesome-webfont.woff +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/css/fonts/fontawesome-webfont.woff2 +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/css/fonts/lato-bold-italic.woff +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/css/fonts/lato-bold-italic.woff2 +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/css/fonts/lato-bold.woff +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/css/fonts/lato-bold.woff2 +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/css/fonts/lato-normal-italic.woff +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/css/fonts/lato-normal-italic.woff2 +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/css/fonts/lato-normal.woff +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/css/fonts/lato-normal.woff2 +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/css/theme.css +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/doctools.js +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/documentation_options.js +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/english-stemmer.js +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/file.png +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/fonts/Lato/lato-bold.eot +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/fonts/Lato/lato-bold.ttf +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/fonts/Lato/lato-bold.woff +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/fonts/Lato/lato-bold.woff2 +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/fonts/Lato/lato-bolditalic.eot +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/fonts/Lato/lato-bolditalic.ttf +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/fonts/Lato/lato-bolditalic.woff +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/fonts/Lato/lato-bolditalic.woff2 +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/fonts/Lato/lato-italic.eot +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/fonts/Lato/lato-italic.ttf +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/fonts/Lato/lato-italic.woff +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/fonts/Lato/lato-italic.woff2 +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/fonts/Lato/lato-regular.eot +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/fonts/Lato/lato-regular.ttf +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/fonts/Lato/lato-regular.woff +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/fonts/Lato/lato-regular.woff2 +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/jquery.js +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/js/badge_only.js +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/js/theme.js +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/js/versions.js +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/language_data.js +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/minus.png +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/plus.png +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/pygments.css +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/searchtools.js +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/sphinx_highlight.js +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/version_switcher.css +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/_static/version_switcher.js +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/causal/benchmarks.html +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/causal/dag.html +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/causal/decisions.html +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/causal/estimands.html +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/causal/estimators.html +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/causal/introduction.html +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/causal/motivation.html +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/causal/refutations.html +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/causal/wizard.html +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/game/decision_rules.html +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/game/introduction.html +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/game/single_player.html +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/genindex.html +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/index.html +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/objects.inv +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/search.html +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/site/2.1/searchindex.js +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/tests/test_cate.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/tests/test_cate_refutation.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/tests/test_dag.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/tests/test_decision.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/tests/test_did.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/tests/test_did_refutation.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/tests/test_game.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/tests/test_iv.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/tests/test_iv_refutation.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/tests/test_matching.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/tests/test_matching_refutation.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/tests/test_ols.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/tests/test_ols_refutation.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/tests/test_policy.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/tests/test_policy_refutation.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/tests/test_rct.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/tests/test_rct_refutation.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/tests/test_rdd.py +0 -0
- {formative_ds-2.2.0 → formative_ds-2.2.1}/tests/test_rdd_refutation.py +0 -0
|
@@ -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.1"
|
|
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.1"
|
|
52
52
|
commit = true
|
|
53
53
|
tag = true
|
|
54
54
|
tag_name = "v{new_version}"
|
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
Benchmarks
|
|
2
|
+
==========
|
|
3
|
+
|
|
4
|
+
You shouldn't trust a causal estimation library at face value.
|
|
5
|
+
This page takes famous studies where the "right answer" is already established, runs them through
|
|
6
|
+
formative, and shows the results side by side with the published figures. Every code block is
|
|
7
|
+
runnable as-is: the data is downloaded from its canonical public source, so you can reproduce
|
|
8
|
+
each number yourself.
|
|
9
|
+
|
|
10
|
+
Each benchmark has three ingredients: a dataset with a known ground truth, the formative code to
|
|
11
|
+
estimate the effect, and a scorecard comparing our numbers against the published ones. Each is
|
|
12
|
+
also available as a runnable Jupyter notebook in the `notebooks/ directory of the repository
|
|
13
|
+
<https://github.com/maxpagels/formative/tree/main/notebooks>`_.
|
|
14
|
+
|
|
15
|
+
The LaLonde benchmark: does job training raise earnings?
|
|
16
|
+
---------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
The `National Supported Work Demonstration <https://en.wikipedia.org/wiki/National_Supported_Work_Demonstration>`_
|
|
19
|
+
(NSW) was a 1970s US programme that gave disadvantaged workers 9–18 months of subsidised work
|
|
20
|
+
experience. Crucially, admission was **randomised**, so the programme doubles as a randomised
|
|
21
|
+
controlled trial: we know the true effect of the training on 1978 earnings.
|
|
22
|
+
|
|
23
|
+
Robert LaLonde's famous 1986 paper *"Evaluating the Econometric Evaluations of Training Programs
|
|
24
|
+
with Experimental Data"* used this to run a devastating test. He threw away the experimental
|
|
25
|
+
control group, replaced it with survey respondents from the Panel Study of Income Dynamics (PSID)
|
|
26
|
+
and the Current Population Survey (CPS) — people who were never in the programme — and asked
|
|
27
|
+
whether standard econometric methods could recover the experimental answer from this
|
|
28
|
+
observational data. Mostly, they could not: naive comparisons were wildly wrong, and regression
|
|
29
|
+
adjustment didn't rescue them. Dehejia & Wahba (1999) then revisited the data and showed that
|
|
30
|
+
propensity score methods land close to the experimental benchmark.
|
|
31
|
+
|
|
32
|
+
This makes the NSW data the classic stress test for observational causal methods, because it lets
|
|
33
|
+
us check three things at once:
|
|
34
|
+
|
|
35
|
+
1. Does :class:`~formative.causal.RCT` reproduce the published experimental effect (**$1,794** in
|
|
36
|
+
the Dehejia-Wahba sample)?
|
|
37
|
+
2. Does the naive comparison on observational data go as badly wrong as LaLonde reported
|
|
38
|
+
(**−$15,205** against PSID controls)?
|
|
39
|
+
3. Does :class:`~formative.causal.PropensityScoreMatching` pull the estimate back to the
|
|
40
|
+
neighbourhood of the experimental answer, as Dehejia & Wahba found?
|
|
41
|
+
|
|
42
|
+
Loading the data
|
|
43
|
+
^^^^^^^^^^^^^^^^
|
|
44
|
+
|
|
45
|
+
The Dehejia-Wahba sample is hosted on `Rajeev Dehejia's data page
|
|
46
|
+
<https://users.nber.org/~rdehejia/nswdata2.html>`_. Each file has the same ten columns: a
|
|
47
|
+
treatment indicator, pre-treatment characteristics (age, education, race, marital status, and
|
|
48
|
+
real earnings in 1974 and 1975), and the outcome — real earnings in 1978 (``re78``).
|
|
49
|
+
|
|
50
|
+
.. code-block:: python
|
|
51
|
+
|
|
52
|
+
import pandas as pd
|
|
53
|
+
|
|
54
|
+
BASE = "https://users.nber.org/~rdehejia/data"
|
|
55
|
+
COLS = [
|
|
56
|
+
"treat", "age", "education", "black", "hispanic",
|
|
57
|
+
"married", "nodegree", "re74", "re75", "re78",
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
def load(name):
|
|
61
|
+
return pd.read_csv(f"{BASE}/{name}.txt", sep=r"\s+", header=None, names=COLS)
|
|
62
|
+
|
|
63
|
+
treated = load("nswre74_treated") # 185 NSW participants
|
|
64
|
+
experimental_control = load("nswre74_control") # 260 randomised controls
|
|
65
|
+
psid = load("psid_controls") # 2,490 PSID respondents (never in NSW)
|
|
66
|
+
|
|
67
|
+
experimental = pd.concat([treated, experimental_control], ignore_index=True)
|
|
68
|
+
observational = pd.concat([treated, psid], ignore_index=True)
|
|
69
|
+
|
|
70
|
+
Step 1: the experimental answer
|
|
71
|
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
72
|
+
|
|
73
|
+
Because NSW admission was randomised, the DAG is as simple as it gets — treatment causes the
|
|
74
|
+
outcome, and nothing causes treatment:
|
|
75
|
+
|
|
76
|
+
.. code-block:: python
|
|
77
|
+
|
|
78
|
+
from formative.causal import DAG, RCT
|
|
79
|
+
|
|
80
|
+
rct_dag = DAG()
|
|
81
|
+
rct_dag.assume("treat").causes("re78")
|
|
82
|
+
|
|
83
|
+
result = RCT(rct_dag, treatment="treat", outcome="re78").fit(experimental)
|
|
84
|
+
print(result.summary())
|
|
85
|
+
|
|
86
|
+
.. code-block:: text
|
|
87
|
+
|
|
88
|
+
RCT Causal Effect: treat → re78
|
|
89
|
+
Estimand: ATE (average treatment effect)
|
|
90
|
+
──────────────────────────────────────────────────
|
|
91
|
+
ATE estimate : 1794.3424
|
|
92
|
+
|
|
93
|
+
Std. error : 632.8534
|
|
94
|
+
95% CI : [550.5745, 3038.1103]
|
|
95
|
+
p-value : 0.0048
|
|
96
|
+
N : 445
|
|
97
|
+
|
|
98
|
+
**$1,794.34** — the published experimental benchmark for this sample is $1,794 (Dehejia & Wahba
|
|
99
|
+
1999, Table 3). This is the ground truth the observational methods below will be judged against.
|
|
100
|
+
|
|
101
|
+
Step 2: the naive comparison goes badly wrong
|
|
102
|
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
103
|
+
|
|
104
|
+
Now we discard the experimental controls and compare NSW participants against PSID respondents,
|
|
105
|
+
as LaLonde did. The comparison group is now nothing like the treated group — PSID respondents
|
|
106
|
+
are older, better educated, far more likely to be employed — so the covariates confound the
|
|
107
|
+
treatment/outcome relationship. The observational DAG declares this:
|
|
108
|
+
|
|
109
|
+
.. code-block:: python
|
|
110
|
+
|
|
111
|
+
from formative.causal import OLSObservational
|
|
112
|
+
|
|
113
|
+
COVARIATES = ["age", "education", "black", "hispanic",
|
|
114
|
+
"married", "nodegree", "re74", "re75"]
|
|
115
|
+
|
|
116
|
+
obs_dag = DAG()
|
|
117
|
+
for c in COVARIATES:
|
|
118
|
+
obs_dag.assume(c).causes("treat", "re78")
|
|
119
|
+
obs_dag.assume("treat").causes("re78")
|
|
120
|
+
|
|
121
|
+
result = OLSObservational(obs_dag, treatment="treat", outcome="re78").fit(observational)
|
|
122
|
+
print(result.summary())
|
|
123
|
+
|
|
124
|
+
.. code-block:: text
|
|
125
|
+
|
|
126
|
+
OLS Causal Effect: treat → re78
|
|
127
|
+
──────────────────────────────────────────────────
|
|
128
|
+
Adjusted estimate : 751.9464 (controlling for: age, black, education, hispanic, married, nodegree, re74, re75)
|
|
129
|
+
Unadjusted estimate : -15204.7775 (no controls)
|
|
130
|
+
Confounding bias : -15956.7239
|
|
131
|
+
|
|
132
|
+
Std. error : 915.2572
|
|
133
|
+
95% CI : [-1042.7399, 2546.6327]
|
|
134
|
+
p-value : 0.4114
|
|
135
|
+
N : 2675
|
|
136
|
+
|
|
137
|
+
This single summary reproduces LaLonde's core finding. The unadjusted estimate — the answer you
|
|
138
|
+
would get by simply comparing average earnings between the two groups — is **−$15,204.78**,
|
|
139
|
+
matching the −$15,205 raw gap in the published data. Taken at face value it says the training
|
|
140
|
+
programme *destroyed* fifteen thousand dollars of annual earnings; in reality it measures how
|
|
141
|
+
much poorer NSW participants were than average PSID respondents to begin with. Linear regression
|
|
142
|
+
adjustment moves the estimate to $752 — the right sign, but still less than half the experimental
|
|
143
|
+
benchmark and statistically indistinguishable from zero, echoing LaLonde's conclusion that
|
|
144
|
+
regression on a badly mismatched comparison group cannot be trusted. The adjusted-vs-unadjusted
|
|
145
|
+
comparison that every formative result carries exists precisely to make this kind of confounding
|
|
146
|
+
visible.
|
|
147
|
+
|
|
148
|
+
Step 3: matching recovers the benchmark
|
|
149
|
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
150
|
+
|
|
151
|
+
Dehejia & Wahba's insight was that most PSID respondents are so unlike NSW participants that a
|
|
152
|
+
linear model extrapolates across incomparable people. Propensity score matching instead compares
|
|
153
|
+
each treated person only with the most similar controls:
|
|
154
|
+
|
|
155
|
+
.. code-block:: python
|
|
156
|
+
|
|
157
|
+
from formative.causal import PropensityScoreMatching
|
|
158
|
+
|
|
159
|
+
result = PropensityScoreMatching(obs_dag, treatment="treat", outcome="re78").fit(observational)
|
|
160
|
+
print(result.summary())
|
|
161
|
+
|
|
162
|
+
.. code-block:: text
|
|
163
|
+
|
|
164
|
+
PSM Causal Effect: treat → re78
|
|
165
|
+
Estimand: ATT (average treatment effect on the treated)
|
|
166
|
+
──────────────────────────────────────────────────────
|
|
167
|
+
ATT estimate : 2125.7131 (controlling for: age, black, education, hispanic, married, nodegree, re74, re75)
|
|
168
|
+
Unadjusted estimate : -15204.7775 (naive mean difference)
|
|
169
|
+
Confounding bias : -17330.4906
|
|
170
|
+
|
|
171
|
+
Std. error : 1079.2522 (bootstrap, N=500)
|
|
172
|
+
95% CI : [-943.4393, 3438.7593] (bootstrap percentile)
|
|
173
|
+
p-value : 0.0489
|
|
174
|
+
N : 2675
|
|
175
|
+
|
|
176
|
+
From the same observational data that produced −$15,205 naively, matching estimates **$2,125.71**
|
|
177
|
+
— within one standard error of the experimental $1,794. Repeating the exercise with the much
|
|
178
|
+
larger CPS comparison group (15,992 controls, naive gap −$8,497.52) gives an ATT of
|
|
179
|
+
**$2,074.02**: two very different comparison groups, matched estimates a mere $50 apart, both
|
|
180
|
+
bracketing the experimental truth. Dehejia & Wahba's own matching estimates were $1,691 (PSID)
|
|
181
|
+
and $1,582 (CPS); the residual spread between their numbers and ours reflects implementation
|
|
182
|
+
choices (they used stratification and different matching variants; formative uses 1-to-1
|
|
183
|
+
nearest-neighbour matching with replacement), and later work (Smith & Todd 2005) showed estimates
|
|
184
|
+
in this range are sensitive to exactly such choices. All of them are within one standard error of
|
|
185
|
+
the experimental answer.
|
|
186
|
+
|
|
187
|
+
Scorecard
|
|
188
|
+
^^^^^^^^^
|
|
189
|
+
|
|
190
|
+
.. list-table::
|
|
191
|
+
:header-rows: 1
|
|
192
|
+
:widths: 40 20 20 20
|
|
193
|
+
|
|
194
|
+
* - Quantity
|
|
195
|
+
- Published
|
|
196
|
+
- formative
|
|
197
|
+
- Verdict
|
|
198
|
+
* - Experimental ATE (RCT)
|
|
199
|
+
- $1,794
|
|
200
|
+
- $1,794.34
|
|
201
|
+
- exact match
|
|
202
|
+
* - Naive mean difference, PSID controls
|
|
203
|
+
- −$15,205
|
|
204
|
+
- −$15,204.78
|
|
205
|
+
- exact match
|
|
206
|
+
* - Naive mean difference, CPS controls
|
|
207
|
+
- −$8,498
|
|
208
|
+
- −$8,497.52
|
|
209
|
+
- exact match
|
|
210
|
+
* - PSM ATT, PSID controls
|
|
211
|
+
- $1,691 (DW 1999)
|
|
212
|
+
- $2,125.71
|
|
213
|
+
- within 1 SE of experimental truth
|
|
214
|
+
* - PSM ATT, CPS controls
|
|
215
|
+
- $1,582 (DW 1999)
|
|
216
|
+
- $2,074.02
|
|
217
|
+
- within 1 SE of experimental truth
|
|
218
|
+
|
|
219
|
+
The pattern is exactly what forty years of literature says it should be: the experimental
|
|
220
|
+
estimate is reproduced to the dollar, the naive observational comparison is catastrophically
|
|
221
|
+
biased (and formative's unadjusted estimate quantifies that bias to the dollar), and propensity
|
|
222
|
+
score matching recovers an estimate statistically indistinguishable from the experimental truth.
|
|
223
|
+
|
|
224
|
+
References
|
|
225
|
+
^^^^^^^^^^
|
|
226
|
+
|
|
227
|
+
- LaLonde, R. (1986). "Evaluating the Econometric Evaluations of Training Programs with
|
|
228
|
+
Experimental Data." *American Economic Review*, 76(4), 604–620.
|
|
229
|
+
- Dehejia, R. & Wahba, S. (1999). "Causal Effects in Nonexperimental Studies: Reevaluating the
|
|
230
|
+
Evaluation of Training Programs." *Journal of the American Statistical Association*, 94(448),
|
|
231
|
+
1053–1062.
|
|
232
|
+
- Smith, J. & Todd, P. (2005). "Does matching overcome LaLonde's critique of nonexperimental
|
|
233
|
+
estimators?" *Journal of Econometrics*, 125(1–2), 305–353.
|
|
234
|
+
- Data: `Rajeev Dehejia's NSW data page <https://users.nber.org/~rdehejia/nswdata2.html>`_.
|
|
235
|
+
|
|
236
|
+
The Card–Krueger benchmark: does raising the minimum wage kill jobs?
|
|
237
|
+
--------------------------------------------------------------------
|
|
238
|
+
|
|
239
|
+
On 1 April 1992, New Jersey raised its minimum wage from $4.25 to $5.05 an hour. Neighbouring
|
|
240
|
+
Pennsylvania kept its minimum at $4.25. The textbook competitive-market prediction is
|
|
241
|
+
unambiguous: a binding wage floor should reduce employment. Card & Krueger (1994) tested it by
|
|
242
|
+
surveying 410 fast-food restaurants — Burger King, KFC, Roy Rogers, and Wendy's — on both sides
|
|
243
|
+
of the state border, once in February–March 1992 (before the rise) and again in November–December
|
|
244
|
+
1992 (after). Because the two states share a labour market and an economic climate, Pennsylvania
|
|
245
|
+
serves as the counterfactual for what would have happened to New Jersey employment without the
|
|
246
|
+
wage rise.
|
|
247
|
+
|
|
248
|
+
This is *the* canonical difference-in-differences study, and its result was famously
|
|
249
|
+
counterintuitive: employment in New Jersey did **not** fall relative to Pennsylvania — the
|
|
250
|
+
published DiD estimate is **+2.76** full-time-equivalent (FTE) jobs per store (Table 3, standard
|
|
251
|
+
error 1.36). The benchmark for :class:`~formative.causal.DiD` is therefore: feed it the public
|
|
252
|
+
survey data and check that it reproduces that coefficient.
|
|
253
|
+
|
|
254
|
+
Loading the data
|
|
255
|
+
^^^^^^^^^^^^^^^^
|
|
256
|
+
|
|
257
|
+
The dataset is distributed on `David Card's data page <https://davidcard.berkeley.edu/data_sets.html>`_
|
|
258
|
+
as a zip containing ``public.dat`` (one row per store, both survey waves side by side) and a
|
|
259
|
+
codebook. Following Card & Krueger, employment is measured in full-time equivalents: full-time
|
|
260
|
+
staff plus managers plus half the part-time staff.
|
|
261
|
+
|
|
262
|
+
.. code-block:: python
|
|
263
|
+
|
|
264
|
+
import io
|
|
265
|
+
import urllib.request
|
|
266
|
+
import zipfile
|
|
267
|
+
|
|
268
|
+
import pandas as pd
|
|
269
|
+
|
|
270
|
+
URL = "https://davidcard.berkeley.edu/data_sets/njmin.zip"
|
|
271
|
+
COLS = [
|
|
272
|
+
"SHEET", "CHAIN", "CO_OWNED", "STATE", "SOUTHJ", "CENTRALJ", "NORTHJ",
|
|
273
|
+
"PA1", "PA2", "SHORE", "NCALLS", "EMPFT", "EMPPT", "NMGRS", "WAGE_ST",
|
|
274
|
+
"INCTIME", "FIRSTINC", "BONUS", "PCTAFF", "MEALS", "OPEN", "HRSOPEN",
|
|
275
|
+
"PSODA", "PFRY", "PENTREE", "NREGS", "NREGS11", "TYPE2", "STATUS2",
|
|
276
|
+
"DATE2", "NCALLS2", "EMPFT2", "EMPPT2", "NMGRS2", "WAGE_ST2", "INCTIME2",
|
|
277
|
+
"FIRSTIN2", "SPECIAL2", "MEALS2", "OPEN2R", "HRSOPEN2", "PSODA2",
|
|
278
|
+
"PFRY2", "PENTREE2", "NREGS2", "NREGS112",
|
|
279
|
+
]
|
|
280
|
+
|
|
281
|
+
with zipfile.ZipFile(io.BytesIO(urllib.request.urlopen(URL).read())) as z:
|
|
282
|
+
raw = pd.read_csv(z.open("public.dat"), sep=r"\s+", header=None,
|
|
283
|
+
names=COLS, na_values=".")
|
|
284
|
+
|
|
285
|
+
# FTE employment = full-timers + managers + 0.5 × part-timers (CK's definition)
|
|
286
|
+
raw["fte_before"] = raw["EMPFT"] + raw["NMGRS"] + 0.5 * raw["EMPPT"]
|
|
287
|
+
raw["fte_after"] = raw["EMPFT2"] + raw["NMGRS2"] + 0.5 * raw["EMPPT2"]
|
|
288
|
+
|
|
289
|
+
Before estimating anything, we can check the raw ingredients against the paper. Card & Krueger's
|
|
290
|
+
Table 3 reports four cell means; formative's DiD will be built from exactly these:
|
|
291
|
+
|
|
292
|
+
.. code-block:: python
|
|
293
|
+
|
|
294
|
+
for state, name in [(1, "NJ"), (0, "PA")]:
|
|
295
|
+
sub = raw[raw.STATE == state]
|
|
296
|
+
print(f"{name}: before {sub.fte_before.mean():.2f}, after {sub.fte_after.mean():.2f}")
|
|
297
|
+
|
|
298
|
+
.. code-block:: text
|
|
299
|
+
|
|
300
|
+
NJ: before 20.44, after 21.03
|
|
301
|
+
PA: before 23.33, after 21.17
|
|
302
|
+
|
|
303
|
+
All four match the published Table 3 to the last digit (NJ: 20.44 → 21.03; PA: 23.33 → 21.17).
|
|
304
|
+
Note the shape of the problem already visible here: New Jersey stores were *smaller* than
|
|
305
|
+
Pennsylvania stores before the wage rise, so a naive post-period comparison would be biased
|
|
306
|
+
against New Jersey — the exact baseline difference DiD exists to remove.
|
|
307
|
+
|
|
308
|
+
Running DiD
|
|
309
|
+
^^^^^^^^^^^
|
|
310
|
+
|
|
311
|
+
:class:`~formative.causal.DiD` expects long-format data — one row per store per period — with
|
|
312
|
+
binary group and time indicators:
|
|
313
|
+
|
|
314
|
+
.. code-block:: python
|
|
315
|
+
|
|
316
|
+
from formative.causal import DAG, DiD
|
|
317
|
+
|
|
318
|
+
long = pd.concat(
|
|
319
|
+
[
|
|
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}),
|
|
322
|
+
],
|
|
323
|
+
ignore_index=True,
|
|
324
|
+
).dropna(subset=["fte"])
|
|
325
|
+
|
|
326
|
+
dag = DAG()
|
|
327
|
+
dag.assume("nj").causes("fte")
|
|
328
|
+
dag.assume("after").causes("fte")
|
|
329
|
+
|
|
330
|
+
result = DiD(dag, group="nj", time="after", outcome="fte").fit(long)
|
|
331
|
+
print(result.summary())
|
|
332
|
+
|
|
333
|
+
.. code-block:: text
|
|
334
|
+
|
|
335
|
+
DiD Causal Effect: (nj × after) → fte
|
|
336
|
+
Estimand: ATT (average treatment effect on the treated)
|
|
337
|
+
──────────────────────────────────────────────────────
|
|
338
|
+
DiD estimate : 2.7536
|
|
339
|
+
Naive post-diff : -0.1382 (treated post − control post)
|
|
340
|
+
Baseline bias removed: -2.8918
|
|
341
|
+
|
|
342
|
+
Std. error : 1.6884
|
|
343
|
+
95% CI : [-0.5607, 6.0679]
|
|
344
|
+
p-value : 0.1033
|
|
345
|
+
N : 794
|
|
346
|
+
|
|
347
|
+
The DiD estimate is **+2.75** FTE jobs — Card & Krueger's published figure is 2.76, an agreement
|
|
348
|
+
to within a hundredth of an FTE (rounding-level noise; note that the published two-decimal cell
|
|
349
|
+
means themselves difference to 2.75). The naive
|
|
350
|
+
post-period difference of −0.14 also matches the paper's reported NJ−PA gap of −0.14, and the
|
|
351
|
+
summary shows DiD stripping out the −2.89 baseline difference between the states. The sign of
|
|
352
|
+
the result is the famous finding: no evidence that the minimum wage rise reduced employment.
|
|
353
|
+
|
|
354
|
+
Restricting to the balanced panel — the 384 stores with employment measured in both waves —
|
|
355
|
+
reproduces the paper's balanced-sample row exactly:
|
|
356
|
+
|
|
357
|
+
.. code-block:: python
|
|
358
|
+
|
|
359
|
+
balanced = raw.dropna(subset=["fte_before", "fte_after"]).SHEET
|
|
360
|
+
result_bal = DiD(dag, group="nj", time="after", outcome="fte").fit(
|
|
361
|
+
long[long.store.isin(balanced)]
|
|
362
|
+
)
|
|
363
|
+
print(f"balanced-sample DiD: {result_bal.effect:.4f}")
|
|
364
|
+
|
|
365
|
+
.. code-block:: text
|
|
366
|
+
|
|
367
|
+
balanced-sample DiD: 2.7500
|
|
368
|
+
|
|
369
|
+
against a published 2.75 (Table 3, row 5, where the underlying mean changes are NJ +0.47 and
|
|
370
|
+
PA −2.28 — both of which this data reproduces exactly).
|
|
371
|
+
|
|
372
|
+
A note on the standard error: formative reports 1.69, computed from the pooled OLS interaction
|
|
373
|
+
model on store-periods. Card & Krueger's published 1.36 comes from a different (and for panel
|
|
374
|
+
data, sharper) calculation — the variance of the store-level *changes*, which nets out
|
|
375
|
+
persistent store-to-store size differences before computing uncertainty. The same data
|
|
376
|
+
reproduces their number too: the change-score formula on the balanced panel gives an SE of
|
|
377
|
+
1.3423 against their published 1.34. The point estimate is identical either way; the two SEs
|
|
378
|
+
answer slightly different questions about it, and formative's is the more conservative of the
|
|
379
|
+
two.
|
|
380
|
+
|
|
381
|
+
Scorecard
|
|
382
|
+
^^^^^^^^^
|
|
383
|
+
|
|
384
|
+
.. list-table::
|
|
385
|
+
:header-rows: 1
|
|
386
|
+
:widths: 40 20 20 20
|
|
387
|
+
|
|
388
|
+
* - Quantity
|
|
389
|
+
- Published
|
|
390
|
+
- formative
|
|
391
|
+
- Verdict
|
|
392
|
+
* - Four Table 3 cell means (NJ/PA × before/after)
|
|
393
|
+
- 20.44, 21.03, 23.33, 21.17
|
|
394
|
+
- 20.44, 21.03, 23.33, 21.17
|
|
395
|
+
- exact match
|
|
396
|
+
* - Naive post-period NJ−PA difference
|
|
397
|
+
- −0.14
|
|
398
|
+
- −0.1382
|
|
399
|
+
- exact match
|
|
400
|
+
* - DiD estimate, all available stores
|
|
401
|
+
- +2.76 (SE 1.36)
|
|
402
|
+
- +2.7536 (SE 1.69)
|
|
403
|
+
- match (see SE note)
|
|
404
|
+
* - DiD estimate, balanced panel
|
|
405
|
+
- +2.75 (SE 1.34)
|
|
406
|
+
- +2.7500 (change-score SE 1.3423)
|
|
407
|
+
- exact match
|
|
408
|
+
|
|
409
|
+
References
|
|
410
|
+
^^^^^^^^^^
|
|
411
|
+
|
|
412
|
+
- Card, D. & Krueger, A. (1994). "Minimum Wages and Employment: A Case Study of the Fast-Food
|
|
413
|
+
Industry in New Jersey and Pennsylvania." *American Economic Review*, 84(4), 772–793.
|
|
414
|
+
- Data: `David Card's data page <https://davidcard.berkeley.edu/data_sets.html>`_ (``njmin.zip``).
|