Changelog¶
All notable changes to MetaCausal are documented here. Dates are
release dates; “Unreleased” entries describe changes on main not yet
tagged.
Unreleased¶
0.7.3 — 2026-07-24¶
README bootstrap recipes now use
n_boot=10, not200/500. A code comment at each call site states the recommended200+ (500+ for the last recipe) for real use. This keeps the examples fast to actually run, and — paired with the new CI gate below — lets every README recipe execute in about a minute instead of the 10+ minutes the documented values would take across the full multi-framework default pool.Added a mechanical README example gate to CI. A new
docsjob step (phmdoctest) extracts every fenced Python block fromREADME.mdand executes it, unmodified, on every push — closing the same class of gap the docstring doctest gate (0.7.0) closed for docstrings: a plain code block, however carefully checked by hand once, can silently drift as the package evolves without something actually running it.Fixed two more non-executable README snippets, found while building that gate. The
CausalForestDML-workaround recipe (under the “Known issue” callout) and the closing “Reproducibility and parallelism” recipe were each missing an import (CausalEnsemble;CausalEnsembleandCausalStackingrespectively) that a reader copy-pasting just that snippet would have hit as aNameError— the same bug class as the two README fixes in 0.7.0 and 0.7.2, just not yet mechanically caught until this release’s gate existed.Removed a stray reference to “the bundled replication runner” from the “Reproducibility and parallelism” section — an artifact of this repository’s split from the manuscript/replication-material repository; no such runner is bundled here.
0.7.2 — 2026-07-20¶
Fixed a data-contamination bug in the README’s “CATE estimation with a supervised strategy” recipe. It relied on
X, T, Ywithout redefining them, per the “Usage recipes” convention — but the preceding “Binary outcome on real data” recipe rebindsX, T, Yto a binary outcome, so running the recipes in order silently fitCausalStackingagainst binary data instead of the continuous Lalonde outcome the recipe’s own comments describe. It now reloadsX, T, Y = load_lalonde()explicitly.Documented a known
CausalForestDMLsegfault risk under bootstrap. EconML’s generalized-random-forest tree builder can intermittently crash (SIGSEGV/SIGBUS) under the repeated component refitsbootstrap()/estimate(..., n_boot=...)perform on the default pool — the same latent upstream bug (py-why/EconML#470, open, unfixed as of EconML 0.16.0) already worked around in the manuscript’s replication material. Added a “Known issue” callout to README’s “Reproducibility and parallelism” section with a copy-pasteabledefault_methods()-filtering workaround.summary()methods now display correctly when typed bare at a REPL/notebook prompt.AteEstimate.summary(),CateEstimate.summary(),CausalEnsemble.summary(), andBootstrapResult.summary()build multi-line, human-formatted text, but as plainstrreturns, bare-expression evaluation (which usesrepr(), notstr()) showed embedded newlines as literal\nrather than rendering them – forcingprint(...)to be remembered every time. They now returnSummaryStr, astrsubclass overriding only__repr__to match__str__; every otherstrbehavior (isinstance, equality, concatenation, formatting, etc.) is unchanged.Added
BootstrapResult.ci_at(), a confidence interval at an arbitrary level from an already-computed bootstrap run, without re-fitting. Previously,bootstrap()only returned a CI at the singlealphait was called with, for the ensemble’s own aggregated ATE; getting a CI for another level, for a specific component (component_boot_ates), or for a derived statistic (e.g. a per-replicate mean across components) required hand-rolling the same Politis-Romano scaled-percentile formula against the raw distributions.ci_at()exposes that formula directly, and defaults to reproducing the ensemble’s own CI.bootstrap()’s internal CI construction now calls the same shared implementation, so there’s a single source of truth for the formula.Fixed
BootstrapResult.component_ate_summary()(and thussummary()’s printed per-component rows) ignoring themethod="subsample"scale correction. It computed per-component CIs with plain percentiles regardless of resampling scheme, while the ensemble-levelate_ci_lower/ate_ci_upperalready correctly applied the Politis–Romanosqrt(m/n)scale/center correction formethod="subsample"— an inconsistency between the two, not a difference in scheme handling. Component CIs now also center on the component’s own full-sample fit estimate (component_ate_estimates) rather than the bootstrap mean, matching the class’s documented point-estimate convention (falls back to the bootstrap mean when unavailable, e.g. on a hand-builtBootstrapResult). Undermethod="nonparametric"(the default), this recentering is a no-op — the scale factor is 1.0, so the formula reduces algebraically to the previous plain-percentile result; onlymethod="subsample"users are affected. Also addedBootstrapResult.n_trainand.scale, needed to reconstruct the correction after the fact.
0.7.1 — 2026-07-18¶
Fixed the default R-Learner pool components silently ignoring their HGB propensity model. CausalML’s
BaseRLearner.fit()assignsself.model_p = self.propensity_learnerafter calling_set_propensity_models(), which readsself.model_p(notpropensity_learner) to decide whether to use a custom propensity model. On a freshBaseRRegressor/BaseRClassifierinstance,model_pdoesn’t exist yet at that point, so every firstfit()silently fell back to CausalML’s ownElasticNetPropensityModel()default instead of theHistGradientBoostingClassifierthe default pools intended — and that default’ssaga+elasticnetgrid search routinely fails to converge on Lalonde-sized data, floodingConvergenceWarnings (particularly noticeable in interactive use, e.g.python -iagainst the manuscript examples). The shipped default pools now setmodel_pdirectly at construction, alongside the existingcv_n_jobs=1workaround. This is a known upstream bug (fixed on CausalML’smastervia uber/causalml#937, not yet in a PyPI release as ofcausalml==0.17.0); the workaround is harmless once CausalML ships the fix.Widened the
causalml,stochtree, anddoublemlcaps to admit each library’s current latest patch/minor (causalml<0.17.1,stochtree<0.4.6,doubleml<0.11.4), validating CI (includingpytest -m integration) againstcausalml==0.17.0,stochtree==0.4.5, anddoubleml==0.11.3. Floors are unchanged; per the versioning policy inpyproject.toml, only the cap moves until the floor itself is deliberately re-validated and bumped.
0.7.0 — 2026-07-17¶
New aggregation strategy:
Select. Chooses the single component model minimizing a pseudo-outcome risk (loss="dr"for DR/AIPW plug-in risk,loss="r"for R-risk on the Robinson residuals) instead of combining components. Implemented as aSupervisedStrategyso it shares cross-fitted nuisance estimation,EnsembleWeightsintrospection, and bootstrap with the true aggregators — it returns a one-hot weight vector rather than a blend.Select(loss="dr")is the closed-form vertex ofQAggregation’snu=1limit, computed directly by argmin rather than via the general solver. Serves as the feasible-selection comparator for ensemble-vs-selection studies.Fixed core/process oversubscription from CausalML’s R-Learner default pool component. CausalML’s
BaseRLearnerfamily (BaseRRegressor/BaseRClassifier, in the default continuous/binary pools) defaultscv_n_jobs=-1for its internalcross_val_predictcall — unlike EconML’sn_jobs, which most wrapped estimators default to serial, this knob is on by default, so left alone it spawned a full-core joblib pool on every single fit, nesting inside MetaCausal’s own outer parallelism (fit()/bootstrap(), or an external harness’s own outer parallel loop) and oversubscribing cores. The shipped default pools now pincv_n_jobs=1at construction, andCausalMLAdaptergates the wrapped model’scv_n_jobsonMETACAUSAL_INNER_WORKER(mirroringEconMLAdapter’s existingn_jobssuppression) so user-supplied R-Learner components get the same protection. The two adapters’ pinning logic is now shared viametacausal._parallel.force_serial.Plotting functions are now also methods.
forest,weights, andcate_profile(frommetacausal.plots) each stay the primary, documented implementation, but are now also reachable as thin delegating methods on the class they act on:BootstrapResult.forest(),BootstrapResult.cate_profile(x, ...),CateEstimate.cate_profile(x, ...),EnsembleWeights.plot(),CausalEnsemble.weights(), andCausalEnsemble.disagreement(X). Both surfaces stay in sync — the methods just call the functions — and matplotlib remains an optional dependency imported only when one of these six methods (or functions) is actually called.Added a Sphinx API reference, published at asmahani.github.io/metacausal. Full public-API coverage via
autodoc/napoleon/autosummary—CausalEnsemble, every result/estimate class,metacausal.aggregation,metacausal.adapters,metacausal.plots,metacausal.datasets— built with thefurotheme, and checked in CI withsphinx-build -W(warnings-as-errors) plus thedoctestbuilder. Deployed to GitHub Pages automatically on every push tomain. Build locally withpip install -e ".[all,docs]"thenpython -m sphinx -b html docs docs/_build/html.Fixed three non-executable docstring examples.
CausalEnsemble,DoubleMLAdapter, andStochtreeAdaptereach had anExample::code block that referenced undefined placeholder data (X_train/T_train/Y_trainorX/T/Y), andCausalEnsemble’s additionally passedDoubleMLIRMthe wrong kwarg (ml_l=, which is PLR-specific; IRM takesml_g=). All three are now self-contained, fast, and written as real>>>doctests that the newdocsCI job actually executes.Fixed two non-executable README snippets. The Visualisation helpers recipe referenced an undefined
grid, and the CATE-with-a-supervised-strategy recipe referenced an undefinedX_eval; both are now defined inline. Every Python code block in this README (except the intentionally-skeletalGenericCATEAdaptertemplate) has been run end to end against this release. Added a note at the top of “Usage recipes” clarifying that snippets reusingX,T,Ywithout redefining them assume the Quick start data.Published the custom-component parallelism cooperation contract.
metacausal.adapters.INNER_WORKER_ENV(the sentinel env var custom components can check to cooperate with MetaCausal’s own-worker parallelism guard) is now a public, documented export, not just an underscore-prefixed internal inmetacausal._parallel. Documented the cooperation contract inGenericCATEAdapter’s docstring and README’s “Extending MetaCausal” section (with a worked example), and added a sentence to “Reproducibility and parallelism” stating known-adaptern_jobs/cv_n_jobssuppression (EconML, CausalML, DoubleML, stochtree) as an ongoing guarantee rather than a changelog-only artifact.
0.6.1 — 2026-07-01¶
Documentation: the README quick start and recipes now use the result objects’
summary()methods and theEnsembleWeightsrepr (from 0.6.0) in place of hand-formatted loops; corrected the stated default continuous-pool size (nine, not ten) and aligned the bootstrap wording with the rest of the docs (“full-pipeline”, not “honest”).
0.6.0 — 2026-06-30¶
Human-readable result summaries:
CausalEnsembleand the main result objects now expose compact__repr__output, andAteEstimate,CateEstimate, andBootstrapResultnow provide explicitsummary()methods for terminal-friendly multi-line reports without dumping full NumPy arrays.summary()acceptsdigits=andsigned=for column formatting;AteEstimate.summary()additionally acceptsshow_ci=Falseto omit the per-component native confidence intervals (cleaner point-estimate table when CIs are unavailable for some methods or reported separately).EconML/sklearn deprecation suppression:
EconMLAdapternow suppresses sklearn’sFutureWarningaboutforce_all_finitebeing renamed toensure_all_finitearound wrapped EconML prediction and analytical-interval calls (ate,cate, and their fallback/effect paths). The filter is adapter-local, narrowed by category + message + module, and scoped to the single call so unrelatedFutureWarnings still surface.
0.5.0 — 2026-06-15¶
bootstrap()flags a point estimate outside its CI under both resampling schemes. TheBootstrapWarningfor an ATE confidence interval that does not contain the point estimate previously fired only under thesubsamplescheme; it now fires undernonparametrictoo, with a scheme-specific explanation. Containment is governed by the same percentile condition for both schemes — the √(m/n) scaling changes only the interval width — and with-replacement resampling (~63% distinct units) or per-replicate weight re-optimization can shift the replicate distribution off the point estimate. Pre-1.0 behavior change: nonparametric bootstraps that previously ran silently may now emit a warning.Single-valued outcomes are rejected with an actionable error.
infer_outcome_typepreviously classified a constantY(all zeros, all ones, or any single repeated value) asbinary, which surfaced later as an opaque scikit-learn “needs samples of at least 2 classes” failure. It now raises a clearValueErrorup front. Pre-1.0 behavior change.Docstring corrections. The supervised aggregation strategies (
CausalStacking,RStacking,QAggregation) documented a staleLGBMRegressor/LGBMClassifierdefault and omitted the regressor-vs-classifier requirement for binary outcomes; they now describe theHistGradientBoosting*defaults and the outcome-type-dependent model contract.GenericCATEAdapterno longer references a non-existent extensibility guide, and itsfn_ateexample now performs a genuine doubly robust (AIPW) computation instead of silently reproducing the defaultmean(cate(X)).Correction to the 0.4.0 parallel-safety note. The intermittent segmentation faults in EconML’s Cython tree builder (
CausalForestDML) are a latent out-of-bounds bug in EconML’s generalized random forest — not merely loky oversubscription. They reproduce single-threaded, surfacing under the repeated component refits a bootstrap performs (EconML #470, unresolved as of econml 0.16.0). The 0.4.0n_jobs=1pin is kept as defensive hardening (it removes genuine oversubscription and lowers crash probability) but does not fully eliminate the crash:bootstrap()withCausalForestDMLin the pool can still segfault intermittently under heavy parallelism.
0.4.0 — 2026-06-07¶
Parallel-safety fix: the default
CausalForestDMLnow builds its forest withn_jobs=1, andEconMLAdapterpins any wrapped joblib-parallel estimator to a single job when it runs inside one of MetaCausal’s own parallel workers (fit,bootstrap, or supervised cross-fitting). EconML defaultsCausalForestDMLton_jobs=-1, whose inner loky pool would otherwise nest inside the outer worker and intermittently segfault EconML’s Cython tree builder under oversubscription. Outputs are unchanged — the forest is seeded deterministically — only the nested parallelism is removed.Default pool update: S-Learner and its classifier sibling (
BaseSClassifier) are now excluded from the default pools for continuous and binary outcomes, respectively; the default pools are now nine and seven components.
0.3.1 — 2026-06-05¶
Dependency declaration:
joblib>=1.2is now a direct core dependency instead of arriving only transitively throughscikit-learn.Top-level adapter imports:
DoubleMLAdapter,EconMLAdapter, andStochtreeAdapterare now exported frommetacausal, so users no longer need submodule import paths for those adapters.Custom CATE shorthand:
CausalEnsemblenow auto-wraps 2- and 3-callable lists/tuples asGenericCATEAdapter, allowing(fn_fit, fn_cate)and(fn_fit, fn_cate, fn_ate)directly inmethods=[...].
0.3.0 — 2026-05-30¶
Analytical CI controls:
DoubleMLAdapter(alpha=...)now forwards toDoubleML.confint(level=1 - alpha), andEconMLAdapter(alpha=..., inference=...)now forwards both the interval level and the fit-time inference backend.CausalML clarification: analytical ATE CI level was already configurable upstream via the wrapped estimator (
ate_alpha/ estimator-specificalpha); the docs now describe that accurately instead of implying a missing adapter feature.API cleanup: removed the unused
alphaargument fromCausalEnsemble.cate(). Analytical component-level CI configuration now lives on the relevant adapter or wrapped upstream estimator, while ensemble CI level remains onbootstrap(alpha=...).
0.2.2 — 2026-05-04¶
Dependency hygiene: the four causal-ML extras (
econml,doubleml,stochtree,causalml) are now pinned to a single patch each — floors at the exact version validated by CI on the most recent main-branch run, caps at the next patch. Motivated by stochtree #376, where a patch release (0.4.0 → 0.4.2) silently changed the semantics ofBCFModel.predict(terms="tau")and broke 0.2.0. Patch-level caps mean every upstream release lands outside the cap, triggers a Dependabot PR, and runspytest -m integrationbefore we widen — closing the silent-install hole that produced the 0.2.1 hotfix.
0.2.1 — 2026-05-04¶
Bug fix:
StochtreeAdapternow callsBCFModel.predict(..., terms="cate")instead ofterms="tau". Withstochtree 0.4.2(which added a parametric treatment-intercept term in the BCF sampler),terms="tau"returned the forest-only piece and excluded the parametric component, producing wildly seed-sensitive ATEs that disagreed sharply with the rest of the default ensemble.terms="cate"returns the full conditional treatment effect — including parametric and random-slope components, when present — for any BCF configuration. Fixes upstream issue stochtree #376 on the metacausal side.
0.2.0 — 2026-05-04¶
New
Outcome-type handling:
CausalEnsembleauto-detects continuous vs binaryYatfit(), materialises the right default pool, and routes nuisance throughpredict_probafor binary. Override viaoutcome_type="continuous"|"binary". Publicmetacausal.infer_outcome_type(Y)utility;binarize_y={"median","positive"}onload_lalonde().Subsample bootstrap (
bootstrap(method="subsample")): m-out-of-n without replacement, T-stratified, with Politis–Romano scaled-percentile CIs. Eliminates duplicate-unit leakage across cross-fit folds.Structured warning hierarchy:
ComponentFailureWarning,ComponentExclusionWarning,BootstrapWarningunder a commonMetaCausalWarningumbrella.CausalMLAdapteraccepts apropensity_model=kwarg, forwarding a fitted propensity to non-TMLE meta-learners.
Breaking changes for custom-strategy / custom-adapter authors
AggregationStrategyand family areabc.ABCwith a unifiedaggregateentry point. Subclasses now implementaggregaterather than per-mode methods.Every adapter must declare
supported_outcome_typesand implementvalidate_outcome_type(detected); the injectablefit_nuisance_fngains anoutcome_typeparameter.
Other
Bounded version constraints on
econml,doubleml,causalml,stochtree(capped at next minor; floors anchored to tested versions).requires-pythonraised to>=3.11(causalml 0.16 floor).Tier-2 integration tests via
pytest -m integration.Bug fixes:
load_lalondeno longer leaks a file handle;EconMLAdaptersuppresses the upstreamDataConversionWarningfromDRLearner(discrete_outcome=True).PyPI metadata polish (classifiers, license badge).
Tested against: doubleml 0.11.2, econml 0.16.0, causalml 0.16.0, stochtree 0.4.0.
0.1.0 — 2026-04-25¶
Initial public release.