Metadata-Version: 2.4
Name: antecedent
Version: 1.9.0
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Science/Research
Classifier: Topic :: Scientific/Engineering :: Information Analysis
Classifier: License :: OSI Approved :: MIT License
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Rust
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Requires-Dist: numpy>=1.26
Summary: Identification-first causal inference: discovery, identification, estimation, interventions, and counterfactuals under structural uncertainty
License: MIT OR Apache-2.0
Requires-Python: >=3.11
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
Project-URL: Documentation, https://antecedent.readthedocs.io/
Project-URL: Homepage, https://github.com/iridae-dev/antecedent
Project-URL: Release notes, https://github.com/iridae-dev/antecedent/blob/main/CHANGELOG.md
Project-URL: Repository, https://github.com/iridae-dev/antecedent

# Antecedent

Antecedent is an identification-first causal inference engine for Python (with
a native Rust core). It takes an analysis from causal structure through
estimation, diagnostics, interventions, and counterfactuals — without silently
treating discovered graphs as ground truth.

Use it when you need a causal effect you can defend: identification is checked
before anything is estimated, refuters run against the estimate by default, and
uncertainty about the causal graph — a CPDAG, a PAG, a posterior over graphs —
is carried through to the effect instead of being resolved by fiat.

Requires CPython 3.11–3.14. Install from PyPI:

```bash
pip install antecedent
```

Paste this block and run it. It simulates a confounded dataset, checks that the
effect is identified, estimates it, and runs refuters against the estimate:

```python
import numpy as np
from antecedent import AverageEffect, analyze

rng = np.random.default_rng(0)
n = 2000
season = rng.normal(size=n)  # confounder
price = 0.7 * season + rng.normal(size=n)  # treatment
sales = 1.5 * price + 2.0 * season + rng.normal(size=n)  # outcome, true effect = 1.5

result = analyze(
    data={"season": season, "price": price, "sales": sales},
    graph=[("season", "price"), ("season", "sales"), ("price", "sales")],
    query=AverageEffect(treatment="price", outcome="sales"),
)

print(
    result.identification
)  # NonparametricallyIdentified via backdoor.adjustment, adjusting for season
print(result.estimate)  # ate=1.48, analytic and bootstrap standard errors
print(result.validation)  # refuters ran and passed
```

The same `analyze()` call scales from this to temporal dose × horizon
``ResponseCurve`` surfaces, pulse and sustained contrasts, Bayesian
graph-posterior mixtures that report unidentified structure mass,
mediation, counterfactuals, and root-cause attribution — see the
[project README](https://github.com/iridae-dev/antecedent#readme) for worked
examples and the [documentation](https://antecedent.readthedocs.io/) for the
full API.

## 1.9.0

The
[1.9.0 release notes](https://github.com/iridae-dev/antecedent/blob/main/docs/release-notes/v1.9.0.md)
cover the calibration of licensed intervals (a two-sided repeated-sampling
coverage gate, with boundary records disclosed at runtime), ADMG
interventional distributions (Rust Study API only), accepted-Dag
counterfactuals, Frequentist DBN-posterior mediation, and staged
attribution / transport / interference cells (Rust Study API only).
Behaviour changes:
`rd.sharp` defaults to the HC1 SE, NaN and null float cells are missing
values, and the default bootstrap count is 199.

## 1.8.0

The
[1.8.0 release notes](https://github.com/iridae-dev/antecedent/blob/main/docs/release-notes/v1.8.0.md)
cover the Bayesian remainder of the staged handle: functional path,
distribution, and ADMG ATE, static mediation, counterfactuals, derivatives,
and ConditionalEffect graph-posterior mixtures.

## 1.7.0

The
[1.7.0 release notes](https://github.com/iridae-dev/antecedent/blob/main/docs/release-notes/v1.7.0.md)
cover class-preserving Bayesian analyses on incomplete temporal graphs.
The
[1.6.0 release notes](https://github.com/iridae-dev/antecedent/blob/main/docs/release-notes/v1.6.0.md)
cover per-horizon temporal identification, sequential overlays, and
observation-adjusted temporal curves. The
[1.5 Python walkthrough](https://github.com/iridae-dev/antecedent/blob/main/docs/local-distributional-joint.md)
remains the guide for retargeting, CDFs, and joint interventions.

## Development

For local development you need a Rust 1.85 toolchain. CI builds and smoke-tests
wheels for the supported CPython range on Linux x86_64/aarch64 (manylinux),
macOS arm64, and Windows x86_64 (default `faer` path; no system BLAS). Tagged
releases publish wheels to PyPI and GitHub Release assets (see
[docs/development.md](https://github.com/iridae-dev/antecedent/blob/main/docs/development.md)).

```bash
cd python
uv venv && source .venv/bin/activate
uv sync --group dev
maturin develop
pytest
```

Lint and types (local gate only — not part of wheel CI):

```bash
bash ../scripts/gate_python_lint.sh   # ruff check/format + mypy
```

## Public API

Primary entry point is the OO facade:

```python
import antecedent

g = antecedent.Dag.from_edges(["z", "t", "y"], [("z", "t"), ("z", "y"), ("t", "y")])
result = antecedent.analyze(
    data,  # dict[str, array] or pandas DataFrame
    graph=g,  # or an edge list
    query=antecedent.AverageEffect(treatment="t", outcome="y"),
    inference=antecedent.Frequentist(),  # or antecedent.Bayesian(...)
)
print(result.identification, result.estimate, result.validation)

# Identify without estimating:
id_only = antecedent.identify(
    graph=g,
    query=antecedent.AverageEffect(treatment="t", outcome="y"),
    identifier=antecedent.Identifier.BACKDOOR_ADJUSTMENT,
)

gcm = antecedent.model.fit_gcm(["z", "t", "y"], columns, list(g.edges()))
draws = gcm.sample_do({"t": 1.0}, n=200)

# Or discover then fit (never invents orientations; refuses incomplete PAG/FCI):
fitted, edges = antecedent.gcm.fit_gcm_discovered(
    data, discovery=antecedent.discovery.PC(alpha=0.05)
)
```

The root namespace is frozen at 52 names as of 1.9 (the 50-name 1.7
contract plus `AnomalyAttribution` and `ChangeAttribution`). Those two
types exist for the query axis; `analyze()` refuses them — the licensed
cells are Rust `Study` only. `TransportQuery` / `InterferenceQuery` stay
stage modules (`antecedent.transport`, `antecedent.interference`).
Everything else is reached through a stage module (`antecedent.discovery`, `antecedent.priors`, `antecedent.errors`, …).

Also exposed:

- Typed graphs: `Dag` / `Cpdag` / `Pag` / `Admg` / `TemporalDag`
  (`d_separated` / `latent_project` on `Dag`; `m_separated` on `Admg` / `Pag`)
- `Identifier` / `Estimator` enums (wire ids) plus string kwargs
- `identify(graph=…, query=AverageEffect(…))` — identify without estimating
- Queries: `AverageEffect`, `MediationEffect`, `Counterfactual`, `PulseEffect`, `SustainedEffect`,
  `InterventionalDistribution`, `PathSpecificEffect`, `ConditionalEffect`,
  `TemporalMediationEffect`, `InterventionResponse`, plus the response family
  (`ResponseCurve`, `AverageDerivative`, `PointDerivative`, `Elasticity`,
  `SemiElasticity`, `DirectionalDerivative`, `ResponseJacobian`), plus
  `AnomalyAttribution` / `ChangeAttribution` (root types; `analyze()`
  refuses). Temporal dose × horizon uses the same `ResponseCurve` /
  `InterventionResponse` types with keyword-only `horizons`, `policy`, and
  `treatment_lag` (see `examples/python/temporal_response_curve.py`).
- `antecedent.discovery` — PC, GES, LiNGAM, NOTEARS, FCI/RFCI, PCMCI family, Bayesian posteriors
- `antecedent.validation.validate_pcmci_*` — discovery stability (block bootstrap, FPR, grids, …)
- `antecedent.model` / `antecedent.counterfactual` — `FittedGcm`, `sample_do`, `counterfactual_ite`
- `antecedent.population` — `PopulationRegistry` / `target_*` for named predicates and custom-distribution IPW
- `antecedent.gcm` — `fit_gcm_discovered` / `attribute_*_discovered` discover-then-attribute composition
- `antecedent.state.CausalState` — incremental state with retained batches, events, suff-stats, particle filter
- `refute="full"|"placebo"|False` on static `analyze` (`refute=True` is a
  `TypeError`; leave it unset for the licensed default). Static and temporal
  `ResponseCurve` / `InterventionResponse` license validation `none` only;
  requesting a scalar refuter suite raises `CausalUnsupportedError`.
- RD: `estimator="rd.sharp"` with `running_variable` / `cutoff` / `bandwidth`
- Graph interchange on the classes: `Dag.from_dot` / `.to_dot` and the JSON / GML / NetworkX peers
- Design / state examples: [`examples/python/rank_designs.py`](https://github.com/iridae-dev/antecedent/blob/main/examples/python/rank_designs.py),
  [`examples/python/causal_state_workflow.py`](https://github.com/iridae-dev/antecedent/blob/main/examples/python/causal_state_workflow.py),
  [`examples/python/temporal_response_curve.py`](https://github.com/iridae-dev/antecedent/blob/main/examples/python/temporal_response_curve.py),
  [`examples/python/staged_static_kinds.py`](https://github.com/iridae-dev/antecedent/blob/main/examples/python/staged_static_kinds.py)
  (see ADR 0016 — no auto-rerun); catalog in [`examples/README.md`](https://github.com/iridae-dev/antecedent/blob/main/examples/README.md)

Build artifacts (`_native.*.so`) are gitignored; always `maturin develop` (or install a wheel) on a fresh checkout.

In 1.6, multi-step Sustained supports `none`/`cheap`/`full` validation in both
inference modes, including DBN posterior mixtures. Full perturbations refit
the sequential model; Bayesian checks retain mechanism PPC and composed-effect
prior sensitivity. Temporal Soft `multiplicative` and `truncated_shift` compose
across single, joint, and multi-step schedules; truncation applies to the
propagated conditional mean, not a stochastic draw. Bayesian temporal
observation curves and Sequence use an observed-data Gaussian SEM with
latent-trajectory Gibbs sampling under an explicit ignorable trajectory
coarsening assumption and distinct priors. See the
[observation contract](https://github.com/iridae-dev/antecedent/blob/main/docs/observation-contract.md)
for the conditioning assumptions; this is not Bayesian IPCW weighting.

In 1.3, `antecedent.estimation.PreparedAnalysis` also stages Frequentist DAG
derivatives, static natural mediation, explicit-DAG unit counterfactuals, and
the published observation-pair contract (selected AIPW, marginal KM, conditional
Cox IPCW). Derivative cheap/full stay n/a; counterfactual sampling uncertainty
is unavailable. See the
[1.3 evidence ledger](https://github.com/iridae-dev/antecedent/blob/main/docs/v1.3-evidence.md)
and the 1.2 ledger below for earlier Bayesian and sequential forms.

In 1.2, `antecedent.estimation.PreparedAnalysis` also supports the licensed
Bayesian conditional, temporal-mediation and response forms, and multi-step
`SustainedEffect(..., window=(-2, -1))`. Choose the validation suite when
preparing these analyses; second-click `refute()` remains AverageEffect-only.
`export_artifact()` retains the fitted posterior or response, while
`export_artifact(payload="query")` retains the original query kind and variable
IDs. Scalar posterior exports do not include the complete assumption or
validation ledger; retain the analysis result alongside them. See the
[1.2 evidence ledger](https://github.com/iridae-dev/antecedent/blob/main/docs/v1.2-evidence.md)
for model restrictions and evidence.

Typed exceptions (`CausalError` and subclasses) mirror Rust `CausalError` categories.
The native module `antecedent._native` remains available for advanced FFI use
(including the flat `AteAnalysisResult` DTO; prefer nested `AnalysisResult`).

