causarray 0.0.4__py3-none-any.whl → 0.0.6__py3-none-any.whl
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.
- causarray/DR_estimation.py +110 -37
- causarray/DR_inference.py +9 -3
- causarray/DR_learner.py +511 -109
- causarray/__about__.py +1 -1
- causarray/__init__.py +13 -3
- causarray/gcate.py +429 -69
- causarray/gcate_glm.py +204 -7
- causarray/gcate_opt.py +188 -21
- causarray/nb_glm_fast.py +777 -0
- causarray/utils.py +102 -9
- {causarray-0.0.4.dist-info → causarray-0.0.6.dist-info}/METADATA +33 -6
- causarray-0.0.6.dist-info/RECORD +15 -0
- {causarray-0.0.4.dist-info → causarray-0.0.6.dist-info}/WHEEL +1 -1
- causarray-0.0.4.dist-info/RECORD +0 -14
- {causarray-0.0.4.dist-info → causarray-0.0.6.dist-info}/licenses/LICENSE +0 -0
causarray/DR_estimation.py
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import numpy as np
|
|
2
2
|
from sklearn.linear_model import LogisticRegression
|
|
3
|
-
from sklearn.
|
|
3
|
+
from sklearn.tree import DecisionTreeClassifier, DecisionTreeRegressor
|
|
4
|
+
from sklearn_ensemble_cv import reset_random_seeds, Ensemble, ECV
|
|
4
5
|
from causarray.gcate_glm import fit_glm
|
|
6
|
+
import causarray.gcate_glm as _gcate_glm # module-qualified so _USE_FAST_BACKEND changes take effect at call time
|
|
5
7
|
from causarray.utils import *
|
|
6
8
|
from causarray.utils import _filter_params
|
|
9
|
+
from joblib import Parallel, delayed
|
|
10
|
+
from tqdm import tqdm
|
|
7
11
|
import pprint
|
|
8
12
|
|
|
9
13
|
from sklearn.model_selection import KFold, ShuffleSplit
|
|
@@ -82,10 +86,15 @@ def cross_fitting(
|
|
|
82
86
|
pprint.pprint(params_ps)
|
|
83
87
|
pprint.pprint(params_glm)
|
|
84
88
|
|
|
85
|
-
if K>1:
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
+
if K > 1:
|
|
90
|
+
n_samples = X.shape[0]
|
|
91
|
+
if K >= n_samples:
|
|
92
|
+
# Use Leave-One-Out Cross-Validation
|
|
93
|
+
folds = [([i for i in range(n_samples) if i != j], [j]) for j in range(n_samples)]
|
|
94
|
+
else:
|
|
95
|
+
# Initialize KFold cross-validator
|
|
96
|
+
kf = KFold(n_splits=int(K), random_state=0, shuffle=True)
|
|
97
|
+
folds = kf.split(X)
|
|
89
98
|
else:
|
|
90
99
|
folds = [(np.arange(X.shape[0]), np.arange(X.shape[0]))]
|
|
91
100
|
|
|
@@ -93,7 +102,29 @@ def cross_fitting(
|
|
|
93
102
|
fit_pi = True if pi_hat is None else False
|
|
94
103
|
pi_hat = np.zeros_like(A, dtype=float) if fit_pi else pi_hat
|
|
95
104
|
fit_Y = True if Y_hat is None else False
|
|
96
|
-
|
|
105
|
+
if fit_Y:
|
|
106
|
+
_yhat_gb = Y.shape[0] * Y.shape[1] * A.shape[1] * 2 * 8 / 1e9
|
|
107
|
+
_mem_limit_gb = kwargs.get('mem_limit_gb', None)
|
|
108
|
+
if _mem_limit_gb is not None and _yhat_gb > _mem_limit_gb:
|
|
109
|
+
import warnings
|
|
110
|
+
warnings.warn(
|
|
111
|
+
f"Y_hat allocation ({_yhat_gb:.1f} GB as float64) exceeds "
|
|
112
|
+
f"mem_limit_gb={_mem_limit_gb} GB; using float32 to halve peak memory.",
|
|
113
|
+
ResourceWarning, stacklevel=3,
|
|
114
|
+
)
|
|
115
|
+
Y_hat = np.zeros((Y.shape[0], Y.shape[1], A.shape[1], 2), dtype=np.float32)
|
|
116
|
+
else:
|
|
117
|
+
Y_hat = np.zeros((Y.shape[0], Y.shape[1], A.shape[1], 2), dtype=float)
|
|
118
|
+
|
|
119
|
+
# perform ECV at once
|
|
120
|
+
if fit_pi and ps_model == 'random_forest_cv':
|
|
121
|
+
info_ecv = run_ecv(X_A, A, **params_ps)
|
|
122
|
+
func_ps, params_ps = _get_func_ps(ps_model, verbose=False, ecv=False,
|
|
123
|
+
kwargs_ensemble=info_ecv['best_params_ensemble'], kwargs_regr=info_ecv['best_params_regr'])
|
|
124
|
+
pprint.pprint('Best parameters for the regression model:')
|
|
125
|
+
pprint.pprint(info_ecv['best_params_regr'])
|
|
126
|
+
pprint.pprint('Best parameters for the ensemble model:')
|
|
127
|
+
pprint.pprint(info_ecv['best_params_ensemble'])
|
|
97
128
|
|
|
98
129
|
# Perform cross-fitting
|
|
99
130
|
for train_index, test_index in folds:
|
|
@@ -127,9 +158,19 @@ def cross_fitting(
|
|
|
127
158
|
|
|
128
159
|
if fit_Y:
|
|
129
160
|
if verbose: pprint.pprint('Fit outcome models...')
|
|
161
|
+
# Subset offset to training fold (for fitting) and test fold (for
|
|
162
|
+
# imputation) when it is a pre-computed array, so that
|
|
163
|
+
# ``fit_glm_auto`` receives arrays with matching leading
|
|
164
|
+
# dimensions in both stages.
|
|
165
|
+
params_glm_fold = params_glm
|
|
166
|
+
offset_test_arr = None
|
|
167
|
+
if 'offset' in params_glm and isinstance(params_glm['offset'], np.ndarray):
|
|
168
|
+
params_glm_fold = dict(params_glm)
|
|
169
|
+
params_glm_fold['offset'] = params_glm['offset'][train_index]
|
|
170
|
+
offset_test_arr = params_glm['offset'][test_index]
|
|
130
171
|
# Fit GLM on training data and predict on test data
|
|
131
|
-
res =
|
|
132
|
-
impute=X_test, **
|
|
172
|
+
res = _gcate_glm.fit_glm_auto(Y_train, X_train, A_train, family=family, alpha=glm_alpha,
|
|
173
|
+
impute=X_test, offset_test=offset_test_arr, **params_glm_fold)
|
|
133
174
|
Y_hat[test_index,:,:,0] = res[1][0]
|
|
134
175
|
Y_hat[test_index,:,:,1] = res[1][1]
|
|
135
176
|
|
|
@@ -178,8 +219,6 @@ def AIPW_mean(Y, A, mu, pi, positive=False):
|
|
|
178
219
|
tau = np.mean(pseudo_y, axis=0)
|
|
179
220
|
|
|
180
221
|
return tau, pseudo_y
|
|
181
|
-
|
|
182
|
-
|
|
183
222
|
|
|
184
223
|
|
|
185
224
|
|
|
@@ -188,51 +227,89 @@ def AIPW_mean(Y, A, mu, pi, positive=False):
|
|
|
188
227
|
|
|
189
228
|
|
|
190
229
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
from sklearn_ensemble_cv import reset_random_seeds, Ensemble, ECV
|
|
194
|
-
from sklearn.tree import DecisionTreeRegressor
|
|
195
|
-
|
|
196
|
-
def fit_rf(X, y, X_test=None, sample_weight=None, M=100, M_max=1000,
|
|
230
|
+
def run_ecv(
|
|
231
|
+
X, y, M=200, M_max=1000,
|
|
197
232
|
# fixed parameters for bagging regressor
|
|
198
|
-
kwargs_ensemble={
|
|
233
|
+
kwargs_ensemble={},
|
|
199
234
|
# fixed parameters for decision tree
|
|
200
|
-
kwargs_regr={
|
|
235
|
+
kwargs_regr={},
|
|
201
236
|
# grid search parameters
|
|
202
|
-
grid_regr
|
|
203
|
-
grid_ensemble
|
|
204
|
-
|
|
237
|
+
grid_regr={},
|
|
238
|
+
grid_ensemble={}
|
|
239
|
+
):
|
|
240
|
+
"""
|
|
241
|
+
Runs Ensemble Cross-Validation (ECV) to find the best hyperparameters.
|
|
242
|
+
"""
|
|
243
|
+
kwargs_ensemble = {**{'verbose': 1, 'bootstrap': True}, **kwargs_ensemble}
|
|
244
|
+
kwargs_regr = {**{'min_samples_split': 20, 'min_samples_leaf': 10, 'max_features': 'sqrt', 'ccp_alpha': 0.02, 'class_weight': 'balanced'}, **kwargs_regr}
|
|
245
|
+
grid_regr = {**{'max_depth': [3, 5, 7]}, **grid_regr}
|
|
246
|
+
grid_ensemble = {**{'random_state': 0, 'max_samples': [0.4, 0.6, 0.8, 1.]}, **grid_ensemble}
|
|
205
247
|
|
|
206
248
|
# Validate integer parameters
|
|
207
249
|
M = int(M)
|
|
208
250
|
M_max = int(M_max)
|
|
209
|
-
# for kwargs in [kwargs_regr, kwargs_ensemble, grid_regr, grid_ensemble]:
|
|
210
|
-
# for param in kwargs:
|
|
211
|
-
# if param in ['max_depth', 'random_state', 'max_leaf_nodes'] and isinstance(kwargs[param], float):
|
|
212
|
-
# kwargs[param] = int(kwargs[param])
|
|
213
251
|
|
|
214
252
|
# Make sure y is 2D
|
|
215
253
|
y = y.reshape(-1, 1) if y.ndim == 1 else y
|
|
216
254
|
|
|
217
255
|
# Run ECV
|
|
218
|
-
|
|
219
|
-
X, y,
|
|
220
|
-
kwargs_regr, kwargs_ensemble,
|
|
256
|
+
_, info_ecv = ECV(
|
|
257
|
+
X, y, DecisionTreeClassifier, grid_regr, grid_ensemble,
|
|
258
|
+
kwargs_regr, kwargs_ensemble,
|
|
221
259
|
M=M, M0=M, M_max=M_max, return_df=True
|
|
222
260
|
)
|
|
223
261
|
|
|
224
262
|
# Replace the in-sample best parameter for 'n_estimators' with extrapolated best parameter
|
|
225
263
|
info_ecv['best_params_ensemble']['n_estimators'] = info_ecv['best_n_estimators_extrapolate']
|
|
226
264
|
|
|
265
|
+
return info_ecv
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def fit_rf(
|
|
269
|
+
X, y, X_test=None, M=100, M_max=1000, ecv=True,
|
|
270
|
+
# fixed parameters for bagging regressor
|
|
271
|
+
kwargs_ensemble={},
|
|
272
|
+
# fixed parameters for decision tree
|
|
273
|
+
kwargs_regr={},
|
|
274
|
+
# grid search parameters
|
|
275
|
+
grid_regr={},
|
|
276
|
+
grid_ensemble={}
|
|
277
|
+
):
|
|
278
|
+
"""
|
|
279
|
+
Fits a Random Forest model using parameters found by ECV.
|
|
280
|
+
"""
|
|
281
|
+
|
|
282
|
+
kwargs_ensemble = {**{'verbose': 1, 'bootstrap': True}, **kwargs_ensemble}
|
|
283
|
+
kwargs_regr = {**{'min_samples_split': 20, 'min_samples_leaf': 10, 'max_features': 'sqrt', 'ccp_alpha': 0.02, 'class_weight': 'balanced'}, **kwargs_regr}
|
|
284
|
+
grid_regr = {**{'max_depth': [3, 5, 7]}, **grid_regr}
|
|
285
|
+
grid_ensemble = {**{'random_state': 0, 'max_samples': [0.4, 0.6, 0.8, 1.]}, **grid_ensemble}
|
|
286
|
+
|
|
287
|
+
# Make sure y is 2D
|
|
288
|
+
y_2d = y.reshape(-1, 1) if y.ndim == 1 else y
|
|
289
|
+
|
|
290
|
+
if ecv:
|
|
291
|
+
# Get best parameters from ECV
|
|
292
|
+
info_ecv = run_ecv(
|
|
293
|
+
X, y_2d, M=M, M_max=M_max,
|
|
294
|
+
kwargs_ensemble=kwargs_ensemble,
|
|
295
|
+
kwargs_regr=kwargs_regr,
|
|
296
|
+
grid_regr=grid_regr,
|
|
297
|
+
grid_ensemble=grid_ensemble
|
|
298
|
+
)
|
|
299
|
+
params_regr = info_ecv['best_params_regr']
|
|
300
|
+
params_ensemble = info_ecv['best_params_ensemble']
|
|
301
|
+
else:
|
|
302
|
+
params_regr = kwargs_regr
|
|
303
|
+
params_ensemble = kwargs_ensemble
|
|
304
|
+
|
|
227
305
|
# Fit the ensemble with the best CV parameters
|
|
228
306
|
regr = Ensemble(
|
|
229
|
-
estimator=
|
|
230
|
-
|
|
231
|
-
|
|
307
|
+
estimator=DecisionTreeClassifier(**params_regr), **params_ensemble).fit(X, y_2d)
|
|
308
|
+
|
|
232
309
|
# Predict
|
|
233
310
|
if X_test is None:
|
|
234
311
|
X_test = X
|
|
235
|
-
return regr.predict(X_test).reshape(-1,
|
|
312
|
+
return regr.predict(X_test).reshape(-1, y_2d.shape[1])
|
|
236
313
|
|
|
237
314
|
|
|
238
315
|
|
|
@@ -252,11 +329,7 @@ def fit_rf_ind_ps(X, Y, *args, **kwargs):
|
|
|
252
329
|
def _fit(X, y, i_ctrl, *args, **kwargs):
|
|
253
330
|
i_case = (y == 1.)
|
|
254
331
|
i_cells = i_ctrl | i_case
|
|
255
|
-
|
|
256
|
-
class_weight = len(y) / (2 * np.bincount(y.astype(int)))
|
|
257
|
-
for a in range(2):
|
|
258
|
-
sample_weight[y == a] = class_weight[a]
|
|
259
|
-
return fit_rf(X[i_cells], y[i_cells], sample_weight=sample_weight[i_cells], *args, **kwargs)
|
|
332
|
+
return fit_rf(X[i_cells], y[i_cells], *args, **kwargs)
|
|
260
333
|
|
|
261
334
|
Y_hat = Parallel(n_jobs=-1)(delayed(_fit)(X, Y[:,j], i_ctrl, *args, **kwargs)
|
|
262
335
|
for j in tqdm(range(Y.shape[1])))
|
causarray/DR_inference.py
CHANGED
|
@@ -150,7 +150,7 @@ def fdx_control(
|
|
|
150
150
|
return V
|
|
151
151
|
|
|
152
152
|
|
|
153
|
-
def bh_correction(tvalues_init):
|
|
153
|
+
def bh_correction(tvalues_init, df=None):
|
|
154
154
|
'''
|
|
155
155
|
Perform BH correction.
|
|
156
156
|
|
|
@@ -179,7 +179,10 @@ def bh_correction(tvalues_init):
|
|
|
179
179
|
qvals_adj = np.full(tvalues_init.shape, np.nan)
|
|
180
180
|
|
|
181
181
|
if np.sum(idx) > 0:
|
|
182
|
-
|
|
182
|
+
if df is not None:
|
|
183
|
+
pvals[idx] = sp.stats.t.sf(np.abs(tvalues_init[idx]), df=df[idx]) * 2
|
|
184
|
+
else:
|
|
185
|
+
pvals[idx] = sp.stats.norm.sf(np.abs(tvalues_init[idx])) * 2
|
|
183
186
|
qvals[idx] = multipletests(pvals[idx], alpha=0.05, method='fdr_bh')[1]
|
|
184
187
|
|
|
185
188
|
# BH correction with empirical null adjustment
|
|
@@ -189,7 +192,10 @@ def bh_correction(tvalues_init):
|
|
|
189
192
|
if mad>0:
|
|
190
193
|
tvalues_init_adj = (tvalues_init - med) / mad
|
|
191
194
|
|
|
192
|
-
|
|
195
|
+
if df is not None:
|
|
196
|
+
pvals_adj[idx] = sp.stats.t.sf(np.abs(tvalues_init_adj[idx]), df=df[idx]) * 2
|
|
197
|
+
else:
|
|
198
|
+
pvals_adj[idx] = sp.stats.norm.sf(np.abs(tvalues_init_adj[idx])) * 2
|
|
193
199
|
qvals_adj[idx] = multipletests(pvals_adj[idx], alpha=0.05, method='fdr_bh')[1]
|
|
194
200
|
|
|
195
201
|
return pvals, qvals, pvals_adj, qvals_adj
|