ergodic 0.1.0__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.1.0/.gitignore +50 -0
- ergodic-0.1.0/CHANGELOG.md +394 -0
- ergodic-0.1.0/LICENSE +202 -0
- ergodic-0.1.0/NOTICE +42 -0
- ergodic-0.1.0/PKG-INFO +141 -0
- ergodic-0.1.0/README.md +101 -0
- ergodic-0.1.0/docs/assets/ergodic_logomark_dark.svg +9252 -0
- ergodic-0.1.0/docs/assets/ergodic_logomark_light.svg +9241 -0
- ergodic-0.1.0/docs/assets/extra.css +128 -0
- ergodic-0.1.0/docs/case-studies/01-birthweight.py +251 -0
- ergodic-0.1.0/docs/case-studies/02-berkeley.py +258 -0
- ergodic-0.1.0/docs/case-studies/03-pension401k.py +479 -0
- ergodic-0.1.0/docs/case-studies/04-synthetic-control.py +282 -0
- ergodic-0.1.0/docs/case-studies/05-discovery.py +289 -0
- ergodic-0.1.0/docs/case-studies/06-clv.py +362 -0
- ergodic-0.1.0/docs/case-studies/07-churn.py +369 -0
- ergodic-0.1.0/docs/case-studies/index.md +21 -0
- ergodic-0.1.0/docs/concepts.md +54 -0
- ergodic-0.1.0/docs/data.md +209 -0
- ergodic-0.1.0/docs/discovery.md +390 -0
- ergodic-0.1.0/docs/forecast.md +395 -0
- ergodic-0.1.0/docs/foundations.md +49 -0
- ergodic-0.1.0/docs/glossary.md +187 -0
- ergodic-0.1.0/docs/graph.md +163 -0
- ergodic-0.1.0/docs/identification.md +291 -0
- ergodic-0.1.0/docs/includes/abbreviations.md +50 -0
- ergodic-0.1.0/docs/index.md +121 -0
- ergodic-0.1.0/docs/inference.md +276 -0
- ergodic-0.1.0/docs/knowledge.md +126 -0
- ergodic-0.1.0/docs/learn/01-why-causality.py +226 -0
- ergodic-0.1.0/docs/learn/02-good-and-bad-controls.py +280 -0
- ergodic-0.1.0/docs/learn/03-estimating-effects.py +234 -0
- ergodic-0.1.0/docs/learn/04-heterogeneous-effects.py +181 -0
- ergodic-0.1.0/docs/learn/05-hidden-confounding.py +237 -0
- ergodic-0.1.0/docs/learn/06-discovering-structure.py +310 -0
- ergodic-0.1.0/docs/learn/07-quasi-experiments.py +352 -0
- ergodic-0.1.0/docs/learn/08-process-mining.py +390 -0
- ergodic-0.1.0/docs/learn/09-process-intelligence.py +368 -0
- ergodic-0.1.0/docs/learn/10-decision-points.py +361 -0
- ergodic-0.1.0/docs/learn/11-forecasting.py +415 -0
- ergodic-0.1.0/docs/learn/index.md +51 -0
- ergodic-0.1.0/docs/panel.md +150 -0
- ergodic-0.1.0/docs/pillars.md +44 -0
- ergodic-0.1.0/docs/process.md +398 -0
- ergodic-0.1.0/docs/reference.md +116 -0
- ergodic-0.1.0/docs/scm.md +228 -0
- ergodic-0.1.0/docs/tutorial.md +150 -0
- ergodic-0.1.0/examples/README.md +23 -0
- ergodic-0.1.0/examples/adjustment_birthweight.py +44 -0
- ergodic-0.1.0/examples/cate/01_cate_basics.py +93 -0
- ergodic-0.1.0/examples/cate/02_meta_learners_compared.py +71 -0
- ergodic-0.1.0/examples/cate/03_is_there_heterogeneity.py +79 -0
- ergodic-0.1.0/examples/cate/04_where_is_the_effect_higher.py +98 -0
- ergodic-0.1.0/examples/cate/05_targeting_with_rate.py +92 -0
- ergodic-0.1.0/examples/cate/06_per_unit_uncertainty.py +157 -0
- ergodic-0.1.0/examples/cate/07_cate_vs_ite.py +113 -0
- ergodic-0.1.0/examples/cate/08_identification_gating.py +100 -0
- ergodic-0.1.0/examples/cate/09_case_study_uplift_end_to_end.py +152 -0
- ergodic-0.1.0/examples/cate/10_causal_forest.py +115 -0
- ergodic-0.1.0/examples/cate/11_policy_trees.py +101 -0
- ergodic-0.1.0/examples/cate/12_model_selection.py +93 -0
- ergodic-0.1.0/examples/cate/13_case_study_phase2.py +191 -0
- ergodic-0.1.0/examples/cate/README.md +79 -0
- ergodic-0.1.0/examples/confounding_berkeley.py +41 -0
- ergodic-0.1.0/examples/discovery_asia.py +40 -0
- ergodic-0.1.0/examples/double_ml_pension.py +48 -0
- ergodic-0.1.0/examples/scm/01_three_rungs.py +101 -0
- ergodic-0.1.0/examples/scm/02_honesty_and_tipping_point.py +112 -0
- ergodic-0.1.0/examples/scm/03_causal_price_optimization.py +105 -0
- ergodic-0.1.0/examples/scm/04_counterfactual_recourse.py +113 -0
- ergodic-0.1.0/examples/scm/05_counterfactual_fairness.py +113 -0
- ergodic-0.1.0/examples/scm/06_causal_attribution.py +89 -0
- ergodic-0.1.0/examples/scm/07_but_for_accounting.py +94 -0
- ergodic-0.1.0/examples/scm/08_off_policy_evaluation.py +114 -0
- ergodic-0.1.0/examples/scm/09_root_cause_attribution.py +101 -0
- ergodic-0.1.0/examples/scm/10_frontdoor_unmeasured_confounder.py +87 -0
- ergodic-0.1.0/examples/scm/README.md +57 -0
- ergodic-0.1.0/examples/synthetic_control_smoking.py +29 -0
- ergodic-0.1.0/public/ergodic_logomark_dark.svg +9252 -0
- ergodic-0.1.0/public/ergodic_logomark_light.svg +9241 -0
- ergodic-0.1.0/pyproject.toml +170 -0
- ergodic-0.1.0/src/ergodic/__init__.py +216 -0
- ergodic-0.1.0/src/ergodic/data/__init__.py +51 -0
- ergodic-0.1.0/src/ergodic/data/_dataset.py +223 -0
- ergodic-0.1.0/src/ergodic/data/_eventlog.py +126 -0
- ergodic-0.1.0/src/ergodic/data/_frame.py +118 -0
- ergodic-0.1.0/src/ergodic/data/_hierarchy.py +126 -0
- ergodic-0.1.0/src/ergodic/data/_hts.py +118 -0
- ergodic-0.1.0/src/ergodic/data/_panel.py +113 -0
- ergodic-0.1.0/src/ergodic/data/_schema.py +311 -0
- ergodic-0.1.0/src/ergodic/data/_tabular.py +60 -0
- ergodic-0.1.0/src/ergodic/data/_timeseries.py +79 -0
- ergodic-0.1.0/src/ergodic/datasets/__init__.py +35 -0
- ergodic-0.1.0/src/ergodic/datasets/_bayesnet.py +210 -0
- ergodic-0.1.0/src/ergodic/datasets/_bunch.py +43 -0
- ergodic-0.1.0/src/ergodic/datasets/_bundled.py +263 -0
- ergodic-0.1.0/src/ergodic/datasets/_tables.py +75 -0
- ergodic-0.1.0/src/ergodic/datasets/data/birthweight.csv +4643 -0
- ergodic-0.1.0/src/ergodic/datasets/data/germany.csv +749 -0
- ergodic-0.1.0/src/ergodic/datasets/data/pension401k.csv +9916 -0
- ergodic-0.1.0/src/ergodic/datasets/data/smoking.csv +1210 -0
- ergodic-0.1.0/src/ergodic/discovery/__init__.py +94 -0
- ergodic-0.1.0/src/ergodic/discovery/_bootstrap.py +336 -0
- ergodic-0.1.0/src/ergodic/discovery/_ci.py +243 -0
- ergodic-0.1.0/src/ergodic/discovery/_discover.py +70 -0
- ergodic-0.1.0/src/ergodic/discovery/_encode.py +56 -0
- ergodic-0.1.0/src/ergodic/discovery/_granger.py +108 -0
- ergodic-0.1.0/src/ergodic/discovery/_lagged.py +172 -0
- ergodic-0.1.0/src/ergodic/discovery/_registry.py +199 -0
- ergodic-0.1.0/src/ergodic/discovery/_result.py +48 -0
- ergodic-0.1.0/src/ergodic/discovery/_score.py +214 -0
- ergodic-0.1.0/src/ergodic/discovery/_stats.py +71 -0
- ergodic-0.1.0/src/ergodic/discovery/_tsresult.py +68 -0
- ergodic-0.1.0/src/ergodic/discovery/algorithms/__init__.py +32 -0
- ergodic-0.1.0/src/ergodic/discovery/algorithms/_astar.py +269 -0
- ergodic-0.1.0/src/ergodic/discovery/algorithms/_boss.py +339 -0
- ergodic-0.1.0/src/ergodic/discovery/algorithms/_fci.py +275 -0
- ergodic-0.1.0/src/ergodic/discovery/algorithms/_ges.py +353 -0
- ergodic-0.1.0/src/ergodic/discovery/algorithms/_glasso.py +521 -0
- ergodic-0.1.0/src/ergodic/discovery/algorithms/_grasp.py +222 -0
- ergodic-0.1.0/src/ergodic/discovery/algorithms/_hc.py +284 -0
- ergodic-0.1.0/src/ergodic/discovery/algorithms/_lingam.py +361 -0
- ergodic-0.1.0/src/ergodic/discovery/algorithms/_mmpc.py +271 -0
- ergodic-0.1.0/src/ergodic/discovery/algorithms/_pc.py +287 -0
- ergodic-0.1.0/src/ergodic/discovery/algorithms/_pcmci.py +546 -0
- ergodic-0.1.0/src/ergodic/discovery/algorithms/_rfci.py +461 -0
- ergodic-0.1.0/src/ergodic/discovery/algorithms/_skeleton.py +86 -0
- ergodic-0.1.0/src/ergodic/discovery/ci/__init__.py +11 -0
- ergodic-0.1.0/src/ergodic/discovery/ci/_discrete.py +68 -0
- ergodic-0.1.0/src/ergodic/discovery/ci/_gcm.py +110 -0
- ergodic-0.1.0/src/ergodic/discovery/ci/_linear.py +73 -0
- ergodic-0.1.0/src/ergodic/discovery/ci/_mixed.py +64 -0
- ergodic-0.1.0/src/ergodic/discovery/scores/__init__.py +11 -0
- ergodic-0.1.0/src/ergodic/discovery/scores/_bdeu.py +95 -0
- ergodic-0.1.0/src/ergodic/discovery/scores/_bic.py +146 -0
- ergodic-0.1.0/src/ergodic/discovery/scores/_discrete_bic.py +85 -0
- ergodic-0.1.0/src/ergodic/discovery/scores/_mixed_bic.py +111 -0
- ergodic-0.1.0/src/ergodic/forecast/__init__.py +220 -0
- ergodic-0.1.0/src/ergodic/forecast/_attribution.py +678 -0
- ergodic-0.1.0/src/ergodic/forecast/_backtest.py +432 -0
- ergodic-0.1.0/src/ergodic/forecast/_baselines.py +237 -0
- ergodic-0.1.0/src/ergodic/forecast/_facade.py +734 -0
- ergodic-0.1.0/src/ergodic/forecast/_fit.py +1057 -0
- ergodic-0.1.0/src/ergodic/forecast/_mechanism.py +411 -0
- ergodic-0.1.0/src/ergodic/forecast/_metrics.py +335 -0
- ergodic-0.1.0/src/ergodic/forecast/_protocol.py +31 -0
- ergodic-0.1.0/src/ergodic/forecast/_reconcile.py +634 -0
- ergodic-0.1.0/src/ergodic/forecast/_result.py +347 -0
- ergodic-0.1.0/src/ergodic/forecast/_scalers.py +140 -0
- ergodic-0.1.0/src/ergodic/forecast/_schedule.py +449 -0
- ergodic-0.1.0/src/ergodic/forecast/_staged.py +774 -0
- ergodic-0.1.0/src/ergodic/forecast/_structure.py +129 -0
- ergodic-0.1.0/src/ergodic/forecast/mechanisms/__init__.py +26 -0
- ergodic-0.1.0/src/ergodic/forecast/mechanisms/_bass.py +253 -0
- ergodic-0.1.0/src/ergodic/forecast/mechanisms/_ou.py +346 -0
- ergodic-0.1.0/src/ergodic/forecast/mechanisms/_structural.py +360 -0
- ergodic-0.1.0/src/ergodic/graph/__init__.py +77 -0
- ergodic-0.1.0/src/ergodic/graph/_graph.py +598 -0
- ergodic-0.1.0/src/ergodic/graph/_io.py +141 -0
- ergodic-0.1.0/src/ergodic/graph/_marks.py +28 -0
- ergodic-0.1.0/src/ergodic/graph/_meek.py +231 -0
- ergodic-0.1.0/src/ergodic/graph/_pag.py +728 -0
- ergodic-0.1.0/src/ergodic/graph/_primitives.py +184 -0
- ergodic-0.1.0/src/ergodic/graph/_separation.py +231 -0
- ergodic-0.1.0/src/ergodic/graph/_surgery.py +80 -0
- ergodic-0.1.0/src/ergodic/identification/__init__.py +61 -0
- ergodic-0.1.0/src/ergodic/identification/_criteria.py +573 -0
- ergodic-0.1.0/src/ergodic/identification/_distribution.py +133 -0
- ergodic-0.1.0/src/ergodic/identification/_evaluate.py +172 -0
- ergodic-0.1.0/src/ergodic/identification/_functional.py +185 -0
- ergodic-0.1.0/src/ergodic/identification/_id.py +276 -0
- ergodic-0.1.0/src/ergodic/identification/_ida.py +163 -0
- ergodic-0.1.0/src/ergodic/identification/_mediation.py +351 -0
- ergodic-0.1.0/src/ergodic/identification/_recognize.py +110 -0
- ergodic-0.1.0/src/ergodic/identification/_record.py +47 -0
- ergodic-0.1.0/src/ergodic/inference/__init__.py +136 -0
- ergodic-0.1.0/src/ergodic/inference/_crossfit.py +63 -0
- ergodic-0.1.0/src/ergodic/inference/_design.py +32 -0
- ergodic-0.1.0/src/ergodic/inference/_estimate.py +163 -0
- ergodic-0.1.0/src/ergodic/inference/_estimator.py +817 -0
- ergodic-0.1.0/src/ergodic/inference/_learner.py +140 -0
- ergodic-0.1.0/src/ergodic/inference/_perunit.py +313 -0
- ergodic-0.1.0/src/ergodic/inference/_refute.py +212 -0
- ergodic-0.1.0/src/ergodic/inference/_registry.py +213 -0
- ergodic-0.1.0/src/ergodic/inference/_result.py +111 -0
- ergodic-0.1.0/src/ergodic/inference/_scores.py +227 -0
- ergodic-0.1.0/src/ergodic/inference/_select.py +278 -0
- ergodic-0.1.0/src/ergodic/inference/_stats.py +77 -0
- ergodic-0.1.0/src/ergodic/inference/_target.py +36 -0
- ergodic-0.1.0/src/ergodic/inference/estimators/__init__.py +49 -0
- ergodic-0.1.0/src/ergodic/inference/estimators/_did.py +868 -0
- ergodic-0.1.0/src/ergodic/inference/estimators/_dml.py +354 -0
- ergodic-0.1.0/src/ergodic/inference/estimators/_doubly_robust.py +152 -0
- ergodic-0.1.0/src/ergodic/inference/estimators/_forest.py +633 -0
- ergodic-0.1.0/src/ergodic/inference/estimators/_frontdoor.py +66 -0
- ergodic-0.1.0/src/ergodic/inference/estimators/_its.py +195 -0
- ergodic-0.1.0/src/ergodic/inference/estimators/_iv.py +188 -0
- ergodic-0.1.0/src/ergodic/inference/estimators/_mediation.py +599 -0
- ergodic-0.1.0/src/ergodic/inference/estimators/_meta.py +126 -0
- ergodic-0.1.0/src/ergodic/inference/estimators/_null.py +60 -0
- ergodic-0.1.0/src/ergodic/inference/estimators/_plugin.py +423 -0
- ergodic-0.1.0/src/ergodic/inference/estimators/_regression.py +169 -0
- ergodic-0.1.0/src/ergodic/inference/estimators/_synthetic_control.py +361 -0
- ergodic-0.1.0/src/ergodic/inference/estimators/_weighting.py +130 -0
- ergodic-0.1.0/src/ergodic/inference/explain/__init__.py +34 -0
- ergodic-0.1.0/src/ergodic/inference/explain/_blp.py +82 -0
- ergodic-0.1.0/src/ergodic/inference/explain/_calibration.py +103 -0
- ergodic-0.1.0/src/ergodic/inference/explain/_clan.py +100 -0
- ergodic-0.1.0/src/ergodic/inference/explain/_gates.py +103 -0
- ergodic-0.1.0/src/ergodic/inference/explain/_linalg.py +53 -0
- ergodic-0.1.0/src/ergodic/inference/explain/_policy.py +459 -0
- ergodic-0.1.0/src/ergodic/inference/explain/_rate.py +118 -0
- ergodic-0.1.0/src/ergodic/inference/explain/_record.py +110 -0
- ergodic-0.1.0/src/ergodic/inference/learners/__init__.py +17 -0
- ergodic-0.1.0/src/ergodic/inference/learners/_linear.py +152 -0
- ergodic-0.1.0/src/ergodic/inference/learners/_neighbors.py +107 -0
- ergodic-0.1.0/src/ergodic/inference/learners/_pymc.py +149 -0
- ergodic-0.1.0/src/ergodic/knowledge.py +444 -0
- ergodic-0.1.0/src/ergodic/process/__init__.py +106 -0
- ergodic-0.1.0/src/ergodic/process/_case_table.py +495 -0
- ergodic-0.1.0/src/ergodic/process/_cases.py +108 -0
- ergodic-0.1.0/src/ergodic/process/_conformance.py +461 -0
- ergodic-0.1.0/src/ergodic/process/_decisions.py +527 -0
- ergodic-0.1.0/src/ergodic/process/_filters.py +254 -0
- ergodic-0.1.0/src/ergodic/process/_map.py +212 -0
- ergodic-0.1.0/src/ergodic/process/_mine.py +489 -0
- ergodic-0.1.0/src/ergodic/process/_net.py +261 -0
- ergodic-0.1.0/src/ergodic/process/_panel.py +190 -0
- ergodic-0.1.0/src/ergodic/process/_performance.py +169 -0
- ergodic-0.1.0/src/ergodic/process/_soundness.py +103 -0
- ergodic-0.1.0/src/ergodic/process/_tree.py +146 -0
- ergodic-0.1.0/src/ergodic/process/_variants.py +64 -0
- ergodic-0.1.0/src/ergodic/py.typed +0 -0
- ergodic-0.1.0/src/ergodic/scm/__init__.py +51 -0
- ergodic-0.1.0/src/ergodic/scm/_bounds.py +225 -0
- ergodic-0.1.0/src/ergodic/scm/_compose.py +739 -0
- ergodic-0.1.0/src/ergodic/scm/_district.py +111 -0
- ergodic-0.1.0/src/ergodic/scm/_mechanism.py +762 -0
- ergodic-0.1.0/src/ergodic/scm/_noise.py +99 -0
- ergodic-0.1.0/src/ergodic/scm/_result.py +128 -0
- ergodic-0.1.0/src/ergodic/scm/_scm.py +1038 -0
- ergodic-0.1.0/tests/conftest.py +15 -0
- ergodic-0.1.0/tests/test_bayes.py +326 -0
- ergodic-0.1.0/tests/test_case_table.py +415 -0
- ergodic-0.1.0/tests/test_cate.py +463 -0
- ergodic-0.1.0/tests/test_cate_forest.py +332 -0
- ergodic-0.1.0/tests/test_cate_phase2_stress.py +619 -0
- ergodic-0.1.0/tests/test_cate_policy.py +243 -0
- ergodic-0.1.0/tests/test_cate_select.py +173 -0
- ergodic-0.1.0/tests/test_cate_stress.py +643 -0
- ergodic-0.1.0/tests/test_conformance.py +294 -0
- ergodic-0.1.0/tests/test_data.py +580 -0
- ergodic-0.1.0/tests/test_datasets.py +185 -0
- ergodic-0.1.0/tests/test_decisions.py +398 -0
- ergodic-0.1.0/tests/test_did.py +303 -0
- ergodic-0.1.0/tests/test_discovery.py +282 -0
- ergodic-0.1.0/tests/test_discovery_bootstrap.py +357 -0
- ergodic-0.1.0/tests/test_discovery_boss.py +376 -0
- ergodic-0.1.0/tests/test_discovery_ci.py +385 -0
- ergodic-0.1.0/tests/test_discovery_fci.py +336 -0
- ergodic-0.1.0/tests/test_discovery_ges.py +262 -0
- ergodic-0.1.0/tests/test_discovery_glasso.py +274 -0
- ergodic-0.1.0/tests/test_discovery_grasp.py +207 -0
- ergodic-0.1.0/tests/test_discovery_hc.py +226 -0
- ergodic-0.1.0/tests/test_discovery_lingam.py +246 -0
- ergodic-0.1.0/tests/test_discovery_mmpc.py +181 -0
- ergodic-0.1.0/tests/test_discovery_pc.py +613 -0
- ergodic-0.1.0/tests/test_discovery_pcmci.py +408 -0
- ergodic-0.1.0/tests/test_discovery_rfci.py +244 -0
- ergodic-0.1.0/tests/test_discovery_score.py +467 -0
- ergodic-0.1.0/tests/test_estimators_e2.py +208 -0
- ergodic-0.1.0/tests/test_forecast_backtest.py +412 -0
- ergodic-0.1.0/tests/test_forecast_backtest_mcmc.py +275 -0
- ergodic-0.1.0/tests/test_forecast_core.py +366 -0
- ergodic-0.1.0/tests/test_forecast_explain.py +697 -0
- ergodic-0.1.0/tests/test_forecast_facade.py +381 -0
- ergodic-0.1.0/tests/test_forecast_mechanisms.py +534 -0
- ergodic-0.1.0/tests/test_forecast_metrics.py +253 -0
- ergodic-0.1.0/tests/test_forecast_reconcile.py +1097 -0
- ergodic-0.1.0/tests/test_forecast_staged.py +836 -0
- ergodic-0.1.0/tests/test_forecast_structure.py +471 -0
- ergodic-0.1.0/tests/test_forecast_zoo.py +349 -0
- ergodic-0.1.0/tests/test_granger.py +72 -0
- ergodic-0.1.0/tests/test_graph.py +342 -0
- ergodic-0.1.0/tests/test_graph_primitives.py +266 -0
- ergodic-0.1.0/tests/test_id_algorithm.py +548 -0
- ergodic-0.1.0/tests/test_id_integration.py +454 -0
- ergodic-0.1.0/tests/test_identification.py +239 -0
- ergodic-0.1.0/tests/test_identification_classes.py +284 -0
- ergodic-0.1.0/tests/test_inductive_miner.py +224 -0
- ergodic-0.1.0/tests/test_inference.py +483 -0
- ergodic-0.1.0/tests/test_its.py +145 -0
- ergodic-0.1.0/tests/test_knowledge.py +323 -0
- ergodic-0.1.0/tests/test_kpi_panel.py +276 -0
- ergodic-0.1.0/tests/test_mediation.py +456 -0
- ergodic-0.1.0/tests/test_pag.py +664 -0
- ergodic-0.1.0/tests/test_plugin.py +479 -0
- ergodic-0.1.0/tests/test_process_filters.py +249 -0
- ergodic-0.1.0/tests/test_process_map.py +224 -0
- ergodic-0.1.0/tests/test_refute.py +94 -0
- ergodic-0.1.0/tests/test_review_fixes.py +64 -0
- ergodic-0.1.0/tests/test_scm.py +937 -0
- ergodic-0.1.0/tests/test_scm_pymc.py +1184 -0
- ergodic-0.1.0/tests/test_sklearn_interop.py +92 -0
- ergodic-0.1.0/tests/test_smoke.py +103 -0
- ergodic-0.1.0/tests/test_synthetic_control.py +216 -0
- ergodic-0.1.0/tests/test_temporal_wiring.py +93 -0
- ergodic-0.1.0/tests/test_workflow_net.py +342 -0
ergodic-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
plans/
|
|
2
|
+
.devcontainer/
|
|
3
|
+
|
|
4
|
+
# ---- Python ----
|
|
5
|
+
__pycache__/
|
|
6
|
+
*.py[cod]
|
|
7
|
+
*$py.class
|
|
8
|
+
*.so
|
|
9
|
+
|
|
10
|
+
# Distribution / packaging
|
|
11
|
+
.Python
|
|
12
|
+
build/
|
|
13
|
+
dist/
|
|
14
|
+
wheels/
|
|
15
|
+
*.egg-info/
|
|
16
|
+
.eggs/
|
|
17
|
+
|
|
18
|
+
# Virtual environments
|
|
19
|
+
.venv/
|
|
20
|
+
venv/
|
|
21
|
+
env/
|
|
22
|
+
|
|
23
|
+
# Test / type / lint caches
|
|
24
|
+
.pytest_cache/
|
|
25
|
+
.mypy_cache/
|
|
26
|
+
.dmypy.json
|
|
27
|
+
.ruff_cache/
|
|
28
|
+
.cache/
|
|
29
|
+
htmlcov/
|
|
30
|
+
.coverage
|
|
31
|
+
.coverage.*
|
|
32
|
+
coverage.xml
|
|
33
|
+
|
|
34
|
+
# Docs build
|
|
35
|
+
site/
|
|
36
|
+
|
|
37
|
+
# Jupyter
|
|
38
|
+
.ipynb_checkpoints/
|
|
39
|
+
|
|
40
|
+
# Environments / secrets
|
|
41
|
+
.env
|
|
42
|
+
|
|
43
|
+
# OS / editor
|
|
44
|
+
.DS_Store
|
|
45
|
+
*.swp
|
|
46
|
+
.idea/
|
|
47
|
+
.vscode/
|
|
48
|
+
|
|
49
|
+
# Claude Code local config
|
|
50
|
+
.claude/
|
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- Initial project scaffolding: the `ergodic` package with `discovery`, `inference`,
|
|
12
|
+
and `process` subpackages (placeholders).
|
|
13
|
+
- Tooling: uv project layout, ruff (lint + format), mypy (strict), pytest.
|
|
14
|
+
- MkDocs Material documentation skeleton, pre-commit hooks, and a Makefile.
|
|
15
|
+
- Causal graph objects (`ergodic.graph`): a mixed-graph core with typed endpoint
|
|
16
|
+
marks covering DAG, ADMG, MAG, CPDAG, and PAG; immutable value semantics; d- and
|
|
17
|
+
m-separation; the do-operator and subgraphs; networkx, adjacency, and dict interop.
|
|
18
|
+
- Domain knowledge (`ergodic.knowledge`): hard prior constraints (required and
|
|
19
|
+
forbidden edges, tiers, roots, sinks, confounded pairs) with composition, conflict
|
|
20
|
+
detection, and projections (`permits_edge`, `is_consistent`, `apply_to`, `edge_mask`).
|
|
21
|
+
- Identification (`ergodic.identification`): `identify_effect` plus the complete
|
|
22
|
+
adjustment criterion, the front-door criterion, and conditional instruments, turning a
|
|
23
|
+
graph and a treatment/outcome into an estimand.
|
|
24
|
+
- Typed data objects (`ergodic.data`): a `Dataset` base with a column `Schema`
|
|
25
|
+
(continuous, categorical, ordinal, datetime, excluded) across five shapes,
|
|
26
|
+
`tabular`, `timeseries`, `panel`, `hts` (with a `Hierarchy` aggregation tree),
|
|
27
|
+
and `event_log`. Immutable, with type inference and overrides, codes and a float
|
|
28
|
+
matrix, missing-value handling, and `aligns_with(graph)`. A dataframe builds it
|
|
29
|
+
and stays at the boundary; pandas native, polars and arrow through `to_pandas`.
|
|
30
|
+
- Effect estimation (`ergodic.inference`): `estimate_effect` identifies an effect
|
|
31
|
+
and estimates it over `TabularData`, returning a distribution-shaped
|
|
32
|
+
`EffectEstimate` (a point with an analytic or bootstrap standard error, or
|
|
33
|
+
draws). A `CausalEstimator` is the strategy and a `Learner` is the model in each
|
|
34
|
+
nuisance slot; the learner is structurally a sklearn estimator. Adjustment:
|
|
35
|
+
`OutcomeRegression` (S- and T-learner), `IPW`, `Stratification`, `AIPW`, and the
|
|
36
|
+
cross-fit `DML`, `DRLearner`, `RLearner`, and `XLearner`; front-door:
|
|
37
|
+
`TwoStageRegression`; instruments: `TwoStageLeastSquares`, `Wald`, and `PLIV`.
|
|
38
|
+
Heterogeneous effects (CATE) through `cate`, binary or continuous treatment for
|
|
39
|
+
`DML` and `PLIV`, and native `OLS`, `LogisticRegression`, and `KNN` learners.
|
|
40
|
+
- Bayesian learners (`ergodic.inference`): a `BayesianLearner` carries a posterior
|
|
41
|
+
over its mean function (detected by `predict_draws`), and a `PyMCLearner` /
|
|
42
|
+
`PyMCClassifier` wraps a user-written PyMC model (pymc optional, `ergodic[bayes]`).
|
|
43
|
+
`OutcomeRegression` (the Bayesian g-formula) and `AIPW` (doubly robust, with a
|
|
44
|
+
Dirichlet Bayesian bootstrap) return a posterior `EffectEstimate` through the same
|
|
45
|
+
accessors, and the T-learner reports per-unit CATE credible intervals via
|
|
46
|
+
`cate_draws` and `cate_interval`.
|
|
47
|
+
- Explicit treatment coding: `estimate_effect(..., treated=...)` picks which level
|
|
48
|
+
counts as treated, and an implicit non-numeric coding now warns with the chosen
|
|
49
|
+
levels (a string treatment used to be coded by alphabetical order, silently).
|
|
50
|
+
- The `"null"` identification strategy and `NullEffect` estimator: a graph with no
|
|
51
|
+
directed path from treatment to outcome identifies trivially as a zero effect.
|
|
52
|
+
- `as_kind` accepts the kind name as a string (`g.as_kind("admg")`).
|
|
53
|
+
- Stratified cross-fitting folds for binary treatments, bootstrap shortfall
|
|
54
|
+
diagnostics, and a clear error when a treatment arm is empty.
|
|
55
|
+
- Causal discovery (`ergodic.discovery`), the first build: a conditional
|
|
56
|
+
independence test is a class constructed with data (the stateful oracle:
|
|
57
|
+
encoding once, sufficient statistics, cached alpha-independent queries),
|
|
58
|
+
with `Linear` (partial correlation), `Discrete` (G squared, natively
|
|
59
|
+
categorical), `Mixed` (degenerate Gaussian for mixed columns), and a
|
|
60
|
+
`DSeparationOracle` ground truth; a decomposable `Score` answers
|
|
61
|
+
`local(node, parents)` with `BIC` (Gaussian); PC-stable orients through
|
|
62
|
+
v-structures and the Meek rules and returns a CPDAG with separating sets;
|
|
63
|
+
`AStar` is exact search over the order lattice; `discover(data, method=...)`
|
|
64
|
+
is the facade and `discover` re-exports at the top level. Background
|
|
65
|
+
knowledge enters in four modes (prune, protect, orient, validate), and
|
|
66
|
+
`DomainKnowledge.restrict_to(skeleton)` confines any search to an adjacency
|
|
67
|
+
support. The Meek rules, `cpdag_of_dag`, and the Dor-Tarsi extension check
|
|
68
|
+
live in `ergodic.graph`, which makes `is_cpdag` a real validator.
|
|
69
|
+
- Causal discovery, the score stack: `GES` (Chickering's greedy equivalence
|
|
70
|
+
search, held to the A* optimum in the tests) and `HillClimb` (steepest
|
|
71
|
+
ascent with a tabu list) consume the same `Score` contract as `AStar`;
|
|
72
|
+
`DiscreteBIC` (multinomial), `BDeu`, and `MixedBIC` (degenerate Gaussian,
|
|
73
|
+
no edge restriction) join the Gaussian `BIC`, with executable
|
|
74
|
+
score-equivalence tests; the `Score` base gains a native-categorical codes
|
|
75
|
+
form (`local_from_codes`); `score="auto"` picks the score from the column
|
|
76
|
+
schema the way `test="auto"` picks the test; and `GCM` (Shah and Peters)
|
|
77
|
+
turns any regression `Learner`, native or sklearn, into a conditional
|
|
78
|
+
independence test that catches nonlinear dependence the partial correlation
|
|
79
|
+
misses. `DSeparationOracle` now answers about the observed margin (latent
|
|
80
|
+
nodes are not query-able variables).
|
|
81
|
+
- Causal discovery with latent confounders: `FCI` returns a PAG (stable
|
|
82
|
+
skeleton, the possible-d-sep phase, Zhang's complete rule closure, no
|
|
83
|
+
selection bias), held in the tests to the canonical PAG of the truth under
|
|
84
|
+
a perfect oracle. The graph layer grew the toolkit underneath:
|
|
85
|
+
m-separation now reads circle marks through definite-status paths (the
|
|
86
|
+
class-invariant answer on a complete PAG), `mag_of_dag` is the latent
|
|
87
|
+
projection, `pag_of_mag`/`mag_of_pag` convert between a MAG and its
|
|
88
|
+
equivalence class, `fci_closure` joins `meek_closure`, `set_mark` edits one
|
|
89
|
+
endpoint, and `is_pag` became a real complete-PAG validator by round trip,
|
|
90
|
+
exactly as `is_cpdag` did. Domain knowledge translates to PAGs through
|
|
91
|
+
ancestry: tiers pin an arrowhead at the later end, `confound(a, b)` orients
|
|
92
|
+
a bidirected edge, directional forbids and roots/sinks validate only.
|
|
93
|
+
- Skeleton screening and stability: `MMPC` (max-min parents-children, AND
|
|
94
|
+
rule) returns an undirected support for `restrict_to`, recovering the MMHC
|
|
95
|
+
hybrid as a composition; `bootstrap` reruns any method on row resamples and
|
|
96
|
+
returns adjacency and orientation frequencies with a thresholded
|
|
97
|
+
`summary()` graph, the first soft output. `Dataset.take(rows)` resamples
|
|
98
|
+
rows, and `discover` dispatches on each algorithm's declared ingredient.
|
|
99
|
+
- Identification on equivalence classes: `identify_effect`, `adjustment_set`,
|
|
100
|
+
`minimal_adjustment_set`, and `is_valid_adjustment_set` accept a CPDAG,
|
|
101
|
+
MAG, or PAG and answer through the generalized adjustment criterion
|
|
102
|
+
(Perkovic et al., 2018), so a success holds in every graph the class
|
|
103
|
+
represents and `estimate_effect` runs straight off a discovery result.
|
|
104
|
+
Held executably to the all-members definition: exhaustive over candidate
|
|
105
|
+
sets against the enumerated members of small random classes. The graph
|
|
106
|
+
layer grew the toolkit underneath: `possible_descendants` and
|
|
107
|
+
`possible_ancestors` (possibly directed reachability), `visible_edges`
|
|
108
|
+
(Zhang's no-hidden-confounding certificate), and the member enumerators
|
|
109
|
+
`dags_of_cpdag` and `mags_of_pag` (deterministic, capped without silent
|
|
110
|
+
truncation). The front-door and instruments raise on a class and point at
|
|
111
|
+
the enumerators (the per-member IDA pattern); mixed graphs raise.
|
|
112
|
+
- Quasi-experimental designs (`ergodic.inference`): estimators that read the
|
|
113
|
+
panel structure instead of a graph, each stating its identification in a
|
|
114
|
+
`Design` record and returning the same `EffectEstimate` surface. `did` over
|
|
115
|
+
a `PanelData` runs the canonical two-period collapse for a single adoption
|
|
116
|
+
date and Callaway-Sant'Anna group-time ATTs for staggered adoption (with
|
|
117
|
+
`comparison="never_treated"` or `"not_yet_treated"` and unit-bootstrap
|
|
118
|
+
inference); static two-way fixed effects ships as an explicit strategy with
|
|
119
|
+
its staggered-adoption caveat and is never auto-picked. `event_study`
|
|
120
|
+
reports ATT by event time with leads as the visible pre-trend check.
|
|
121
|
+
`synthetic_control` fits simplex weights on the pre-period by projected
|
|
122
|
+
gradient and reports per-period gaps with Abadie's in-space placebo
|
|
123
|
+
inference (the RMSPE-ratio rank p-value; intervals come from the placebo
|
|
124
|
+
band on purpose). `interrupted_time_series` over a `TimeSeriesData` is
|
|
125
|
+
segmented regression with level and slope changes, optional seasonal terms,
|
|
126
|
+
and Newey-West standard errors. All four re-export at the top level.
|
|
127
|
+
- Temporal causal discovery (`ergodic.discovery`): `discover(data,
|
|
128
|
+
method="pcmci", max_lag=...)` accepts a `TimeSeriesData` or `PanelData`
|
|
129
|
+
(panels pool windows within entities) and returns a
|
|
130
|
+
`TimeSeriesDiscoveryResult` holding a window graph over lagged nodes like
|
|
131
|
+
`X[t-1]`, per-link p-values, and a variable-level `summary_edges()`. The
|
|
132
|
+
algorithm is PCMCI-shaped: a PC1 lagged-parent screen, momentary
|
|
133
|
+
conditional independence tests for lagged links (conditioning sets drawn
|
|
134
|
+
from a deeper lagged frame), a PC-stable contemporaneous phase conditioned
|
|
135
|
+
on lagged parents, and collider plus within-slice Meek orientation. It
|
|
136
|
+
reuses the existing `CITest` stack through `lagged_tabular`, so
|
|
137
|
+
`test="auto"` and the cache come free. `granger` is the predictive
|
|
138
|
+
baseline and returns a pair-level table rather than a graph (feedback
|
|
139
|
+
across lags is normal; the docstring says what Granger does not claim).
|
|
140
|
+
Shape guards keep the methods honest: iid methods reject temporal data
|
|
141
|
+
(rows are not exchangeable), `pcmci` rejects tabular data, and `bootstrap`
|
|
142
|
+
refuses temporal methods until a moving-block variant exists.
|
|
143
|
+
- The learn series: six beginner notebook guides (`docs/learn/`) that teach
|
|
144
|
+
causal inference from zero on commercial examples, confounding and Simpson's
|
|
145
|
+
paradox, good and bad controls, estimation with doubly robust and cross-fit
|
|
146
|
+
estimators, heterogeneous effects and uplift targeting, instruments and the
|
|
147
|
+
front door, and structure discovery ending at the discover-then-estimate
|
|
148
|
+
bridge. Each guide is a py:percent script that mkdocs-jupyter executes at
|
|
149
|
+
build time with fixed seeds, so the outputs and figures on the site are real
|
|
150
|
+
and a broken example fails `make docs`. Docs review alongside: the API
|
|
151
|
+
reference gains the discovery section (guide pages no longer inline `:::`
|
|
152
|
+
blocks), stale forward references brought current, and the index routes
|
|
153
|
+
beginners to the series.
|
|
154
|
+
- Learn guide 7, quasi-experiments: a staggered promo rollout recovered with
|
|
155
|
+
difference-in-differences and an event study, and a flagship market's price
|
|
156
|
+
change measured against its synthetic control, with the naive comparisons
|
|
157
|
+
failing first. A new "Panel and time-series designs" guide page documents
|
|
158
|
+
the design family, and the discovery guide gains its time-series section.
|
|
159
|
+
- Process intelligence (`ergodic.process`), the first build. The descriptive
|
|
160
|
+
layer recovers the daily workflow over an `EventLog`: `process_map` (the
|
|
161
|
+
directly-follows map as a value object with frequency and gap statistics
|
|
162
|
+
and a `simplify` that records exactly what it dropped), `performance`
|
|
163
|
+
(cycle times, and a waiting/service split when a lifecycle column carries
|
|
164
|
+
start/complete pairs; with one timestamp per activity the split is honestly
|
|
165
|
+
impossible and the result says so), `variant_table`, and six composable
|
|
166
|
+
filters (time, variants, endpoints, activities, follows, performance), each
|
|
167
|
+
declaring whether it drops events or whole cases and refusing to return an
|
|
168
|
+
empty log. The causal layer is the point: `case_table` encodes a log into
|
|
169
|
+
one row per case with timestamp-gated features (case-start features in
|
|
170
|
+
`when="anytime"`; landmark modes `("after_events", k)` and
|
|
171
|
+
`("after_elapsed", x)` measure prefix features at the landmark, drop
|
|
172
|
+
already-treated cases with counts, and state their population), the named
|
|
173
|
+
process confounders (open-case workload, calendar features), and an
|
|
174
|
+
auto-built `DomainKnowledge` whose tiers forbid post-treatment adjustment
|
|
175
|
+
mechanically, feeding `discover`, `identify_effect`, and `estimate_effect`
|
|
176
|
+
unchanged; `kpi_panel` aggregates a log into a `PanelData` (or a single
|
|
177
|
+
`TimeSeriesData`) so a process change becomes a natural experiment for the
|
|
178
|
+
quasi-experimental designs. Learn guide 8 walks the whole arc: the process
|
|
179
|
+
map convicts a review step of double its true cost, the case table acquits
|
|
180
|
+
it, and a routing rollout is measured with `kpi_panel` into `did`.
|
|
181
|
+
- The process model layer (`ergodic.process`, second build). `discover_model`
|
|
182
|
+
is the Inductive Miner implemented from the papers (pm4py is AGPL and is not
|
|
183
|
+
used): recursive cuts of the directly-follows graph (exclusive choice,
|
|
184
|
+
sequence, parallel, loop) into a block-structured `ProcessTree` (build one
|
|
185
|
+
by hand with `seq`, `xor`, `par`, `loop`, and `TAU`; structural equality,
|
|
186
|
+
canonical child order), with the infrequent variant behind `noise=` and
|
|
187
|
+
provenance on the returned `ProcessModel` (flower fall-through paths,
|
|
188
|
+
filtered arcs, dropped events). `ProcessTree.to_net()` converts to a
|
|
189
|
+
`WorkflowNet` that is sound by construction, and `is_sound` verifies any
|
|
190
|
+
net for real by reachability (option to complete, proper completion, no
|
|
191
|
+
dead transitions; capped, raising past the cap rather than guessing).
|
|
192
|
+
`conformance` checks a log against a model by token replay (the standard
|
|
193
|
+
missing/remaining/consumed/produced fitness) and by optimal alignments
|
|
194
|
+
(Dijkstra over the synchronous product with deterministic tie-breaking;
|
|
195
|
+
per-variant costs and sync/log/model moves). `decision_points` and
|
|
196
|
+
`decision_table` read an exclusive split causally: the branch taken is the
|
|
197
|
+
treatment, the landmark is the moment the case reveals its branch, every
|
|
198
|
+
covariate is measured strictly before it (workload, calendar, prefix
|
|
199
|
+
features, case attributes), the auto-built `DomainKnowledge` tiers order
|
|
200
|
+
covariates before branch before outcome, the default outcome is the
|
|
201
|
+
remaining time from the decision, and a first-class `PositivityReport`
|
|
202
|
+
(per-branch counts and shares, propensity overlap for two-way splits)
|
|
203
|
+
warns when the branches barely overlap. Splits with a silent branch are
|
|
204
|
+
refused with the reason. Learn guide 9 walks the arc: the miner finds the
|
|
205
|
+
routing split, conformance separates deviation from structure, the naive
|
|
206
|
+
lane comparison overstates the cost of review by a factor of three, and
|
|
207
|
+
the decision table recovers the planted effect.
|
|
208
|
+
- Learn guide 8, process mining: the descriptive workflow on its own, in
|
|
209
|
+
three steps on a simulated fulfillment flow, with the process maps drawn
|
|
210
|
+
as real graphs (traffic-scaled arrows, counts and median gaps on every
|
|
211
|
+
arc): see the process, measure it (the waiting-versus-service split finds
|
|
212
|
+
the planted pick queue, eight hours of waiting in front of 26 minutes of
|
|
213
|
+
work), and drill into the slice that hurts, with `simplify` declaring what
|
|
214
|
+
it dropped. The process guides renumber to make room: process intelligence
|
|
215
|
+
is guide 9 and decision points is guide 10.
|
|
216
|
+
- Selection bias end to end in the graph layer: `is_mag` reads the full
|
|
217
|
+
Richardson-Spirtes ancestral conditions (undirected endpoints carry no
|
|
218
|
+
arrowheads), `mag_of_dag` projects latent and selection nodes together
|
|
219
|
+
through the one-witness-set theorem, `fci_closure` runs Zhang's complete
|
|
220
|
+
ten rules (R5 to R7 are the selection rules), `mag_of_pag` resolves
|
|
221
|
+
circle-tail edges by arrowhead augmentation, and `mags_of_pag` enumerates
|
|
222
|
+
the selection members behind an explicit `selection=` switch. The
|
|
223
|
+
d-separation oracle conditions every query on `graph.selection`, so FCI
|
|
224
|
+
needs no algorithmic change and lands exactly on the canonical PAG of
|
|
225
|
+
random latent-plus-selection worlds, held executably (Richardson-Spirtes
|
|
226
|
+
Theorem 4.18, round trips, and the chordless-cycle world only R5 can
|
|
227
|
+
orient). Identification raises on selection marks instead of silently
|
|
228
|
+
misreading them.
|
|
229
|
+
- Discovery D4, the iid completion. PC gains `colliders="conservative"`
|
|
230
|
+
and `"majority"` (Ramsey 2006; Colombo-Maathuis 2014): unshielded triples
|
|
231
|
+
are voted across both neighborhoods' subsets, ambiguous triples are never
|
|
232
|
+
oriented and gate Meek R1/R3 through `meek_closure`'s new `ambiguous=`
|
|
233
|
+
parameter, and the face stays honest. RFCI (`method="rfci"`) skips FCI's
|
|
234
|
+
possible-d-sep phase, re-tests triples and discriminating paths instead,
|
|
235
|
+
and returns an RFCI-PAG whose marks stay sound (held executably against
|
|
236
|
+
random latent, confounded, and selection worlds, with exact FCI agreement
|
|
237
|
+
wherever possible-d-sep has nothing to do). BOSS and GRaSP (`method=
|
|
238
|
+
"boss"`, `"grasp"`) search permutations through a grow-shrink projection,
|
|
239
|
+
BOSS with a BES pass reusing GES's backward phase, both held to the A*
|
|
240
|
+
optimum. DirectLiNGAM (`method="lingam"`) fully orients a DAG from
|
|
241
|
+
non-Gaussian data with the Hyvarinen-Smith pairwise likelihood ratio and
|
|
242
|
+
native backward-BIC pruning. A graphical-lasso screen (`method="glasso"`)
|
|
243
|
+
estimates a sparse precision by block coordinate descent (matching
|
|
244
|
+
sklearn entry for entry), picks the penalty by EBIC at the
|
|
245
|
+
support-restricted refit MLE, and feeds `restrict_to` like MMPC. The
|
|
246
|
+
facade learns methods that consume neither a test nor a score.
|
|
247
|
+
- Temporal unblockers: `bootstrap` accepts `method="pcmci"` through a
|
|
248
|
+
moving-block design (a series resamples blocks that become entities of a
|
|
249
|
+
panel, so no lag window ever crosses a block seam; a panel resamples
|
|
250
|
+
whole entities; `block_length` overrides the default), and PCMCI reads
|
|
251
|
+
`DomainKnowledge` with variables as processes: forbids, tiers, roots, and
|
|
252
|
+
sinks prune at every lag, tiers orient contemporaneous edges, self-memory
|
|
253
|
+
survives, `require` validates after the run, and `confound` raises.
|
|
254
|
+
- Real-world datasets (`ergodic.datasets`), the first set: sklearn-style loaders
|
|
255
|
+
that return a `Bunch` carrying the typed dataset under `data` plus its causal
|
|
256
|
+
question (treatment, outcome, confounders or instrument, and the `target` the
|
|
257
|
+
literature already measured). `load_birthweight` (maternal smoking, backdoor
|
|
258
|
+
adjustment), `load_berkeley` (the 1973 admissions table, Simpson's paradox),
|
|
259
|
+
`load_pension401k` (401(k) eligibility, double machine learning), `load_smoking`
|
|
260
|
+
(Proposition 99, synthetic control) and `load_germany` (reunification), and
|
|
261
|
+
`load_asia` / `load_earthquake` (small networks written from their papers and
|
|
262
|
+
sampled, for discovery). The bundled data ships in the wheel; provenance and
|
|
263
|
+
citations are in the NOTICE. Example scripts in `examples/` run each analysis
|
|
264
|
+
next to its known answer, and the tests check the recovery (smoking lands within
|
|
265
|
+
the placebo band at -21 packs, double ML moves the pension gap from $19.5k to
|
|
266
|
+
about $9k, the Berkeley gap reverses under department).
|
|
267
|
+
- Refutation (`ergodic.inference`): `refute_effect(graph, data, treatment, outcome,
|
|
268
|
+
method=...)` stress-tests an estimate by re-running it under a perturbation and
|
|
269
|
+
returns a `Refutation`. The model-agnostic checks: `"placebo_treatment"` scrambles
|
|
270
|
+
the treatment and the effect should vanish, `"random_common_cause"` adds an
|
|
271
|
+
irrelevant covariate and the effect should hold, `"data_subset"` re-estimates on a
|
|
272
|
+
random slice. None proves an estimate right; a failure is a real warning. The
|
|
273
|
+
sharper confounder-specific sensitivity (partial R-squared, the E-value) comes
|
|
274
|
+
later. `refute_effect` and `Refutation` re-export at the top level.
|
|
275
|
+
- Case studies (`docs/case-studies/`): five narrative pages that apply the tools to
|
|
276
|
+
a famous dataset and recover the number the original study published. Smoking and
|
|
277
|
+
birthweight (backdoor adjustment), the 1973 Berkeley admissions paradox (Simpson's
|
|
278
|
+
paradox), 401(k) eligibility (double machine learning), Proposition 99 (synthetic
|
|
279
|
+
control), and structure discovery on a known network. Each opens with a causal
|
|
280
|
+
balance table, shows the naive answer failing, recovers the published figure, and
|
|
281
|
+
ends with a refutation check. They execute on every docs build, so the prose and
|
|
282
|
+
the code stay in step.
|
|
283
|
+
- The general do-calculus ID algorithm (`ergodic.identification`):
|
|
284
|
+
`identify_distribution(graph, outcome, do, given=...)` runs Shpitser-Pearl ID
|
|
285
|
+
(and IDC when `given` is set) on a single DAG or ADMG, deciding whether
|
|
286
|
+
`P(outcome | do(do), given)` is identifiable and returning the estimand as a
|
|
287
|
+
typed `Functional` (the `Prob` / `Product` / `Marginal` / `Ratio` algebra, with
|
|
288
|
+
`render`, `free_vars`, and `simplify`; public but marked provisional) or the
|
|
289
|
+
`Hedge` witness when it is not. An empty `do` is the observational query.
|
|
290
|
+
`identify_effect` now falls through to general ID after its named ladder misses
|
|
291
|
+
on a single graph, so a graph that is do-calculus identifiable but outside
|
|
292
|
+
adjustment, the front-door, and instruments (the Verma graph and its kin)
|
|
293
|
+
returns `strategy="id"` with the functional set; a non-identifiable graph
|
|
294
|
+
returns `strategy="none"` with the hedge. The `Identification` record gained
|
|
295
|
+
`functional` and `hedge` fields. `ida(graph, treatment, outcome)` identifies
|
|
296
|
+
across an equivalence class: it enumerates the members of a CPDAG and runs
|
|
297
|
+
`identify_effect` per member, returning an `IDA` record (`members`,
|
|
298
|
+
`n_identifiable`, `all_identifiable`, `agree`, `strategies`, `estimands`); PAG
|
|
299
|
+
and MAG members need a DAG realization and raise a clear "not built" for the
|
|
300
|
+
general case. Held executably to the soundness-against-truth anchor (the
|
|
301
|
+
functional evaluated on random discrete worlds matches the truth) and the
|
|
302
|
+
identifiability catalog.
|
|
303
|
+
- The discrete plug-in estimator (`ergodic.inference`): `estimate_effect` on a
|
|
304
|
+
discrete `strategy="id"` graph estimates the general functional directly,
|
|
305
|
+
evaluating the empirical sum-product with a bootstrap standard error. Discrete-
|
|
306
|
+
only and binary-treatment guarded, with a positivity check that raises on an
|
|
307
|
+
empty stratum the formula divides by (the discrete analog of the overlap check);
|
|
308
|
+
a continuous variable raises and points at the SCM path (not built).
|
|
309
|
+
- Mediation (`ergodic.identification`, `ergodic.inference`):
|
|
310
|
+
`identify_mediation(graph, treatment, outcome, mediators)` decides the natural
|
|
311
|
+
direct and indirect effects (NDE, NIE) via a covariate set that blocks the X-Y,
|
|
312
|
+
X-M, and M-Y back-doors (the three adjustment conditions), stating the cross-
|
|
313
|
+
world assumption (no mediator-outcome confounder affected by treatment) in words
|
|
314
|
+
since no data can test it, and returns a `MediationID`. The controlled direct
|
|
315
|
+
effect (CDE) routes through the general ID engine as a plain interventional
|
|
316
|
+
contrast and can identify even when NDE and NIE do not.
|
|
317
|
+
`estimate_mediation(graph, data, treatment, outcome, mediators, ...)` is the
|
|
318
|
+
g-computation estimator: NDE, NIE, and the total effect (each a distribution-
|
|
319
|
+
shaped `EffectEstimate`), the per-level CDE, a `Design` record naming the
|
|
320
|
+
assumptions, and `proportion_mediated` (flagged when the total effect is near
|
|
321
|
+
zero). Binary treatment and a discrete mediator; held to a planted linear world
|
|
322
|
+
where NDE and NIE recover the structural coefficients.
|
|
323
|
+
- Forecasting (`ergodic.forecast`), v1: `forecast(data, horizon)` fits a Bayesian
|
|
324
|
+
`TemporalMechanism` per series and returns a distribution-shaped `ForecastResult`
|
|
325
|
+
(per-node draws, `median`, `interval`, `to_frame`, provenance, and per-node
|
|
326
|
+
sampler diagnostics). The mechanism zoo is `StructuralMechanism` (a damped local
|
|
327
|
+
linear trend times a season plus a regression on graph parents, composed from
|
|
328
|
+
those pieces rather than fused), `CausalOU` (drivers move the long-run attractor,
|
|
329
|
+
so the transient answers "when" and not just "how much"), `Bass` (the diffusion
|
|
330
|
+
S-curve), and `ScenarioMechanism`; mechanisms are auto-assigned from the schema
|
|
331
|
+
with the seasonal period read off the time index, and count or categorical
|
|
332
|
+
columns raise at a named seam. Hierarchies reconcile without refitting through
|
|
333
|
+
`reconcile(result, hierarchy, method=...)`: `bottom_up`, `top_down` (historical or
|
|
334
|
+
per-draw forecast proportions), `middle_out`, and the `mint` projection (diagonal
|
|
335
|
+
posterior weights or `mint_shrink`, with `trust={node: w}`), each coherent to
|
|
336
|
+
machine epsilon read relative to the size of each parent. Drivers ride a window
|
|
337
|
+
graph over lagged names, exactly what `discover(..., method="pcmci")` returns:
|
|
338
|
+
staged fitting walks the `TrainingSchedule` and hands each node's whole forecast
|
|
339
|
+
posterior down to its children by injecting it into the child's trace, joint
|
|
340
|
+
fitting puts every node in one model and keeps the cross-node correlation, and
|
|
341
|
+
`fit="auto"` picks between them. `scenario=` pins a known future (an array, a
|
|
342
|
+
scalar, or `"hold"`) and states that it makes no causal claim; pinning an
|
|
343
|
+
endogenous node is refused by name and points at the `do=` that waits for
|
|
344
|
+
identification on window graphs. `backtest(model, data, ...)` scores anything that
|
|
345
|
+
fits a series and forecasts a horizon at rolling origins, with `Naive`,
|
|
346
|
+
`SeasonalNaive`, and `Drift` baselines, fifteen metrics including MASE and CRPS,
|
|
347
|
+
per-fold seeds, and the sampler's own report aggregated per fold;
|
|
348
|
+
`forecast_model(**options)` wraps the facade as a backtestable model.
|
|
349
|
+
`ForecastResult.explain()` returns the attribution waterfall: a baseline bar, a
|
|
350
|
+
seasonality bar, and one bar per driver over any `Window` of the horizon, each
|
|
351
|
+
carrying posterior draws and a credible interval, summing to the total with a
|
|
352
|
+
reported `closure_error`. The structural engine recomposes the mechanism's own
|
|
353
|
+
recorded pieces and costs no sampling; the replay engine (for a mechanism that
|
|
354
|
+
does not expose them) zeroes the factor's sites in a copy of the trace and
|
|
355
|
+
forecasts again on the stored seed. pymc is optional, `pip install
|
|
356
|
+
ergodic[forecast]`, and the bare install never imports it.
|
|
357
|
+
|
|
358
|
+
### Fixed
|
|
359
|
+
- Instruments: descendants of the treatment are no longer certified as instruments
|
|
360
|
+
(the exclusion test was unsound); latent nodes are excluded from adjustment sets,
|
|
361
|
+
front-door sets, instruments, and conditioning sets; identification validates its
|
|
362
|
+
inputs (unknown names and overlapping treatment/outcome raise); multi-treatment
|
|
363
|
+
adjustment validity follows proper causal paths exactly.
|
|
364
|
+
- Graph construction: conflicting duplicate edges on one node pair raise instead of
|
|
365
|
+
silently keeping the last one (also through `add_edge`, `from_networkx`, and
|
|
366
|
+
`from_dict`); write a bow as an explicit latent confounder with `latent=`.
|
|
367
|
+
Separation queries reject unknown node names instead of returning a wrong answer.
|
|
368
|
+
- Domain knowledge: `forbid_adjacency` covers bidirected and undirected edges;
|
|
369
|
+
`conflicts` catches cycles spanning required edges and tier order together;
|
|
370
|
+
precedence is transitively closed; `confound(a, a)` raises; the cycle checks no
|
|
371
|
+
longer recurse (deep graphs build fine).
|
|
372
|
+
- Data: datasets copy their columns at the boundary, so mutating the source
|
|
373
|
+
dataframe or array no longer mutates the dataset; tz-aware datetimes infer as
|
|
374
|
+
datetime; missing categoricals and NaT reach `matrix()` as NaN instead of
|
|
375
|
+
sentinel numbers; values outside declared categories raise; HTS mean coherence
|
|
376
|
+
and `aggregate` share one definition.
|
|
377
|
+
- Inference: learner cloning works for sklearn pipelines and meta-estimators; the
|
|
378
|
+
KNN zero-feature fallback averages the whole training set; the AIPW ATT posterior
|
|
379
|
+
reweights the treated share consistently with the Bayesian bootstrap; the
|
|
380
|
+
R-learner names its final slot when a learner lacks `sample_weight`.
|
|
381
|
+
- Docs: repaired the broken examples (`as_kind("admg")`, a single learner alias on
|
|
382
|
+
AIPW, the phantom `"gbm"` alias) and refreshed stale passages.
|
|
383
|
+
|
|
384
|
+
### Changed
|
|
385
|
+
- `effect()` lost its unused `level` parameter; pick the interval level on the
|
|
386
|
+
result (`estimate.interval(0.99)`).
|
|
387
|
+
- IV estimands now state the linear (parametric) modeling assumption.
|
|
388
|
+
- `m_separated` on a CPDAG-kind graph now answers for the equivalence class:
|
|
389
|
+
undirected edges read as circle marks and paths need definite status,
|
|
390
|
+
matching the PAG reading (a literal tail-tail walk could claim connection
|
|
391
|
+
where every member of the class is separated). Spell marks with `mixed(...)`
|
|
392
|
+
to walk them literally. `identify_effect` on a CPDAG or MAG used to run
|
|
393
|
+
silently with single-graph semantics; it now dispatches on the face, and
|
|
394
|
+
mixed graphs raise instead of being misread.
|
ergodic-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
|
|
2
|
+
Apache License
|
|
3
|
+
Version 2.0, January 2004
|
|
4
|
+
http://www.apache.org/licenses/
|
|
5
|
+
|
|
6
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
7
|
+
|
|
8
|
+
1. Definitions.
|
|
9
|
+
|
|
10
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
11
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
12
|
+
|
|
13
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
14
|
+
the copyright owner that is granting the License.
|
|
15
|
+
|
|
16
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
17
|
+
other entities that control, are controlled by, or are under common
|
|
18
|
+
control with that entity. For the purposes of this definition,
|
|
19
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
20
|
+
direction or management of such entity, whether by contract or
|
|
21
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
22
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
23
|
+
|
|
24
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
25
|
+
exercising permissions granted by this License.
|
|
26
|
+
|
|
27
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
28
|
+
including but not limited to software source code, documentation
|
|
29
|
+
source, and configuration files.
|
|
30
|
+
|
|
31
|
+
"Object" form shall mean any form resulting from mechanical
|
|
32
|
+
transformation or translation of a Source form, including but
|
|
33
|
+
not limited to compiled object code, generated documentation,
|
|
34
|
+
and conversions to other media types.
|
|
35
|
+
|
|
36
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
37
|
+
Object form, made available under the License, as indicated by a
|
|
38
|
+
copyright notice that is included in or attached to the work
|
|
39
|
+
(an example is provided in the Appendix below).
|
|
40
|
+
|
|
41
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
42
|
+
form, that is based on (or derived from) the Work and for which the
|
|
43
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
44
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
45
|
+
of this License, Derivative Works shall not include works that remain
|
|
46
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
47
|
+
the Work and Derivative Works thereof.
|
|
48
|
+
|
|
49
|
+
"Contribution" shall mean any work of authorship, including
|
|
50
|
+
the original version of the Work and any modifications or additions
|
|
51
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
52
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
53
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
54
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
55
|
+
means any form of electronic, verbal, or written communication sent
|
|
56
|
+
to the Licensor or its representatives, including but not limited to
|
|
57
|
+
communication on electronic mailing lists, source code control systems,
|
|
58
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
59
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
60
|
+
excluding communication that is conspicuously marked or otherwise
|
|
61
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
62
|
+
|
|
63
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
64
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
65
|
+
subsequently incorporated within the Work.
|
|
66
|
+
|
|
67
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
68
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
69
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
70
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
71
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
72
|
+
Work and such Derivative Works in Source or Object form.
|
|
73
|
+
|
|
74
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
75
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
76
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
77
|
+
(except as stated in this section) patent license to make, have made,
|
|
78
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
79
|
+
where such license applies only to those patent claims licensable
|
|
80
|
+
by such Contributor that are necessarily infringed by their
|
|
81
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
82
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
83
|
+
institute patent litigation against any entity (including a
|
|
84
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
85
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
86
|
+
or contributory patent infringement, then any patent licenses
|
|
87
|
+
granted to You under this License for that Work shall terminate
|
|
88
|
+
as of the date such litigation is filed.
|
|
89
|
+
|
|
90
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
91
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
92
|
+
modifications, and in Source or Object form, provided that You
|
|
93
|
+
meet the following conditions:
|
|
94
|
+
|
|
95
|
+
(a) You must give any other recipients of the Work or
|
|
96
|
+
Derivative Works a copy of this License; and
|
|
97
|
+
|
|
98
|
+
(b) You must cause any modified files to carry prominent notices
|
|
99
|
+
stating that You changed the files; and
|
|
100
|
+
|
|
101
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
102
|
+
that You distribute, all copyright, patent, trademark, and
|
|
103
|
+
attribution notices from the Source form of the Work,
|
|
104
|
+
excluding those notices that do not pertain to any part of
|
|
105
|
+
the Derivative Works; and
|
|
106
|
+
|
|
107
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
108
|
+
distribution, then any Derivative Works that You distribute must
|
|
109
|
+
include a readable copy of the attribution notices contained
|
|
110
|
+
within such NOTICE file, excluding those notices that do not
|
|
111
|
+
pertain to any part of the Derivative Works, in at least one
|
|
112
|
+
of the following places: within a NOTICE text file distributed
|
|
113
|
+
as part of the Derivative Works; within the Source form or
|
|
114
|
+
documentation, if provided along with the Derivative Works; or,
|
|
115
|
+
within a display generated by the Derivative Works, if and
|
|
116
|
+
wherever such third-party notices normally appear. The contents
|
|
117
|
+
of the NOTICE file are for informational purposes only and
|
|
118
|
+
do not modify the License. You may add Your own attribution
|
|
119
|
+
notices within Derivative Works that You distribute, alongside
|
|
120
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
121
|
+
that such additional attribution notices cannot be construed
|
|
122
|
+
as modifying the License.
|
|
123
|
+
|
|
124
|
+
You may add Your own copyright statement to Your modifications and
|
|
125
|
+
may provide additional or different license terms and conditions
|
|
126
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
127
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
128
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
129
|
+
the conditions stated in this License.
|
|
130
|
+
|
|
131
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
132
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
133
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
134
|
+
this License, without any additional terms or conditions.
|
|
135
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
136
|
+
the terms of any separate license agreement you may have executed
|
|
137
|
+
with Licensor regarding such Contributions.
|
|
138
|
+
|
|
139
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
140
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
141
|
+
except as required for reasonable and customary use in describing the
|
|
142
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
143
|
+
|
|
144
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
145
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
146
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
147
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
148
|
+
implied, including, without limitation, any warranties or conditions
|
|
149
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
150
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
151
|
+
appropriateness of using or redistributing the Work and assume any
|
|
152
|
+
risks associated with Your exercise of permissions under this License.
|
|
153
|
+
|
|
154
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
155
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
156
|
+
unless required by applicable law (such as deliberate and grossly
|
|
157
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
158
|
+
liable to You for damages, including any direct, indirect, special,
|
|
159
|
+
incidental, or consequential damages of any character arising as a
|
|
160
|
+
result of this License or out of the use or inability to use the
|
|
161
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
162
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
163
|
+
other commercial damages or losses), even if such Contributor
|
|
164
|
+
has been advised of the possibility of such damages.
|
|
165
|
+
|
|
166
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
167
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
168
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
169
|
+
or other liability obligations and/or rights consistent with this
|
|
170
|
+
License. However, in accepting such obligations, You may act only
|
|
171
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
172
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
173
|
+
defend, and hold each Contributor harmless for any liability
|
|
174
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
175
|
+
of your accepting any such warranty or additional liability.
|
|
176
|
+
|
|
177
|
+
END OF TERMS AND CONDITIONS
|
|
178
|
+
|
|
179
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
180
|
+
|
|
181
|
+
To apply the Apache License to your work, attach the following
|
|
182
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
183
|
+
replaced with your own identifying information. (Don't include
|
|
184
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
185
|
+
comment syntax for the file format. We also recommend that a
|
|
186
|
+
file or class name and description of purpose be included on the
|
|
187
|
+
same "printed page" as the copyright notice for easier
|
|
188
|
+
identification within third-party archives.
|
|
189
|
+
|
|
190
|
+
Copyright [yyyy] [name of copyright owner]
|
|
191
|
+
|
|
192
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
193
|
+
you may not use this file except in compliance with the License.
|
|
194
|
+
You may obtain a copy of the License at
|
|
195
|
+
|
|
196
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
197
|
+
|
|
198
|
+
Unless required by applicable law or agreed to in writing, software
|
|
199
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
200
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
201
|
+
See the License for the specific language governing permissions and
|
|
202
|
+
limitations under the License.
|