@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.
- package/README.md +1 -1
- package/dist/src/causal/causal.d.ts +1377 -1383
- package/dist/src/causal/causal.d.ts.map +1 -1
- package/dist/src/causal/causal.js +266 -478
- 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,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
|
-
*
|
|
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
|
+
// ============================================================================
|
|
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
|
|
88
|
-
*
|
|
89
|
-
*
|
|
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
|
|
95
|
-
/**
|
|
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
|
-
/**
|
|
103
|
+
/** Confounders to adjust for (the backdoor set). */
|
|
102
104
|
common_causes: ArrayType(StringType),
|
|
103
|
-
/**
|
|
105
|
+
/** Confounder columns holding categories (string or int codes), one-hot encoded. */
|
|
104
106
|
categorical: OptionType(ArrayType(StringType)),
|
|
105
|
-
/**
|
|
107
|
+
/** Estimator (default: linear_regression). */
|
|
106
108
|
method: OptionType(CausalEstimatorType),
|
|
107
|
-
/** Target population (default: ate) */
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
|
|
111
|
-
/**
|
|
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
|
|
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
|
-
*
|
|
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
|
|
120
|
-
/**
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
*
|
|
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
|
|
152
|
-
/**
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
194
|
-
*
|
|
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
|
|
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
|
-
//
|
|
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
|
-
|
|
230
|
-
*/
|
|
231
|
-
|
|
232
|
-
/**
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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
|
-
|
|
244
|
-
|
|
245
|
-
/**
|
|
246
|
-
|
|
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
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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
|
-
*
|
|
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
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
/**
|
|
286
|
-
|
|
287
|
-
/**
|
|
288
|
-
|
|
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
|
-
|
|
292
|
-
*/
|
|
293
|
-
|
|
294
|
-
/**
|
|
295
|
-
|
|
296
|
-
/**
|
|
297
|
-
|
|
298
|
-
/**
|
|
299
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
312
|
-
*
|
|
313
|
-
*
|
|
314
|
-
*
|
|
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
|
|
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
|
-
//
|
|
323
|
+
// Shared vocabulary
|
|
384
324
|
CausalWeightingSchemeType,
|
|
385
325
|
CausalEstimatorType,
|
|
386
326
|
CausalTargetUnitsType,
|
|
387
|
-
PropensityTrimType,
|
|
388
327
|
CausalBootstrapConfigType,
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
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
|
-
*
|
|
406
|
-
*
|
|
407
|
-
*
|
|
408
|
-
*
|
|
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
|
|
357
|
+
* import { East, ArrayType, StructType, FloatType, BooleanType, variant } from "@elaraai/east";
|
|
358
|
+
* import { Causal } from "@elaraai/east-py-datascience";
|
|
414
359
|
*
|
|
415
|
-
* const
|
|
416
|
-
*
|
|
417
|
-
*
|
|
418
|
-
*
|
|
419
|
-
*
|
|
420
|
-
*
|
|
421
|
-
*
|
|
422
|
-
*
|
|
423
|
-
*
|
|
424
|
-
*
|
|
425
|
-
*
|
|
426
|
-
*
|
|
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
|
-
|
|
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
|
};
|