CausalEngine Tutorial
Install the public PyPI distribution CausalScience before starting:
python -m pip install CausalScience
python -m causalscience.demo
The published release is 0.1.0a13; Python examples use import causalscience.
See the installation guide.
Then open one Python, IPython, or Jupyter session and run the cells in order:
later cells reuse variables created earlier.
The computational walkthrough and recorded outputs retain their CausalEngine
0.1.0a13 baseline at b00fb28d,
originally introduced at bd14e4dc. Installation and public imports have been
updated without rewriting those historical output records.
Use Copy code to copy an input cell. The outputs below are from a recorded run of b00fb28d; this page does not run Python. Estimates from other environments or samples can differ.
Contents
Run six public estimation APIs
1. Setup
Import the installed package and set the data and estimator seeds. Keep this session open for every following cell.
import numpy as np
import pandas as pd
import causalscience as ce
from causalscience.indexed_graph import Slot, ObsKernel, Product, Sum
assert ce.__version__ == "0.1.0a13"
rng = np.random.default_rng(27)
n = 2400
common = dict(seed=0, num_boost_round=30, params={"nthread": 1, "max_depth": 4})
print("CausalEngine:", ce.__version__)
print("NumPy:", np.__version__, "pandas:", pd.__version__)
print("Data seed: 27; rows per example:", n)
print("Estimator seed:", common["seed"], "boosting rounds:", common["num_boost_round"])
Recorded output
CausalEngine: 0.1.0a13 NumPy: 2.4.6 pandas: 3.1.0 Data seed: 27; rows per example: 2400 Estimator seed: 0 boosting rounds: 30
The version check confirms which package is loaded. The seeds and settings make this recorded example reproducible in the listed environment.
2. Back-door (BD) mean
Estimate the mean outcome under A=1. In this synthetic model, Z and C are declared to be a valid back-door adjustment set; that assumption is supplied by the example.
# Back-door mean: baseline Z and C suffice for adjustment in this SCM.
z = rng.binomial(1, .5, n)
c = rng.binomial(1, .2 + .5*z)
a = rng.binomial(1, .1 + .3*c + .2*z)
y = rng.binomial(1, .05 + .15*c + .1*z + a*(.2 + .2*z))
bd_data = pd.DataFrame(dict(Z=z, C=c, A=a, Y=y))
bd_schema = {v: "binary" for v in bd_data}
bd_options = dict(treatment="A", outcome="Y", adjustment=["Z", "C"], schema=bd_schema)
bd = ce.estimate_bd(bd_data, action=1, **bd_options, **common)
print("Status:", bd["status"], "/", bd["estimation_status"])
print("Interventional mean (A=1):", bd["estimate"])
Recorded output
Status: ESTIMATED / ACCEPT Interventional mean (A=1): [0.46923811]
The returned value is an interventional mean. The ground truth interventional mean for this synthetic model is 0.4675.
3. Sequential back-door (SBD) mean
Estimate the mean when both actions are set to 1. The variable L is measured after A0 and before A1, so its position in the sequence matters.
# Sequential back-door: new blocks, in time order; L depends on A0.
c = rng.binomial(1, .5, n)
a0 = rng.binomial(1, .2 + .4*c)
l = rng.binomial(1, .1 + .2*c + .5*a0)
a1 = rng.binomial(1, .1 + .1*c + .6*l + .1*a0)
y = rng.binomial(1, .05 + .15*c + .3*l + .1*a0 + .25*a1)
sbd_data = pd.DataFrame(dict(C=c, A0=a0, L=l, A1=a1, Y=y))
sbd = ce.estimate_sbd(sbd_data, treatments=["A0", "A1"], outcome="Y",
covariates=[["C"], ["L"]], actions={"A0": 1, "A1": 1},
schema={v: "binary" for v in sbd_data}, **common)
print("Status:", sbd["status"], "/", sbd["estimation_status"])
print("Interventional mean (A0=1, A1=1):", sbd["estimate"])
Recorded output
Status: ESTIMATED / ACCEPT Interventional mean (A0=1, A1=1): [0.69158194]
This value is the mean under the two-action regime. The ground truth interventional mean for this synthetic model is 0.685.
4. Front-door (FD) mean
Estimate the mean under A=1 using the conditional Front-door structure. C is the baseline covariate and M the mediator. U is used only to generate the example: it is not included in fd_data.
# Conditional front-door: C enters every conditional and the outer average.
c = rng.binomial(1, .5, n)
u = rng.binomial(1, .5, n)
a = rng.binomial(1, .1 + .3*c + .4*u)
m = rng.binomial(1, .1 + .5*a + .25*c)
y = rng.binomial(1, .05 + .4*m + .25*c + .2*u)
fd_data = pd.DataFrame(dict(C=c, A=a, M=m, Y=y))
fd = ce.estimate_fd(fd_data, treatment="A", mediators=["M"], outcome="Y",
covariates=["C"], action=1, schema={v: "binary" for v in fd_data}, **common)
print("Status:", fd["status"], "/", fd["estimation_status"])
print("Interventional mean (A=1):", fd["estimate"])
Recorded output
Status: ESTIMATED / ACCEPT Interventional mean (A=1): [0.5759384]
The ground truth interventional mean for this synthetic model is 0.565. A successful numerical status does not verify the causal assumptions.
5. Unified Covariate Adjustment (UCA) mean
Supply the normalized expression explicitly. Here it represents the same functional as the BD cell, so the assertion checks that their estimates agree.
# UCA: explicitly supply a normalized expression, with no graph object.
Z, C, A, Y = map(Slot, ("Z", "C", "A", "Y"))
formula = Sum((Z, C), Product((
ObsKernel((("Z", Z), ("C", C))),
ObsKernel((("Y", Y),), (("A", A), ("Z", Z), ("C", C))),
)))
uca = ce.estimate_uca(bd_data, formula=formula, actions={"A": 1}, outcome="Y",
schema=bd_schema, **common)
np.testing.assert_array_equal(uca["estimate"], bd["estimate"])
print("Status:", uca["status"], "/", uca["estimation_status"])
print("Interventional mean (A=1):", uca["estimate"])
Recorded output
Status: ESTIMATED / ACCEPT Interventional mean (A=1): [0.46923811]
The equality assertion passes for the explicitly supplied functional. This call does not discover or verify a graph.
6. Average Treatment Effect (ATE)
Request a contrast: the mean under A=1 minus the mean under A=0. This is an effect, rather than the single-intervention means returned by the preceding cells.
# ATE: paired action 1 minus 0, retaining covariance of the two scores.
ate = ce.estimate_ate(bd_data, estimator="bd", actions={"A": 1},
reference_actions={"A": 0}, **bd_options, **common)
print("Status:", ate["status"], "/", ate["estimation_status"])
print("Paired ATE (A=1 minus A=0):", ate["estimate"])
Recorded output
Status: ESTIMATED / ACCEPT Paired ATE (A=1 minus A=0): [0.31323824]
The ground truth ATE for this synthetic model is 0.3. The finite-data estimate need not equal it exactly.
7. Conditional Average Treatment Effect (CATE)
Predict the effect for the two baseline groups Z=0 and Z=1. The two rows from model.predict are those conditional predictions; the separately printed inherited estimate is a marginal paired ATE summary.
# CATE: regress paired pseudo-outcomes on the baseline modifier Z.
cate = ce.estimate_cate(bd_data, estimator="bd", modifiers=["Z"],
actions={"A": 1}, reference_actions={"A": 0}, **bd_options, **common,
cate_num_boost_round=30, cate_params={"nthread": 1, "max_depth": 4})
print("Status:", cate["status"], "/", cate["estimation_status"])
print("Marginal paired ATE summary (not a conditional CATE value):", cate["estimate"])
for name, result in dict(BD=bd, SBD=sbd, FD=fd, UCA=uca, ATE=ate, CATE=cate).items():
assert result["estimation_status"] == "ACCEPT", result.get("reason")
assert result["graph_verification"] == "not_run"
assert result["causal_interval"] is None
print("Conditional CATE predictions at Z=0,1:", cate["model"].predict(pd.DataFrame({"Z": [0, 1]})))
Recorded output
Status: ESTIMATED / ACCEPT Marginal paired ATE summary (not a conditional CATE value): [0.31323824] Conditional CATE predictions at Z=0,1: [[0.21757159] [0.41964203]]
The first prediction is for Z=0 and the second for Z=1. The ground truth CATE values for these groups are 0.2 and 0.4.
Recorded output environment
Rerun October 27, 2026. Python 3.11.14 · causalengine 0.1.0a13 · numpy 2.4.6 · pandas 3.1.0 · xgboost 3.2.0 · scipy 1.17.1 · scikit-learn 1.9.1 · osqp 1.1.3. Package source: b00fb28d. These website cells are adapted from the upstream walkthrough. Their computational AST, assertions and prediction call match the source; code and outputs were checked together against this installed main revision.
Interpret the result
BD, SBD, FD and UCA above return interventional means for the requested fixed
actions. They are not automatically treatment-effect contrasts. The independent
SCM means are .4675 for BD/UCA, .685 for SBD, and .565 for FD. The first
SCM has ATE .3 and CATE .2 at Z=0, .4 at Z=1. Finite-data estimates need
not equal these truths; the assertions check executable contracts, not accuracy.
ESTIMATED/ACCEPT here means the supplied functional was numerically executed.
Graph-free calls record user-assumed identification; they run neither graph
discovery nor FMC/MC identification. Use identify_query and fit_query_mean
when a graph should supply the identifying formula. The default backward learner
is XGBoost and weights use normalized minimum-L2 balance in the supported feature
class. Positivity, valid causal assumptions and nuisance learning remain required.
CATE requires baseline modifiers and a supported baseline-anchor program with
shared source-row evaluation. It uses a separate pseudo-outcome training and
validation split. model.predict returns conditional effects. Inherited
estimate, standard_error and conditional_score_interval remain marginal
paired ATE summaries; validation_pseudo_mse measures prediction of noisy scores,
not true CATE error. No automatic causal or pointwise CATE interval is returned.
Continuous modifiers are supported with explicit schema, while continuous
fixed-point interventions remain unsupported by this default score route.
Malformed roles/schema raise ValueError; unsupported numerical schedules or
support return a reason. See the full API and boundaries.
Continue
- Whole-call API benchmark: four appropriate random SCMs, exact truth and complete API time.
- UCA component pilot: Outcome/IPW/full-score components and shared-fit phase profiling.
- HTML graph tool:
python -m causalscience.graph_tool; keep the process alive and close with Ctrl-C. - Tutorial status index: maintained examples versus historical notebooks.
Current source tutorials
The latest tutorial index separates maintained examples, checkout tools, benchmarks, and historical notebooks. From the current source checkout with dependencies installed, run:
PYTHONPATH=src python -B -m tutorials.demo_scm
PYTHONPATH=src python -B -m tutorials.demo_identification
PYTHONPATH=src python -B -m tutorials.demo_sbd
Use the Document’s SCM generation and truth section for observed-X conditional average potential outcomes (CAPOs) and CSCM_capo, including continuous, binary and count outcomes. CSCM_capo keeps the X/A mechanisms and changes the outcome parameterization; its direct truth query is fast, while generation can require numerical conditional centering. The Wiki implementation map locates the code.
The installed English graph-identification tool is python -m causalscience.graph_tool. The checkout’s Korean SCM-generation editor is separate. Historical notebooks keep their retired API and are reference material; use the maintained walkthrough above for current estimation APIs.