ergodic 0.2.0__tar.gz → 0.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ergodic-0.2.0 → ergodic-0.2.1}/CHANGELOG.md +19 -3
- {ergodic-0.2.0 → ergodic-0.2.1}/PKG-INFO +1 -1
- {ergodic-0.2.0 → ergodic-0.2.1}/docsite/pages/compatibility.md +1 -1
- {ergodic-0.2.0 → ergodic-0.2.1}/docsite/pages/install.md +2 -2
- {ergodic-0.2.0 → ergodic-0.2.1}/docsite/pages/start.md +1 -1
- {ergodic-0.2.0 → ergodic-0.2.1}/docsite/pages/welt.md +10 -5
- {ergodic-0.2.0 → ergodic-0.2.1}/docsite/site.toml +1 -1
- {ergodic-0.2.0 → ergodic-0.2.1}/scripts/docsite/__main__.py +7 -1
- {ergodic-0.2.0 → ergodic-0.2.1}/scripts/docsite/links.py +10 -1
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/__init__.py +1 -1
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/process/_map.py +24 -3
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/welt/_learner.py +45 -15
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_docs_welt_page.py +37 -5
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_docsite.py +6 -1
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_process_map.py +11 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_welt.py +39 -3
- {ergodic-0.2.0 → ergodic-0.2.1}/.gitignore +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/CONTRIBUTING.md +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/LICENSE +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/NOTICE +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/README.md +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/SECURITY.md +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docs/case-studies/01-birthweight.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docs/case-studies/02-berkeley.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docs/case-studies/03-pension401k.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docs/case-studies/04-synthetic-control.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docs/case-studies/05-discovery.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docs/case-studies/06-clv.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docs/case-studies/07-churn.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docs/learn/01-why-causality.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docs/learn/02-good-and-bad-controls.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docs/learn/03-estimating-effects.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docs/learn/04-heterogeneous-effects.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docs/learn/05-hidden-confounding.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docs/learn/06-discovering-structure.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docs/learn/07-quasi-experiments.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docs/learn/08-process-mining.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docs/learn/09-process-intelligence.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docs/learn/10-decision-points.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docs/learn/11-forecasting.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docs/learn/12-structural-causal-models.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docs/learn/13-hidden-regimes.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docs/learn/14-causal-forests-and-policy-trees.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docsite/assets/docs.css +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docsite/assets/docs.js +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docsite/assets/ergodic-mark-dark.svg +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docsite/assets/ergodic-mark-light.svg +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docsite/assets/fonts/OFL-Geist-Mono.txt +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docsite/assets/fonts/OFL-Geist.txt +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docsite/assets/fonts/geist-latin-ext-wght-normal.woff2 +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docsite/assets/fonts/geist-latin-wght-normal.woff2 +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docsite/assets/fonts/geist-mono-latin-ext-wght-normal.woff2 +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docsite/assets/fonts/geist-mono-latin-wght-normal.woff2 +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docsite/assets/site.css +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docsite/assets/tokens.css +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docsite/pages/adjustment-sets.md +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docsite/pages/assumptions.md +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docsite/pages/backtest-a-forecast.md +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docsite/pages/bootstrap-stability.md +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docsite/pages/certify-and-calibrate.md +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docsite/pages/changelog.md +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docsite/pages/choose-a-method.md +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docsite/pages/conformance-and-decisions.md +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docsite/pages/decode-a-graph.md +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docsite/pages/difference-in-differences.md +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docsite/pages/discover-a-graph.md +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docsite/pages/domain-knowledge.md +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docsite/pages/edge-probabilities.md +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docsite/pages/estimate-average-effect.md +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docsite/pages/fit-an-scm.md +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docsite/pages/forecast-with-drivers.md +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docsite/pages/glossary.md +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docsite/pages/graphs-and-marks.md +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docsite/pages/heterogeneous-effects.md +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docsite/pages/identify-an-effect.md +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docsite/pages/interventions-and-counterfactuals.md +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docsite/pages/map-a-process.md +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docsite/pages/mediation.md +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docsite/pages/reconcile-a-hierarchy.md +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docsite/pages/recover-hidden-regimes.md +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docsite/pages/refute-an-estimate.md +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docsite/pages/run-scenarios.md +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docsite/pages/synthetic-control.md +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docsite/pages/target-a-policy.md +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docsite/pages/time-series-discovery.md +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/docsite/provenance.json +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/examples/README.md +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/examples/adjustment_birthweight.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/examples/cate/01_cate_basics.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/examples/cate/02_meta_learners_compared.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/examples/cate/03_is_there_heterogeneity.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/examples/cate/04_where_is_the_effect_higher.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/examples/cate/05_targeting_with_rate.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/examples/cate/06_per_unit_uncertainty.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/examples/cate/07_cate_vs_ite.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/examples/cate/08_identification_gating.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/examples/cate/09_case_study_uplift_end_to_end.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/examples/cate/10_causal_forest.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/examples/cate/11_policy_trees.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/examples/cate/12_model_selection.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/examples/cate/13_case_study_phase2.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/examples/cate/README.md +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/examples/confounding_berkeley.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/examples/discovery_asia.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/examples/double_ml_pension.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/examples/hidden_regimes.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/examples/latent_switch.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/examples/scm/01_three_rungs.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/examples/scm/02_honesty_and_tipping_point.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/examples/scm/03_causal_price_optimization.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/examples/scm/04_counterfactual_recourse.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/examples/scm/05_counterfactual_fairness.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/examples/scm/06_causal_attribution.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/examples/scm/07_but_for_accounting.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/examples/scm/08_off_policy_evaluation.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/examples/scm/09_root_cause_attribution.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/examples/scm/10_frontdoor_unmeasured_confounder.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/examples/scm/README.md +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/examples/synthetic_control_smoking.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/public/ergodic_logomark_dark.svg +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/public/ergodic_logomark_light.svg +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/pyproject.toml +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/scripts/docsite/__init__.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/scripts/docsite/build.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/scripts/docsite/config.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/scripts/docsite/examples.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/scripts/docsite/graphite.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/scripts/docsite/notebooks.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/scripts/docsite/reference.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/data/__init__.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/data/_dataset.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/data/_eventlog.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/data/_frame.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/data/_hierarchy.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/data/_hts.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/data/_panel.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/data/_schema.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/data/_tabular.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/data/_timeseries.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/datasets/__init__.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/datasets/_bayesnet.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/datasets/_bunch.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/datasets/_bundled.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/datasets/_tables.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/datasets/data/birthweight.csv +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/datasets/data/germany.csv +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/datasets/data/pension401k.csv +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/datasets/data/smoking.csv +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/discovery/__init__.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/discovery/_bootstrap.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/discovery/_ci.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/discovery/_decode.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/discovery/_discover.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/discovery/_encode.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/discovery/_granger.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/discovery/_lagged.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/discovery/_probabilities.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/discovery/_registry.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/discovery/_result.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/discovery/_score.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/discovery/_stats.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/discovery/_tsresult.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/discovery/algorithms/__init__.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/discovery/algorithms/_astar.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/discovery/algorithms/_boss.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/discovery/algorithms/_fci.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/discovery/algorithms/_ges.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/discovery/algorithms/_glasso.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/discovery/algorithms/_grasp.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/discovery/algorithms/_hc.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/discovery/algorithms/_lingam.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/discovery/algorithms/_mmpc.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/discovery/algorithms/_pc.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/discovery/algorithms/_pcmci.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/discovery/algorithms/_rfci.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/discovery/algorithms/_skeleton.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/discovery/ci/__init__.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/discovery/ci/_discrete.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/discovery/ci/_gcm.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/discovery/ci/_linear.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/discovery/ci/_mixed.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/discovery/scores/__init__.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/discovery/scores/_bdeu.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/discovery/scores/_bic.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/discovery/scores/_discrete_bic.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/discovery/scores/_mixed_bic.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/forecast/__init__.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/forecast/_attribution.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/forecast/_backtest.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/forecast/_baselines.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/forecast/_facade.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/forecast/_fit.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/forecast/_mechanism.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/forecast/_metrics.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/forecast/_protocol.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/forecast/_reconcile.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/forecast/_result.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/forecast/_scalers.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/forecast/_schedule.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/forecast/_staged.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/forecast/_structure.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/forecast/mechanisms/__init__.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/forecast/mechanisms/_bass.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/forecast/mechanisms/_ou.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/forecast/mechanisms/_structural.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/graph/__init__.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/graph/_graph.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/graph/_io.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/graph/_marks.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/graph/_meek.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/graph/_pag.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/graph/_primitives.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/graph/_separation.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/graph/_surgery.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/identification/__init__.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/identification/_criteria.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/identification/_distribution.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/identification/_evaluate.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/identification/_functional.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/identification/_id.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/identification/_ida.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/identification/_mediation.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/identification/_recognize.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/identification/_record.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/inference/__init__.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/inference/_crossfit.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/inference/_design.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/inference/_estimate.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/inference/_estimator.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/inference/_learner.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/inference/_perunit.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/inference/_refute.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/inference/_registry.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/inference/_result.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/inference/_scores.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/inference/_select.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/inference/_stats.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/inference/_target.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/inference/estimators/__init__.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/inference/estimators/_did.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/inference/estimators/_dml.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/inference/estimators/_doubly_robust.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/inference/estimators/_forest.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/inference/estimators/_frontdoor.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/inference/estimators/_its.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/inference/estimators/_iv.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/inference/estimators/_mediation.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/inference/estimators/_meta.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/inference/estimators/_null.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/inference/estimators/_plugin.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/inference/estimators/_regression.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/inference/estimators/_synthetic_control.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/inference/estimators/_weighting.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/inference/explain/__init__.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/inference/explain/_blp.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/inference/explain/_calibration.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/inference/explain/_clan.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/inference/explain/_gates.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/inference/explain/_linalg.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/inference/explain/_policy.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/inference/explain/_rate.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/inference/explain/_record.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/inference/learners/__init__.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/inference/learners/_linear.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/inference/learners/_neighbors.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/inference/learners/_pymc.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/knowledge.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/latent/__init__.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/latent/_engine.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/latent/_facade.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/latent/_identify.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/latent/_likelihood.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/latent/_marginalize.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/latent/_result.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/latent/likelihoods/__init__.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/latent/likelihoods/_ar.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/latent/likelihoods/_spectral.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/process/__init__.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/process/_case_table.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/process/_cases.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/process/_conformance.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/process/_decisions.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/process/_filters.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/process/_mine.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/process/_net.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/process/_panel.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/process/_performance.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/process/_soundness.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/process/_tree.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/process/_variants.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/py.typed +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/scm/__init__.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/scm/_bounds.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/scm/_compose.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/scm/_district.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/scm/_mechanism.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/scm/_noise.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/scm/_result.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/scm/_sampling.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/scm/_scm.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/welt/__init__.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/welt/_client.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/welt/_data.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/src/ergodic/welt/_discovery.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/conftest.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/data/exp0626_decoder_parity.json +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_bayes.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_case_table.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_cate.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_cate_forest.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_cate_phase2_stress.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_cate_policy.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_cate_select.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_cate_stress.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_conformance.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_data.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_datasets.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_decisions.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_decode.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_did.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_discovery.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_discovery_bootstrap.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_discovery_boss.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_discovery_ci.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_discovery_fci.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_discovery_ges.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_discovery_glasso.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_discovery_grasp.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_discovery_hc.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_discovery_lingam.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_discovery_mmpc.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_discovery_pc.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_discovery_pcmci.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_discovery_rfci.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_discovery_score.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_estimators_e2.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_forecast_backtest.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_forecast_backtest_mcmc.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_forecast_core.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_forecast_explain.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_forecast_facade.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_forecast_mechanisms.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_forecast_metrics.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_forecast_reconcile.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_forecast_staged.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_forecast_structure.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_forecast_zoo.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_granger.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_graph.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_graph_format.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_graph_primitives.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_id_algorithm.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_id_integration.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_identification.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_identification_classes.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_inductive_miner.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_inference.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_its.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_knowledge.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_kpi_panel.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_latent_calibrate.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_latent_certify.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_latent_facade.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_latent_golden.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_latent_identify.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_latent_marginalize.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_mediation.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_native_learners.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_pag.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_plugin.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_process_filters.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_refute.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_reprs.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_review_fixes.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_scm.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_scm_pymc.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_sharp_edges.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_sklearn_interop.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_smoke.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_synthetic_control.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_temporal_wiring.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_user_facing_wording.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_welt_isolation.py +0 -0
- {ergodic-0.2.0 → ergodic-0.2.1}/tests/test_workflow_net.py +0 -0
|
@@ -7,9 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
-
## [0.2.
|
|
10
|
+
## [0.2.1] - 2026-10-15
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
### Fixed
|
|
13
|
+
- `WeltLearner` fits a slot with no feature columns locally. An empty adjustment set
|
|
14
|
+
leaves nothing for a model to learn, so the nuisance is the training mean (or the
|
|
15
|
+
class frequencies for classification): no upload, no paid fit, no charge to
|
|
16
|
+
`max_fits`, and `predictor_id_` is `None`. In 0.2.0 it raised, so the "Discover in
|
|
17
|
+
Welt, estimate in ergodic" guide stopped whenever Welt's Arrow found no confounders
|
|
18
|
+
for the treatment, as it does on the guide's own data today. With no adjustment
|
|
19
|
+
variables, DML is then the cross-fitted regression slope of the outcome on the
|
|
20
|
+
treatment, identical to DML with ergodic's `ols` learner.
|
|
21
|
+
- `ArcStats` treats two NaN gaps (START and END arcs) as equal and hashes them alike,
|
|
22
|
+
so `process_map(log) == process_map(log)` holds on Python 3.13 and 3.14, where
|
|
23
|
+
dataclass equality compares fields one by one.
|
|
24
|
+
|
|
25
|
+
## [0.2.0] - 2026-10-15
|
|
26
|
+
|
|
27
|
+
Released together with welt-client 0.13.
|
|
13
28
|
|
|
14
29
|
### Added
|
|
15
30
|
- A versioned graph format. `MixedGraph.to_dict()` writes
|
|
@@ -710,7 +725,8 @@ inference, discovery, process, SCM, and forecast pillars.
|
|
|
710
725
|
silently with single-graph semantics; it now dispatches on the face, and
|
|
711
726
|
mixed graphs raise instead of being misread.
|
|
712
727
|
|
|
713
|
-
[Unreleased]: https://github.com/ergodic-ai/ergodic/compare/v0.2.
|
|
728
|
+
[Unreleased]: https://github.com/ergodic-ai/ergodic/compare/v0.2.1...HEAD
|
|
729
|
+
[0.2.1]: https://github.com/ergodic-ai/ergodic/compare/v0.2.0...v0.2.1
|
|
714
730
|
[0.2.0]: https://github.com/ergodic-ai/ergodic/compare/v0.1.1...v0.2.0
|
|
715
731
|
[0.1.1]: https://github.com/ergodic-ai/ergodic/compare/v0.1.0...v0.1.1
|
|
716
732
|
[0.1.0]: https://github.com/ergodic-ai/ergodic/releases/tag/v0.1.0
|
|
@@ -74,7 +74,7 @@ Only a graph that Welt declares a native DAG converts. `CausalResult.to_ergodic(
|
|
|
74
74
|
in welt-client keeps every variable, unconnected ones included, and the native
|
|
75
75
|
edge directions. It never projects, thresholds or relabels a graph into a DAG.
|
|
76
76
|
|
|
77
|
-
| Welt model | Native graph | In ergodic 0.2
|
|
77
|
+
| Welt model | Native graph | In ergodic 0.2 |
|
|
78
78
|
| --- | --- | --- |
|
|
79
79
|
| `arrow` | DAG | Converts. `ergodic.welt.discover` returns it as a `DiscoveryResult`. |
|
|
80
80
|
| `cdfm` | Directed, may hold cycles | Refused before paying by default. With an explicit `decoder=`, its scores are decoded locally into a DAG labelled decoded. |
|
|
@@ -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.2.
|
|
75
|
+
ergodic 0.2.1 writes ergodic.graph/1
|
|
76
76
|
```
|
|
77
77
|
|
|
78
78
|
- **Step 1.** The version, and `ergodic.graph/1`: the format `MixedGraph.to_dict()`
|
|
@@ -104,7 +104,7 @@ xgboost yourself to use their models (the `welt` extra brings scikit-learn in).
|
|
|
104
104
|
|
|
105
105
|
<details markdown="1"><summary>What are the neural and foundation extras?</summary>
|
|
106
106
|
|
|
107
|
-
`neural` and `foundation` install PyTorch 2.2 or later. ergodic 0.2
|
|
107
|
+
`neural` and `foundation` install PyTorch 2.2 or later. ergodic 0.2 has no
|
|
108
108
|
learner that uses it: asking `cate` for a neural or foundation-model learner raises
|
|
109
109
|
`NotImplementedError`.
|
|
110
110
|
|
|
@@ -95,6 +95,6 @@ treatment does not affect.
|
|
|
95
95
|
- [Estimate an average effect](estimate-average-effect.md): adjust for known confounders on real data.
|
|
96
96
|
- [Notebooks](notebooks.md): the learn series and case studies.
|
|
97
97
|
- [Python reference](reference.md): every function and its parameters.
|
|
98
|
-
- [Example scripts](https://github.com/ergodic-ai/ergodic/tree/v0.2.
|
|
98
|
+
- [Example scripts](https://github.com/ergodic-ai/ergodic/tree/v0.2.1/examples): runnable analyses of the bundled datasets.
|
|
99
99
|
|
|
100
100
|
</div>
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# Discover in Welt, estimate in ergodic
|
|
2
2
|
|
|
3
3
|
Learn a causal graph with a hosted Welt model, then identify and estimate an effect
|
|
4
|
-
on it in ergodic.
|
|
4
|
+
on it in ergodic. When the graph leaves variables to adjust for, Welt also fits the
|
|
5
|
+
nuisance models inside the estimate.
|
|
5
6
|
|
|
6
7
|
!!! need "You'll need"
|
|
7
8
|
- Python 3.11, 3.12 or 3.13.
|
|
@@ -72,8 +73,10 @@ depends on the Arrow version Welt serves. Each step prints:
|
|
|
72
73
|
variables, so a directed path from `nprenatal` to `bweight` is identified by
|
|
73
74
|
adjustment. The record names the adjustment set.
|
|
74
75
|
- **Step 4.** An `EffectEstimate(dml, analytic: …)` line: the average change in
|
|
75
|
-
grams per extra prenatal visit, its standard error and 95% interval. Then
|
|
76
|
-
|
|
76
|
+
grams per extra prenatal visit, its standard error and 95% interval. Then the
|
|
77
|
+
paid fits: 4 (two slots times two folds) when the adjustment set has variables,
|
|
78
|
+
or 0 when it is empty, because a slot with no features is just a mean and runs
|
|
79
|
+
locally.
|
|
77
80
|
|
|
78
81
|
## Read the provenance
|
|
79
82
|
|
|
@@ -215,8 +218,10 @@ With no directed path from `nprenatal` to `bweight`, `identify_effect` returns t
|
|
|
215
218
|
`null` strategy: by the graph, the effect is zero. `strategy="dml"` then raises,
|
|
216
219
|
because DML estimates adjustment effects only.
|
|
217
220
|
|
|
218
|
-
With an empty adjustment set, DML's nuisance models have no features
|
|
219
|
-
`WeltLearner`
|
|
221
|
+
With an empty adjustment set, DML's nuisance models have no features: each one is
|
|
222
|
+
just the training mean. `WeltLearner` fits that locally, with no upload and no paid
|
|
223
|
+
fit, so the program prints `paid fits: 0` and the estimate is the regression slope
|
|
224
|
+
of `bweight` on `nprenatal` across folds.
|
|
220
225
|
|
|
221
226
|
</details>
|
|
222
227
|
|
|
@@ -36,6 +36,11 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
36
36
|
)
|
|
37
37
|
links = commands.add_parser("links", help="check internal links of a built site")
|
|
38
38
|
links.add_argument("output", type=Path, nargs="?", default=ROOT / "site")
|
|
39
|
+
links.add_argument(
|
|
40
|
+
"--retained-elsewhere",
|
|
41
|
+
action="store_true",
|
|
42
|
+
help="count links into the retained versions (site.toml previous) as present",
|
|
43
|
+
)
|
|
39
44
|
notebooks = commands.add_parser("notebooks", help="execute every notebook (release CI)")
|
|
40
45
|
notebooks.add_argument("--timeout", type=int, default=1800)
|
|
41
46
|
notebooks.add_argument("--workers", type=int, default=1)
|
|
@@ -93,7 +98,8 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
93
98
|
if args.command == "links":
|
|
94
99
|
from .links import check_links
|
|
95
100
|
|
|
96
|
-
|
|
101
|
+
elsewhere = load_site().previous_versions if args.retained_elsewhere else ()
|
|
102
|
+
problems = check_links(args.output, elsewhere)
|
|
97
103
|
for problem in problems:
|
|
98
104
|
print(problem, file=sys.stderr)
|
|
99
105
|
print(f"Links: {len(problems)} problem(s) in {args.output}")
|
|
@@ -22,9 +22,16 @@ def target_file(base: Path, reference: str) -> Path:
|
|
|
22
22
|
return target / "index.html" if target.is_dir() else target
|
|
23
23
|
|
|
24
24
|
|
|
25
|
-
def check_links(output: Path) -> list[str]:
|
|
25
|
+
def check_links(output: Path, elsewhere: tuple[str, ...] = ()) -> list[str]:
|
|
26
|
+
"""Problems with the site's internal links, assets and anchors.
|
|
27
|
+
|
|
28
|
+
``elsewhere`` names version folders (``"0.2.0"`` for ``v0.2.0/``) that another
|
|
29
|
+
build publishes next to this one: links into them count as present. The full
|
|
30
|
+
published site is checked without it.
|
|
31
|
+
"""
|
|
26
32
|
problems = []
|
|
27
33
|
root = output.resolve()
|
|
34
|
+
retained = [root / f"v{version}" for version in elsewhere]
|
|
28
35
|
for page in sorted(root.rglob("*.html")):
|
|
29
36
|
text = page.read_text()
|
|
30
37
|
for match in ATTRIBUTE.finditer(text):
|
|
@@ -39,6 +46,8 @@ def check_links(output: Path) -> list[str]:
|
|
|
39
46
|
if not target.is_relative_to(root):
|
|
40
47
|
problems.append(f"{where}: {reference} leaves the site")
|
|
41
48
|
continue
|
|
49
|
+
if any(target == folder or target.is_relative_to(folder) for folder in retained):
|
|
50
|
+
continue
|
|
42
51
|
if not target.exists():
|
|
43
52
|
problems.append(f"{where}: missing {reference}")
|
|
44
53
|
continue
|
|
@@ -46,19 +46,40 @@ def _is_boundary(pair: tuple[str, str]) -> bool:
|
|
|
46
46
|
return pair[0] == START or pair[1] == END
|
|
47
47
|
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
def _same_gap(a: float, b: float) -> bool:
|
|
50
|
+
return a == b or (math.isnan(a) and math.isnan(b))
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass(frozen=True, eq=False)
|
|
50
54
|
class ArcStats:
|
|
51
55
|
"""Traversal count and gap timing for one arc of the map.
|
|
52
56
|
|
|
53
57
|
``mean_gap`` and ``median_gap`` are seconds between the two events. A
|
|
54
|
-
START or END arc has no second event, so both are NaN there
|
|
55
|
-
|
|
58
|
+
START or END arc has no second event, so both are NaN there. Equality treats
|
|
59
|
+
two NaN gaps as equal, so identically built maps compare equal on every
|
|
60
|
+
Python (dataclass equality compares fields one by one from 3.13 on, where
|
|
61
|
+
``nan == nan`` is false).
|
|
56
62
|
"""
|
|
57
63
|
|
|
58
64
|
count: int
|
|
59
65
|
mean_gap: float
|
|
60
66
|
median_gap: float
|
|
61
67
|
|
|
68
|
+
def __eq__(self, other: object) -> bool:
|
|
69
|
+
if not isinstance(other, ArcStats):
|
|
70
|
+
return NotImplemented
|
|
71
|
+
return (
|
|
72
|
+
self.count == other.count
|
|
73
|
+
and _same_gap(self.mean_gap, other.mean_gap)
|
|
74
|
+
and _same_gap(self.median_gap, other.median_gap)
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
def __hash__(self) -> int:
|
|
78
|
+
def key(gap: float) -> float | None:
|
|
79
|
+
return None if math.isnan(gap) else gap
|
|
80
|
+
|
|
81
|
+
return hash((self.count, key(self.mean_gap), key(self.median_gap)))
|
|
82
|
+
|
|
62
83
|
|
|
63
84
|
@dataclass(frozen=True)
|
|
64
85
|
class ProcessMap:
|
|
@@ -51,11 +51,6 @@ def _features(X: Any) -> npt.NDArray[np.float64]:
|
|
|
51
51
|
matrix = np.asarray(X, dtype=np.float64)
|
|
52
52
|
if matrix.ndim != 2:
|
|
53
53
|
raise ValueError(f"X must be a 2-D matrix; got shape {matrix.shape}")
|
|
54
|
-
if matrix.shape[1] == 0:
|
|
55
|
-
raise ValueError(
|
|
56
|
-
"WeltLearner needs at least one feature column, and this slot has none (an "
|
|
57
|
-
"empty adjustment set). Leave this slot to the default learner"
|
|
58
|
-
)
|
|
59
54
|
if matrix.shape[0] == 0:
|
|
60
55
|
raise ValueError("X has no rows")
|
|
61
56
|
return matrix
|
|
@@ -85,6 +80,11 @@ class WeltLearner:
|
|
|
85
80
|
is a paid prediction, sent in batches no larger than the model's prediction-row
|
|
86
81
|
limit. DML with ``n_folds=k`` fits ``2 * k`` times.
|
|
87
82
|
|
|
83
|
+
A slot with no feature columns (an empty adjustment set) has nothing for a model
|
|
84
|
+
to learn: its conditional mean is the training mean, or the class frequencies for
|
|
85
|
+
classification. The learner fits that locally, with no upload, no paid fit and no
|
|
86
|
+
charge to ``max_fits``, and ``predictor_id_`` is ``None``.
|
|
87
|
+
|
|
88
88
|
The learner and its clones share one budget of ``max_fits`` fits. It caps paid
|
|
89
89
|
fits only, not predictions: each fit is followed by one prediction call per
|
|
90
90
|
batch of held-out rows (DML on 8,000 rows with five folds and a 100-row limit
|
|
@@ -113,8 +113,10 @@ class WeltLearner:
|
|
|
113
113
|
leaves the job running in Welt.
|
|
114
114
|
|
|
115
115
|
Attributes:
|
|
116
|
-
predictor_id_: The fitted Welt predictor's id
|
|
117
|
-
|
|
116
|
+
predictor_id_: The fitted Welt predictor's id; ``None`` after a no-feature
|
|
117
|
+
(local) fit.
|
|
118
|
+
model_version_: The exact model version Welt fitted; ``None`` after a
|
|
119
|
+
no-feature fit.
|
|
118
120
|
classes_: The class labels, sorted (classification only).
|
|
119
121
|
supports_sample_weight: Always ``False``; read by ergodic's weighted slots.
|
|
120
122
|
"""
|
|
@@ -224,7 +226,8 @@ class WeltLearner:
|
|
|
224
226
|
"""Fit a Welt predictor on these rows: one paid fit.
|
|
225
227
|
|
|
226
228
|
Args:
|
|
227
|
-
X: The feature matrix, one row per unit
|
|
229
|
+
X: The feature matrix, one row per unit. With no columns the fit is the
|
|
230
|
+
local training mean (or class frequencies) and costs nothing.
|
|
228
231
|
y: The target, one value per row: numbers for regression, labels for
|
|
229
232
|
classification.
|
|
230
233
|
sample_weight: Must be ``None``. Welt fits take no row weights, and a
|
|
@@ -234,8 +237,8 @@ class WeltLearner:
|
|
|
234
237
|
This learner, fitted.
|
|
235
238
|
|
|
236
239
|
Raises:
|
|
237
|
-
ValueError: ``sample_weight`` was given, ``X`` has no
|
|
238
|
-
|
|
240
|
+
ValueError: ``sample_weight`` was given, ``X`` has no rows, or ``y``
|
|
241
|
+
does not match ``X``. Nothing is uploaded.
|
|
239
242
|
RuntimeError: The shared ``max_fits`` budget is spent.
|
|
240
243
|
"""
|
|
241
244
|
if sample_weight is not None:
|
|
@@ -254,10 +257,12 @@ class WeltLearner:
|
|
|
254
257
|
raise ValueError("regression targets must be finite")
|
|
255
258
|
else:
|
|
256
259
|
labels = [_label(value) for value in target]
|
|
260
|
+
for name in ("predictor_id_", "model_version_", "classes_", "_order", "_local"):
|
|
261
|
+
self.__dict__.pop(name, None)
|
|
262
|
+
if matrix.shape[1] == 0:
|
|
263
|
+
return self._fit_local(labels)
|
|
257
264
|
columns = [f"x{i}" for i in range(matrix.shape[1])]
|
|
258
265
|
rows = [[*row, label] for row, label in zip(matrix_rows(matrix), labels, strict=True)]
|
|
259
|
-
for name in ("predictor_id_", "model_version_", "classes_", "_order"):
|
|
260
|
-
self.__dict__.pop(name, None)
|
|
261
266
|
with using(self.client) as client:
|
|
262
267
|
# The free catalogue read comes first, so a failed lookup costs no fit
|
|
263
268
|
# and cannot strand a paid predictor.
|
|
@@ -278,8 +283,8 @@ class WeltLearner:
|
|
|
278
283
|
predictor = job.result(timeout=self.timeout)
|
|
279
284
|
if predictor.get("task") != self.task:
|
|
280
285
|
raise ValueError("Welt fitted a predictor for a different task")
|
|
281
|
-
self.predictor_id_: str = predictor["id"]
|
|
282
|
-
self.model_version_: str = predictor["model_version"]
|
|
286
|
+
self.predictor_id_: str | None = predictor["id"]
|
|
287
|
+
self.model_version_: str | None = predictor["model_version"]
|
|
283
288
|
self._n_features = matrix.shape[1]
|
|
284
289
|
if self.task == "classification":
|
|
285
290
|
classes = np.asarray(predictor["classes"])
|
|
@@ -287,6 +292,20 @@ class WeltLearner:
|
|
|
287
292
|
self.classes_ = classes[self._order]
|
|
288
293
|
return self
|
|
289
294
|
|
|
295
|
+
def _fit_local(self, labels: list[Any]) -> Self:
|
|
296
|
+
"""The no-feature fit: the training mean, or the class frequencies, kept here."""
|
|
297
|
+
self.predictor_id_ = None
|
|
298
|
+
self.model_version_ = None
|
|
299
|
+
self._n_features = 0
|
|
300
|
+
if self.task == "regression":
|
|
301
|
+
self._local: npt.NDArray[Any] = np.asarray([np.mean(labels)], dtype=np.float64)
|
|
302
|
+
return self
|
|
303
|
+
classes, counts = np.unique(np.asarray(labels), return_counts=True)
|
|
304
|
+
self.classes_ = classes
|
|
305
|
+
self._order = np.arange(len(classes))
|
|
306
|
+
self._local = counts / counts.sum()
|
|
307
|
+
return self
|
|
308
|
+
|
|
290
309
|
def _limit(self, client: Any) -> int | None:
|
|
291
310
|
"""The model's prediction-row limit from Welt's catalogue, if it states one."""
|
|
292
311
|
for profile in client.model(self.model).get("task_profiles", []):
|
|
@@ -295,12 +314,16 @@ class WeltLearner:
|
|
|
295
314
|
return int(limit) if limit else None
|
|
296
315
|
return None
|
|
297
316
|
|
|
298
|
-
def
|
|
317
|
+
def _checked(self, X: npt.NDArray[np.float64]) -> npt.NDArray[np.float64]:
|
|
299
318
|
if not hasattr(self, "predictor_id_"):
|
|
300
319
|
raise RuntimeError("WeltLearner is not fitted; call fit first")
|
|
301
320
|
matrix = _features(X)
|
|
302
321
|
if matrix.shape[1] != self._n_features:
|
|
303
322
|
raise ValueError(f"X has {matrix.shape[1]} columns; the fit had {self._n_features}")
|
|
323
|
+
return matrix
|
|
324
|
+
|
|
325
|
+
def _predict(self, X: npt.NDArray[np.float64], probabilities: bool) -> list[dict[str, Any]]:
|
|
326
|
+
matrix = self._checked(X)
|
|
304
327
|
columns = [f"x{i}" for i in range(matrix.shape[1])]
|
|
305
328
|
rows = matrix_rows(matrix)
|
|
306
329
|
size = self._budget.prediction_rows.get((self.model, self.task)) or len(rows)
|
|
@@ -334,6 +357,11 @@ class WeltLearner:
|
|
|
334
357
|
Numbers in the target's units for regression, class labels for
|
|
335
358
|
classification.
|
|
336
359
|
"""
|
|
360
|
+
if "_local" in self.__dict__:
|
|
361
|
+
n = self._checked(X).shape[0]
|
|
362
|
+
if self.task == "regression":
|
|
363
|
+
return np.full(n, self._local[0], dtype=np.float64)
|
|
364
|
+
return np.full(n, self.classes_[int(np.argmax(self._local))])
|
|
337
365
|
answers = self._predict(X, probabilities=False)
|
|
338
366
|
values = [value for answer in answers for value in answer["predictions"]]
|
|
339
367
|
if self.task == "regression":
|
|
@@ -341,6 +369,8 @@ class WeltLearner:
|
|
|
341
369
|
return np.asarray(values)
|
|
342
370
|
|
|
343
371
|
def _predict_proba(self, X: npt.NDArray[np.float64]) -> npt.NDArray[np.float64]:
|
|
372
|
+
if "_local" in self.__dict__:
|
|
373
|
+
return np.tile(self._local, (self._checked(X).shape[0], 1))
|
|
344
374
|
answers = self._predict(X, probabilities=True)
|
|
345
375
|
blocks = []
|
|
346
376
|
for answer in answers:
|
|
@@ -26,9 +26,7 @@ from scripts.docsite.examples import programs_of # noqa: E402
|
|
|
26
26
|
from tests.test_welt import FakeWelt # noqa: E402
|
|
27
27
|
|
|
28
28
|
|
|
29
|
-
def
|
|
30
|
-
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
31
|
-
) -> None:
|
|
29
|
+
def _run_page_program(fake: FakeWelt, monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> list[str]:
|
|
32
30
|
site = load_site()
|
|
33
31
|
page = site.pages_dir / "welt.md"
|
|
34
32
|
(program,) = [p for p in programs_of(page, site) if p.name == "welt_effect.py"]
|
|
@@ -36,7 +34,6 @@ def test_the_welt_page_program_runs_against_the_fake(
|
|
|
36
34
|
|
|
37
35
|
monkeypatch.delenv("WELT_WORKSPACE", raising=False)
|
|
38
36
|
monkeypatch.delenv("WELT_BASE_URL", raising=False)
|
|
39
|
-
fake = FakeWelt()
|
|
40
37
|
real = welt.Client
|
|
41
38
|
|
|
42
39
|
def client(*args: Any, **kwargs: Any) -> Any:
|
|
@@ -49,7 +46,14 @@ def test_the_welt_page_program_runs_against_the_fake(
|
|
|
49
46
|
printed = io.StringIO()
|
|
50
47
|
with contextlib.redirect_stdout(printed):
|
|
51
48
|
runpy.run_path(str(script), run_name="__main__")
|
|
52
|
-
|
|
49
|
+
return printed.getvalue().splitlines()
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_the_welt_page_program_runs_against_the_fake(
|
|
53
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
54
|
+
) -> None:
|
|
55
|
+
fake = FakeWelt()
|
|
56
|
+
lines = _run_page_program(fake, monkeypatch, tmp_path)
|
|
53
57
|
|
|
54
58
|
# The fake's discovery returns a chain over the uploaded column order (the last
|
|
55
59
|
# column left unconnected), so the page's story holds: a path from nprenatal to
|
|
@@ -69,6 +73,34 @@ def test_the_welt_page_program_runs_against_the_fake(
|
|
|
69
73
|
assert sent[("POST", "datasets")] == 5
|
|
70
74
|
|
|
71
75
|
|
|
76
|
+
def test_the_welt_page_program_runs_when_the_graph_leaves_nothing_to_adjust(
|
|
77
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
78
|
+
) -> None:
|
|
79
|
+
"""The graph production Arrow returned on 2026-10-15: nprenatal has no parents.
|
|
80
|
+
|
|
81
|
+
The adjustment set is empty, so both DML slots are training means fitted locally:
|
|
82
|
+
the page still prints an estimate, and the only paid request is the discovery.
|
|
83
|
+
"""
|
|
84
|
+
edges = [
|
|
85
|
+
("nprenatal", "bweight"),
|
|
86
|
+
("fage", "mage"),
|
|
87
|
+
("fage", "medu"),
|
|
88
|
+
("mage", "medu"),
|
|
89
|
+
]
|
|
90
|
+
flat = [[0.0 if i == j else 0.25 for j in range(5)] for i in range(5)]
|
|
91
|
+
fake = FakeWelt(native={"arrow": {"values": flat, "edges": edges, "decoder": {"name": "x"}}})
|
|
92
|
+
lines = _run_page_program(fake, monkeypatch, tmp_path)
|
|
93
|
+
|
|
94
|
+
assert "bweight <-- nprenatal" in lines
|
|
95
|
+
assert any(line.startswith("Identification(adjustment:") for line in lines)
|
|
96
|
+
assert any(line.startswith("EffectEstimate(dml, analytic:") for line in lines)
|
|
97
|
+
assert lines[-1] == "paid fits: 0"
|
|
98
|
+
sent = Counter((method, path.split("/")[2]) for method, path in fake.calls)
|
|
99
|
+
assert sent[("POST", "discoveries")] == 1
|
|
100
|
+
assert sent[("POST", "fits")] == 0
|
|
101
|
+
assert sent[("POST", "predictors")] == 0
|
|
102
|
+
|
|
103
|
+
|
|
72
104
|
# CDFM in the fake: an adaptive threshold of 0.3 and four native edges above it, three
|
|
73
105
|
# of which (medu -> nprenatal -> bweight -> medu) form a cycle.
|
|
74
106
|
_COLUMNS = ["mage", "medu", "nprenatal", "bweight", "fage"]
|
|
@@ -227,7 +227,12 @@ def test_build_is_versioned_and_linked(tmp_path: Path) -> None:
|
|
|
227
227
|
changelog = (output / "changelog.html").read_text()
|
|
228
228
|
release = re.search(r"^## \[(\d+\.\d+\.\d+)\]", (ROOT / "CHANGELOG.md").read_text(), re.M)
|
|
229
229
|
assert release is not None and f">{release[1]}</a> - " in changelog
|
|
230
|
-
|
|
230
|
+
# The retained versions come from their own tags' builds; until those are placed,
|
|
231
|
+
# links into them are the only problems, and --retained-elsewhere excuses them.
|
|
232
|
+
assert check_links(output, site.previous_versions) == []
|
|
233
|
+
missing = check_links(output)
|
|
234
|
+
assert missing # The picker links to every retained version.
|
|
235
|
+
assert all(any(f"v{v}/" in problem for v in site.previous_versions) for problem in missing)
|
|
231
236
|
|
|
232
237
|
|
|
233
238
|
def test_build_fails_on_an_undocumented_public_name(
|
|
@@ -214,6 +214,17 @@ def test_performance_unknown_lifecycle_column_raises() -> None:
|
|
|
214
214
|
# -- determinism ---------------------------------------------------------------
|
|
215
215
|
|
|
216
216
|
|
|
217
|
+
def test_arc_stats_with_nan_gaps_compare_and_hash_equal() -> None:
|
|
218
|
+
from ergodic.process._map import ArcStats
|
|
219
|
+
|
|
220
|
+
# Distinct NaN objects, so equality cannot lean on identity.
|
|
221
|
+
left = ArcStats(count=2, mean_gap=float("nan"), median_gap=float("nan"))
|
|
222
|
+
right = ArcStats(count=2, mean_gap=float("nan"), median_gap=float("nan"))
|
|
223
|
+
assert left == right and hash(left) == hash(right)
|
|
224
|
+
assert left != ArcStats(count=3, mean_gap=float("nan"), median_gap=float("nan"))
|
|
225
|
+
assert ArcStats(1, 2.0, 2.0) != ArcStats(1, 2.0, float("nan"))
|
|
226
|
+
|
|
227
|
+
|
|
217
228
|
def test_repeated_calls_give_equal_objects() -> None:
|
|
218
229
|
plain = _two_case_log()
|
|
219
230
|
skewed = _skewed_log()
|
|
@@ -588,9 +588,45 @@ def test_sample_weight_is_refused_not_ignored() -> None:
|
|
|
588
588
|
assert learner.fits_used == 0
|
|
589
589
|
|
|
590
590
|
|
|
591
|
-
def
|
|
592
|
-
|
|
593
|
-
|
|
591
|
+
def test_a_slot_with_no_features_is_fitted_locally_for_free() -> None:
|
|
592
|
+
"""An empty adjustment set leaves the mean (or class frequencies): no request, no fit."""
|
|
593
|
+
fake = FakeWelt()
|
|
594
|
+
with fake.client() as client:
|
|
595
|
+
regression = WeltLearner(client=client, max_fits=1).fit(
|
|
596
|
+
np.empty((4, 0)), np.array([1.0, 2, 3, 6])
|
|
597
|
+
)
|
|
598
|
+
np.testing.assert_allclose(regression.predict(np.empty((3, 0))), [3.0, 3.0, 3.0])
|
|
599
|
+
classifier = WeltLearner(task="classification", client=client, max_fits=1)
|
|
600
|
+
classifier.fit(np.empty((4, 0)), np.array([1.0, 0.0, 1.0, 1.0]))
|
|
601
|
+
np.testing.assert_allclose(classifier.predict_proba(np.empty((2, 0))), [[0.25, 0.75]] * 2)
|
|
602
|
+
assert classifier.predict(np.empty((2, 0))).tolist() == [1.0, 1.0]
|
|
603
|
+
with pytest.raises(ValueError, match="columns"):
|
|
604
|
+
regression.predict(np.ones((3, 1)))
|
|
605
|
+
assert fake.calls == []
|
|
606
|
+
assert regression.fits_used == 0 and classifier.fits_used == 0
|
|
607
|
+
assert regression.predictor_id_ is None and regression.model_version_ is None
|
|
608
|
+
assert classifier.classes_.tolist() == [0, 1]
|
|
609
|
+
with pytest.raises(ValueError, match="no rows"):
|
|
610
|
+
WeltLearner(client=object()).fit(np.empty((0, 0)), np.array([]))
|
|
611
|
+
|
|
612
|
+
|
|
613
|
+
def test_dml_with_an_empty_adjustment_set_pays_for_nothing() -> None:
|
|
614
|
+
"""No confounders: both DML nuisances are training means, exactly as ergodic's OLS."""
|
|
615
|
+
fake = FakeWelt()
|
|
616
|
+
data = continuous(n=200).select(["X", "Y"])
|
|
617
|
+
graph = dag(["X -> Y"])
|
|
618
|
+
native = estimate_effect(
|
|
619
|
+
graph, data, "X", "Y", strategy="dml", learner="ols", random_state=3, n_folds=2
|
|
620
|
+
)
|
|
621
|
+
with fake.client() as client:
|
|
622
|
+
learner = WeltLearner("tabicl-v2", client=client)
|
|
623
|
+
hosted = estimate_effect(
|
|
624
|
+
graph, data, "X", "Y", strategy="dml", learner=learner, random_state=3, n_folds=2
|
|
625
|
+
)
|
|
626
|
+
assert hosted.value == pytest.approx(native.value, rel=1e-9)
|
|
627
|
+
assert hosted.std_error == pytest.approx(native.std_error, rel=1e-9)
|
|
628
|
+
assert learner.fits_used == 0
|
|
629
|
+
assert fake.calls == []
|
|
594
630
|
|
|
595
631
|
|
|
596
632
|
def test_regression_fit_and_batched_predict() -> None:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|