Ergodic
Causal AI: causal discovery, causal inference, and process intelligence, in one Python package.
Pre-alpha. This is early scaffolding. The public API is not settled and will change.
By ergodic.ai.
What's inside
ergodic is organized around its pillars, all built on two shared objects.
| Subpackage | Focus |
|---|---|
ergodic.discovery |
Causal discovery: learn causal structure (DAGs) from data. |
ergodic.inference |
Causal inference: estimate causal effects from data and a causal model. |
ergodic.process |
Process mining and intelligence: discover and analyze processes from event logs. |
ergodic.forecast |
Forecasting: Bayesian trajectories with causal structure in them. |
The shared objects are written and tested: ergodic.graph (a mixed-graph family covering DAG, ADMG, MAG, CPDAG, and PAG), ergodic.knowledge (DomainKnowledge for prior constraints), ergodic.identification (graph to estimand: adjustment, front-door, instruments; on a CPDAG, MAG, or PAG the generalized adjustment criterion answers for every graph in the class), and ergodic.data (typed datasets: tabular, time series, panel, hierarchical, event log).
Causal inference is built through effect estimation: estimate_effect turns a graph and a dataset into an effect with its uncertainty, over a library of estimators (doubly robust, double machine learning, the meta-learners, instruments) with sklearn-style learners in every nuisance slot, heterogeneous effects through cate, and an optional Bayesian posterior path through PyMC (pip install ergodic[bayes]). When treatment varies over time instead of across a graph, the quasi-experimental designs read the panel directly: did (canonical and group-time for staggered adoption), event_study (leads as the visible pre-trend check), synthetic_control (simplex weights with placebo inference), and interrupted_time_series (segmented regression with HAC errors), each carrying its identifying assumptions in a Design record.
Causal discovery is built on two pluggable ingredients: a conditional independence test is a class constructed with data (the stateful, caching oracle behind PC, FCI, RFCI, and the MMPC skeleton screen, with a GCM test that turns any regression learner into a test), and a decomposable score drives the score-based searches (GES, hill climbing with tabu, exact A*, and the order-based BOSS and GRaSP, held to the exact optimum in tests), with scores for continuous, categorical, and mixed data. discover(data) returns a CPDAG with its separating sets and diagnostics (PC ships the conservative and majority collider rules alongside the standard one), discover(data, method="fci") returns a PAG that tolerates latent confounders and selection bias both (Zhang's complete ten rules; RFCI is the fast relaxation), discover(data, method="lingam") fully orients a DAG when the noise is non-Gaussian, two screens (MMPC, glasso) confine any search through restrict_to, a bootstrap wrapper turns any method into edge stability frequencies, domain knowledge enters every search, and a d-separation oracle makes the recovery guarantees executable. Time series and panels get temporal discovery: discover(data, method="pcmci", max_lag=...) returns a window graph over lagged nodes like X[t-1] with domain knowledge read at every lag, the bootstrap resamples moving blocks (a series) or whole entities (a panel) so stability extends to PCMCI, and granger is the predictive baseline, a pair-level table on purpose. The pillars then meet in two calls: estimate_effect identifies on the discovered class itself and estimates whenever one estimand covers every member.
Process intelligence reads event logs. The descriptive layer recovers the daily workflow (process_map with provenance-tracked simplification, performance with a waiting/service split when lifecycle data exists, variant_table, and a composable filter vocabulary that says whether it drops events or cases), and the causal layer asks what the tools above can't: case_table encodes a log into one row per case with timestamp-gated features, workload confounders, and auto-built DomainKnowledge tiers, feeding discover and estimate_effect unchanged, and kpi_panel aggregates a log into the panel that did, event_study, and synthetic_control consume, so a process change becomes a natural experiment. The model layer goes past the map: discover_model is an inductive miner that cuts the log into a block-structured process tree and a workflow net that is sound by construction (is_sound verifies any net by reachability), conformance checks the log against a model by token replay and optimal alignments, and decision_points with decision_table read an exclusive split causally: the branch taken is a treatment, the adjusters are measured at the moment the case reveals its branch, and positivity ships as a first-class report.
Forecasting points the same machinery forward. forecast(data, horizon) fits a Bayesian mechanism per series and returns posterior trajectories, not a point with an error bar bolted on: a damped trend, a season, and one regression term per driver, composed so the fitted forecast can be taken apart again. Series that add up are made to agree through a reconciliation menu that costs no refitting (bottom_up, top_down, middle_out, and the MinT projection, with trust weights), drivers ride the same window graph temporal discovery returns (staged fitting hands each node's whole posterior down to its children, joint fitting keeps the cross-node correlation), and a driver whose future is genuinely known arrives through scenario=, which pins a path and says plainly that it makes no causal claim (do= waits for identification on the window graph). backtest scores any of it at rolling origins against naive baselines in one table with MASE, CRPS, and coverage, carrying each fold's sampler diagnostics, and ForecastResult.explain() returns the attribution waterfall: a baseline bar, a seasonality bar, and one bar per driver, each with a credible interval, adding up to the total. PyMC is optional (pip install ergodic[forecast]).
New to causal inference? The docs open with a learn series: eleven notebook guides that teach the ideas from zero on commercial examples (Simpson's paradox, bad controls, doubly robust estimation, uplift targeting, instruments and the front door, structure discovery, quasi-experiments with difference-in-differences and synthetic control, process mining over event logs with the maps drawn for real, causal process intelligence, decision points in a mined process model, and forecasting a product hierarchy with its drivers), each one a simulation with the true answer written in the code and executed live when the docs build. See the docs for the series and the reference guides.
Installation
pip install ergodic
# or, with uv:
uv add ergodic
Requires Python 3.11+.
Quick start
from ergodic import dag, DomainKnowledge
# build a causal graph from edge glyphs
g = dag(["Smoking -> Tar", "Tar -> Cancer", "Smoking -> Cancer"])
g.d_separated("Smoking", "Cancer", "Tar") # False: the direct edge remains
g.do("Tar") # the graph after intervening on Tar
# state prior knowledge and check a graph against it
dk = DomainKnowledge().with_tiers([["Smoking"], ["Tar"], ["Cancer"]])
dk.is_consistent(g) # True
Estimate a causal effect in one call:
import numpy as np
import pandas as pd
from ergodic import dag, tabular, estimate_effect
rng = np.random.default_rng(0)
n = 4000
z = rng.normal(size=n)
a = (rng.uniform(size=n) < 1.0 / (1.0 + np.exp(-0.8 * z))).astype(float)
y = 2.0 * a + 1.5 * z + rng.normal(size=n) # the true effect of A on Y is 2.0
g = dag(["Z -> A", "Z -> Y", "A -> Y"])
data = tabular(pd.DataFrame({"A": a, "Z": z, "Y": y}))
estimate_effect(g, data, "A", "Y")
# EffectEstimate(aipw, analytic: ate=1.997, se=0.0342, 95% CI [1.93, 2.06], n=4000)
Development
This project uses uv for environment and dependency management.
# create the dev environment (with docs deps)
uv sync --group docs
# common tasks (run `make help` to list them)
make lint # ruff lint
make format # ruff format + autofix
make typecheck # mypy (strict)
make test # pytest
make check # lint + typecheck + test
make docs-serve # live docs preview at http://127.0.0.1:8000
Optional git hooks:
uv run pre-commit install
License
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ergodic-0.1.0.tar.gz.
File metadata
- Download URL: ergodic-0.1.0.tar.gz
- Upload date:
- Size: 3.1 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.11.19 {"installer":{"name":"uv","version":"0.11.19","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
17597f8687cf9f2ddbf6d9feceb0553fd78b1b58973ddc07eb702695d6bd35b3
|
|
| MD5 |
d9b90fa437bc773394db2f7af80815aa
|
|
| BLAKE2b-256 |
1e0abbba3038e08734012c6c3b1665532025c5d54d0a35e0d68551dc1df9d07c
|
File details
Details for the file ergodic-0.1.0-py3-none-any.whl.
File metadata
- Download URL: ergodic-0.1.0-py3-none-any.whl
- Upload date:
- Size: 702.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.11.19 {"installer":{"name":"uv","version":"0.11.19","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0eb070a72053050c37aa522bfc7eccaf504a4b59f21320e1133e30abc286bb4a
|
|
| MD5 |
5daedca653bb14614cf4b7e70fea78db
|
|
| BLAKE2b-256 |
1255a0305be116e47da3e4d8c624dc15a87cb8a66cf250649698db8edb2c3346
|