formative-ds 2.2.2__tar.gz → 2.2.4__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.2 → formative_ds-2.2.4}/PKG-INFO +3 -3
- formative_ds-2.2.4/TODO.md +30 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/docs/causal/benchmarks.rst +22 -5
- {formative_ds-2.2.2 → formative_ds-2.2.4}/docs/causal/decisions.rst +69 -24
- {formative_ds-2.2.2 → formative_ds-2.2.4}/docs/causal/introduction.rst +10 -5
- {formative_ds-2.2.2 → formative_ds-2.2.4}/formative/causal/dag.py +1 -1
- {formative_ds-2.2.2 → formative_ds-2.2.4}/pyproject.toml +3 -3
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_sources/causal/benchmarks.rst.txt +22 -5
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_sources/causal/decisions.rst.txt +69 -24
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_sources/causal/introduction.rst.txt +10 -5
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/documentation_options.js +1 -1
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/causal/benchmarks.html +25 -8
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/causal/dag.html +6 -6
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/causal/decisions.html +75 -30
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/causal/estimands.html +3 -3
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/causal/estimators.html +11 -11
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/causal/introduction.html +16 -12
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/causal/motivation.html +7 -7
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/causal/refutations.html +3 -3
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/causal/wizard.html +3 -3
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/game/decision_rules.html +3 -3
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/game/introduction.html +3 -3
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/game/single_player.html +3 -3
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/genindex.html +3 -3
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/index.html +5 -5
- formative_ds-2.2.4/site/2.2/objects.inv +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/search.html +3 -3
- formative_ds-2.2.4/site/2.2/searchindex.js +1 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/uv.lock +131 -130
- formative_ds-2.2.2/site/2.2/objects.inv +0 -0
- formative_ds-2.2.2/site/2.2/searchindex.js +0 -1
- {formative_ds-2.2.2 → formative_ds-2.2.4}/.gitattributes +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/.github/workflows/ci.yml +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/.github/workflows/publish.yml +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/.gitignore +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/CLAUDE.md +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/LICENSE +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/Makefile +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/README.md +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/docs/_static/version_switcher.css +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/docs/_static/version_switcher.js +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/docs/causal/dag.rst +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/docs/causal/estimands.rst +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/docs/causal/estimators.rst +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/docs/causal/motivation.rst +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/docs/causal/refutations.rst +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/docs/causal/wizard.rst +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/docs/conf.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/docs/design/policy_learning.md +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/docs/game/decision_rules.rst +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/docs/game/introduction.rst +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/docs/game/single_player.rst +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/docs/index.rst +1 -1
- {formative_ds-2.2.2 → formative_ds-2.2.4}/examples/decision/01_decision_basic.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/examples/did/01_did_basic.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/examples/iv/01_iv_basic.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/examples/iv/02_iv_vs_ols_bias.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/examples/iv/03_iv_with_observed_confounder.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/examples/iv/04_iv_refutation.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/examples/matching/01_basic_matching.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/examples/matching/02_matching_vs_naive.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/examples/matching/03_matching_refutation.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/examples/ols/01_ols_basic.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/examples/ols/02_ols_unobserved_confounder.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/examples/ols/03_ols_multiple_confounders.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/examples/rct/01_rct_basic.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/examples/rdd/01_rdd_basic.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/examples/rdd/02_rdd_positive_running_var.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/formative/__init__.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/formative/causal/__init__.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/formative/causal/_assumptions.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/formative/causal/_exceptions.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/formative/causal/_explain.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/formative/causal/decision.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/formative/causal/estimators/__init__.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/formative/causal/estimators/_base.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/formative/causal/estimators/_cate.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/formative/causal/estimators/did.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/formative/causal/estimators/iv.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/formative/causal/estimators/matching.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/formative/causal/estimators/ols.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/formative/causal/estimators/policy.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/formative/causal/estimators/rct.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/formative/causal/estimators/rdd.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/formative/causal/refutations/__init__.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/formative/causal/refutations/_check.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/formative/causal/refutations/cate.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/formative/causal/refutations/did.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/formative/causal/refutations/iv.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/formative/causal/refutations/matching.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/formative/causal/refutations/ols.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/formative/causal/refutations/policy.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/formative/causal/refutations/rct.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/formative/causal/refutations/rdd.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/formative/game/__init__.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/formative/game/expected_value.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/formative/game/hurwicz.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/formative/game/laplace.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/formative/game/maximax.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/formative/game/maximin.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/formative/game/minimax.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/notebooks/card_krueger_benchmark.ipynb +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/notebooks/lalonde_benchmark.ipynb +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/scripts/snapshot_docs.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_sources/causal/benchmarks.rst.txt +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_sources/causal/dag.rst.txt +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_sources/causal/decisions.rst.txt +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_sources/causal/estimands.rst.txt +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_sources/causal/estimators.rst.txt +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_sources/causal/introduction.rst.txt +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_sources/causal/motivation.rst.txt +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_sources/causal/refutations.rst.txt +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_sources/causal/wizard.rst.txt +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_sources/game/decision_rules.rst.txt +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_sources/game/introduction.rst.txt +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_sources/game/single_player.rst.txt +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_sources/index.rst.txt +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/_sphinx_javascript_frameworks_compat.js +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/base-stemmer.js +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/basic.css +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/check-solid.svg +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/clipboard.min.js +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/copy-button.svg +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/copybutton.css +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/copybutton.js +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/copybutton_funcs.js +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/css/badge_only.css +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/css/fonts/Roboto-Slab-Bold.woff +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/css/fonts/Roboto-Slab-Bold.woff2 +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/css/fonts/Roboto-Slab-Regular.woff +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/css/fonts/Roboto-Slab-Regular.woff2 +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/css/fonts/fontawesome-webfont.eot +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/css/fonts/fontawesome-webfont.svg +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/css/fonts/fontawesome-webfont.ttf +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/css/fonts/fontawesome-webfont.woff +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/css/fonts/fontawesome-webfont.woff2 +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/css/fonts/lato-bold-italic.woff +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/css/fonts/lato-bold-italic.woff2 +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/css/fonts/lato-bold.woff +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/css/fonts/lato-bold.woff2 +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/css/fonts/lato-normal-italic.woff +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/css/fonts/lato-normal-italic.woff2 +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/css/fonts/lato-normal.woff +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/css/fonts/lato-normal.woff2 +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/css/theme.css +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/doctools.js +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/documentation_options.js +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/english-stemmer.js +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/file.png +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/fonts/Lato/lato-bold.eot +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/fonts/Lato/lato-bold.ttf +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/fonts/Lato/lato-bold.woff +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/fonts/Lato/lato-bold.woff2 +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/fonts/Lato/lato-bolditalic.eot +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/fonts/Lato/lato-bolditalic.ttf +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/fonts/Lato/lato-bolditalic.woff +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/fonts/Lato/lato-bolditalic.woff2 +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/fonts/Lato/lato-italic.eot +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/fonts/Lato/lato-italic.ttf +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/fonts/Lato/lato-italic.woff +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/fonts/Lato/lato-italic.woff2 +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/fonts/Lato/lato-regular.eot +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/fonts/Lato/lato-regular.ttf +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/fonts/Lato/lato-regular.woff +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/fonts/Lato/lato-regular.woff2 +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/jquery.js +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/js/badge_only.js +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/js/theme.js +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/js/versions.js +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/language_data.js +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/minus.png +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/plus.png +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/pygments.css +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/searchtools.js +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/sphinx_highlight.js +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/version_switcher.css +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/_static/version_switcher.js +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/causal/benchmarks.html +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/causal/dag.html +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/causal/decisions.html +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/causal/estimands.html +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/causal/estimators.html +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/causal/introduction.html +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/causal/motivation.html +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/causal/refutations.html +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/causal/wizard.html +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/game/decision_rules.html +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/game/introduction.html +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/game/single_player.html +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/genindex.html +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/index.html +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/objects.inv +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/search.html +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.1/searchindex.js +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_sources/causal/dag.rst.txt +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_sources/causal/estimands.rst.txt +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_sources/causal/estimators.rst.txt +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_sources/causal/motivation.rst.txt +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_sources/causal/refutations.rst.txt +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_sources/causal/wizard.rst.txt +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_sources/game/decision_rules.rst.txt +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_sources/game/introduction.rst.txt +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_sources/game/single_player.rst.txt +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_sources/index.rst.txt +1 -1
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/_sphinx_javascript_frameworks_compat.js +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/base-stemmer.js +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/basic.css +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/check-solid.svg +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/clipboard.min.js +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/copy-button.svg +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/copybutton.css +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/copybutton.js +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/copybutton_funcs.js +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/css/badge_only.css +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/css/fonts/Roboto-Slab-Bold.woff +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/css/fonts/Roboto-Slab-Bold.woff2 +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/css/fonts/Roboto-Slab-Regular.woff +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/css/fonts/Roboto-Slab-Regular.woff2 +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/css/fonts/fontawesome-webfont.eot +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/css/fonts/fontawesome-webfont.svg +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/css/fonts/fontawesome-webfont.ttf +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/css/fonts/fontawesome-webfont.woff +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/css/fonts/fontawesome-webfont.woff2 +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/css/fonts/lato-bold-italic.woff +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/css/fonts/lato-bold-italic.woff2 +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/css/fonts/lato-bold.woff +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/css/fonts/lato-bold.woff2 +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/css/fonts/lato-normal-italic.woff +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/css/fonts/lato-normal-italic.woff2 +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/css/fonts/lato-normal.woff +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/css/fonts/lato-normal.woff2 +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/css/theme.css +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/doctools.js +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/english-stemmer.js +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/file.png +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/fonts/Lato/lato-bold.eot +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/fonts/Lato/lato-bold.ttf +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/fonts/Lato/lato-bold.woff +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/fonts/Lato/lato-bold.woff2 +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/fonts/Lato/lato-bolditalic.eot +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/fonts/Lato/lato-bolditalic.ttf +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/fonts/Lato/lato-bolditalic.woff +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/fonts/Lato/lato-bolditalic.woff2 +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/fonts/Lato/lato-italic.eot +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/fonts/Lato/lato-italic.ttf +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/fonts/Lato/lato-italic.woff +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/fonts/Lato/lato-italic.woff2 +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/fonts/Lato/lato-regular.eot +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/fonts/Lato/lato-regular.ttf +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/fonts/Lato/lato-regular.woff +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/fonts/Lato/lato-regular.woff2 +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/jquery.js +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/js/badge_only.js +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/js/theme.js +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/js/versions.js +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/language_data.js +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/minus.png +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/plus.png +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/pygments.css +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/searchtools.js +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/sphinx_highlight.js +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/version_switcher.css +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/2.2/_static/version_switcher.js +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/site/versions.json +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/tests/test_cate.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/tests/test_cate_refutation.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/tests/test_dag.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/tests/test_decision.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/tests/test_did.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/tests/test_did_refutation.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/tests/test_game.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/tests/test_iv.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/tests/test_iv_refutation.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/tests/test_matching.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/tests/test_matching_refutation.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/tests/test_ols.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/tests/test_ols_refutation.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/tests/test_policy.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/tests/test_policy_refutation.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/tests/test_rct.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/tests/test_rct_refutation.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/tests/test_rdd.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/tests/test_rdd_refutation.py +0 -0
- {formative_ds-2.2.2 → formative_ds-2.2.4}/vercel.json +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: formative-ds
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.4
|
|
4
4
|
Summary: A Python library for quantitative reasoning.
|
|
5
5
|
Project-URL: Homepage, https://getformative.dev
|
|
6
6
|
Project-URL: Documentation, https://docs.getformative.dev
|
|
@@ -9,7 +9,7 @@ Author: Max Pagels
|
|
|
9
9
|
License: MIT
|
|
10
10
|
License-File: LICENSE
|
|
11
11
|
Keywords: DAG,causal estimation,causal inference,econometrics,statistics
|
|
12
|
-
Classifier: Development Status ::
|
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
13
13
|
Classifier: Intended Audience :: Science/Research
|
|
14
14
|
Classifier: License :: OSI Approved :: MIT License
|
|
15
15
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# TODO
|
|
2
|
+
|
|
3
|
+
## Relax DAG validation for policy-learning features
|
|
4
|
+
|
|
5
|
+
`RCTResult.learn_policy()` currently validates candidate features with
|
|
6
|
+
`_validate_modifier_dag` (shared with CATE effect modifiers), which requires
|
|
7
|
+
each feature to be an **ancestor of the outcome**. That requirement is
|
|
8
|
+
stricter than the statistics need:
|
|
9
|
+
|
|
10
|
+
- In an RCT, treatment is independent of every pre-treatment variable, so the
|
|
11
|
+
doubly robust value estimate is honest for a policy conditioning on *any*
|
|
12
|
+
pre-treatment feature — causal, proxy, or noise.
|
|
13
|
+
- Proxy features (e.g. `income → zip`, `income → outcome`: zip causes nothing
|
|
14
|
+
but predicts who benefits) are valid and useful targeting features, yet are
|
|
15
|
+
rejected when the DAG is specified honestly.
|
|
16
|
+
- Noise features can't inflate the honest out-of-fold value, and the
|
|
17
|
+
placebo/random-modifier refutation checks already guard against them.
|
|
18
|
+
|
|
19
|
+
The **mediator exclusion must stay**: a descendant of the treatment is not
|
|
20
|
+
actionable at assignment time and conditioning on it creates selection bias.
|
|
21
|
+
|
|
22
|
+
Plan:
|
|
23
|
+
|
|
24
|
+
- Give policy learning its own validation: feature must be a DAG node and not
|
|
25
|
+
a descendant of the treatment; drop the ancestor-of-outcome requirement.
|
|
26
|
+
- Keep the stricter check for CATE effect modifiers, where the output is an
|
|
27
|
+
interpretive claim ("the effect differs by X") rather than just a rule and
|
|
28
|
+
its value.
|
|
29
|
+
- Update the corresponding passage in `docs/causal/decisions.rst` (~line 231)
|
|
30
|
+
and the `learn_policy` docstring in `formative/causal/estimators/rct.py`.
|
|
@@ -118,7 +118,9 @@ treatment/outcome relationship. The observational DAG declares this:
|
|
|
118
118
|
obs_dag.assume(c).causes("treat", "re78")
|
|
119
119
|
obs_dag.assume("treat").causes("re78")
|
|
120
120
|
|
|
121
|
-
result = OLSObservational(
|
|
121
|
+
result = OLSObservational(
|
|
122
|
+
obs_dag, treatment="treat", outcome="re78"
|
|
123
|
+
).fit(observational)
|
|
122
124
|
print(result.summary())
|
|
123
125
|
|
|
124
126
|
.. code-block:: text
|
|
@@ -156,7 +158,9 @@ each treated person only with the most similar controls:
|
|
|
156
158
|
|
|
157
159
|
from formative.causal import PropensityScoreMatching
|
|
158
160
|
|
|
159
|
-
result = PropensityScoreMatching(
|
|
161
|
+
result = PropensityScoreMatching(
|
|
162
|
+
obs_dag, treatment="treat", outcome="re78"
|
|
163
|
+
).fit(observational)
|
|
160
164
|
print(result.summary())
|
|
161
165
|
|
|
162
166
|
.. code-block:: text
|
|
@@ -293,7 +297,10 @@ Table 3 reports four cell means; formative's DiD will be built from exactly thes
|
|
|
293
297
|
|
|
294
298
|
for state, name in [(1, "NJ"), (0, "PA")]:
|
|
295
299
|
sub = raw[raw.STATE == state]
|
|
296
|
-
print(
|
|
300
|
+
print(
|
|
301
|
+
f"{name}: before {sub.fte_before.mean():.2f}, "
|
|
302
|
+
f"after {sub.fte_after.mean():.2f}"
|
|
303
|
+
)
|
|
297
304
|
|
|
298
305
|
.. code-block:: text
|
|
299
306
|
|
|
@@ -317,8 +324,18 @@ binary group and time indicators:
|
|
|
317
324
|
|
|
318
325
|
long = pd.concat(
|
|
319
326
|
[
|
|
320
|
-
pd.DataFrame({
|
|
321
|
-
|
|
327
|
+
pd.DataFrame({
|
|
328
|
+
"store": raw.SHEET,
|
|
329
|
+
"nj": raw.STATE,
|
|
330
|
+
"after": 0,
|
|
331
|
+
"fte": raw.fte_before,
|
|
332
|
+
}),
|
|
333
|
+
pd.DataFrame({
|
|
334
|
+
"store": raw.SHEET,
|
|
335
|
+
"nj": raw.STATE,
|
|
336
|
+
"after": 1,
|
|
337
|
+
"fte": raw.fte_after,
|
|
338
|
+
}),
|
|
322
339
|
],
|
|
323
340
|
ignore_index=True,
|
|
324
341
|
).dropna(subset=["fte"])
|
|
@@ -52,7 +52,11 @@ earnings using OLS with a DAG that encodes family background as a confounder:
|
|
|
52
52
|
training = 0.6 * background + rng.normal(size=N)
|
|
53
53
|
earnings = 3.0 * training + 1.2 * background + rng.normal(size=N)
|
|
54
54
|
|
|
55
|
-
df
|
|
55
|
+
df = pd.DataFrame({
|
|
56
|
+
"background": background,
|
|
57
|
+
"training": training,
|
|
58
|
+
"earnings": earnings,
|
|
59
|
+
})
|
|
56
60
|
dag = DAG()
|
|
57
61
|
dag.assume("background").causes("training", "earnings")
|
|
58
62
|
dag.assume("training").causes("earnings")
|
|
@@ -85,7 +89,7 @@ But how confident are we in that estimate? And is it robust to estimation error?
|
|
|
85
89
|
Decision confidence : 100.0%
|
|
86
90
|
Robust to estimation error : Yes — decision is stable across 95% CI
|
|
87
91
|
|
|
88
|
-
|
|
92
|
+
Robustness
|
|
89
93
|
~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
90
94
|
|
|
91
95
|
``decide()`` uses expected value maximisation: it picks "treat" when
|
|
@@ -96,7 +100,7 @@ treats a certain $37 gain and a 50/50 gamble between $0 and $74 identically.
|
|
|
96
100
|
The ``robust`` flag is a first step toward robustness: it checks whether
|
|
97
101
|
the decision flips anywhere inside the 95% confidence interval. But it
|
|
98
102
|
only returns ``True`` or ``False``, and it is implicitly using the most
|
|
99
|
-
conservative possible standard (the
|
|
103
|
+
conservative possible standard (the confidence interval bounds).
|
|
100
104
|
|
|
101
105
|
For finer control, call ``to_outcomes()`` on the report and pass the result
|
|
102
106
|
to any rule in ``formative.game``:
|
|
@@ -118,8 +122,8 @@ to any rule in ``formative.game``:
|
|
|
118
122
|
|
|
119
123
|
By default, the three scenarios correspond to the 10th, 50th, and 90th
|
|
120
124
|
percentiles of the net-benefit sampling distribution (assumed normal with
|
|
121
|
-
the
|
|
122
|
-
every scenario
|
|
125
|
+
the standard error derived from the 95% confidence interval). The ``"don't treat"`` payoff is 0 in
|
|
126
|
+
every scenario.
|
|
123
127
|
|
|
124
128
|
You can supply your own scenario names and quantiles:
|
|
125
129
|
|
|
@@ -129,17 +133,19 @@ You can supply your own scenario names and quantiles:
|
|
|
129
133
|
scenarios={"bear": 0.05, "base": 0.50, "bull": 0.95}
|
|
130
134
|
)
|
|
131
135
|
|
|
132
|
-
|
|
133
|
-
``maximin
|
|
134
|
-
set to the
|
|
136
|
+
How does this differ from using ``robust``? ``robust=True`` is equivalent to
|
|
137
|
+
``maximin``, returning the same choice when the scenarios are
|
|
138
|
+
set to the confidence interval bounds (quantiles 0.025 and 0.975). ``to_outcomes()``
|
|
135
139
|
generalises that check: different rules express different risk attitudes, and
|
|
136
140
|
you can dial in your own pessimism level via ``hurwicz(alpha=...)``.
|
|
137
141
|
|
|
138
142
|
Should we treat each segment?
|
|
139
143
|
-----------------------------
|
|
140
144
|
|
|
141
|
-
An average effect can hide groups where the treatment is worthless
|
|
142
|
-
it pays for itself several times over.
|
|
145
|
+
An average effect can hide groups where the treatment is worthless, or where
|
|
146
|
+
it pays for itself several times over. It will also cause you to scale up a treatment
|
|
147
|
+
that is not worth it for everyone.
|
|
148
|
+
When an estimator is fitted with an
|
|
143
149
|
``effect_modifier`` (see :doc:`estimands`), ``decide_by_group(cost, benefit)``
|
|
144
150
|
runs the same cost-benefit analysis within each level of the modifier and
|
|
145
151
|
returns a mapping of level → :class:`~formative.DecisionReport`, so different
|
|
@@ -161,19 +167,33 @@ and everyone else not at all:
|
|
|
161
167
|
tenure = rng.choice(["<2y", "2-5y", ">5y"], size=N)
|
|
162
168
|
region = rng.choice(["north", "south"], size=N)
|
|
163
169
|
coaching = rng.integers(0, 2, size=N)
|
|
164
|
-
retention =
|
|
170
|
+
retention = (
|
|
171
|
+
6.0 * coaching * (tenure == "<2y")
|
|
172
|
+
+ 2.0 * (tenure == ">5y")
|
|
173
|
+
+ rng.normal(size=N)
|
|
174
|
+
)
|
|
165
175
|
|
|
166
|
-
df = pd.DataFrame({
|
|
176
|
+
df = pd.DataFrame({
|
|
177
|
+
"tenure": tenure,
|
|
178
|
+
"region": region,
|
|
179
|
+
"coaching": coaching,
|
|
180
|
+
"retention": retention,
|
|
181
|
+
})
|
|
167
182
|
|
|
168
183
|
dag = DAG()
|
|
169
184
|
dag.assume("coaching").causes("retention")
|
|
170
185
|
dag.assume("tenure").causes("retention")
|
|
171
186
|
dag.assume("region").causes("retention")
|
|
172
187
|
|
|
173
|
-
result = RCT(
|
|
188
|
+
result = RCT(
|
|
189
|
+
dag, treatment="coaching", outcome="retention", effect_modifier="tenure"
|
|
190
|
+
).fit(df)
|
|
174
191
|
for level, decision in result.decide_by_group(cost=2.5, benefit=1.0).items():
|
|
175
192
|
lo, hi = decision.net_benefit_ci
|
|
176
|
-
print(
|
|
193
|
+
print(
|
|
194
|
+
f"{level:>5}: {decision.optimal:<12} "
|
|
195
|
+
f"net benefit {decision.net_benefit:+.2f} [{lo:+.2f}, {hi:+.2f}]"
|
|
196
|
+
)
|
|
177
197
|
|
|
178
198
|
.. code-block:: text
|
|
179
199
|
|
|
@@ -203,10 +223,9 @@ one less thing to get wrong.
|
|
|
203
223
|
How it works
|
|
204
224
|
~~~~~~~~~~~~
|
|
205
225
|
|
|
206
|
-
``learn_policy()`` implements doubly robust policy learning
|
|
207
|
-
Athey & Wager (2021):
|
|
226
|
+
``learn_policy()`` implements doubly robust policy learning:
|
|
208
227
|
|
|
209
|
-
1. **Score every unit.** Each unit gets a cross-fitted
|
|
228
|
+
1. **Score every unit.** Each unit gets a cross-fitted score — an
|
|
210
229
|
unbiased estimate of its individual treatment effect. Outcome models are
|
|
211
230
|
fit by OLS on the candidate features over four folds and evaluated on the
|
|
212
231
|
fifth, so no unit is scored by a model that saw it.
|
|
@@ -225,7 +244,7 @@ Athey & Wager (2021):
|
|
|
225
244
|
|
|
226
245
|
Because the tree may split on any level of any candidate feature, features
|
|
227
246
|
must be discrete: bin continuous columns before passing them in. Candidate
|
|
228
|
-
features face the same DAG validation as effect modifiers
|
|
247
|
+
features face the same DAG validation as effect modifiers; each must cause
|
|
229
248
|
the outcome and must not be a descendant of the treatment (targeting on a
|
|
230
249
|
mediator is not actionable at assignment time).
|
|
231
250
|
|
|
@@ -238,7 +257,13 @@ features instead of choosing a segmentation ourselves:
|
|
|
238
257
|
.. code-block:: python
|
|
239
258
|
|
|
240
259
|
result = RCT(dag, treatment="coaching", outcome="retention").fit(df)
|
|
241
|
-
policy = result.learn_policy(
|
|
260
|
+
policy = result.learn_policy(
|
|
261
|
+
df,
|
|
262
|
+
modifiers=["tenure", "region"],
|
|
263
|
+
cost=2.5,
|
|
264
|
+
benefit=1.0,
|
|
265
|
+
max_depth=2
|
|
266
|
+
)
|
|
242
267
|
print(policy.summary())
|
|
243
268
|
|
|
244
269
|
.. code-block:: text
|
|
@@ -286,10 +311,17 @@ it only helps recent hires in the north:
|
|
|
286
311
|
coaching = rng.integers(0, 2, size=N)
|
|
287
312
|
helped = (tenure == "<2y") & (region == "north")
|
|
288
313
|
retention = 7.0 * coaching * helped + 2.0 * (tenure == ">5y") + rng.normal(size=N)
|
|
289
|
-
df = pd.DataFrame({
|
|
314
|
+
df = pd.DataFrame({
|
|
315
|
+
"tenure": tenure,
|
|
316
|
+
"region": region,
|
|
317
|
+
"coaching": coaching,
|
|
318
|
+
"retention": retention,
|
|
319
|
+
})
|
|
290
320
|
|
|
291
321
|
result = RCT(dag, treatment="coaching", outcome="retention").fit(df)
|
|
292
|
-
policy = result.learn_policy(
|
|
322
|
+
policy = result.learn_policy(
|
|
323
|
+
df, modifiers=["tenure", "region"], cost=2.5, benefit=1.0, max_depth=2
|
|
324
|
+
)
|
|
293
325
|
print(policy.summary())
|
|
294
326
|
|
|
295
327
|
.. code-block:: text
|
|
@@ -316,7 +348,9 @@ back to treating all recent hires and burns money on the southern half:
|
|
|
316
348
|
|
|
317
349
|
.. code-block:: python
|
|
318
350
|
|
|
319
|
-
result.learn_policy(
|
|
351
|
+
result.learn_policy(
|
|
352
|
+
df, modifiers=["tenure", "region"], cost=2.5, benefit=1.0, max_depth=1
|
|
353
|
+
).value
|
|
320
354
|
# +0.34 per unit — versus +0.75 at depth 2
|
|
321
355
|
|
|
322
356
|
When the deeper policy's value is clearly higher (as here), ship it; when the
|
|
@@ -339,7 +373,12 @@ segment-2 employees in the north:
|
|
|
339
373
|
training = rng.integers(0, 2, size=N)
|
|
340
374
|
effect = 4.0 * (segment == 0) + 5.0 * ((segment == 2) & (region == "north"))
|
|
341
375
|
earnings = effect * training + 0.5 * segment + rng.normal(size=N)
|
|
342
|
-
df = pd.DataFrame({
|
|
376
|
+
df = pd.DataFrame({
|
|
377
|
+
"segment": segment,
|
|
378
|
+
"region": region,
|
|
379
|
+
"training": training,
|
|
380
|
+
"earnings": earnings,
|
|
381
|
+
})
|
|
343
382
|
|
|
344
383
|
dag = DAG()
|
|
345
384
|
dag.assume("training").causes("earnings")
|
|
@@ -347,7 +386,13 @@ segment-2 employees in the north:
|
|
|
347
386
|
dag.assume("region").causes("earnings")
|
|
348
387
|
|
|
349
388
|
result = RCT(dag, treatment="training", outcome="earnings").fit(df)
|
|
350
|
-
policy = result.learn_policy(
|
|
389
|
+
policy = result.learn_policy(
|
|
390
|
+
df,
|
|
391
|
+
modifiers=["segment", "region"],
|
|
392
|
+
cost=1.0,
|
|
393
|
+
benefit=1.0,
|
|
394
|
+
max_depth=2
|
|
395
|
+
)
|
|
351
396
|
print(policy.summary())
|
|
352
397
|
|
|
353
398
|
.. code-block:: text
|
|
@@ -49,7 +49,7 @@ you choose.
|
|
|
49
49
|
|
|
50
50
|
After you have obtained the result object, you can print a summary of the estimate and
|
|
51
51
|
its assumptions. The assumptions are marked as testable or untestable depending on whether
|
|
52
|
-
formative can check them. Most assumptions in causal inference are untestable by nature
|
|
52
|
+
formative can check them. Most assumptions in causal inference are untestable by nature, i.e.
|
|
53
53
|
things you must argue for based on domain knowledge and theory.
|
|
54
54
|
|
|
55
55
|
.. code-block:: python
|
|
@@ -83,7 +83,7 @@ communicating with non-technical audiences.
|
|
|
83
83
|
**3. Refute**
|
|
84
84
|
|
|
85
85
|
Once you have a result, run statistical checks that probe whether its assumptions hold
|
|
86
|
-
in the data. Each check returns a clear pass or fail
|
|
86
|
+
in the data. Each check returns a clear pass or fail, but no set of tests can guarantee
|
|
87
87
|
validity. Use them as diagnostics, not proof. Causal inference is a judgment call, not a
|
|
88
88
|
mathematical certainty.
|
|
89
89
|
|
|
@@ -104,10 +104,10 @@ mathematical certainty.
|
|
|
104
104
|
|
|
105
105
|
**4. Decide**
|
|
106
106
|
|
|
107
|
-
An estimate is rarely the end goal
|
|
107
|
+
An estimate is rarely the end goal; the question is whether to act on it. Pass the
|
|
108
108
|
cost per unit of treatment applied and the value of one unit of improvement in the
|
|
109
109
|
outcome, and formative turns the estimate and its uncertainty into a treat/don't-treat
|
|
110
|
-
|
|
110
|
+
decision. Suppose a unit of education costs $8 and a unit of income is worth $15:
|
|
111
111
|
|
|
112
112
|
.. code-block:: python
|
|
113
113
|
|
|
@@ -128,7 +128,12 @@ call. Suppose a unit of education costs $8 and a unit of income is worth $15:
|
|
|
128
128
|
Decision confidence : 100.0%
|
|
129
129
|
Robust to estimation error : Yes — decision is stable across 95% CI
|
|
130
130
|
|
|
131
|
-
|
|
131
|
+
You may ask why a cost must be passed; is it not enough if the estimate is positive?
|
|
132
|
+
The answer is that a positive estimate does not guarantee that the treatment is worth it.
|
|
133
|
+
If the estimate gives an effect of +1.0, but the treatment costs $10 and the outcome is worth $1,
|
|
134
|
+
then the treatment is not worth it. The decision layer makes this explicit.
|
|
135
|
+
|
|
136
|
+
Depending on the estimator you choose, decisions can go beyond a single call for everyone:
|
|
132
137
|
per-group decisions via an effect modifier, or a learned treatment rule that
|
|
133
138
|
decides *whom* to treat. See :doc:`decisions` for the full decision layer.
|
|
134
139
|
|
|
@@ -34,7 +34,7 @@ class DAG:
|
|
|
34
34
|
your data.
|
|
35
35
|
|
|
36
36
|
Build the graph by calling ``assume().causes()`` for each causal relationship
|
|
37
|
-
you believe holds. Include latent variables
|
|
37
|
+
you believe holds. Include latent variables — if a node is not in
|
|
38
38
|
your data at ``fit()`` time, the estimator treats it as unobserved.
|
|
39
39
|
|
|
40
40
|
Example::
|
|
@@ -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.4"
|
|
8
8
|
description = "A Python library for quantitative reasoning."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -12,7 +12,7 @@ license = {text = "MIT"}
|
|
|
12
12
|
authors = [{name = "Max Pagels"}]
|
|
13
13
|
keywords = ["causal inference", "causal estimation", "econometrics", "statistics", "DAG"]
|
|
14
14
|
classifiers = [
|
|
15
|
-
"Development Status ::
|
|
15
|
+
"Development Status :: 5 - Production/Stable",
|
|
16
16
|
"Intended Audience :: Science/Research",
|
|
17
17
|
"Topic :: Scientific/Engineering :: Mathematics",
|
|
18
18
|
"License :: OSI Approved :: MIT License",
|
|
@@ -48,7 +48,7 @@ docs = ["sphinx>=7,<10", "sphinx-rtd-theme>=2,<4", "sphinx-copybutton>=0.5,<0.6"
|
|
|
48
48
|
packages = ["formative"]
|
|
49
49
|
|
|
50
50
|
[tool.bumpversion]
|
|
51
|
-
current_version = "2.2.
|
|
51
|
+
current_version = "2.2.4"
|
|
52
52
|
commit = true
|
|
53
53
|
tag = true
|
|
54
54
|
tag_name = "v{new_version}"
|
|
@@ -118,7 +118,9 @@ treatment/outcome relationship. The observational DAG declares this:
|
|
|
118
118
|
obs_dag.assume(c).causes("treat", "re78")
|
|
119
119
|
obs_dag.assume("treat").causes("re78")
|
|
120
120
|
|
|
121
|
-
result = OLSObservational(
|
|
121
|
+
result = OLSObservational(
|
|
122
|
+
obs_dag, treatment="treat", outcome="re78"
|
|
123
|
+
).fit(observational)
|
|
122
124
|
print(result.summary())
|
|
123
125
|
|
|
124
126
|
.. code-block:: text
|
|
@@ -156,7 +158,9 @@ each treated person only with the most similar controls:
|
|
|
156
158
|
|
|
157
159
|
from formative.causal import PropensityScoreMatching
|
|
158
160
|
|
|
159
|
-
result = PropensityScoreMatching(
|
|
161
|
+
result = PropensityScoreMatching(
|
|
162
|
+
obs_dag, treatment="treat", outcome="re78"
|
|
163
|
+
).fit(observational)
|
|
160
164
|
print(result.summary())
|
|
161
165
|
|
|
162
166
|
.. code-block:: text
|
|
@@ -293,7 +297,10 @@ Table 3 reports four cell means; formative's DiD will be built from exactly thes
|
|
|
293
297
|
|
|
294
298
|
for state, name in [(1, "NJ"), (0, "PA")]:
|
|
295
299
|
sub = raw[raw.STATE == state]
|
|
296
|
-
print(
|
|
300
|
+
print(
|
|
301
|
+
f"{name}: before {sub.fte_before.mean():.2f}, "
|
|
302
|
+
f"after {sub.fte_after.mean():.2f}"
|
|
303
|
+
)
|
|
297
304
|
|
|
298
305
|
.. code-block:: text
|
|
299
306
|
|
|
@@ -317,8 +324,18 @@ binary group and time indicators:
|
|
|
317
324
|
|
|
318
325
|
long = pd.concat(
|
|
319
326
|
[
|
|
320
|
-
pd.DataFrame({
|
|
321
|
-
|
|
327
|
+
pd.DataFrame({
|
|
328
|
+
"store": raw.SHEET,
|
|
329
|
+
"nj": raw.STATE,
|
|
330
|
+
"after": 0,
|
|
331
|
+
"fte": raw.fte_before,
|
|
332
|
+
}),
|
|
333
|
+
pd.DataFrame({
|
|
334
|
+
"store": raw.SHEET,
|
|
335
|
+
"nj": raw.STATE,
|
|
336
|
+
"after": 1,
|
|
337
|
+
"fte": raw.fte_after,
|
|
338
|
+
}),
|
|
322
339
|
],
|
|
323
340
|
ignore_index=True,
|
|
324
341
|
).dropna(subset=["fte"])
|
|
@@ -52,7 +52,11 @@ earnings using OLS with a DAG that encodes family background as a confounder:
|
|
|
52
52
|
training = 0.6 * background + rng.normal(size=N)
|
|
53
53
|
earnings = 3.0 * training + 1.2 * background + rng.normal(size=N)
|
|
54
54
|
|
|
55
|
-
df
|
|
55
|
+
df = pd.DataFrame({
|
|
56
|
+
"background": background,
|
|
57
|
+
"training": training,
|
|
58
|
+
"earnings": earnings,
|
|
59
|
+
})
|
|
56
60
|
dag = DAG()
|
|
57
61
|
dag.assume("background").causes("training", "earnings")
|
|
58
62
|
dag.assume("training").causes("earnings")
|
|
@@ -85,7 +89,7 @@ But how confident are we in that estimate? And is it robust to estimation error?
|
|
|
85
89
|
Decision confidence : 100.0%
|
|
86
90
|
Robust to estimation error : Yes — decision is stable across 95% CI
|
|
87
91
|
|
|
88
|
-
|
|
92
|
+
Robustness
|
|
89
93
|
~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
90
94
|
|
|
91
95
|
``decide()`` uses expected value maximisation: it picks "treat" when
|
|
@@ -96,7 +100,7 @@ treats a certain $37 gain and a 50/50 gamble between $0 and $74 identically.
|
|
|
96
100
|
The ``robust`` flag is a first step toward robustness: it checks whether
|
|
97
101
|
the decision flips anywhere inside the 95% confidence interval. But it
|
|
98
102
|
only returns ``True`` or ``False``, and it is implicitly using the most
|
|
99
|
-
conservative possible standard (the
|
|
103
|
+
conservative possible standard (the confidence interval bounds).
|
|
100
104
|
|
|
101
105
|
For finer control, call ``to_outcomes()`` on the report and pass the result
|
|
102
106
|
to any rule in ``formative.game``:
|
|
@@ -118,8 +122,8 @@ to any rule in ``formative.game``:
|
|
|
118
122
|
|
|
119
123
|
By default, the three scenarios correspond to the 10th, 50th, and 90th
|
|
120
124
|
percentiles of the net-benefit sampling distribution (assumed normal with
|
|
121
|
-
the
|
|
122
|
-
every scenario
|
|
125
|
+
the standard error derived from the 95% confidence interval). The ``"don't treat"`` payoff is 0 in
|
|
126
|
+
every scenario.
|
|
123
127
|
|
|
124
128
|
You can supply your own scenario names and quantiles:
|
|
125
129
|
|
|
@@ -129,17 +133,19 @@ You can supply your own scenario names and quantiles:
|
|
|
129
133
|
scenarios={"bear": 0.05, "base": 0.50, "bull": 0.95}
|
|
130
134
|
)
|
|
131
135
|
|
|
132
|
-
|
|
133
|
-
``maximin
|
|
134
|
-
set to the
|
|
136
|
+
How does this differ from using ``robust``? ``robust=True`` is equivalent to
|
|
137
|
+
``maximin``, returning the same choice when the scenarios are
|
|
138
|
+
set to the confidence interval bounds (quantiles 0.025 and 0.975). ``to_outcomes()``
|
|
135
139
|
generalises that check: different rules express different risk attitudes, and
|
|
136
140
|
you can dial in your own pessimism level via ``hurwicz(alpha=...)``.
|
|
137
141
|
|
|
138
142
|
Should we treat each segment?
|
|
139
143
|
-----------------------------
|
|
140
144
|
|
|
141
|
-
An average effect can hide groups where the treatment is worthless
|
|
142
|
-
it pays for itself several times over.
|
|
145
|
+
An average effect can hide groups where the treatment is worthless, or where
|
|
146
|
+
it pays for itself several times over. It will also cause you to scale up a treatment
|
|
147
|
+
that is not worth it for everyone.
|
|
148
|
+
When an estimator is fitted with an
|
|
143
149
|
``effect_modifier`` (see :doc:`estimands`), ``decide_by_group(cost, benefit)``
|
|
144
150
|
runs the same cost-benefit analysis within each level of the modifier and
|
|
145
151
|
returns a mapping of level → :class:`~formative.DecisionReport`, so different
|
|
@@ -161,19 +167,33 @@ and everyone else not at all:
|
|
|
161
167
|
tenure = rng.choice(["<2y", "2-5y", ">5y"], size=N)
|
|
162
168
|
region = rng.choice(["north", "south"], size=N)
|
|
163
169
|
coaching = rng.integers(0, 2, size=N)
|
|
164
|
-
retention =
|
|
170
|
+
retention = (
|
|
171
|
+
6.0 * coaching * (tenure == "<2y")
|
|
172
|
+
+ 2.0 * (tenure == ">5y")
|
|
173
|
+
+ rng.normal(size=N)
|
|
174
|
+
)
|
|
165
175
|
|
|
166
|
-
df = pd.DataFrame({
|
|
176
|
+
df = pd.DataFrame({
|
|
177
|
+
"tenure": tenure,
|
|
178
|
+
"region": region,
|
|
179
|
+
"coaching": coaching,
|
|
180
|
+
"retention": retention,
|
|
181
|
+
})
|
|
167
182
|
|
|
168
183
|
dag = DAG()
|
|
169
184
|
dag.assume("coaching").causes("retention")
|
|
170
185
|
dag.assume("tenure").causes("retention")
|
|
171
186
|
dag.assume("region").causes("retention")
|
|
172
187
|
|
|
173
|
-
result = RCT(
|
|
188
|
+
result = RCT(
|
|
189
|
+
dag, treatment="coaching", outcome="retention", effect_modifier="tenure"
|
|
190
|
+
).fit(df)
|
|
174
191
|
for level, decision in result.decide_by_group(cost=2.5, benefit=1.0).items():
|
|
175
192
|
lo, hi = decision.net_benefit_ci
|
|
176
|
-
print(
|
|
193
|
+
print(
|
|
194
|
+
f"{level:>5}: {decision.optimal:<12} "
|
|
195
|
+
f"net benefit {decision.net_benefit:+.2f} [{lo:+.2f}, {hi:+.2f}]"
|
|
196
|
+
)
|
|
177
197
|
|
|
178
198
|
.. code-block:: text
|
|
179
199
|
|
|
@@ -203,10 +223,9 @@ one less thing to get wrong.
|
|
|
203
223
|
How it works
|
|
204
224
|
~~~~~~~~~~~~
|
|
205
225
|
|
|
206
|
-
``learn_policy()`` implements doubly robust policy learning
|
|
207
|
-
Athey & Wager (2021):
|
|
226
|
+
``learn_policy()`` implements doubly robust policy learning:
|
|
208
227
|
|
|
209
|
-
1. **Score every unit.** Each unit gets a cross-fitted
|
|
228
|
+
1. **Score every unit.** Each unit gets a cross-fitted score — an
|
|
210
229
|
unbiased estimate of its individual treatment effect. Outcome models are
|
|
211
230
|
fit by OLS on the candidate features over four folds and evaluated on the
|
|
212
231
|
fifth, so no unit is scored by a model that saw it.
|
|
@@ -225,7 +244,7 @@ Athey & Wager (2021):
|
|
|
225
244
|
|
|
226
245
|
Because the tree may split on any level of any candidate feature, features
|
|
227
246
|
must be discrete: bin continuous columns before passing them in. Candidate
|
|
228
|
-
features face the same DAG validation as effect modifiers
|
|
247
|
+
features face the same DAG validation as effect modifiers; each must cause
|
|
229
248
|
the outcome and must not be a descendant of the treatment (targeting on a
|
|
230
249
|
mediator is not actionable at assignment time).
|
|
231
250
|
|
|
@@ -238,7 +257,13 @@ features instead of choosing a segmentation ourselves:
|
|
|
238
257
|
.. code-block:: python
|
|
239
258
|
|
|
240
259
|
result = RCT(dag, treatment="coaching", outcome="retention").fit(df)
|
|
241
|
-
policy = result.learn_policy(
|
|
260
|
+
policy = result.learn_policy(
|
|
261
|
+
df,
|
|
262
|
+
modifiers=["tenure", "region"],
|
|
263
|
+
cost=2.5,
|
|
264
|
+
benefit=1.0,
|
|
265
|
+
max_depth=2
|
|
266
|
+
)
|
|
242
267
|
print(policy.summary())
|
|
243
268
|
|
|
244
269
|
.. code-block:: text
|
|
@@ -286,10 +311,17 @@ it only helps recent hires in the north:
|
|
|
286
311
|
coaching = rng.integers(0, 2, size=N)
|
|
287
312
|
helped = (tenure == "<2y") & (region == "north")
|
|
288
313
|
retention = 7.0 * coaching * helped + 2.0 * (tenure == ">5y") + rng.normal(size=N)
|
|
289
|
-
df = pd.DataFrame({
|
|
314
|
+
df = pd.DataFrame({
|
|
315
|
+
"tenure": tenure,
|
|
316
|
+
"region": region,
|
|
317
|
+
"coaching": coaching,
|
|
318
|
+
"retention": retention,
|
|
319
|
+
})
|
|
290
320
|
|
|
291
321
|
result = RCT(dag, treatment="coaching", outcome="retention").fit(df)
|
|
292
|
-
policy = result.learn_policy(
|
|
322
|
+
policy = result.learn_policy(
|
|
323
|
+
df, modifiers=["tenure", "region"], cost=2.5, benefit=1.0, max_depth=2
|
|
324
|
+
)
|
|
293
325
|
print(policy.summary())
|
|
294
326
|
|
|
295
327
|
.. code-block:: text
|
|
@@ -316,7 +348,9 @@ back to treating all recent hires and burns money on the southern half:
|
|
|
316
348
|
|
|
317
349
|
.. code-block:: python
|
|
318
350
|
|
|
319
|
-
result.learn_policy(
|
|
351
|
+
result.learn_policy(
|
|
352
|
+
df, modifiers=["tenure", "region"], cost=2.5, benefit=1.0, max_depth=1
|
|
353
|
+
).value
|
|
320
354
|
# +0.34 per unit — versus +0.75 at depth 2
|
|
321
355
|
|
|
322
356
|
When the deeper policy's value is clearly higher (as here), ship it; when the
|
|
@@ -339,7 +373,12 @@ segment-2 employees in the north:
|
|
|
339
373
|
training = rng.integers(0, 2, size=N)
|
|
340
374
|
effect = 4.0 * (segment == 0) + 5.0 * ((segment == 2) & (region == "north"))
|
|
341
375
|
earnings = effect * training + 0.5 * segment + rng.normal(size=N)
|
|
342
|
-
df = pd.DataFrame({
|
|
376
|
+
df = pd.DataFrame({
|
|
377
|
+
"segment": segment,
|
|
378
|
+
"region": region,
|
|
379
|
+
"training": training,
|
|
380
|
+
"earnings": earnings,
|
|
381
|
+
})
|
|
343
382
|
|
|
344
383
|
dag = DAG()
|
|
345
384
|
dag.assume("training").causes("earnings")
|
|
@@ -347,7 +386,13 @@ segment-2 employees in the north:
|
|
|
347
386
|
dag.assume("region").causes("earnings")
|
|
348
387
|
|
|
349
388
|
result = RCT(dag, treatment="training", outcome="earnings").fit(df)
|
|
350
|
-
policy = result.learn_policy(
|
|
389
|
+
policy = result.learn_policy(
|
|
390
|
+
df,
|
|
391
|
+
modifiers=["segment", "region"],
|
|
392
|
+
cost=1.0,
|
|
393
|
+
benefit=1.0,
|
|
394
|
+
max_depth=2
|
|
395
|
+
)
|
|
351
396
|
print(policy.summary())
|
|
352
397
|
|
|
353
398
|
.. code-block:: text
|