ergodic 0.3.0__tar.gz → 0.3.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ergodic-0.3.0 → ergodic-0.3.2}/CHANGELOG.md +66 -1
- {ergodic-0.3.0 → ergodic-0.3.2}/PKG-INFO +2 -2
- {ergodic-0.3.0 → ergodic-0.3.2}/README.md +1 -1
- {ergodic-0.3.0 → ergodic-0.3.2}/docs/case-studies/01-birthweight.py +8 -8
- {ergodic-0.3.0 → ergodic-0.3.2}/docs/case-studies/03-pension401k.py +1 -1
- {ergodic-0.3.0 → ergodic-0.3.2}/docs/case-studies/06-clv.py +5 -4
- {ergodic-0.3.0 → ergodic-0.3.2}/docs/case-studies/07-churn.py +7 -3
- {ergodic-0.3.0 → ergodic-0.3.2}/docs/learn/09-process-intelligence.py +1 -1
- {ergodic-0.3.0 → ergodic-0.3.2}/docsite/pages/assumptions.md +3 -3
- {ergodic-0.3.0 → ergodic-0.3.2}/docsite/pages/choose-a-method.md +21 -2
- {ergodic-0.3.0 → ergodic-0.3.2}/docsite/pages/discover-a-graph.md +14 -0
- ergodic-0.3.2/docsite/pages/estimate-average-effect.md +168 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/docsite/pages/evaluate-a-graph.md +9 -2
- {ergodic-0.3.0 → ergodic-0.3.2}/docsite/pages/heterogeneous-effects.md +14 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/docsite/pages/install.md +1 -1
- {ergodic-0.3.0 → ergodic-0.3.2}/docsite/pages/refute-an-estimate.md +9 -9
- {ergodic-0.3.0 → ergodic-0.3.2}/docsite/pages/start.md +1 -1
- {ergodic-0.3.0 → ergodic-0.3.2}/docsite/pages/test-a-graph.md +20 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/docsite/pages/time-series-discovery.md +6 -6
- {ergodic-0.3.0 → ergodic-0.3.2}/docsite/site.toml +1 -1
- ergodic-0.3.2/scripts/audits/qualify_aipw.py +103 -0
- ergodic-0.3.2/scripts/audits/qualify_ci_discovery.py +49 -0
- ergodic-0.3.2/scripts/audits/qualify_gcm.py +51 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/__init__.py +1 -1
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/discovery/_posterior.py +41 -3
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/discovery/_stats.py +9 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/discovery/algorithms/_mmpc.py +13 -9
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/discovery/ci/_gcm.py +55 -5
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/discovery/ci/_linear.py +5 -3
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/inference/_crossfit.py +2 -2
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/inference/_estimate.py +6 -4
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/inference/_estimator.py +42 -14
- ergodic-0.3.2/src/ergodic/inference/_overlap.py +90 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/inference/_perunit.py +4 -6
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/inference/_registry.py +13 -9
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/inference/_result.py +7 -1
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/inference/_scores.py +18 -1
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/inference/_select.py +6 -6
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/inference/_target.py +4 -1
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/inference/estimators/_dml.py +3 -0
- ergodic-0.3.2/src/ergodic/inference/estimators/_doubly_robust.py +292 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/inference/estimators/_iv.py +116 -7
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/inference/estimators/_regression.py +2 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/inference/estimators/_weighting.py +56 -16
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/inference/explain/_record.py +16 -8
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_bayes.py +34 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_cate.py +4 -3
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_discovery_ci.py +109 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_discovery_graphtest.py +10 -4
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_discovery_mmpc.py +29 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_estimators_e2.py +2 -2
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_graph_posterior.py +43 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_inference.py +1 -1
- ergodic-0.3.2/tests/test_inference_guardrails.py +205 -0
- ergodic-0.3.2/tests/test_release_cate_regressions.py +194 -0
- ergodic-0.3.0/docsite/pages/estimate-average-effect.md +0 -100
- ergodic-0.3.0/src/ergodic/inference/estimators/_doubly_robust.py +0 -152
- {ergodic-0.3.0 → ergodic-0.3.2}/.gitignore +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/CONTRIBUTING.md +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/LICENSE +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/NOTICE +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/SECURITY.md +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/docs/case-studies/02-berkeley.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/docs/case-studies/04-synthetic-control.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/docs/case-studies/05-discovery.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/docs/learn/01-why-causality.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/docs/learn/02-good-and-bad-controls.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/docs/learn/03-estimating-effects.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/docs/learn/04-heterogeneous-effects.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/docs/learn/05-hidden-confounding.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/docs/learn/06-discovering-structure.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/docs/learn/07-quasi-experiments.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/docs/learn/08-process-mining.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/docs/learn/10-decision-points.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/docs/learn/11-forecasting.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/docs/learn/12-structural-causal-models.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/docs/learn/13-hidden-regimes.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/docs/learn/14-causal-forests-and-policy-trees.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/docsite/assets/docs.css +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/docsite/assets/docs.js +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/docsite/assets/ergodic-mark-dark.svg +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/docsite/assets/ergodic-mark-light.svg +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/docsite/assets/fonts/OFL-Geist-Mono.txt +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/docsite/assets/fonts/OFL-Geist.txt +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/docsite/assets/fonts/geist-latin-ext-wght-normal.woff2 +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/docsite/assets/fonts/geist-latin-wght-normal.woff2 +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/docsite/assets/fonts/geist-mono-latin-ext-wght-normal.woff2 +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/docsite/assets/fonts/geist-mono-latin-wght-normal.woff2 +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/docsite/assets/site.css +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/docsite/assets/tokens.css +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/docsite/pages/adjustment-sets.md +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/docsite/pages/backtest-a-forecast.md +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/docsite/pages/bootstrap-stability.md +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/docsite/pages/certify-and-calibrate.md +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/docsite/pages/changelog.md +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/docsite/pages/compatibility.md +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/docsite/pages/conformance-and-decisions.md +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/docsite/pages/decode-a-graph.md +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/docsite/pages/difference-in-differences.md +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/docsite/pages/domain-knowledge.md +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/docsite/pages/edge-probabilities.md +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/docsite/pages/fit-an-scm.md +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/docsite/pages/forecast-with-drivers.md +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/docsite/pages/forecast-with-welt.md +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/docsite/pages/glossary.md +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/docsite/pages/graphs-and-marks.md +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/docsite/pages/identify-an-effect.md +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/docsite/pages/interventions-and-counterfactuals.md +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/docsite/pages/map-a-process.md +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/docsite/pages/mediation.md +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/docsite/pages/reconcile-a-hierarchy.md +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/docsite/pages/recover-hidden-regimes.md +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/docsite/pages/run-scenarios.md +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/docsite/pages/synthetic-control.md +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/docsite/pages/target-a-policy.md +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/docsite/pages/welt.md +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/docsite/provenance.json +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/examples/README.md +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/examples/adjustment_birthweight.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/examples/cate/01_cate_basics.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/examples/cate/02_meta_learners_compared.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/examples/cate/03_is_there_heterogeneity.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/examples/cate/04_where_is_the_effect_higher.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/examples/cate/05_targeting_with_rate.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/examples/cate/06_per_unit_uncertainty.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/examples/cate/07_cate_vs_ite.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/examples/cate/08_identification_gating.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/examples/cate/09_case_study_uplift_end_to_end.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/examples/cate/10_causal_forest.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/examples/cate/11_policy_trees.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/examples/cate/12_model_selection.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/examples/cate/13_case_study_phase2.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/examples/cate/README.md +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/examples/confounding_berkeley.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/examples/discovery_asia.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/examples/double_ml_pension.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/examples/hidden_regimes.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/examples/latent_switch.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/examples/scm/01_three_rungs.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/examples/scm/02_honesty_and_tipping_point.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/examples/scm/03_causal_price_optimization.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/examples/scm/04_counterfactual_recourse.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/examples/scm/05_counterfactual_fairness.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/examples/scm/06_causal_attribution.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/examples/scm/07_but_for_accounting.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/examples/scm/08_off_policy_evaluation.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/examples/scm/09_root_cause_attribution.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/examples/scm/10_frontdoor_unmeasured_confounder.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/examples/scm/README.md +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/examples/synthetic_control_smoking.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/public/ergodic_logomark_dark.svg +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/public/ergodic_logomark_light.svg +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/pyproject.toml +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/scripts/docsite/__init__.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/scripts/docsite/__main__.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/scripts/docsite/build.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/scripts/docsite/config.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/scripts/docsite/examples.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/scripts/docsite/graphite.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/scripts/docsite/links.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/scripts/docsite/notebooks.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/scripts/docsite/reference.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/data/__init__.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/data/_dataset.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/data/_eventlog.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/data/_frame.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/data/_hierarchy.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/data/_hts.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/data/_panel.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/data/_schema.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/data/_tabular.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/data/_timeseries.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/datasets/__init__.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/datasets/_bayesnet.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/datasets/_bunch.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/datasets/_bundled.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/datasets/_tables.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/datasets/data/birthweight.csv +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/datasets/data/germany.csv +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/datasets/data/pension401k.csv +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/datasets/data/smoking.csv +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/discovery/__init__.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/discovery/_average.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/discovery/_bootstrap.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/discovery/_ci.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/discovery/_decode.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/discovery/_discover.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/discovery/_encode.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/discovery/_evaluate.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/discovery/_exact.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/discovery/_granger.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/discovery/_graphtest.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/discovery/_lagged.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/discovery/_mcmc.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/discovery/_probabilities.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/discovery/_registry.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/discovery/_result.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/discovery/_score.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/discovery/_target.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/discovery/_tsresult.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/discovery/algorithms/__init__.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/discovery/algorithms/_astar.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/discovery/algorithms/_boss.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/discovery/algorithms/_fci.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/discovery/algorithms/_ges.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/discovery/algorithms/_glasso.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/discovery/algorithms/_grasp.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/discovery/algorithms/_hc.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/discovery/algorithms/_lingam.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/discovery/algorithms/_pc.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/discovery/algorithms/_pcmci.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/discovery/algorithms/_rfci.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/discovery/algorithms/_skeleton.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/discovery/ci/__init__.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/discovery/ci/_discrete.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/discovery/ci/_mixed.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/discovery/scores/__init__.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/discovery/scores/_bdeu.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/discovery/scores/_bge.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/discovery/scores/_bic.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/discovery/scores/_discrete_bic.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/discovery/scores/_mixed_bic.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/forecast/__init__.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/forecast/_attribution.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/forecast/_backtest.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/forecast/_baselines.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/forecast/_facade.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/forecast/_fit.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/forecast/_mechanism.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/forecast/_metrics.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/forecast/_protocol.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/forecast/_quantiles.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/forecast/_reconcile.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/forecast/_result.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/forecast/_scalers.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/forecast/_schedule.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/forecast/_staged.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/forecast/_structure.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/forecast/mechanisms/__init__.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/forecast/mechanisms/_bass.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/forecast/mechanisms/_ou.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/forecast/mechanisms/_structural.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/graph/__init__.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/graph/_graph.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/graph/_io.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/graph/_marks.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/graph/_meek.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/graph/_pag.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/graph/_primitives.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/graph/_separation.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/graph/_surgery.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/identification/__init__.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/identification/_criteria.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/identification/_distribution.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/identification/_evaluate.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/identification/_functional.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/identification/_id.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/identification/_ida.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/identification/_mediation.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/identification/_recognize.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/identification/_record.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/inference/__init__.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/inference/_design.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/inference/_learner.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/inference/_refute.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/inference/_stats.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/inference/estimators/__init__.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/inference/estimators/_did.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/inference/estimators/_forest.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/inference/estimators/_frontdoor.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/inference/estimators/_its.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/inference/estimators/_mediation.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/inference/estimators/_meta.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/inference/estimators/_null.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/inference/estimators/_plugin.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/inference/estimators/_synthetic_control.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/inference/explain/__init__.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/inference/explain/_blp.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/inference/explain/_calibration.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/inference/explain/_clan.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/inference/explain/_gates.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/inference/explain/_linalg.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/inference/explain/_policy.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/inference/explain/_rate.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/inference/learners/__init__.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/inference/learners/_linear.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/inference/learners/_neighbors.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/inference/learners/_pymc.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/knowledge.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/latent/__init__.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/latent/_engine.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/latent/_facade.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/latent/_identify.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/latent/_likelihood.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/latent/_marginalize.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/latent/_result.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/latent/likelihoods/__init__.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/latent/likelihoods/_ar.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/latent/likelihoods/_spectral.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/process/__init__.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/process/_case_table.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/process/_cases.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/process/_conformance.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/process/_decisions.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/process/_filters.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/process/_map.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/process/_mine.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/process/_net.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/process/_panel.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/process/_performance.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/process/_soundness.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/process/_tree.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/process/_variants.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/py.typed +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/scm/__init__.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/scm/_bounds.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/scm/_compose.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/scm/_district.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/scm/_mechanism.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/scm/_noise.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/scm/_result.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/scm/_sampling.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/scm/_scm.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/welt/__init__.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/welt/_client.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/welt/_data.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/welt/_discovery.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/welt/_forecast.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/src/ergodic/welt/_learner.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/conftest.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/data/exp0626_decoder_parity.json +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_average_effect.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_case_table.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_cate_forest.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_cate_phase2_stress.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_cate_policy.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_cate_select.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_cate_stress.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_conformance.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_data.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_datasets.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_decisions.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_decode.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_decode_knowledge.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_did.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_discovery.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_discovery_bootstrap.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_discovery_boss.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_discovery_fci.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_discovery_ges.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_discovery_glasso.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_discovery_grasp.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_discovery_hc.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_discovery_lingam.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_discovery_pc.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_discovery_pcmci.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_discovery_rfci.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_discovery_score.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_docs_welt_forecast_page.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_docs_welt_page.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_docsite.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_evaluate_graph.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_forecast_backtest.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_forecast_backtest_mcmc.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_forecast_core.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_forecast_explain.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_forecast_facade.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_forecast_mechanisms.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_forecast_metrics.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_forecast_quantiles.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_forecast_reconcile.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_forecast_staged.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_forecast_structure.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_forecast_zoo.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_granger.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_graph.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_graph_format.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_graph_primitives.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_id_algorithm.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_id_integration.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_identification.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_identification_classes.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_inductive_miner.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_its.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_knowledge.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_knowledge_soft.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_kpi_panel.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_latent_calibrate.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_latent_certify.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_latent_facade.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_latent_golden.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_latent_identify.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_latent_marginalize.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_mediation.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_native_learners.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_pag.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_plugin.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_process_filters.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_process_map.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_refute.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_reprs.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_review_fixes.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_scm.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_scm_pymc.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_score_posterior.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_sharp_edges.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_sklearn_interop.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_smoke.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_synthetic_control.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_temporal_wiring.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_user_facing_wording.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_welt.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_welt_forecast.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_welt_isolation.py +0 -0
- {ergodic-0.3.0 → ergodic-0.3.2}/tests/test_workflow_net.py +0 -0
|
@@ -7,6 +7,69 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.3.2] - 2026-10-24
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- Linear and GCM use `erfc` for normal tails: e.g. z=9 now gives approximately
|
|
14
|
+
2.26e-19 instead of zero. Values below binary64 range still underflow (z=40);
|
|
15
|
+
they are not floored or presented as accurately representable probabilities.
|
|
16
|
+
PCMCI can change its test count: the documented time-series fixture retains
|
|
17
|
+
all five links, while its unconstrained CI queries change from 49 to 47
|
|
18
|
+
(the knowledge-constrained run remains at 37).
|
|
19
|
+
- `GraphSamples.top(k)` groups numerical weight ties at relative tolerance 1e-12,
|
|
20
|
+
then log-target ties at absolute tolerance 1e-9, anchored at each group's largest
|
|
21
|
+
value, and sorts final ties by named directed-edge tuples. Weights are unchanged.
|
|
22
|
+
- MMPC forward associations within absolute p-value distance 1e-15 now tie by
|
|
23
|
+
variable name; alpha decisions keep unrounded p-values. This stabilizes greedy
|
|
24
|
+
admission when tiny tails split, without claiming invariance to variable renaming.
|
|
25
|
+
On 150 registered seven-node worlds, the tie change alone changed no skeletons
|
|
26
|
+
(137 total skeleton errors before and after); PC/FCI/RFCI were also unchanged.
|
|
27
|
+
|
|
28
|
+
### Documentation
|
|
29
|
+
- GCM's default KNN remains uncalibrated. A registered direct-CI study (200 paired
|
|
30
|
+
seeds per family/sample size) found 7.5–12% null rejection at nominal 5%.
|
|
31
|
+
OLS failed on nonlinear conditional means, so neither qualified as a general
|
|
32
|
+
default. Discover and graph-testing guides now disclose the measured limits.
|
|
33
|
+
|
|
34
|
+
## [0.3.1] - 2026-10-20
|
|
35
|
+
|
|
36
|
+
Correctness and inference safeguards from the pre-release audit (API-179 through
|
|
37
|
+
API-182). No new model families or hosted capabilities are added.
|
|
38
|
+
|
|
39
|
+
### Fixed
|
|
40
|
+
- Fitted CATE predictors reuse their training category vocabulary. Reordering
|
|
41
|
+
categorical metadata no longer swaps predictions; unknown categories and
|
|
42
|
+
incompatible prediction schemas fail explicitly.
|
|
43
|
+
- `estimate_effect(strategy="t_learner")` selects separate treatment-arm models,
|
|
44
|
+
consistently with `cate(method="t_learner")`. Contradictory alias settings fail.
|
|
45
|
+
- GCM refuses constant endpoints, interpolating nuisance fits and degenerate
|
|
46
|
+
residual products rather than returning a misleading independence p-value.
|
|
47
|
+
- Failed cross-fitted heterogeneity rankings no longer fall back to in-sample
|
|
48
|
+
rankings for inferential summaries or CATE model selection.
|
|
49
|
+
DML has no honest out-of-fold ranking proxy, so its GATES, CLAN, RATE and
|
|
50
|
+
calibration summaries now refuse too; BLP and descriptive effects remain available.
|
|
51
|
+
|
|
52
|
+
### Changed
|
|
53
|
+
- Frequentist AIPW uses cross-fitting by default. Explicit in-sample mode remains
|
|
54
|
+
available; Bayesian nuisance fits retain their documented in-sample posterior
|
|
55
|
+
path. Results report the resolved mode and propensity diagnostics.
|
|
56
|
+
- Poor estimated overlap triggers diagnostics and warnings; severely degenerate
|
|
57
|
+
propensity fits fail by default. An explicit override does not certify valid
|
|
58
|
+
inference, and no rows are silently trimmed.
|
|
59
|
+
- Generic instrumental-variable estimates are labeled `linear_iv`. A binary
|
|
60
|
+
complier `late` interpretation requires explicit assumptions and the supported
|
|
61
|
+
treatment/instrument configuration. Scalar results retain identification.
|
|
62
|
+
|
|
63
|
+
### Migration
|
|
64
|
+
|
|
65
|
+
Point estimates and intervals can change because of corrected fitting and method
|
|
66
|
+
selection. Set seeds explicitly when comparing runs. To request the old parametric
|
|
67
|
+
AIPW recipe, use `mode="in_sample"`; this does not make arbitrary interpolating
|
|
68
|
+
learners valid. Inspect overlap diagnostics rather than suppressing failures.
|
|
69
|
+
Existing code that reads the IV target string must accept `linear_iv` for generic
|
|
70
|
+
IV coefficients. See the effect-estimation and heterogeneous-effect guides for
|
|
71
|
+
precise modes, assumptions and failure behavior.
|
|
72
|
+
|
|
10
73
|
## [0.3.0] - 2026-10-17
|
|
11
74
|
|
|
12
75
|
To be released together with welt-client 0.14 (API-164): decoding with domain knowledge
|
|
@@ -943,7 +1006,9 @@ inference, discovery, process, SCM, and forecast pillars.
|
|
|
943
1006
|
silently with single-graph semantics; it now dispatches on the face, and
|
|
944
1007
|
mixed graphs raise instead of being misread.
|
|
945
1008
|
|
|
946
|
-
[Unreleased]: https://github.com/ergodic-ai/ergodic/compare/v0.3.
|
|
1009
|
+
[Unreleased]: https://github.com/ergodic-ai/ergodic/compare/v0.3.2...HEAD
|
|
1010
|
+
[0.3.2]: https://github.com/ergodic-ai/ergodic/compare/v0.3.1...v0.3.2
|
|
1011
|
+
[0.3.1]: https://github.com/ergodic-ai/ergodic/compare/v0.3.0...v0.3.1
|
|
947
1012
|
[0.3.0]: https://github.com/ergodic-ai/ergodic/compare/v0.2.1...v0.3.0
|
|
948
1013
|
[0.2.1]: https://github.com/ergodic-ai/ergodic/compare/v0.2.0...v0.2.1
|
|
949
1014
|
[0.2.0]: https://github.com/ergodic-ai/ergodic/compare/v0.1.1...v0.2.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: ergodic
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: Causal AI: causal discovery, causal inference, and process intelligence.
|
|
5
5
|
Project-URL: Homepage, https://docs.ergodic.dev
|
|
6
6
|
Project-URL: Documentation, https://docs.ergodic.dev
|
|
@@ -89,7 +89,7 @@ Alarm --> MaryCalls
|
|
|
89
89
|
Alarm <-- Burglary
|
|
90
90
|
Alarm <-- Earthquake
|
|
91
91
|
Identification(adjustment: P(JohnCalls | do(Burglary)) = sum_z P(JohnCalls | Burglary, z) P(z), with z = {Earthquake})
|
|
92
|
-
EffectEstimate(aipw, analytic: ate=0.
|
|
92
|
+
EffectEstimate(aipw, analytic: ate=0.7997, se=0.0337, 95% CI [0.734, 0.866], n=10000)
|
|
93
93
|
```
|
|
94
94
|
|
|
95
95
|
The true effect, from the network's own tables, is 0.793: inside the interval. The
|
|
@@ -43,7 +43,7 @@ Alarm --> MaryCalls
|
|
|
43
43
|
Alarm <-- Burglary
|
|
44
44
|
Alarm <-- Earthquake
|
|
45
45
|
Identification(adjustment: P(JohnCalls | do(Burglary)) = sum_z P(JohnCalls | Burglary, z) P(z), with z = {Earthquake})
|
|
46
|
-
EffectEstimate(aipw, analytic: ate=0.
|
|
46
|
+
EffectEstimate(aipw, analytic: ate=0.7997, se=0.0337, 95% CI [0.734, 0.866], n=10000)
|
|
47
47
|
```
|
|
48
48
|
|
|
49
49
|
The true effect, from the network's own tables, is 0.793: inside the interval. The
|
|
@@ -170,10 +170,10 @@ adjusted
|
|
|
170
170
|
# [estimating effects](../learn/03-estimating-effects.py); this page trusts the default and
|
|
171
171
|
# reads the number.
|
|
172
172
|
#
|
|
173
|
-
# The number is about -
|
|
174
|
-
# closing the back door moved the estimate by some
|
|
173
|
+
# The number is about -220 g, with a 95 percent interval that runs roughly -271 to -168. So
|
|
174
|
+
# closing the back door moved the estimate by some 56 g, from -275 toward zero. That 56 g was
|
|
175
175
|
# the imbalance from the balance table: the part of the raw gap that was really the mothers
|
|
176
|
-
# differing, not the smoking. What's left, -
|
|
176
|
+
# differing, not the smoking. What's left, -220 g, is the effect of smoking with those
|
|
177
177
|
# differences held fixed.
|
|
178
178
|
|
|
179
179
|
# %%
|
|
@@ -190,8 +190,8 @@ summary.round(1)
|
|
|
190
190
|
# %% [markdown]
|
|
191
191
|
# The published adjusted figure on this same extract is about -231 g: the doubly robust (AIPW)
|
|
192
192
|
# estimate in the Stata treatment-effects manual, with a standard error of 26 g. Our
|
|
193
|
-
# adjustment lands at -
|
|
194
|
-
# published number and ended
|
|
193
|
+
# adjustment lands at -220 g, well inside that interval. We started 44 g away from the
|
|
194
|
+
# published number and ended 12 g away, having moved in the right direction for the right
|
|
195
195
|
# reason. The remaining gap is down to which confounders enter and how each model is fit (the
|
|
196
196
|
# manual's own estimators range from about -210 to -240 g), and the published interval and
|
|
197
197
|
# ours overlap heavily. This is what a closed back door looks like on real data: not the
|
|
@@ -200,7 +200,7 @@ summary.round(1)
|
|
|
200
200
|
#
|
|
201
201
|
# ## What we had to assume
|
|
202
202
|
#
|
|
203
|
-
# Be exact about the claim. The -
|
|
203
|
+
# Be exact about the claim. The -220 g holds if the 17 measured confounders close every back
|
|
204
204
|
# door between smoking and birthweight. That is an assumption about the world, and no amount
|
|
205
205
|
# of data inside this table can prove it. If something we didn't measure moves both smoking
|
|
206
206
|
# and birthweight, say a genetic factor, or diet, or stress, then a path is still open and
|
|
@@ -234,7 +234,7 @@ print(f"placebo estimate: {ref.refuted_value:+.1f} g (mean over 20 scrambles)")
|
|
|
234
234
|
print(f"verdict: {'passed' if ref.passed else 'FAILED'}")
|
|
235
235
|
|
|
236
236
|
# %% [markdown]
|
|
237
|
-
# The real estimate is -
|
|
237
|
+
# The real estimate is -220 g; the placebo, averaged over 20 random shuffles, is about +2 g,
|
|
238
238
|
# a rounding error next to the real effect. The pipeline finds nothing when there's nothing
|
|
239
239
|
# to find. That's a green light, and it's worth being clear about what color of green. It
|
|
240
240
|
# rules out one failure mode, a bug or a quirk manufacturing an effect from noise. It says
|
|
@@ -243,7 +243,7 @@ print(f"verdict: {'passed' if ref.passed else 'FAILED'}")
|
|
|
243
243
|
# fire inspection.
|
|
244
244
|
#
|
|
245
245
|
# That's the case, end to end. A balance table that showed the bias before any model ran, a
|
|
246
|
-
# naive -275 g that the imbalance inflated, an adjusted -
|
|
246
|
+
# naive -275 g that the imbalance inflated, an adjusted -220 g that landed on the published
|
|
247
247
|
# neighborhood, and a placebo check that came back clean. The one input the data couldn't
|
|
248
248
|
# supply was the graph: the arrows that said which 17 variables to adjust for and which arrow
|
|
249
249
|
# to measure. Here we knew them from how the records are collected. When you don't, you can
|
|
@@ -121,7 +121,7 @@ naive = estimate_effect(dag([f"{ds.treatment} -> {ds.outcome}"]), ds.data, ds.tr
|
|
|
121
121
|
print(f"naive gap: ${naive.value:,.0f}")
|
|
122
122
|
|
|
123
123
|
# %% [markdown]
|
|
124
|
-
# About $19,
|
|
124
|
+
# About $19,560, the headline. The balance table already told us why it's too big. Higher
|
|
125
125
|
# earners are both more often eligible and wealthier, so the contrast sweeps the income gap
|
|
126
126
|
# into the effect and reports the sum. The single arrow is a claim, that nothing else moves
|
|
127
127
|
# both eligibility and wealth, and the table is nine counts against it. Income alone, an SMD
|
|
@@ -168,9 +168,10 @@ lo, hi = ate.interval()
|
|
|
168
168
|
print(f"adjusted effect: ${ate.value:.2f} CI [{lo:.1f}, {hi:.1f}] truth ${tau.mean():.2f}")
|
|
169
169
|
|
|
170
170
|
# %% [markdown]
|
|
171
|
-
# The adjusted effect lands at 23.
|
|
172
|
-
#
|
|
173
|
-
#
|
|
171
|
+
# The adjusted effect lands at 23.87 dollars, interval 22.4 to 25.3, close to the planted 23.39.
|
|
172
|
+
# It costs 25 and returns about 24: the point estimate suggests a small average loss, but the
|
|
173
|
+
# interval includes break-even. Under the graph assumptions, adjustment removes the measured
|
|
174
|
+
# confounding; it does not prove the assumptions. A team that
|
|
174
175
|
# stopped here would kill the program. That would be a mistake, and the reason is the second error.
|
|
175
176
|
#
|
|
176
177
|
# ## Predicted value is not movable value
|
|
@@ -345,7 +346,7 @@ print(f"real effect : ${ref.estimand_value:.2f}")
|
|
|
345
346
|
print(f"placebo (mean) : ${ref.refuted_value:.2f} p = {ref.p_value:.3f} passed: {ref.passed}")
|
|
346
347
|
|
|
347
348
|
# %% [markdown]
|
|
348
|
-
# Scrambled, the effect collapses to roughly zero, far from the real 23.
|
|
349
|
+
# Scrambled, the effect collapses to roughly zero, far from the real 23.87, and the placebo runs
|
|
349
350
|
# almost never reach it. That rules out a leak firing on noise. It says nothing about an unmeasured
|
|
350
351
|
# trait, some loyalty the columns don't capture, that pushes a customer toward both the perk and a
|
|
351
352
|
# fat balance.
|
|
@@ -186,11 +186,15 @@ print(f"adjusted effect : {est.value:+.3f} CI [{lo:.3f}, {hi:.3f}]")
|
|
|
186
186
|
print(f"truth : {true_effect:+.3f}")
|
|
187
187
|
|
|
188
188
|
# %% [markdown]
|
|
189
|
-
# The sign flips. The naive plus 22 points becomes minus
|
|
189
|
+
# The sign flips. The naive plus 22 points becomes minus 13.1, interval minus 15.6 to minus 10.6,
|
|
190
190
|
# straddling the planted minus 12.2. Once the comparison holds risk fixed, the offer cuts churn by
|
|
191
|
-
# about
|
|
191
|
+
# about 13 points, close to the truth. The program the quarterly numbers condemned is preventing
|
|
192
192
|
# real departures, not causing them. The naive table had it exactly backwards.
|
|
193
193
|
#
|
|
194
|
+
# The fit also warns about limited overlap: 28.4% of propensity estimates are in the
|
|
195
|
+
# tails outside [0.01, 0.99], and 1.9% require clipping. Treat the estimate cautiously;
|
|
196
|
+
# adjustment does not create comparisons where the data provides little support.
|
|
197
|
+
#
|
|
194
198
|
# ## Where the data runs out
|
|
195
199
|
#
|
|
196
200
|
# Adjustment worked, but it leans on having low-risk and high-risk customers in both groups to
|
|
@@ -361,7 +365,7 @@ print(f"placebo (mean) : {ref.refuted_value:+.3f} p = {ref.p_value:.3f} pass
|
|
|
361
365
|
# tests the pipeline, not the assumption.
|
|
362
366
|
#
|
|
363
367
|
# Two readings of the same quarter. The naive table said the save program triples churn and should
|
|
364
|
-
# die. Holding risk fixed, the program cuts churn by about
|
|
368
|
+
# die. Holding risk fixed, the program cuts churn by about 13 points and should grow, aimed at the
|
|
365
369
|
# customers it can move rather than the ones most likely to leave. The companion
|
|
366
370
|
# [lifetime-value study](06-clv.py) tells the mirror-image story on a continuous outcome, where the
|
|
367
371
|
# trap is targeting the valuable instead of the movable. For the methods, see
|
|
@@ -193,7 +193,7 @@ lo_d, hi_d = disc.interval()
|
|
|
193
193
|
print(f"discovered graph: {disc.value:+.2f} h 95% CI [{lo_d:+.2f}, {hi_d:+.2f}]")
|
|
194
194
|
|
|
195
195
|
# %% [markdown]
|
|
196
|
-
# Both routes print +4.
|
|
196
|
+
# Both routes print +4.87 with an interval covering the planted five, a long way from the
|
|
197
197
|
# variant table's 10.2. They agree to the printed digit because both graphs close the same
|
|
198
198
|
# back door, adjusting for complexity; the hand graph adjusts for workload too, which is
|
|
199
199
|
# unrelated to the review decision here and moves nothing visible. Discovery found the
|
|
@@ -46,15 +46,15 @@ print(identify_effect(hidden, smoking, weight))
|
|
|
46
46
|
```
|
|
47
47
|
|
|
48
48
|
```output from="assumptions_demo.py"
|
|
49
|
-
17 confounders: EffectEstimate(aipw, analytic: ate=-
|
|
49
|
+
17 confounders: EffectEstimate(aipw, analytic: ate=-219.5, se=26.3, 95% CI [-271, -168], n=4642)
|
|
50
50
|
no confounders: EffectEstimate(aipw, analytic: ate=-275.3, se=21.2, 95% CI [-317, -234], n=4642)
|
|
51
51
|
Identification(not identifiable, ['mbsmoke'] -> ['bweight'])
|
|
52
52
|
```
|
|
53
53
|
|
|
54
54
|
- **Step 2.** With all 17 confounders in the graph, smoking lowers birthweight by
|
|
55
|
-
about
|
|
55
|
+
about 220 g.
|
|
56
56
|
- **Step 3.** With none, the estimate is the raw difference, about 275 g. Nothing in
|
|
57
|
-
the data says which graph is right; the
|
|
57
|
+
the data says which graph is right; the 56 g gap is your assumption at work.
|
|
58
58
|
- **Step 4.** With an unmeasured cause `U` of both smoking and birthweight, and no
|
|
59
59
|
mediator or instrument to route around it, the effect is not identifiable. ergodic
|
|
60
60
|
says so instead of returning a number.
|
|
@@ -124,10 +124,15 @@ heuristic except A*, and GES returns the mode's equivalence class as a CPDAG.
|
|
|
124
124
|
|
|
125
125
|
## Give GCM a learner
|
|
126
126
|
|
|
127
|
-
`GCM`
|
|
127
|
+
`GCM` uses a regression model to form a residual covariance test. `learner=` takes an alias (`"ols"`,
|
|
128
128
|
`"knn"`) or any scikit-learn regressor, such as
|
|
129
129
|
`RandomForestRegressor(min_samples_leaf=20)`. The default is the built-in
|
|
130
|
-
nearest-neighbours regressor.
|
|
130
|
+
nearest-neighbours regressor. Fits and residuals use the same rows. A learner
|
|
131
|
+
that interpolates its training targets, such as an unrestricted decision tree,
|
|
132
|
+
leaves no residual variation and raises `ValueError`. Constant endpoints and
|
|
133
|
+
constant residual products also raise: GCM cannot form a valid p-value in these
|
|
134
|
+
cases. Use a less flexible learner when a fit removes all residual variation.
|
|
135
|
+
Nonzero residual variation alone does not establish a learner's calibration.
|
|
131
136
|
|
|
132
137
|
Give the data in one place. Inside `discover`, build the test without data, as in
|
|
133
138
|
step 5:
|
|
@@ -166,3 +171,17 @@ and restrict the search to the screen (see
|
|
|
166
171
|
- [Python reference](reference-discovery.md): every method, test and score.
|
|
167
172
|
|
|
168
173
|
</div>
|
|
174
|
+
|
|
175
|
+
!!! caution "GCM learner calibration"
|
|
176
|
+
The default GCM learner remains KNN; it is not a generally calibrated CI test.
|
|
177
|
+
A paired 200-seed direct-CI diagnostic at alpha 0.05 found null rejection rates
|
|
178
|
+
of 9.5%/7.5% (linear), 12%/10% (quadratic), and 10%/9% (sine), at n=200/1000.
|
|
179
|
+
OLS gave 7.5%/5%, 100%/100%, and 99%/100%, respectively: replacing KNN with
|
|
180
|
+
OLS would fail badly for these nonlinear conditional means. Neither qualified
|
|
181
|
+
across all cells of the registered 2–8% investigation band, so the default
|
|
182
|
+
is unchanged. All cells used 200 trials with zero refusals; 95% Wilson intervals
|
|
183
|
+
for the KNN rates span roughly 4.6–17.2%. These are direct-CI results, distinct
|
|
184
|
+
from the earlier ~30% rejection of true graphs by Fisher's combined test.
|
|
185
|
+
Learner choice must match the conditional mean; these diagnostics do not
|
|
186
|
+
establish calibration for arbitrary data or guarantee detection of dependence.
|
|
187
|
+
The reproducible protocol is in `scripts/audits/qualify_gcm.py`.
|
|
@@ -150,3 +150,17 @@ decodes the model's edge probabilities locally. See
|
|
|
150
150
|
- [Python reference](reference-discovery.md): every method and its options.
|
|
151
151
|
|
|
152
152
|
</div>
|
|
153
|
+
|
|
154
|
+
!!! caution "GCM learner calibration"
|
|
155
|
+
The default GCM learner remains KNN; it is not a generally calibrated CI test.
|
|
156
|
+
A paired 200-seed direct-CI diagnostic at alpha 0.05 found null rejection rates
|
|
157
|
+
of 9.5%/7.5% (linear), 12%/10% (quadratic), and 10%/9% (sine), at n=200/1000.
|
|
158
|
+
OLS gave 7.5%/5%, 100%/100%, and 99%/100%, respectively: replacing KNN with
|
|
159
|
+
OLS would fail badly for these nonlinear conditional means. Neither qualified
|
|
160
|
+
across all cells of the registered 2–8% investigation band, so the default
|
|
161
|
+
is unchanged. All cells used 200 trials with zero refusals; 95% Wilson intervals
|
|
162
|
+
for the KNN rates span roughly 4.6–17.2%. These are direct-CI results, distinct
|
|
163
|
+
from the earlier ~30% rejection of true graphs by Fisher's combined test.
|
|
164
|
+
Learner choice must match the conditional mean; these diagnostics do not
|
|
165
|
+
establish calibration for arbitrary data or guarantee detection of dependence.
|
|
166
|
+
The reproducible protocol is in `scripts/audits/qualify_gcm.py`.
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# Estimate an average effect
|
|
2
|
+
|
|
3
|
+
Write down which variables confound a treatment and an outcome, then estimate the
|
|
4
|
+
average effect with an interval. The example measures what smoking during pregnancy
|
|
5
|
+
does to birthweight.
|
|
6
|
+
|
|
7
|
+
!!! need "You'll need"
|
|
8
|
+
- ergodic, installed with `pip install "ergodic"`.
|
|
9
|
+
- A list of confounders: variables that affect both the treatment and the outcome.
|
|
10
|
+
|
|
11
|
+
## Run the program
|
|
12
|
+
|
|
13
|
+
The bundled birthweight data is the extract of US natality records used by Cattaneo
|
|
14
|
+
(2010): 4,642 singleton births in Pennsylvania, with the mother's smoking, the baby's
|
|
15
|
+
weight in grams and 17 background variables.
|
|
16
|
+
|
|
17
|
+
```python file="average_effect.py"
|
|
18
|
+
from ergodic import dag, estimate_effect
|
|
19
|
+
from ergodic.datasets import load_birthweight
|
|
20
|
+
|
|
21
|
+
# 1. Load 4,642 births: the mother's smoking, the birthweight and 17 confounders.
|
|
22
|
+
births = load_birthweight()
|
|
23
|
+
smoking, weight = births.treatment, births.outcome
|
|
24
|
+
print(smoking, "->", weight, "with", len(births.confounders), "confounders")
|
|
25
|
+
|
|
26
|
+
# 2. Draw the graph: each confounder affects both smoking and birthweight.
|
|
27
|
+
edges = [f"{c} -> {smoking}" for c in births.confounders]
|
|
28
|
+
edges += [f"{c} -> {weight}" for c in births.confounders]
|
|
29
|
+
graph = dag([*edges, f"{smoking} -> {weight}"])
|
|
30
|
+
|
|
31
|
+
# 3. Compare the raw difference in mean birthweight.
|
|
32
|
+
rows = births.data.to_pandas()
|
|
33
|
+
means = rows.groupby(smoking, observed=True)[weight].mean()
|
|
34
|
+
print(f"raw difference: {means['smoker'] - means['nonsmoker']:.1f} g")
|
|
35
|
+
|
|
36
|
+
# 4. Estimate the average effect, adjusting for the confounders.
|
|
37
|
+
estimate = estimate_effect(
|
|
38
|
+
graph, births.data, smoking, weight, treated=births.treated, random_state=0
|
|
39
|
+
)
|
|
40
|
+
print(estimate)
|
|
41
|
+
low, high = estimate.interval()
|
|
42
|
+
print(f"95% interval: {low:.0f} g to {high:.0f} g")
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
```output from="average_effect.py"
|
|
46
|
+
mbsmoke -> bweight with 17 confounders
|
|
47
|
+
raw difference: -275.3 g
|
|
48
|
+
EffectEstimate(aipw, analytic: ate=-219.5, se=26.3, 95% CI [-271, -168], n=4642)
|
|
49
|
+
95% interval: -271 g to -168 g
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
- Babies of smokers weigh 275 g less on average. Part of that gap comes from the
|
|
53
|
+
confounders, not from smoking.
|
|
54
|
+
- Adjusted for the confounders, smoking lowers birthweight by about 220 g. The 95%
|
|
55
|
+
interval runs from 271 g to 168 g lower. The Stata treatment-effects manual's
|
|
56
|
+
doubly robust estimate on the same extract is −231 g (standard error 26 g), well
|
|
57
|
+
inside that interval; `births.target` records it with its source.
|
|
58
|
+
- `aipw` is the augmented inverse-probability-weighted estimator. It combines an
|
|
59
|
+
outcome model and a propensity model, and stays consistent if either one is right.
|
|
60
|
+
|
|
61
|
+
## Draw the graph
|
|
62
|
+
|
|
63
|
+
The graph is your assumption about the world. Each confounder points at both the
|
|
64
|
+
treatment and the outcome:
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
edges = [f"{c} -> {smoking}" for c in births.confounders]
|
|
68
|
+
edges += [f"{c} -> {weight}" for c in births.confounders]
|
|
69
|
+
graph = dag([*edges, f"{smoking} -> {weight}"])
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`estimate_effect` reads the adjustment set from this graph. You never pass the
|
|
73
|
+
confounders to it directly.
|
|
74
|
+
|
|
75
|
+
## Choose the treated level
|
|
76
|
+
|
|
77
|
+
`treated=` names the level that counts as treatment. Here the `mbsmoke` column holds
|
|
78
|
+
`"smoker"` and `"nonsmoker"`, and `births.treated` is `"smoker"`. The estimate is
|
|
79
|
+
the mean outcome if everyone were treated minus the mean if no one were.
|
|
80
|
+
|
|
81
|
+
!!! caution "The estimate is only as good as the graph"
|
|
82
|
+
It assumes no confounder is missing from the list. A variable that affects both
|
|
83
|
+
smoking and birthweight, but is not in the data, biases the result. No
|
|
84
|
+
check on this page can detect such a variable.
|
|
85
|
+
|
|
86
|
+
By default the propensity model is logistic regression. The outcome model is
|
|
87
|
+
ordinary least squares for a continuous outcome, as here, and logistic regression
|
|
88
|
+
for a binary one. Pass `learner=` (an alias, a learner, or a mapping from slot to
|
|
89
|
+
learner) to use other models.
|
|
90
|
+
|
|
91
|
+
## Cross-fitting and overlap
|
|
92
|
+
|
|
93
|
+
AIPW now defaults to `mode="auto"`: frequentist nuisances use held-out predictions
|
|
94
|
+
from five shuffled, treatment-stratified folds. Set `n_folds=` and `random_state=`
|
|
95
|
+
explicitly for a recorded protocol. If no seed is supplied, AIPW uses seed 0 for
|
|
96
|
+
the split. Diagnostics report `n_folds_requested`, the number of nonempty folds
|
|
97
|
+
actually used as `n_folds`, and `crossfit_seed`. Both arms need at least two rows;
|
|
98
|
+
fold counts must be integers from 2 through the number of rows.
|
|
99
|
+
|
|
100
|
+
Cross-fitting prevents evaluating nuisance residuals on their own fitting rows.
|
|
101
|
+
It does not make every learner safe: valid ATE intervals also need overlap,
|
|
102
|
+
suitable nuisance convergence rates and independent observations. An unrestricted
|
|
103
|
+
decision-tree propensity model can predict only 0 and 1 even on held-out rows;
|
|
104
|
+
the default guard refuses that fit. This remains AIPW, with its counterfactual
|
|
105
|
+
outcome regressions and propensity residual corrections, rather than a partially
|
|
106
|
+
linear DML model. See [Chernozhukov et al.](https://arxiv.org/abs/1608.00060).
|
|
107
|
+
|
|
108
|
+
`mode="in_sample"` selects the old parametric fitting path explicitly. Its
|
|
109
|
+
influence-score interval needs appropriate parametric or empirical-process
|
|
110
|
+
conditions; unrestricted interpolating learners are not qualified. With any
|
|
111
|
+
Bayesian nuisance, `auto` preserves the in-sample posterior or posterior-mean
|
|
112
|
+
fallback without repeatedly fitting a sampler. `cross_fitted=0` and
|
|
113
|
+
`bayesian_in_sample=1` report that choice. Explicit `mode="cross_fitted"` refuses
|
|
114
|
+
Bayesian slots because cross-fitted posterior inference is not implemented.
|
|
115
|
+
|
|
116
|
+
Inspect `estimate.diagnostics`: `propensity_min`, `propensity_max`, and the
|
|
117
|
+
`propensity_q01`, `q05`, `median`, `q95` and `q99` entries use raw predictions,
|
|
118
|
+
before clipping. `propensity_clipped_fraction` records clipping;
|
|
119
|
+
`treated_effective_n`, `control_effective_n`, and each arm's `weight_max` and
|
|
120
|
+
`weight_q99` describe the clipped ATE inverse weights. `att_control_effective_n`
|
|
121
|
+
and `att_control_weight_max` describe ATT odds weights. For a Bayesian posterior,
|
|
122
|
+
these summaries use the posterior mean propensity; `posterior_draw_clipped_fraction`
|
|
123
|
+
and `posterior_draw_tail_fraction` separately summarize all propensity draw cells.
|
|
124
|
+
|
|
125
|
+
AIPW, IPW and stratification accept `overlap="warn"` (default), `"raise"`, or
|
|
126
|
+
`"ignore"`, plus `overlap_threshold=0.01` and `clip=0.001`. The default warns when
|
|
127
|
+
at least 10% of raw predictions lie outside `[0.01, 0.99]`, or an arm's effective
|
|
128
|
+
size falls below 10% of its observed count. It refuses when at least 50% require
|
|
129
|
+
clipping. `raise` also refuses moderate warnings; `ignore` explicitly overrides
|
|
130
|
+
these diagnostic guards, but invalid probabilities always fail. These thresholds
|
|
131
|
+
are diagnostic choices, not a test of population positivity. A result below the
|
|
132
|
+
thresholds is not a certificate of valid inference. Clipping cannot restore
|
|
133
|
+
missing support. No rows are trimmed; stratification refuses an occupied stratum
|
|
134
|
+
with a missing arm instead of dropping it and changing the estimand.
|
|
135
|
+
|
|
136
|
+
## Instrumental-variable targets
|
|
137
|
+
|
|
138
|
+
Instrument estimators now default to `target="linear_iv"`, including continuous
|
|
139
|
+
treatment and PLIV. This replaces the old automatic `late` label. The numerical
|
|
140
|
+
coefficient is unchanged; consumers checking `EffectTarget.LATE` should migrate to
|
|
141
|
+
`EffectTarget.LINEAR_IV` when they mean the generic linear IV coefficient.
|
|
142
|
+
|
|
143
|
+
A complier LATE requires additional assumptions. For one unconditional binary
|
|
144
|
+
instrument and a binary treatment, 2SLS or Wald accepts `target="late"` only with
|
|
145
|
+
`late_assumptions=True`. This asserts exogeneity, exclusion, relevance,
|
|
146
|
+
monotonicity, consistency and no interference; it does not verify them. Adjusted
|
|
147
|
+
or multivariate instruments and PLIV retain the generic linear IV target.
|
|
148
|
+
See [Angrist, Imbens and Rubin](https://doi.org/10.1080/01621459.1996.10476902).
|
|
149
|
+
|
|
150
|
+
Scalar results retain `identification`; IV results also state `assumptions`.
|
|
151
|
+
Their `first_stage_partial_r2`, `first_stage_f` and degrees of freedom describe
|
|
152
|
+
first-stage strength. PLIV computes them on its cross-fitted residuals, marked by
|
|
153
|
+
`first_stage_residualized=1`. The classical F statistic is descriptive, assumes a
|
|
154
|
+
homoskedastic linear first stage for its usual interpretation, and does not test
|
|
155
|
+
exclusion or exogeneity. Values below 10 warn; larger values do not establish
|
|
156
|
+
instrument validity. Ordinary normal IV intervals can be unreliable with weak
|
|
157
|
+
instruments; weak-IV robust intervals are not implemented here.
|
|
158
|
+
|
|
159
|
+
## Next steps
|
|
160
|
+
|
|
161
|
+
<div class="cards" markdown="1">
|
|
162
|
+
|
|
163
|
+
- [Refute an estimate](refute-an-estimate.md): stress-test this estimate with placebo and subset checks.
|
|
164
|
+
- [Average an effect over the graphs](evaluate-a-graph.md#average-an-effect-over-the-graphs): estimate across a posterior over DAGs.
|
|
165
|
+
- [Estimate heterogeneous effects](heterogeneous-effects.md): find who gains more or less from the treatment.
|
|
166
|
+
- [Python reference](reference-inference.md): every option of `estimate_effect`.
|
|
167
|
+
|
|
168
|
+
</div>
|
|
@@ -440,10 +440,10 @@ print(round(result.zero_effect_mass, 3), result.unidentified_mass, result.trunca
|
|
|
440
440
|
0.811
|
|
441
441
|
0.404
|
|
442
442
|
adjustment_set mass n_dags estimate std_error
|
|
443
|
-
0 c 0.757 3 1.
|
|
443
|
+
0 c 0.757 3 1.970 0.066
|
|
444
444
|
1 (empty) 0.156 6 3.563 0.072
|
|
445
445
|
2 (no directed path) 0.087 16 0.000 0.000
|
|
446
|
-
2.
|
|
446
|
+
2.047 0.857
|
|
447
447
|
0.087 0.0 0.0
|
|
448
448
|
```
|
|
449
449
|
|
|
@@ -525,3 +525,10 @@ Nothing touches numpy's global random state, and the same `seed` gives the same
|
|
|
525
525
|
- [Python reference](reference-discovery.md): `graph_posterior`, `evaluate_graph`, `GraphSamples`.
|
|
526
526
|
|
|
527
527
|
</div>
|
|
528
|
+
|
|
529
|
+
`GraphSamples.top(k)` lists the heaviest DAGs first. Weight ties within 1e-12
|
|
530
|
+
relative to the group's maximum are ordered by log target; log-target ties
|
|
531
|
+
within absolute 1e-9 are ordered by sorted named directed edges. Groups are
|
|
532
|
+
anchored at their maximum, not chained through approximate pairwise equality.
|
|
533
|
+
This removes floating-point noise from equivalent-graph ordering without
|
|
534
|
+
rounding returned masses or changing the posterior.
|
|
@@ -101,6 +101,9 @@ Demirer, Duflo and Fernández-Val, 2025; Yadlowsky and others, 2024).
|
|
|
101
101
|
`method=` picks the learner. The default `"dr_learner"` is doubly robust. Other
|
|
102
102
|
names: `"r_learner"`, `"x_learner"`, `"t_learner"`, `"s_learner"`, `"dml"` and
|
|
103
103
|
`"causal_forest"`. `learner=` fills the nuisance models, as in `estimate_effect`.
|
|
104
|
+
The same S/T names work with `estimate_effect(strategy=...)`: `t_learner` fits
|
|
105
|
+
separate outcome models for each arm, and `s_learner` fits one pooled model.
|
|
106
|
+
Passing a contradictory `per_arm=` flag raises an error in either facade.
|
|
104
107
|
|
|
105
108
|
- The uncertainty depends on the method. The DR-learner with its default linear
|
|
106
109
|
final model has analytic standard errors. `"causal_forest"` (Athey, Tibshirani
|
|
@@ -126,6 +129,17 @@ names: `"r_learner"`, `"x_learner"`, `"t_learner"`, `"s_learner"`, `"dml"` and
|
|
|
126
129
|
- `explain()` describes the units the model was fit on. To score new rows, fit an
|
|
127
130
|
estimator such as `DRLearner` with `fit(identification, data)`, then call its
|
|
128
131
|
`cate(new_rows)`. That result has no scores, so its `explain()` raises.
|
|
132
|
+
- New rows use the covariate types and category vocabulary saved during `fit`.
|
|
133
|
+
Column order and categorical metadata order can change, and a batch can omit
|
|
134
|
+
known category levels. Unknown values, missing required columns, changed
|
|
135
|
+
covariate types and missing covariate values raise an error. Treatment and
|
|
136
|
+
outcome columns are unnecessary when scoring new rows.
|
|
137
|
+
- If a fold fails while building the out-of-fold effect proxy, `explain().blp()`
|
|
138
|
+
still projects the cross-fit scores on covariates. GATES, CLAN, RATE and calibration
|
|
139
|
+
refuse to return intervals or p-values and report the ranking failure. They
|
|
140
|
+
never substitute the fitted in-sample effects. `select_cate` also refuses a
|
|
141
|
+
candidate without an out-of-fold proxy. DML supplies constant effects with no
|
|
142
|
+
out-of-fold CATE ranking, so it also refuses these summaries while retaining BLP.
|
|
129
143
|
- `effects.ate()` returns the average effect. With the DR-learner it is the mean of
|
|
130
144
|
the doubly robust scores, whose standard error is valid (the per-unit standard
|
|
131
145
|
errors share one fitted model, so they are never combined as if independent); for
|
|
@@ -72,7 +72,7 @@ for module, use in optional.items():
|
|
|
72
72
|
```
|
|
73
73
|
|
|
74
74
|
```output from="install_check.py" lines="1"
|
|
75
|
-
ergodic 0.3.
|
|
75
|
+
ergodic 0.3.2 writes ergodic.graph/1
|
|
76
76
|
```
|
|
77
77
|
|
|
78
78
|
- **Step 1.** The version, and `ergodic.graph/1`: the format `MixedGraph.to_dict()`
|
|
@@ -54,18 +54,18 @@ print(check)
|
|
|
54
54
|
```
|
|
55
55
|
|
|
56
56
|
```output from="refute_effect.py"
|
|
57
|
-
EffectEstimate(aipw, analytic: ate=-
|
|
58
|
-
Refutation(placebo_treatment: estimate=-
|
|
59
|
-
spread of the perturbed estimates: 21.
|
|
60
|
-
Refutation(random_common_cause: estimate=-
|
|
61
|
-
spread of the perturbed estimates: 0
|
|
62
|
-
Refutation(data_subset: estimate=-
|
|
63
|
-
spread of the perturbed estimates:
|
|
64
|
-
Refutation(placebo_treatment: estimate=-275.3, under perturbation=4.
|
|
57
|
+
EffectEstimate(aipw, analytic: ate=-219.5, se=26.3, 95% CI [-271, -168], n=4642)
|
|
58
|
+
Refutation(placebo_treatment: estimate=-219.5, under perturbation=4.747, p=0.0099, passed, n=100)
|
|
59
|
+
spread of the perturbed estimates: 21.5 g
|
|
60
|
+
Refutation(random_common_cause: estimate=-219.5, under perturbation=-219.3, passed, n=100)
|
|
61
|
+
spread of the perturbed estimates: 1.0 g
|
|
62
|
+
Refutation(data_subset: estimate=-219.5, under perturbation=-218.7, passed, n=100)
|
|
63
|
+
spread of the perturbed estimates: 14.2 g
|
|
64
|
+
Refutation(placebo_treatment: estimate=-275.3, under perturbation=4.526, p=0.0099, passed, n=100)
|
|
65
65
|
```
|
|
66
66
|
|
|
67
67
|
- **Placebo treatment.** With the smoking column shuffled, the effect drops from
|
|
68
|
-
−
|
|
68
|
+
−220 g to a few grams. In none of the 100 shuffles was the placebo effect as large
|
|
69
69
|
in size as the real one, so `p` is the smallest value 100 runs can give: 1/101.
|
|
70
70
|
- **Random common cause.** Adding an independent random confounder to the graph
|
|
71
71
|
leaves the estimate where it was, within a gram.
|
|
@@ -50,7 +50,7 @@ Alarm --> MaryCalls
|
|
|
50
50
|
Alarm <-- Burglary
|
|
51
51
|
Alarm <-- Earthquake
|
|
52
52
|
Identification(adjustment: P(JohnCalls | do(Burglary)) = sum_z P(JohnCalls | Burglary, z) P(z), with z = {Earthquake})
|
|
53
|
-
EffectEstimate(aipw, analytic: ate=0.
|
|
53
|
+
EffectEstimate(aipw, analytic: ate=0.7997, se=0.0337, 95% CI [0.734, 0.866], n=10000)
|
|
54
54
|
```
|
|
55
55
|
|
|
56
56
|
- **Step 1.** Five yes/no columns. Burglaries are rare: about 1 row in 100.
|
|
@@ -210,3 +210,23 @@ own variables, and they hold in the margin over those variables.
|
|
|
210
210
|
- [Python reference](reference-discovery.md): `test_graph` and `GraphTest` in full.
|
|
211
211
|
|
|
212
212
|
</div>
|
|
213
|
+
|
|
214
|
+
!!! caution "GCM learner calibration"
|
|
215
|
+
The default GCM learner remains KNN; it is not a generally calibrated CI test.
|
|
216
|
+
A paired 200-seed direct-CI diagnostic at alpha 0.05 found null rejection rates
|
|
217
|
+
of 9.5%/7.5% (linear), 12%/10% (quadratic), and 10%/9% (sine), at n=200/1000.
|
|
218
|
+
OLS gave 7.5%/5%, 100%/100%, and 99%/100%, respectively: replacing KNN with
|
|
219
|
+
OLS would fail badly for these nonlinear conditional means. Neither qualified
|
|
220
|
+
across all cells of the registered 2–8% investigation band, so the default
|
|
221
|
+
is unchanged. All cells used 200 trials with zero refusals; 95% Wilson intervals
|
|
222
|
+
for the KNN rates span roughly 4.6–17.2%. These are direct-CI results, distinct
|
|
223
|
+
from the earlier ~30% rejection of true graphs by Fisher's combined test.
|
|
224
|
+
Learner choice must match the conditional mean; these diagnostics do not
|
|
225
|
+
establish calibration for arbitrary data or guarantee detection of dependence.
|
|
226
|
+
The reproducible protocol is in `scripts/audits/qualify_gcm.py`.
|
|
227
|
+
|
|
228
|
+
Linear and GCM compute two-sided normal tails with `erfc`, so strong statistics
|
|
229
|
+
such as z=9 retain their nonzero p-values (about 2.26e-19). Tails below binary64's
|
|
230
|
+
representable range still become zero, for example z=40. Graph-test aggregation
|
|
231
|
+
retains its existing safeguards for zero tails; no finite float can represent
|
|
232
|
+
those smaller probabilities accurately.
|
|
@@ -54,14 +54,14 @@ print(f"{pruned} links pruned; {tests[0]} tests instead of {tests[1]}")
|
|
|
54
54
|
|
|
55
55
|
```output from="time_series.py"
|
|
56
56
|
TimeSeriesDiscoveryResult(pcmci/linear: dag on 9 window nodes, max_lag=2, 5 lagged links, 0 contemporaneous, alpha=0.05, n=496)
|
|
57
|
-
ads[t-1] -> ads[t] p=
|
|
58
|
-
ads[t-1] -> visits[t] p=
|
|
59
|
-
sales[t-1] -> sales[t] p=
|
|
60
|
-
visits[t-2] -> sales[t] p=4.
|
|
61
|
-
visits[t-1] -> visits[t] p=
|
|
57
|
+
ads[t-1] -> ads[t] p=1.9e-38
|
|
58
|
+
ads[t-1] -> visits[t] p=6.1e-35
|
|
59
|
+
sales[t-1] -> sales[t] p=6.1e-34
|
|
60
|
+
visits[t-2] -> sales[t] p=4.5e-15
|
|
61
|
+
visits[t-1] -> visits[t] p=1.5e-26
|
|
62
62
|
granger: [('ads', 'visits'), ('visits', 'sales')]
|
|
63
63
|
True
|
|
64
|
-
9 links pruned; 37 tests instead of
|
|
64
|
+
9 links pruned; 37 tests instead of 47
|
|
65
65
|
```
|
|
66
66
|
|
|
67
67
|
- **Step 2.** The result holds a window graph over nodes such as `ads[t-1]`. With
|