@elaraai/east-py-datascience 1.0.11 → 1.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,18 +5,21 @@
5
5
  /**
6
6
  * Causal inference for East.
7
7
  *
8
- * Provides causal effect estimation (DoWhy backdoor adjustment),
9
- * refutation tests, heterogeneous treatment effects (EconML LinearDML),
10
- * and accumulated local effects dose-response curves (PyALE).
8
+ * One declarative entry point {@link Causal.experiment} — answers *"did X
9
+ * change Y, and can I trust it?"* for a binary treatment: the naive vs
10
+ * backdoor-adjusted effect, confounder balance, propensity overlap, a
11
+ * robustness check, and an honesty **verdict** that refuses (`adjusted = none`)
12
+ * when the data can't support an answer. The raw DoWhy / EconML / PyALE
13
+ * estimators are internal implementation it composes — not a public surface.
11
14
  *
12
15
  * @packageDocumentation
13
16
  */
14
- import { East, StructType, VariantType, OptionType, ArrayType, IntegerType, FloatType, BooleanType, StringType, BlobType, NullType, } from "@elaraai/east";
15
- import { VectorType, MatrixType } from "../types.js";
17
+ import { East, StructType, VariantType, OptionType, ArrayType, IntegerType, FloatType, BooleanType, StringType, NullType, } from "@elaraai/east";
18
+ import { VectorType } from "../types.js";
16
19
  // Re-export shared types for convenience
17
- export { VectorType, MatrixType } from "../types.js";
20
+ export { VectorType } from "../types.js";
18
21
  // ============================================================================
19
- // Config Types
22
+ // Shared vocabulary
20
23
  // ============================================================================
21
24
  /**
22
25
  * Inverse propensity weighting scheme for the propensity score
@@ -34,7 +37,7 @@ export const CausalWeightingSchemeType = VariantType({
34
37
  * Backdoor-adjusted effect estimator.
35
38
  */
36
39
  export const CausalEstimatorType = VariantType({
37
- /** Linear regression of outcome on treatment + common causes */
40
+ /** Linear regression of outcome on treatment + common causes (default) */
38
41
  linear_regression: NullType,
39
42
  /** Inverse propensity score weighting (binary treatment only) */
40
43
  propensity_score_weighting: StructType({
@@ -53,540 +56,325 @@ export const CausalTargetUnitsType = VariantType({
53
56
  /** Average treatment effect on the controls */
54
57
  atc: NullType,
55
58
  });
56
- /**
57
- * Propensity-based sample trimming applied before estimation
58
- * (propensity score weighting only).
59
- */
60
- export const PropensityTrimType = VariantType({
61
- /** Keep units inside the common-support overlap of treated and control propensities */
62
- overlap: NullType,
63
- /** Keep units with propensity inside explicit bounds */
64
- bounds: StructType({
65
- /** Minimum propensity score to keep */
66
- lower: FloatType,
67
- /** Maximum propensity score to keep */
68
- upper: FloatType,
69
- }),
70
- });
71
59
  /**
72
60
  * Bootstrap confidence interval configuration.
73
61
  *
74
- * When `cluster_column` is set, whole clusters are resampled with
75
- * replacement (the cluster is the exchangeable unit) — use this when rows
76
- * are autocorrelated within a group (e.g. days within a batch).
62
+ * When `cluster_column` is set, whole clusters are resampled with replacement
63
+ * (the cluster is the exchangeable unit) — use this when rows are autocorrelated
64
+ * within a group.
77
65
  */
78
66
  export const CausalBootstrapConfigType = StructType({
79
- /** Number of bootstrap replicates */
67
+ /** Number of bootstrap replicates (default 200) */
80
68
  reps: IntegerType,
81
69
  /** Column whose values identify clusters to resample (default: resample rows) */
82
70
  cluster_column: OptionType(StringType),
83
71
  /** Confidence level for the percentile interval (default 0.95) */
84
72
  confidence_level: OptionType(FloatType),
85
73
  });
74
+ /** A confidence interval — the one CI type used across the result. */
75
+ export const CiType = StructType({ lower: FloatType, upper: FloatType });
76
+ // ============================================================================
77
+ // Experiment — the single declarative entry point (snake_case; mirrors the
78
+ // e3-ui `<Experiment>` contract exactly so the two replicas unify structurally).
79
+ // ============================================================================
80
+ /**
81
+ * Which robustness checks to run inside {@link causal_experiment}.
82
+ */
83
+ export const RefuteSpecType = StructType({
84
+ /** Permuted-treatment negative control — a real effect should vanish. */
85
+ placebo: BooleanType,
86
+ /** Inject an independent random common cause — the effect should hold. */
87
+ random_common_cause: BooleanType,
88
+ /** Re-estimate on random subsamples — the effect should be stable. */
89
+ data_subset: BooleanType,
90
+ /** Unobserved-confounder strengths to simulate → the sensitivity / tipping curve. */
91
+ sensitivity: OptionType(ArrayType(FloatType)),
92
+ });
86
93
  /**
87
- * Configuration for backdoor-adjusted causal effect estimation.
88
- *
89
- * The data matrix is interpreted via `columns`: every column referenced by
90
- * `treatment`, `outcome`, `common_causes`, `categorical` and
91
- * `bootstrap.cluster_column` must appear in `columns`. Categorical columns
92
- * hold integer-valued category codes and are one-hot encoded internally.
94
+ * Configuration for {@link causal_experiment} the staged "question + method"
95
+ * the surface edits. Binary treatment only (v1). Reuses the estimator / target
96
+ * / bootstrap vocabularies.
93
97
  */
94
- export const CausalEffectConfigType = StructType({
95
- /** Column names for the data matrix (one per column, in order) */
96
- columns: ArrayType(StringType),
97
- /** Treatment column (must be 0/1 for propensity score weighting) */
98
+ export const CausalExperimentConfigType = StructType({
99
+ /** Binary (0/1) treatment column. */
98
100
  treatment: StringType,
99
- /** Outcome column */
101
+ /** Outcome column. */
100
102
  outcome: StringType,
101
- /** Confounder columns to adjust for (the backdoor set) */
103
+ /** Confounders to adjust for (the backdoor set). */
102
104
  common_causes: ArrayType(StringType),
103
- /** Columns holding integer category codes, one-hot encoded internally */
105
+ /** Confounder columns holding categories (string or int codes), one-hot encoded. */
104
106
  categorical: OptionType(ArrayType(StringType)),
105
- /** Effect estimator (default: linear_regression) */
107
+ /** Estimator (default: linear_regression). */
106
108
  method: OptionType(CausalEstimatorType),
107
- /** Target population (default: ate) */
108
- target_units: OptionType(CausalTargetUnitsType),
109
- /** Propensity trimming applied before estimation (psw only) */
110
- trim: OptionType(PropensityTrimType),
111
- /** Bootstrap confidence interval (omit to skip CI computation) */
109
+ /** Target population (default: ate). */
110
+ estimand: OptionType(CausalTargetUnitsType),
111
+ /** Which robustness checks to run. */
112
+ refute: OptionType(RefuteSpecType),
113
+ /** Continuous column for the ALE dose-response curve (the "How much?" view). */
114
+ dose_feature: OptionType(StringType),
115
+ /** Positivity guard — common-support fraction below this → non_identifiable_positivity (default 0.10). */
116
+ min_overlap: OptionType(FloatType),
117
+ /** Not-estimable guard — minority-arm fraction below this → not_estimable (default 0.02). */
118
+ min_treatment_variation: OptionType(FloatType),
119
+ /** Bootstrap CI config (default: 200 reps, 0.95). */
112
120
  bootstrap: OptionType(CausalBootstrapConfigType),
113
- /** Random seed for propensity fitting and bootstrap resampling */
121
+ /** Random seed. */
114
122
  random_state: OptionType(IntegerType),
115
123
  });
124
+ /** One confounder's before-adjustment imbalance (categoricals → one row per level). */
125
+ export const BalanceRowType = StructType({
126
+ column: StringType,
127
+ /** The original confounder this row belongs to — equals `column` for a numeric
128
+ * confounder; the base confounder for a one-hot categorical level. */
129
+ base_column: StringType,
130
+ treated_mean: FloatType,
131
+ control_mean: FloatType,
132
+ /** Standardized mean difference, (mt-mc)/sqrt((vt+vc)/2). */
133
+ std_diff: FloatType,
134
+ });
135
+ /** Positivity / common-support diagnostic (binary treatment). */
136
+ export const OverlapDiagnosticType = StructType({
137
+ /** Propensity histogram (20 bins over [0,1]) for the treated arm. */
138
+ treated_propensity: VectorType(FloatType),
139
+ /** Propensity histogram for the control arm. */
140
+ control_propensity: VectorType(FloatType),
141
+ /** Fraction of rows inside the treated/control common support. */
142
+ common_support_frac: FloatType,
143
+ /** Whether common support clears `min_overlap`. */
144
+ positivity_ok: BooleanType,
145
+ });
146
+ /** The robustness summary the verdict + Trust tab consume. */
147
+ export const RefutationType = StructType({
148
+ /** Effect under a permuted (placebo) treatment — should be ≈ 0. */
149
+ placebo_effect: OptionType(FloatType),
150
+ /** Whether the placebo effect vanished. */
151
+ placebo_passes: OptionType(BooleanType),
152
+ /** Whether a decoy random common cause left the estimate inside its CI. */
153
+ random_cc_within_ci: OptionType(BooleanType),
154
+ /** Mean effect across data subsamples (stability). */
155
+ data_subset_effect: OptionType(FloatType),
156
+ /** Std of the effect across data subsamples. */
157
+ data_subset_std: OptionType(FloatType),
158
+ /** Closed-form E-value — confounder strength needed to explain the effect away. */
159
+ robustness_value: OptionType(FloatType),
160
+ /** Unobserved-confounder sensitivity (tipping) curve: effect at each simulated strength. */
161
+ sensitivity: OptionType(StructType({
162
+ strengths: VectorType(FloatType),
163
+ effects: VectorType(FloatType),
164
+ })),
165
+ });
166
+ /** A dose-response (ALE) curve of a continuous feature on the outcome. */
167
+ export const DoseResponseType = StructType({
168
+ feature: StringType,
169
+ grid: VectorType(FloatType),
170
+ effect: VectorType(FloatType),
171
+ lower: OptionType(VectorType(FloatType)),
172
+ upper: OptionType(VectorType(FloatType)),
173
+ /** Rows per dose bin — drives the surface's "you are here" (busiest bin) marker. */
174
+ size: VectorType(IntegerType),
175
+ });
116
176
  /**
117
- * Refutation test for an estimated causal effect.
177
+ * The honesty verdict a thin tag; the numbers live top-level on the result.
178
+ * Only `not_estimable` carries a (human-readable) reason.
118
179
  */
119
- export const CausalRefuterType = VariantType({
120
- /** Replace treatment with a permuted placebo - effect should vanish */
121
- placebo_treatment: StructType({
122
- /** Number of simulations (default 100) */
123
- num_simulations: OptionType(IntegerType),
124
- }),
125
- /** Add an independent random common cause - effect should be unchanged */
126
- random_common_cause: StructType({
127
- /** Number of simulations (default 100) */
128
- num_simulations: OptionType(IntegerType),
129
- }),
130
- /** Re-estimate on random data subsets - effect should be stable */
131
- data_subset: StructType({
132
- /** Fraction of rows kept per simulation (default 0.8) */
133
- subset_fraction: OptionType(FloatType),
134
- /** Number of simulations (default 100) */
135
- num_simulations: OptionType(IntegerType),
136
- }),
137
- /**
138
- * Simulate an unobserved confounder at each given strength - a
139
- * sensitivity/tipping curve. Strength acts on both treatment
140
- * (flip probability for binary treatment, linear coefficient otherwise)
141
- * and outcome (linear coefficient).
142
- */
143
- unobserved_common_cause: StructType({
144
- /** Confounder effect strengths to simulate, one new effect per entry */
145
- effect_strengths: ArrayType(FloatType),
146
- }),
180
+ export const ExperimentVerdictType = VariantType({
181
+ /** A real, robust, material effect. */
182
+ causal: NullType,
183
+ /** A small but real effect, or no clear effect after adjustment. */
184
+ modest: NullType,
185
+ /** Adjusted, but the estimate isn't trustworthy (placebo fails). */
186
+ adjustment_insufficient: NullType,
187
+ /** No like-for-like comparison exists (positivity violated). */
188
+ non_identifiable_positivity: NullType,
189
+ /** The estimand can't be formed (treatment barely varies). */
190
+ not_estimable: StringType,
147
191
  });
148
192
  /**
149
- * Nuisance model for DML residualization stages.
193
+ * The complete, honest result of {@link causal_experiment}. `adjusted` is
194
+ * `none` when the engine refuses (positivity / no-variation); the `verdict` tag
195
+ * carries the headline; every word/colour on the surface derives from these numbers.
150
196
  */
151
- export const CausalNuisanceModelType = VariantType({
152
- /** Random forest (regressor, or classifier for a discrete treatment) */
153
- random_forest: StructType({
154
- /** Number of trees (default 100) */
155
- n_estimators: OptionType(IntegerType),
156
- /** Minimum samples per leaf (default 5) */
157
- min_samples_leaf: OptionType(IntegerType),
158
- /** Maximum tree depth (default unlimited) */
159
- max_depth: OptionType(IntegerType),
160
- }),
161
- /** Gradient boosting (regressor, or classifier for a discrete treatment) */
162
- gradient_boosting: StructType({
163
- /** Number of boosting stages (default 100) */
164
- n_estimators: OptionType(IntegerType),
165
- /** Learning rate (default 0.1) */
166
- learning_rate: OptionType(FloatType),
167
- /** Maximum tree depth (default 3) */
168
- max_depth: OptionType(IntegerType),
169
- }),
170
- /** Linear/logistic regression */
171
- linear: NullType,
197
+ export const CausalExperimentResultType = StructType({
198
+ /** The raw (unadjusted) mean difference always computable. */
199
+ naive: FloatType,
200
+ naive_ci: OptionType(CiType),
201
+ /** The adjusted (like-for-like) effect + CI; `none` ⇒ the engine refused. */
202
+ adjusted: OptionType(StructType({ effect: FloatType, ci: OptionType(CiType) })),
203
+ n_total: IntegerType,
204
+ n_treated: IntegerType,
205
+ n_control: IntegerType,
206
+ n_dropped: IntegerType,
207
+ /** Per-confounder before-adjustment imbalance, most-imbalanced first. */
208
+ balance: ArrayType(BalanceRowType),
209
+ overlap: OverlapDiagnosticType,
210
+ refutation: OptionType(RefutationType),
211
+ /** ALE dose-response of `dose_feature` (present when `config.dose_feature` is set). */
212
+ dose_response: OptionType(DoseResponseType),
213
+ verdict: ExperimentVerdictType,
172
214
  });
173
215
  /**
174
- * Configuration for double machine learning (EconML LinearDML).
216
+ * Type-parameter placeholder for the generic row struct `T`. At a call site the
217
+ * caller passes the concrete row `StructType`; `applyTypeArgs` substitutes this
218
+ * bare `"T"` reference wherever it appears in the input types (inside
219
+ * `ArrayType(ROW_T)`).
175
220
  */
176
- export const CausalDMLConfigType = StructType({
177
- /** Nuisance model for the outcome stage (default: random_forest) */
178
- model_y: OptionType(CausalNuisanceModelType),
179
- /** Nuisance model for the treatment stage (default: random_forest) */
180
- model_t: OptionType(CausalNuisanceModelType),
181
- /** Treat the treatment as discrete/categorical (default false) */
182
- discrete_treatment: OptionType(BooleanType),
183
- /** Cross-fitting folds (default 2) */
184
- cv_folds: OptionType(IntegerType),
185
- /** Confidence level for effect/ATE intervals (default 0.95) */
186
- confidence_level: OptionType(FloatType),
187
- /** Random seed */
188
- random_state: OptionType(IntegerType),
189
- });
221
+ const ROW_T = "T";
190
222
  /**
191
- * Configuration for an accumulated local effects (ALE) dose-response curve.
223
+ * One declarative causal experiment naive vs adjusted effect, balance,
224
+ * overlap, robustness, and an honesty verdict, in a single call. Generic over
225
+ * the row struct (fields = columns), binary treatment.
192
226
  *
193
- * Fits a gradient-boosting emulator of the outcome on all non-outcome
194
- * columns, then computes the correlation-robust ALE curve of `feature`.
227
+ * @param data - Array of row structs (one per unit; fields are the columns)
228
+ * @param config - The experiment configuration
229
+ * @returns The honest result + verdict; `adjusted` is `none` when refused
195
230
  */
196
- export const CausalALEConfigType = StructType({
197
- /** Column names for the data matrix (one per column, in order) */
198
- columns: ArrayType(StringType),
199
- /** Outcome column the emulator predicts */
200
- outcome: StringType,
201
- /** Continuous feature column to compute the ALE curve for */
202
- feature: StringType,
203
- /** Columns holding integer category codes, one-hot encoded internally */
204
- categorical: OptionType(ArrayType(StringType)),
205
- /** Number of grid intervals (default 10) */
206
- grid_size: OptionType(IntegerType),
207
- /** Include confidence intervals (default true) */
208
- include_ci: OptionType(BooleanType),
209
- /** Confidence level for the CI (default 0.95) */
210
- confidence_level: OptionType(FloatType),
211
- /** Emulator (HistGradientBoosting) hyperparameters */
212
- emulator: OptionType(StructType({
213
- /** Number of boosting iterations (default 300) */
214
- n_estimators: OptionType(IntegerType),
215
- /** Learning rate (default 0.05) */
216
- learning_rate: OptionType(FloatType),
217
- /** Maximum tree depth (default unlimited) */
218
- max_depth: OptionType(IntegerType),
219
- /** Minimum samples per leaf (default 20; lower for small datasets) */
220
- min_samples_leaf: OptionType(IntegerType),
221
- })),
222
- /** Random seed for the emulator */
223
- random_state: OptionType(IntegerType),
224
- });
231
+ export const causal_experiment = East.genericPlatform("causal_experiment", ["T"], [ArrayType(ROW_T), CausalExperimentConfigType], CausalExperimentResultType);
225
232
  // ============================================================================
226
- // Model Blob Types
233
+ // Validation-design contract — `causal_design_validation`
234
+ //
235
+ // Turns a finished experiment into the recipe for a REAL randomised controlled
236
+ // trial that would confirm the effect (how many units, the split, which
237
+ // categories to match the groups on) — or, when a plain trial can't be run, what
238
+ // to change. One design family; the framing + size `basis` + visuals vary with
239
+ // the verdict. Replicated verbatim (snake_case) in e3-ui's experiment types.
227
240
  // ============================================================================
228
- /**
229
- * Model blob for a fitted LinearDML estimator.
230
- */
231
- export const CausalDMLModelBlobType = VariantType({
232
- /** Fitted EconML LinearDML estimator */
233
- causal_dml: StructType({
234
- /** Serialized estimator (cloudpickle) */
235
- data: BlobType,
236
- /** Number of effect-modifier (X) features */
237
- n_features_x: IntegerType,
238
- /** Confidence level used for effect/ATE intervals */
239
- confidence_level: FloatType,
240
- }),
241
+ /** Why the trial is sized the way it is — set from the verdict. */
242
+ export const DesignBasisType = VariantType({
243
+ /** Clear effect → power the trial to detect the observed effect. */
244
+ detect_observed: NullType,
245
+ /** Fuzzy / maybe-nothing power to the smallest effect worth acting on. */
246
+ resolve_vs_null: NullType,
247
+ /** A trust check failed → randomise to remove the bias adjustment couldn't. */
248
+ de_bias: NullType,
249
+ /** No overlap randomise within the comparable range. */
250
+ restrict_to_overlap: NullType,
251
+ /** No control group exists hold back a random sample next time. */
252
+ create_control: NullType,
241
253
  });
242
- // ============================================================================
243
- // Result Types
244
- // ============================================================================
245
- /**
246
- * Estimated causal effect.
247
- */
248
- export const CausalEffectResultType = StructType({
249
- /** Point estimate of the effect (outcome units per treatment unit) */
250
- effect: FloatType,
251
- /** Bootstrap percentile confidence interval (present when bootstrap configured) */
252
- ci: OptionType(StructType({
253
- /** Lower bound */
254
- lower: FloatType,
255
- /** Upper bound */
256
- upper: FloatType,
257
- })),
258
- /** Rows used for estimation (after trimming) */
259
- n_samples: IntegerType,
260
- /** Treated rows used */
254
+ /** One sizing option — a split and the head-count it needs (even split first). */
255
+ export const TrialOptionType = StructType({
256
+ label: StringType,
257
+ /** Fraction assigned to the treatment (0..1; 0.5 = even). */
258
+ treated_share: FloatType,
261
259
  n_treated: IntegerType,
262
- /** Control rows used */
263
260
  n_control: IntegerType,
261
+ n_total: IntegerType,
264
262
  });
265
- /**
266
- * Refutation test result.
267
- */
268
- export const CausalRefuteResultType = StructType({
269
- /** The original estimated effect being refuted */
270
- estimated_effect: FloatType,
271
- /**
272
- * Effect under the refutation. One entry for placebo/random-common-cause/
273
- * data-subset; one entry per strength for unobserved_common_cause.
274
- */
275
- new_effects: VectorType(FloatType),
276
- /** Refutation p-value where the refuter provides one */
277
- p_value: OptionType(FloatType),
263
+ /** The "chance of detecting it" curve — total head-count → power (0..1). */
264
+ export const PowerCurveType = StructType({
265
+ n: VectorType(IntegerType),
266
+ power: VectorType(FloatType),
278
267
  });
279
268
  /**
280
- * Average treatment effect with confidence interval.
269
+ * The validation-trial recipe a randomised controlled trial sized from the
270
+ * observed effect (or the materiality threshold) and the outcome spread, with the
271
+ * groups matched on the confounders that were most imbalanced.
281
272
  */
282
- export const CausalATEResultType = StructType({
283
- /** Average treatment effect over the given X */
284
- ate: FloatType,
285
- /** Lower bound at the model's confidence level */
286
- lower: FloatType,
287
- /** Upper bound at the model's confidence level */
288
- upper: FloatType,
273
+ export const ExperimentDesignType = StructType({
274
+ verdict: ExperimentVerdictType,
275
+ basis: DesignBasisType,
276
+ /** Effect size the trial is powered to detect (observed, or materiality). */
277
+ target_effect: FloatType,
278
+ /** Outcome spread (pooled SD) used to size it. */
279
+ outcome_sd: FloatType,
280
+ target_power: FloatType,
281
+ alpha: FloatType,
282
+ /** Chance the CURRENT sample would already detect `target_effect`; `none` when there's no comparison group. */
283
+ current_power: OptionType(FloatType),
284
+ /** Categories both groups must be matched on (most-imbalanced confounders). */
285
+ match_on: ArrayType(StringType),
286
+ /** Ranked split options (even split first; cost-saving alternates). */
287
+ options: ArrayType(TrialOptionType),
288
+ /** The detect-chance curve for the chart. */
289
+ power_curve: PowerCurveType,
290
+ /** One generated, plain-language sentence framing the recipe. */
291
+ rationale: StringType,
289
292
  });
290
- /**
291
- * Accumulated local effects curve.
292
- */
293
- export const ALEResultType = StructType({
294
- /** Feature grid values (interval edges) */
295
- grid: VectorType(FloatType),
296
- /** Centered ALE effect at each grid value (outcome units) */
297
- effect: VectorType(FloatType),
298
- /** Lower CI at each grid value (present when include_ci) */
299
- lower: OptionType(VectorType(FloatType)),
300
- /** Upper CI at each grid value (present when include_ci) */
301
- upper: OptionType(VectorType(FloatType)),
302
- /** Number of samples in each grid interval */
303
- size: VectorType(IntegerType),
293
+ /** Optional knobs for {@link causal_design_validation} (all developer-defaulted). */
294
+ export const DesignConfigType = StructType({
295
+ /** Significance level (default 0.05). */
296
+ alpha: OptionType(FloatType),
297
+ /** Target chance of detecting the effect (default 0.8). */
298
+ target_power: OptionType(FloatType),
299
+ /** Smallest effect worth acting on sizes the trial to this when set / when there's no trustworthy observed effect. */
300
+ materiality: OptionType(FloatType),
301
+ /** Treatment shares to offer as options (default [0.5]). */
302
+ treated_shares: OptionType(ArrayType(FloatType)),
304
303
  });
305
- // ============================================================================
306
- // Platform Functions
307
- // ============================================================================
308
304
  /**
309
- * Estimate a backdoor-adjusted causal effect of treatment on outcome.
305
+ * Design the real controlled trial that would validate a finished experiment.
306
+ * Generic over the row struct (same as {@link causal_experiment}); takes the data,
307
+ * the experiment config, the experiment result, and optional design knobs, and
308
+ * returns the trial recipe (sample size, split options, match-on categories, the
309
+ * power curve, and a plain-language rationale).
310
310
  *
311
- * Identifies the effect with DoWhy given the common causes, then estimates
312
- * it by linear regression or inverse propensity score weighting. Optionally
313
- * trims to propensity common support and computes a (cluster) bootstrap
314
- * confidence interval.
315
- *
316
- * @param data - Data matrix (rows = units, columns named by config.columns)
317
- * @param config - Estimation configuration
318
- * @returns Effect estimate with optional CI and sample counts
311
+ * @param data - The rows the experiment ran on
312
+ * @param config - The experiment configuration (names treatment / outcome / confounders)
313
+ * @param result - The {@link causal_experiment} result whose verdict drives the recipe
314
+ * @param design_config - Optional alpha / power / materiality / split knobs
315
+ * @returns The validation-trial recipe
319
316
  */
320
- export const causal_effect = East.platform("causal_effect", [MatrixType(FloatType), CausalEffectConfigType], CausalEffectResultType);
321
- /**
322
- * Refute an estimated causal effect.
323
- *
324
- * Re-estimates the effect from `config`, then applies the given refuter
325
- * (placebo treatment, random common cause, data subset, or simulated
326
- * unobserved confounder sensitivity curve).
327
- *
328
- * @param data - Data matrix (rows = units, columns named by config.columns)
329
- * @param config - Estimation configuration (same as causal_effect)
330
- * @param refuter - Refutation test to apply
331
- * @returns Original effect, refuted effect(s), and p-value where available
332
- */
333
- export const causal_refute = East.platform("causal_refute", [MatrixType(FloatType), CausalEffectConfigType, CausalRefuterType], CausalRefuteResultType);
334
- /**
335
- * Fit an EconML LinearDML estimator for heterogeneous treatment effects.
336
- *
337
- * Cross-fits nuisance models for outcome and treatment, then fits a linear
338
- * CATE model on the residuals.
339
- *
340
- * @param Y - Outcome vector
341
- * @param T - Treatment vector
342
- * @param X - Effect modifier matrix (CATE varies with these)
343
- * @param W - Additional confounders adjusted for but not modifying the effect
344
- * @param config - DML configuration
345
- * @returns Model blob for use with dmlEffect / dmlAte
346
- */
347
- export const causal_dml_train = East.platform("causal_dml_train", [VectorType(FloatType), VectorType(FloatType), MatrixType(FloatType), OptionType(MatrixType(FloatType)), CausalDMLConfigType], CausalDMLModelBlobType);
348
- /**
349
- * Per-row conditional average treatment effects (CATE) from a fitted DML model.
350
- *
351
- * @param model - Fitted DML model blob
352
- * @param X - Effect modifier matrix
353
- * @returns CATE per row (outcome units per treatment unit)
354
- */
355
- export const causal_dml_effect = East.platform("causal_dml_effect", [CausalDMLModelBlobType, MatrixType(FloatType)], VectorType(FloatType));
356
- /**
357
- * Average treatment effect with confidence interval from a fitted DML model.
358
- *
359
- * @param model - Fitted DML model blob
360
- * @param X - Effect modifier matrix to average over
361
- * @returns ATE with interval at the confidence level configured at training
362
- */
363
- export const causal_dml_ate = East.platform("causal_dml_ate", [CausalDMLModelBlobType, MatrixType(FloatType)], CausalATEResultType);
364
- /**
365
- * Accumulated local effects dose-response curve of a feature on an outcome.
366
- *
367
- * Fits a gradient boosting emulator on all non-outcome columns, then
368
- * computes the ALE curve of the feature - robust to correlated features,
369
- * unlike partial dependence.
370
- *
371
- * @param data - Data matrix (rows = units, columns named by config.columns)
372
- * @param config - ALE configuration
373
- * @returns ALE curve with grid, centered effect, optional CI, and bin sizes
374
- */
375
- export const causal_ale = East.platform("causal_ale", [MatrixType(FloatType), CausalALEConfigType], ALEResultType);
317
+ export const causal_design_validation = East.genericPlatform("causal_design_validation", ["T"], [ArrayType(ROW_T), CausalExperimentConfigType, CausalExperimentResultType, DesignConfigType], ExperimentDesignType);
376
318
  // ============================================================================
377
319
  // Grouped Export
378
320
  // ============================================================================
379
- /**
380
- * Type definitions for causal inference functions.
381
- */
321
+ /** Type definitions for causal inference. */
382
322
  export const CausalTypes = {
383
- // Config types
323
+ // Shared vocabulary
384
324
  CausalWeightingSchemeType,
385
325
  CausalEstimatorType,
386
326
  CausalTargetUnitsType,
387
- PropensityTrimType,
388
327
  CausalBootstrapConfigType,
389
- CausalEffectConfigType,
390
- CausalRefuterType,
391
- CausalNuisanceModelType,
392
- CausalDMLConfigType,
393
- CausalALEConfigType,
394
- // Model blob types
395
- CausalDMLModelBlobType,
396
- // Result types
397
- CausalEffectResultType,
398
- CausalRefuteResultType,
399
- CausalATEResultType,
400
- ALEResultType,
328
+ CiType,
329
+ // Experiment contract
330
+ RefuteSpecType,
331
+ CausalExperimentConfigType,
332
+ BalanceRowType,
333
+ OverlapDiagnosticType,
334
+ RefutationType,
335
+ DoseResponseType,
336
+ ExperimentVerdictType,
337
+ CausalExperimentResultType,
338
+ // Validation-design contract
339
+ DesignBasisType,
340
+ TrialOptionType,
341
+ PowerCurveType,
342
+ ExperimentDesignType,
343
+ DesignConfigType,
401
344
  };
402
345
  /**
403
- * Causal inference.
346
+ * Causal inference — one declarative entry point.
404
347
  *
405
- * Backdoor-adjusted effect estimation and refutation (DoWhy),
406
- * heterogeneous treatment effects via double machine learning
407
- * (EconML LinearDML), and accumulated local effects dose-response
408
- * curves (PyALE).
348
+ * `Causal.experiment(data, config)` runs a complete, honest causal experiment
349
+ * for a binary treatment and returns the naive vs adjusted effect, confounder
350
+ * balance, propensity overlap, a robustness check, and a verdict
351
+ * (`causal` / `modest` / `adjustment_insufficient` / `non_identifiable_positivity`
352
+ * / `not_estimable`). It refuses (`adjusted = none`) when the data can't support
353
+ * an estimate. DoWhy / EconML / PyALE are internal implementation it composes.
409
354
  *
410
355
  * @example
411
356
  * ```ts
412
- * import { East, FloatType, variant } from "@elaraai/east";
413
- * import { Causal, MatrixType } from "@elaraai/east-py-datascience";
357
+ * import { East, ArrayType, StructType, FloatType, BooleanType, variant } from "@elaraai/east";
358
+ * import { Causal } from "@elaraai/east-py-datascience";
414
359
  *
415
- * const estimate = East.function(
416
- * [MatrixType(FloatType)],
417
- * Causal.Types.CausalEffectResultType,
418
- * ($, data) => {
419
- * const config = $.let({
420
- * columns: ["treated", "outcome", "confounder"],
421
- * treatment: "treated",
422
- * outcome: "outcome",
423
- * common_causes: ["confounder"],
424
- * categorical: variant('none', null),
425
- * method: variant('some', variant('linear_regression', null)),
426
- * target_units: variant('some', variant('ate', null)),
427
- * trim: variant('none', null),
428
- * bootstrap: variant('none', null),
429
- * random_state: variant('some', 42n),
430
- * }, Causal.Types.CausalEffectConfigType);
431
- * return $.return(Causal.effect(data, config));
432
- * }
433
- * );
360
+ * const Row = StructType({ treated: BooleanType, outcome: FloatType, z: FloatType });
361
+ * const run = East.function([ArrayType(Row)], Causal.Types.CausalExperimentResultType, ($, data) => {
362
+ * const config = $.let({
363
+ * treatment: "treated", outcome: "outcome", common_causes: ["z"],
364
+ * categorical: variant('none', null),
365
+ * method: variant('none', null), estimand: variant('none', null),
366
+ * refute: variant('some', { placebo: true, random_common_cause: false }),
367
+ * min_overlap: variant('some', 0.1), min_treatment_variation: variant('some', 0.02),
368
+ * bootstrap: variant('none', null), random_state: variant('some', 42n),
369
+ * }, Causal.Types.CausalExperimentConfigType);
370
+ * return $.return(Causal.experiment([Row], data, config));
371
+ * });
434
372
  * ```
435
373
  */
436
374
  export const Causal = {
437
- /**
438
- * Estimate a backdoor-adjusted causal effect of treatment on outcome.
439
- *
440
- * Linear regression or inverse propensity score weighting (with
441
- * optional propensity trimming), plus optional cluster bootstrap CI.
442
- *
443
- * @example
444
- * ```ts
445
- * import { East, FloatType, variant } from "@elaraai/east";
446
- * import { Causal, CausalEffectConfigType, MatrixType } from "@elaraai/east-py-datascience";
447
- *
448
- * const attEstimate = East.function(
449
- * [MatrixType(FloatType)],
450
- * Causal.Types.CausalEffectResultType,
451
- * ($, data) => {
452
- * const config = $.let({
453
- * columns: ["treated", "outcome", "z1", "z2", "batch"],
454
- * treatment: "treated",
455
- * outcome: "outcome",
456
- * common_causes: ["z1", "z2"],
457
- * categorical: variant('none', null),
458
- * method: variant('some', variant('propensity_score_weighting', {
459
- * weighting_scheme: variant('some', variant('ips_stabilized_weight', null)),
460
- * })),
461
- * target_units: variant('some', variant('att', null)),
462
- * trim: variant('some', variant('overlap', null)),
463
- * bootstrap: variant('some', {
464
- * reps: 200n,
465
- * cluster_column: variant('some', "batch"),
466
- * confidence_level: variant('some', 0.95),
467
- * }),
468
- * random_state: variant('some', 42n),
469
- * }, CausalEffectConfigType);
470
- * return $.return(Causal.effect(data, config));
471
- * }
472
- * );
473
- * ```
474
- */
475
- effect: causal_effect,
476
- /**
477
- * Refute an estimated causal effect with placebo, random common cause,
478
- * data subset, or unobserved-confounder sensitivity tests.
479
- *
480
- * @example
481
- * ```ts
482
- * import { East, FloatType, variant } from "@elaraai/east";
483
- * import { Causal, MatrixType } from "@elaraai/east-py-datascience";
484
- *
485
- * const placebo = East.function(
486
- * [MatrixType(FloatType), Causal.Types.CausalEffectConfigType],
487
- * Causal.Types.CausalRefuteResultType,
488
- * ($, data, config) => {
489
- * const refuter = $.let(variant('placebo_treatment', {
490
- * num_simulations: variant('some', 50n),
491
- * }), Causal.Types.CausalRefuterType);
492
- * // new_effects should be near zero if the estimate is causal
493
- * return $.return(Causal.refute(data, config, refuter));
494
- * }
495
- * );
496
- * ```
497
- */
498
- refute: causal_refute,
499
- /**
500
- * Fit an EconML LinearDML estimator for heterogeneous treatment effects.
501
- *
502
- * @example
503
- * ```ts
504
- * import { East, FloatType, variant } from "@elaraai/east";
505
- * import { Causal, CausalDMLConfigType, MatrixType, VectorType } from "@elaraai/east-py-datascience";
506
- *
507
- * const train = East.function(
508
- * [VectorType(FloatType), VectorType(FloatType), MatrixType(FloatType), MatrixType(FloatType)],
509
- * Causal.Types.CausalDMLModelBlobType,
510
- * ($, Y, T, X, W) => {
511
- * const config = $.let({
512
- * model_y: variant('some', variant('random_forest', {
513
- * n_estimators: variant('some', 100n),
514
- * min_samples_leaf: variant('some', 5n),
515
- * max_depth: variant('none', null),
516
- * })),
517
- * model_t: variant('none', null),
518
- * discrete_treatment: variant('none', null),
519
- * cv_folds: variant('some', 2n),
520
- * confidence_level: variant('some', 0.95),
521
- * random_state: variant('some', 42n),
522
- * }, CausalDMLConfigType);
523
- * return $.return(Causal.dmlTrain(Y, T, X, variant('some', W), config));
524
- * }
525
- * );
526
- * ```
527
- */
528
- dmlTrain: causal_dml_train,
529
- /**
530
- * Per-row conditional average treatment effects from a fitted DML model.
531
- *
532
- * @example
533
- * ```ts
534
- * import { East, FloatType } from "@elaraai/east";
535
- * import { Causal, MatrixType, VectorType } from "@elaraai/east-py-datascience";
536
- *
537
- * const cate = East.function(
538
- * [Causal.Types.CausalDMLModelBlobType, MatrixType(FloatType)],
539
- * VectorType(FloatType),
540
- * ($, model, X) => $.return(Causal.dmlEffect(model, X))
541
- * );
542
- * ```
543
- */
544
- dmlEffect: causal_dml_effect,
545
- /**
546
- * Average treatment effect with confidence interval from a fitted DML model.
547
- *
548
- * @example
549
- * ```ts
550
- * import { East, FloatType } from "@elaraai/east";
551
- * import { Causal, MatrixType } from "@elaraai/east-py-datascience";
552
- *
553
- * const ate = East.function(
554
- * [Causal.Types.CausalDMLModelBlobType, MatrixType(FloatType)],
555
- * Causal.Types.CausalATEResultType,
556
- * ($, model, X) => $.return(Causal.dmlAte(model, X))
557
- * );
558
- * ```
559
- */
560
- dmlAte: causal_dml_ate,
561
- /**
562
- * Accumulated local effects dose-response curve of a feature on an outcome.
563
- *
564
- * @example
565
- * ```ts
566
- * import { East, FloatType, variant } from "@elaraai/east";
567
- * import { Causal, CausalALEConfigType, MatrixType } from "@elaraai/east-py-datascience";
568
- *
569
- * const doseResponse = East.function(
570
- * [MatrixType(FloatType)],
571
- * Causal.Types.ALEResultType,
572
- * ($, data) => {
573
- * const config = $.let({
574
- * columns: ["dose", "z", "response"],
575
- * outcome: "response",
576
- * feature: "dose",
577
- * categorical: variant('none', null),
578
- * grid_size: variant('some', 10n),
579
- * include_ci: variant('some', true),
580
- * confidence_level: variant('some', 0.95),
581
- * emulator: variant('none', null),
582
- * random_state: variant('some', 42n),
583
- * }, CausalALEConfigType);
584
- * return $.return(Causal.ale(data, config));
585
- * }
586
- * );
587
- * ```
588
- */
589
- ale: causal_ale,
375
+ experiment: causal_experiment,
376
+ /** Design the real controlled trial that would validate an experiment result. */
377
+ designValidation: causal_design_validation,
590
378
  /** Type definitions */
591
379
  Types: CausalTypes,
592
380
  };