@elaraai/east-py-datascience 1.0.12 → 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.
- package/README.md +1 -1
- package/dist/src/causal/causal.d.ts +1376 -1358
- package/dist/src/causal/causal.d.ts.map +1 -1
- package/dist/src/causal/causal.js +264 -481
- package/dist/src/causal/causal.js.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -2
- package/dist/src/index.js.map +1 -1
- package/package.json +3 -3
|
@@ -5,18 +5,21 @@
|
|
|
5
5
|
/**
|
|
6
6
|
* Causal inference for East.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
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,
|
|
15
|
-
import { VectorType
|
|
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
|
|
20
|
+
export { VectorType } from "../types.js";
|
|
18
21
|
// ============================================================================
|
|
19
|
-
//
|
|
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
|
-
*
|
|
76
|
-
*
|
|
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
|
-
*
|
|
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
|
|
95
|
-
/**
|
|
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
|
-
/**
|
|
103
|
+
/** Confounders to adjust for (the backdoor set). */
|
|
100
104
|
common_causes: ArrayType(StringType),
|
|
101
|
-
/**
|
|
105
|
+
/** Confounder columns holding categories (string or int codes), one-hot encoded. */
|
|
102
106
|
categorical: OptionType(ArrayType(StringType)),
|
|
103
|
-
/**
|
|
107
|
+
/** Estimator (default: linear_regression). */
|
|
104
108
|
method: OptionType(CausalEstimatorType),
|
|
105
|
-
/** Target population (default: ate) */
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
|
|
109
|
-
/**
|
|
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
|
|
121
|
+
/** Random seed. */
|
|
112
122
|
random_state: OptionType(IntegerType),
|
|
113
123
|
});
|
|
114
|
-
/**
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
|
|
148
|
-
*/
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
-
|
|
173
|
-
*/
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
|
|
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
|
-
|
|
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
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
|
|
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
|
-
*
|
|
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
|
|
228
|
-
/**
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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
|
-
*
|
|
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
|
|
245
|
-
/**
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
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 (
|
|
309
|
-
* `ArrayType(ROW_T)`).
|
|
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
|
-
*
|
|
315
|
-
*
|
|
316
|
-
*
|
|
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 -
|
|
323
|
-
* @returns
|
|
228
|
+
* @param config - The experiment configuration
|
|
229
|
+
* @returns The honest result + verdict; `adjusted` is `none` when refused
|
|
324
230
|
*/
|
|
325
|
-
export const
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
*/
|
|
338
|
-
|
|
339
|
-
/**
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
*/
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
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
|
-
*
|
|
363
|
-
*
|
|
364
|
-
*
|
|
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
|
|
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
|
-
*
|
|
371
|
-
*
|
|
372
|
-
*
|
|
373
|
-
*
|
|
374
|
-
*
|
|
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 -
|
|
377
|
-
* @param config -
|
|
378
|
-
* @
|
|
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
|
|
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
|
-
//
|
|
323
|
+
// Shared vocabulary
|
|
389
324
|
CausalWeightingSchemeType,
|
|
390
325
|
CausalEstimatorType,
|
|
391
326
|
CausalTargetUnitsType,
|
|
392
|
-
PropensityTrimType,
|
|
393
327
|
CausalBootstrapConfigType,
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
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
|
-
*
|
|
411
|
-
*
|
|
412
|
-
*
|
|
413
|
-
*
|
|
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
|
|
357
|
+
* import { East, ArrayType, StructType, FloatType, BooleanType, variant } from "@elaraai/east";
|
|
358
|
+
* import { Causal } from "@elaraai/east-py-datascience";
|
|
419
359
|
*
|
|
420
|
-
* const
|
|
421
|
-
*
|
|
422
|
-
*
|
|
423
|
-
*
|
|
424
|
-
*
|
|
425
|
-
*
|
|
426
|
-
*
|
|
427
|
-
*
|
|
428
|
-
*
|
|
429
|
-
*
|
|
430
|
-
*
|
|
431
|
-
*
|
|
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
|
-
|
|
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
|
};
|