@elaraai/east-py-datascience 1.0.12 → 1.0.14

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,545 +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
+ // ============================================================================
86
80
  /**
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.
81
+ * Which robustness checks to run inside {@link causal_experiment}.
93
82
  */
94
- export const CausalEffectConfigType = StructType({
95
- /** Treatment column (must be 0/1 for propensity score weighting) */
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
+ });
93
+ /**
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.
97
+ */
98
+ export const CausalExperimentConfigType = StructType({
99
+ /** Binary (0/1) treatment column. */
96
100
  treatment: StringType,
97
- /** Outcome column */
101
+ /** Outcome column. */
98
102
  outcome: StringType,
99
- /** Confounder columns to adjust for (the backdoor set) */
103
+ /** Confounders to adjust for (the backdoor set). */
100
104
  common_causes: ArrayType(StringType),
101
- /** Columns holding integer category codes, one-hot encoded internally */
105
+ /** Confounder columns holding categories (string or int codes), one-hot encoded. */
102
106
  categorical: OptionType(ArrayType(StringType)),
103
- /** Effect estimator (default: linear_regression) */
107
+ /** Estimator (default: linear_regression). */
104
108
  method: OptionType(CausalEstimatorType),
105
- /** Target population (default: ate) */
106
- target_units: OptionType(CausalTargetUnitsType),
107
- /** Propensity trimming applied before estimation (psw only) */
108
- trim: OptionType(PropensityTrimType),
109
- /** 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). */
110
120
  bootstrap: OptionType(CausalBootstrapConfigType),
111
- /** Random seed for propensity fitting and bootstrap resampling */
121
+ /** Random seed. */
112
122
  random_state: OptionType(IntegerType),
113
123
  });
114
- /**
115
- * Refutation test for an estimated causal effect.
116
- */
117
- export const CausalRefuterType = VariantType({
118
- /** Replace treatment with a permuted placebo - effect should vanish */
119
- placebo_treatment: StructType({
120
- /** Number of simulations (default 100) */
121
- num_simulations: OptionType(IntegerType),
122
- }),
123
- /** Add an independent random common cause - effect should be unchanged */
124
- random_common_cause: StructType({
125
- /** Number of simulations (default 100) */
126
- num_simulations: OptionType(IntegerType),
127
- }),
128
- /** Re-estimate on random data subsets - effect should be stable */
129
- data_subset: StructType({
130
- /** Fraction of rows kept per simulation (default 0.8) */
131
- subset_fraction: OptionType(FloatType),
132
- /** Number of simulations (default 100) */
133
- num_simulations: OptionType(IntegerType),
134
- }),
135
- /**
136
- * Simulate an unobserved confounder at each given strength - a
137
- * sensitivity/tipping curve. Strength acts on both treatment
138
- * (flip probability for binary treatment, linear coefficient otherwise)
139
- * and outcome (linear coefficient).
140
- */
141
- unobserved_common_cause: StructType({
142
- /** Confounder effect strengths to simulate, one new effect per entry */
143
- effect_strengths: ArrayType(FloatType),
144
- }),
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,
145
134
  });
146
- /**
147
- * Nuisance model for DML residualization stages.
148
- */
149
- export const CausalNuisanceModelType = VariantType({
150
- /** Random forest (regressor, or classifier for a discrete treatment) */
151
- random_forest: StructType({
152
- /** Number of trees (default 100) */
153
- n_estimators: OptionType(IntegerType),
154
- /** Minimum samples per leaf (default 5) */
155
- min_samples_leaf: OptionType(IntegerType),
156
- /** Maximum tree depth (default unlimited) */
157
- max_depth: OptionType(IntegerType),
158
- }),
159
- /** Gradient boosting (regressor, or classifier for a discrete treatment) */
160
- gradient_boosting: StructType({
161
- /** Number of boosting stages (default 100) */
162
- n_estimators: OptionType(IntegerType),
163
- /** Learning rate (default 0.1) */
164
- learning_rate: OptionType(FloatType),
165
- /** Maximum tree depth (default 3) */
166
- max_depth: OptionType(IntegerType),
167
- }),
168
- /** Linear/logistic regression */
169
- linear: NullType,
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,
170
145
  });
171
- /**
172
- * Configuration for double machine learning (EconML LinearDML).
173
- */
174
- export const CausalDMLConfigType = StructType({
175
- /** Nuisance model for the outcome stage (default: random_forest) */
176
- model_y: OptionType(CausalNuisanceModelType),
177
- /** Nuisance model for the treatment stage (default: random_forest) */
178
- model_t: OptionType(CausalNuisanceModelType),
179
- /** Treat the treatment as discrete/categorical (default false) */
180
- discrete_treatment: OptionType(BooleanType),
181
- /** Cross-fitting folds (default 2) */
182
- cv_folds: OptionType(IntegerType),
183
- /** Confidence level for effect/ATE intervals (default 0.95) */
184
- confidence_level: OptionType(FloatType),
185
- /** Random seed */
186
- random_state: OptionType(IntegerType),
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
+ })),
187
165
  });
188
- /**
189
- * Configuration for an accumulated local effects (ALE) dose-response curve.
190
- *
191
- * Fits a gradient-boosting emulator of the outcome on all non-outcome
192
- * columns, then computes the correlation-robust ALE curve of `feature`.
193
- */
194
- export const CausalALEConfigType = StructType({
195
- /** Outcome column the emulator predicts */
196
- outcome: StringType,
197
- /** Continuous feature column to compute the ALE curve for */
166
+ /** A dose-response (ALE) curve of a continuous feature on the outcome. */
167
+ export const DoseResponseType = StructType({
198
168
  feature: StringType,
199
- /** Columns holding integer category codes, one-hot encoded internally */
200
- categorical: OptionType(ArrayType(StringType)),
201
- /** Number of grid intervals (default 10) */
202
- grid_size: OptionType(IntegerType),
203
- /** Include confidence intervals (default true) */
204
- include_ci: OptionType(BooleanType),
205
- /** Confidence level for the CI (default 0.95) */
206
- confidence_level: OptionType(FloatType),
207
- /** Emulator (HistGradientBoosting) hyperparameters */
208
- emulator: OptionType(StructType({
209
- /** Number of boosting iterations (default 300) */
210
- n_estimators: OptionType(IntegerType),
211
- /** Learning rate (default 0.05) */
212
- learning_rate: OptionType(FloatType),
213
- /** Maximum tree depth (default unlimited) */
214
- max_depth: OptionType(IntegerType),
215
- /** Minimum samples per leaf (default 20; lower for small datasets) */
216
- min_samples_leaf: OptionType(IntegerType),
217
- })),
218
- /** Random seed for the emulator */
219
- random_state: OptionType(IntegerType),
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),
220
175
  });
221
- // ============================================================================
222
- // Model Blob Types
223
- // ============================================================================
224
176
  /**
225
- * Model blob for a fitted LinearDML estimator.
177
+ * The honesty verdict a thin tag; the numbers live top-level on the result.
178
+ * Only `not_estimable` carries a (human-readable) reason.
226
179
  */
227
- export const CausalDMLModelBlobType = VariantType({
228
- /** Fitted EconML LinearDML estimator */
229
- causal_dml: StructType({
230
- /** Serialized estimator (cloudpickle) */
231
- data: BlobType,
232
- /** Number of effect-modifier (X) features */
233
- n_features_x: IntegerType,
234
- /** Confidence level used for effect/ATE intervals */
235
- confidence_level: FloatType,
236
- }),
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,
237
191
  });
238
- // ============================================================================
239
- // Result Types
240
- // ============================================================================
241
192
  /**
242
- * Estimated causal effect.
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.
243
196
  */
244
- export const CausalEffectResultType = StructType({
245
- /** Point estimate of the effect (outcome units per treatment unit) */
246
- effect: FloatType,
247
- /** Bootstrap percentile confidence interval (present when bootstrap configured) */
248
- ci: OptionType(StructType({
249
- /** Lower bound */
250
- lower: FloatType,
251
- /** Upper bound */
252
- upper: FloatType,
253
- })),
254
- /** Rows used for estimation (after trimming) */
255
- n_samples: IntegerType,
256
- /** Treated rows used */
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,
257
204
  n_treated: IntegerType,
258
- /** Control rows used */
259
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,
260
214
  });
261
- /**
262
- * Refutation test result.
263
- */
264
- export const CausalRefuteResultType = StructType({
265
- /** The original estimated effect being refuted */
266
- estimated_effect: FloatType,
267
- /**
268
- * Effect under the refutation. One entry for placebo/random-common-cause/
269
- * data-subset; one entry per strength for unobserved_common_cause.
270
- */
271
- new_effects: VectorType(FloatType),
272
- /** Refutation p-value where the refuter provides one */
273
- p_value: OptionType(FloatType),
274
- });
275
- /**
276
- * Average treatment effect with confidence interval.
277
- */
278
- export const CausalATEResultType = StructType({
279
- /** Average treatment effect over the given X */
280
- ate: FloatType,
281
- /** Lower bound at the model's confidence level */
282
- lower: FloatType,
283
- /** Upper bound at the model's confidence level */
284
- upper: FloatType,
285
- });
286
- /**
287
- * Accumulated local effects curve.
288
- */
289
- export const ALEResultType = StructType({
290
- /** Feature grid values (interval edges) */
291
- grid: VectorType(FloatType),
292
- /** Centered ALE effect at each grid value (outcome units) */
293
- effect: VectorType(FloatType),
294
- /** Lower CI at each grid value (present when include_ci) */
295
- lower: OptionType(VectorType(FloatType)),
296
- /** Upper CI at each grid value (present when include_ci) */
297
- upper: OptionType(VectorType(FloatType)),
298
- /** Number of samples in each grid interval */
299
- size: VectorType(IntegerType),
300
- });
301
- // ============================================================================
302
- // Platform Functions
303
- // ============================================================================
304
- /**
305
215
  /**
306
216
  * Type-parameter placeholder for the generic row struct `T`. At a call site the
307
217
  * caller passes the concrete row `StructType`; `applyTypeArgs` substitutes this
308
- * bare `"T"` reference wherever it appears in the input types (here, inside
309
- * `ArrayType(ROW_T)`). The cast is the documented way to nest a type parameter
310
- * in a `genericPlatform` input.
218
+ * bare `"T"` reference wherever it appears in the input types (inside
219
+ * `ArrayType(ROW_T)`).
311
220
  */
312
221
  const ROW_T = "T";
313
222
  /**
314
- * Estimate a backdoor-adjusted causal effect of treatment on outcome.
315
- *
316
- * Identifies the effect with DoWhy given the common causes, then estimates
317
- * it by linear regression or inverse propensity score weighting. Optionally
318
- * trims to propensity common support and computes a (cluster) bootstrap
319
- * confidence interval.
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.
320
226
  *
321
227
  * @param data - Array of row structs (one per unit; fields are the columns)
322
- * @param config - Estimation configuration
323
- * @returns Effect estimate with optional CI and sample counts
228
+ * @param config - The experiment configuration
229
+ * @returns The honest result + verdict; `adjusted` is `none` when refused
324
230
  */
325
- export const causal_effect = East.genericPlatform("causal_effect", ["T"], [ArrayType(ROW_T), CausalEffectConfigType], CausalEffectResultType);
326
- /**
327
- * Refute an estimated causal effect.
328
- *
329
- * Re-estimates the effect from `config`, then applies the given refuter
330
- * (placebo treatment, random common cause, data subset, or simulated
331
- * unobserved confounder sensitivity curve).
332
- *
333
- * @param data - Data matrix (rows = units, columns named by config.columns)
334
- * @param config - Estimation configuration (same as causal_effect)
335
- * @param refuter - Refutation test to apply
336
- * @returns Original effect, refuted effect(s), and p-value where available
337
- */
338
- export const causal_refute = East.genericPlatform("causal_refute", ["T"], [ArrayType(ROW_T), CausalEffectConfigType, CausalRefuterType], CausalRefuteResultType);
339
- /**
340
- * Fit an EconML LinearDML estimator for heterogeneous treatment effects.
341
- *
342
- * Cross-fits nuisance models for outcome and treatment, then fits a linear
343
- * CATE model on the residuals.
344
- *
345
- * @param Y - Outcome vector
346
- * @param T - Treatment vector
347
- * @param X - Effect modifier matrix (CATE varies with these)
348
- * @param W - Additional confounders adjusted for but not modifying the effect
349
- * @param config - DML configuration
350
- * @returns Model blob for use with dmlEffect / dmlAte
351
- */
352
- export const causal_dml_train = East.platform("causal_dml_train", [VectorType(FloatType), VectorType(FloatType), MatrixType(FloatType), OptionType(MatrixType(FloatType)), CausalDMLConfigType], CausalDMLModelBlobType);
353
- /**
354
- * Per-row conditional average treatment effects (CATE) from a fitted DML model.
355
- *
356
- * @param model - Fitted DML model blob
357
- * @param X - Effect modifier matrix
358
- * @returns CATE per row (outcome units per treatment unit)
359
- */
360
- export const causal_dml_effect = East.platform("causal_dml_effect", [CausalDMLModelBlobType, MatrixType(FloatType)], VectorType(FloatType));
231
+ export const causal_experiment = East.genericPlatform("causal_experiment", ["T"], [ArrayType(ROW_T), CausalExperimentConfigType], CausalExperimentResultType);
232
+ // ============================================================================
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.
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,
253
+ });
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,
259
+ n_treated: IntegerType,
260
+ n_control: IntegerType,
261
+ n_total: IntegerType,
262
+ });
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),
267
+ });
361
268
  /**
362
- * Average treatment effect with confidence interval from a fitted DML model.
363
- *
364
- * @param model - Fitted DML model blob
365
- * @param X - Effect modifier matrix to average over
366
- * @returns ATE with interval at the confidence level configured at training
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.
367
272
  */
368
- export const causal_dml_ate = East.platform("causal_dml_ate", [CausalDMLModelBlobType, MatrixType(FloatType)], CausalATEResultType);
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,
292
+ });
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)),
303
+ });
369
304
  /**
370
- * Accumulated local effects dose-response curve of a feature on an outcome.
371
- *
372
- * Fits a gradient boosting emulator on all non-outcome columns, then
373
- * computes the ALE curve of the feature - robust to correlated features,
374
- * unlike partial dependence.
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).
375
310
  *
376
- * @param data - Data matrix (rows = units, columns named by config.columns)
377
- * @param config - ALE configuration
378
- * @returns ALE curve with grid, centered effect, optional CI, and bin sizes
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
379
316
  */
380
- export const causal_ale = East.genericPlatform("causal_ale", ["T"], [ArrayType(ROW_T), CausalALEConfigType], ALEResultType);
317
+ export const causal_design_validation = East.genericPlatform("causal_design_validation", ["T"], [ArrayType(ROW_T), CausalExperimentConfigType, CausalExperimentResultType, DesignConfigType], ExperimentDesignType);
381
318
  // ============================================================================
382
319
  // Grouped Export
383
320
  // ============================================================================
384
- /**
385
- * Type definitions for causal inference functions.
386
- */
321
+ /** Type definitions for causal inference. */
387
322
  export const CausalTypes = {
388
- // Config types
323
+ // Shared vocabulary
389
324
  CausalWeightingSchemeType,
390
325
  CausalEstimatorType,
391
326
  CausalTargetUnitsType,
392
- PropensityTrimType,
393
327
  CausalBootstrapConfigType,
394
- CausalEffectConfigType,
395
- CausalRefuterType,
396
- CausalNuisanceModelType,
397
- CausalDMLConfigType,
398
- CausalALEConfigType,
399
- // Model blob types
400
- CausalDMLModelBlobType,
401
- // Result types
402
- CausalEffectResultType,
403
- CausalRefuteResultType,
404
- CausalATEResultType,
405
- 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,
406
344
  };
407
345
  /**
408
- * Causal inference.
346
+ * Causal inference — one declarative entry point.
409
347
  *
410
- * Backdoor-adjusted effect estimation and refutation (DoWhy),
411
- * heterogeneous treatment effects via double machine learning
412
- * (EconML LinearDML), and accumulated local effects dose-response
413
- * 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.
414
354
  *
415
355
  * @example
416
356
  * ```ts
417
- * import { East, FloatType, variant } from "@elaraai/east";
418
- * 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";
419
359
  *
420
- * const estimate = East.function(
421
- * [MatrixType(FloatType)],
422
- * Causal.Types.CausalEffectResultType,
423
- * ($, data) => {
424
- * const config = $.let({
425
- * columns: ["treated", "outcome", "confounder"],
426
- * treatment: "treated",
427
- * outcome: "outcome",
428
- * common_causes: ["confounder"],
429
- * categorical: variant('none', null),
430
- * method: variant('some', variant('linear_regression', null)),
431
- * target_units: variant('some', variant('ate', null)),
432
- * trim: variant('none', null),
433
- * bootstrap: variant('none', null),
434
- * random_state: variant('some', 42n),
435
- * }, Causal.Types.CausalEffectConfigType);
436
- * return $.return(Causal.effect(data, config));
437
- * }
438
- * );
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
+ * });
439
372
  * ```
440
373
  */
441
374
  export const Causal = {
442
- /**
443
- * Estimate a backdoor-adjusted causal effect of treatment on outcome.
444
- *
445
- * Linear regression or inverse propensity score weighting (with
446
- * optional propensity trimming), plus optional cluster bootstrap CI.
447
- *
448
- * @example
449
- * ```ts
450
- * import { East, FloatType, variant } from "@elaraai/east";
451
- * import { Causal, CausalEffectConfigType, MatrixType } from "@elaraai/east-py-datascience";
452
- *
453
- * const attEstimate = East.function(
454
- * [MatrixType(FloatType)],
455
- * Causal.Types.CausalEffectResultType,
456
- * ($, data) => {
457
- * const config = $.let({
458
- * columns: ["treated", "outcome", "z1", "z2", "batch"],
459
- * treatment: "treated",
460
- * outcome: "outcome",
461
- * common_causes: ["z1", "z2"],
462
- * categorical: variant('none', null),
463
- * method: variant('some', variant('propensity_score_weighting', {
464
- * weighting_scheme: variant('some', variant('ips_stabilized_weight', null)),
465
- * })),
466
- * target_units: variant('some', variant('att', null)),
467
- * trim: variant('some', variant('overlap', null)),
468
- * bootstrap: variant('some', {
469
- * reps: 200n,
470
- * cluster_column: variant('some', "batch"),
471
- * confidence_level: variant('some', 0.95),
472
- * }),
473
- * random_state: variant('some', 42n),
474
- * }, CausalEffectConfigType);
475
- * return $.return(Causal.effect(data, config));
476
- * }
477
- * );
478
- * ```
479
- */
480
- effect: causal_effect,
481
- /**
482
- * Refute an estimated causal effect with placebo, random common cause,
483
- * data subset, or unobserved-confounder sensitivity tests.
484
- *
485
- * @example
486
- * ```ts
487
- * import { East, FloatType, variant } from "@elaraai/east";
488
- * import { Causal, MatrixType } from "@elaraai/east-py-datascience";
489
- *
490
- * const placebo = East.function(
491
- * [MatrixType(FloatType), Causal.Types.CausalEffectConfigType],
492
- * Causal.Types.CausalRefuteResultType,
493
- * ($, data, config) => {
494
- * const refuter = $.let(variant('placebo_treatment', {
495
- * num_simulations: variant('some', 50n),
496
- * }), Causal.Types.CausalRefuterType);
497
- * // new_effects should be near zero if the estimate is causal
498
- * return $.return(Causal.refute(data, config, refuter));
499
- * }
500
- * );
501
- * ```
502
- */
503
- refute: causal_refute,
504
- /**
505
- * Fit an EconML LinearDML estimator for heterogeneous treatment effects.
506
- *
507
- * @example
508
- * ```ts
509
- * import { East, FloatType, variant } from "@elaraai/east";
510
- * import { Causal, CausalDMLConfigType, MatrixType, VectorType } from "@elaraai/east-py-datascience";
511
- *
512
- * const train = East.function(
513
- * [VectorType(FloatType), VectorType(FloatType), MatrixType(FloatType), MatrixType(FloatType)],
514
- * Causal.Types.CausalDMLModelBlobType,
515
- * ($, Y, T, X, W) => {
516
- * const config = $.let({
517
- * model_y: variant('some', variant('random_forest', {
518
- * n_estimators: variant('some', 100n),
519
- * min_samples_leaf: variant('some', 5n),
520
- * max_depth: variant('none', null),
521
- * })),
522
- * model_t: variant('none', null),
523
- * discrete_treatment: variant('none', null),
524
- * cv_folds: variant('some', 2n),
525
- * confidence_level: variant('some', 0.95),
526
- * random_state: variant('some', 42n),
527
- * }, CausalDMLConfigType);
528
- * return $.return(Causal.dmlTrain(Y, T, X, variant('some', W), config));
529
- * }
530
- * );
531
- * ```
532
- */
533
- dmlTrain: causal_dml_train,
534
- /**
535
- * Per-row conditional average treatment effects from a fitted DML model.
536
- *
537
- * @example
538
- * ```ts
539
- * import { East, FloatType } from "@elaraai/east";
540
- * import { Causal, MatrixType, VectorType } from "@elaraai/east-py-datascience";
541
- *
542
- * const cate = East.function(
543
- * [Causal.Types.CausalDMLModelBlobType, MatrixType(FloatType)],
544
- * VectorType(FloatType),
545
- * ($, model, X) => $.return(Causal.dmlEffect(model, X))
546
- * );
547
- * ```
548
- */
549
- dmlEffect: causal_dml_effect,
550
- /**
551
- * Average treatment effect with confidence interval from a fitted DML model.
552
- *
553
- * @example
554
- * ```ts
555
- * import { East, FloatType } from "@elaraai/east";
556
- * import { Causal, MatrixType } from "@elaraai/east-py-datascience";
557
- *
558
- * const ate = East.function(
559
- * [Causal.Types.CausalDMLModelBlobType, MatrixType(FloatType)],
560
- * Causal.Types.CausalATEResultType,
561
- * ($, model, X) => $.return(Causal.dmlAte(model, X))
562
- * );
563
- * ```
564
- */
565
- dmlAte: causal_dml_ate,
566
- /**
567
- * Accumulated local effects dose-response curve of a feature on an outcome.
568
- *
569
- * @example
570
- * ```ts
571
- * import { East, FloatType, variant } from "@elaraai/east";
572
- * import { Causal, CausalALEConfigType, MatrixType } from "@elaraai/east-py-datascience";
573
- *
574
- * const doseResponse = East.function(
575
- * [MatrixType(FloatType)],
576
- * Causal.Types.ALEResultType,
577
- * ($, data) => {
578
- * const config = $.let({
579
- * columns: ["dose", "z", "response"],
580
- * outcome: "response",
581
- * feature: "dose",
582
- * categorical: variant('none', null),
583
- * grid_size: variant('some', 10n),
584
- * include_ci: variant('some', true),
585
- * confidence_level: variant('some', 0.95),
586
- * emulator: variant('none', null),
587
- * random_state: variant('some', 42n),
588
- * }, CausalALEConfigType);
589
- * return $.return(Causal.ale(data, config));
590
- * }
591
- * );
592
- * ```
593
- */
594
- ale: causal_ale,
375
+ experiment: causal_experiment,
376
+ /** Design the real controlled trial that would validate an experiment result. */
377
+ designValidation: causal_design_validation,
595
378
  /** Type definitions */
596
379
  Types: CausalTypes,
597
380
  };