metacausal.adapters.GenericCATEAdapter¶
- class metacausal.adapters.GenericCATEAdapter(fn_fit, fn_cate, fn_ate=None, name='custom_cate', supported_outcome_types=('continuous',))[source]¶
Bases:
objectWrap callables as a CATE-capable causal estimator.
Reduces the barrier for prototyping custom CATE estimators to two or three functions — one for fitting, one for CATE prediction, and an optional one for ATE — without implementing the full
CausalEstimatorprotocol.- Parameters:
fn_fit (callable) –
fn_fit(X, T, Y, **kwargs) -> stateCalled during
fit(). Receives the training arrays and any keyword arguments forwarded byCausalEnsemble(e.g.random_state). Returns an opaque state object (fitted model, dict, namedtuple,Nonefor stateless callables …) passed tofn_cateandfn_ateat prediction time.Reproducibility: if
fn_fituses randomness, it is the caller’s responsibility to consumekwargs.get("random_state").CausalEnsembleforwards the seed but cannot enforce its use.Deep-copy requirement:
statemust survivecopy.deepcopy()so that bootstrap resampling and supervised cross-fitting work correctly. Closures over PyTorch tensors, open file handles, or GPU state may fail; store a picklable specification (CPU weights, a file path, or constructor arguments) and rebuild such objects lazily insidefn_cate/fn_ateinstead.fn_cate (callable) –
fn_cate(state, X) -> array-like of shape (n,)Called during
cate(). Receives the fitted state and an evaluation covariate matrixXof shape(n, p). Must return a 1-D array (or(n, 1)array, which is automatically squeezed) of CATE predictions.fn_ate (callable or None) –
fn_ate(state, X) -> float | ComponentAteEstimateOptional. Called during
ate(). Receives the fitted state and the evaluation covariate matrixX. Returns either a scalar ATE or aComponentAteEstimate(useful when the estimator provides a native, more efficient ATE — e.g. a DR/AIPW mean rather thanmean(cate(X))).If
None(default),ate()falls back tomean(cate(X_eval)).name (str) – Display name used in
CateEstimate.component_estimatesand warning messages. Default"custom_cate".
Notes
Parallelism cooperation contract. MetaCausal automatically suppresses the internal joblib-parallel knobs (
n_jobs,cv_n_jobs, …) of the estimators it wraps for EconML, CausalML, DoubleML, and stochtree whenever they run inside one of MetaCausal’s own workers (fit/bootstrap, or supervised cross-fitting) – this stops the wrapped estimator’s own default from nesting a second worker pool inside the outer one and oversubscribing cores. That automatic suppression cannot reach into an opaquefn_fit/fn_cate/fn_atecallable, so if your model parallelizes internally, you are responsible for one of:Constructing it with its own parallelism knob fixed at
1up front, before passing it tofn_fit; orCooperating with the same signal MetaCausal’s built-in adapters use: inside
fn_fit, checkos.environ.get(metacausal.adapters.INNER_WORKER_ENV)and pin your model to serial when it is set (meaning you are already running inside a MetaCausal worker, so any further internal parallelism would nest and oversubscribe).
See “Extending MetaCausal” in the project README for a worked example of the second option.
Examples
Wrap a T-learner whose native ATE is a doubly robust (AIPW) mean computed at fit time, where the observed
TandYare available. This is the reasonfn_ateexists: it returns an estimate the defaultmean(cate(X))fallback cannot produce, because the fallback never sees the outcomes.>>> import numpy as np >>> from sklearn.linear_model import LinearRegression, LogisticRegression >>> from metacausal import CausalEnsemble >>> from metacausal.adapters import GenericCATEAdapter >>> from metacausal.datasets import load_lalonde >>> X, T, Y = load_lalonde() >>> def fit_fn(X, T, Y, **kwargs): ... treated = T == 1 ... m1 = LinearRegression().fit(X[treated], Y[treated]) ... m0 = LinearRegression().fit(X[~treated], Y[~treated]) ... e = LogisticRegression(max_iter=1000).fit(X, T).predict_proba(X)[:, 1] ... mu1, mu0 = m1.predict(X), m0.predict(X) ... # AIPW / doubly robust ATE on the training sample ... ate_dr = float(np.mean( ... (mu1 - mu0) ... + T * (Y - mu1) / e ... - (1 - T) * (Y - mu0) / (1 - e) ... )) ... return {"m1": m1, "m0": m0, "ate_dr": ate_dr} >>> def cate_fn(state, X): ... return state["m1"].predict(X) - state["m0"].predict(X) >>> def ate_fn(state, X): ... # native DR ATE precomputed at fit time; differs from mean(cate(X)) ... return state["ate_dr"] >>> adapter = GenericCATEAdapter(fit_fn, cate_fn, fn_ate=ate_fn, ... name="t_learner") >>> ens = CausalEnsemble(methods=[adapter], aggregation="median") >>> _ = ens.fit(X, T, Y, random_state=42) >>> ens.ate() AteEstimate(ate=..., n_methods=1, aggregation='Median', spread=...)
Methods
__init__Return the ATE.
Return CATE predictions by calling
fn_cate(state, X).Fit by calling
fn_fit(X, T, Y, **kwargs)and storing the state.No-op: the wrapped callables are opaque, so the user's
supported_outcome_typesdeclaration is the contract.Attributes
Details
- ate(X=None)[source]¶
Return the ATE.
If
fn_atewas provided, callsfn_ate(state, X_eval). Otherwise returnsmean(cate(X_eval)).X_evalisXif given, otherwise the training data fromfit().- Parameters:
X (ndarray | None)
- Return type:
- cate(X)[source]¶
Return CATE predictions by calling
fn_cate(state, X).Accepts
(n,)or(n, 1)output; raisesValueErrorotherwise.- Parameters:
X (ndarray)
- Return type: