causarray 0.0.1__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 +269 -0
- causarray/DR_inference.py +201 -0
- causarray/DR_learner.py +296 -0
- causarray/__about__.py +1 -0
- causarray/__init__.py +21 -0
- causarray/gcate.py +237 -0
- causarray/gcate_glm.py +256 -0
- causarray/gcate_likelihood.py +143 -0
- causarray/gcate_opt.py +298 -0
- causarray/utils.py +243 -0
- causarray-0.0.1.dist-info/METADATA +70 -0
- causarray-0.0.1.dist-info/RECORD +14 -0
- causarray-0.0.1.dist-info/WHEEL +4 -0
- causarray-0.0.1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from sklearn.linear_model import LogisticRegression
|
|
3
|
+
from sklearn.ensemble import RandomForestClassifier
|
|
4
|
+
from causarray.gcate_glm import fit_glm
|
|
5
|
+
from causarray.utils import *
|
|
6
|
+
from causarray.utils import _filter_params
|
|
7
|
+
import pprint
|
|
8
|
+
|
|
9
|
+
from sklearn.model_selection import KFold, ShuffleSplit
|
|
10
|
+
|
|
11
|
+
def _get_func_ps(ps_model, **kwargs):
|
|
12
|
+
if ps_model=='random_forest_cv':
|
|
13
|
+
params_ps = _filter_params(fit_rf, kwargs)
|
|
14
|
+
func_ps = lambda X, Y, X_test:fit_rf_ind_ps(X, Y[:,None], X_test=X_test, **params_ps)[:,0]
|
|
15
|
+
elif ps_model=='logistic':
|
|
16
|
+
clf_ps = LogisticRegression
|
|
17
|
+
kwargs = {**{'fit_intercept':False, 'C':1e0, 'class_weight':'balanced', 'random_state':0}, **kwargs}
|
|
18
|
+
params_ps = _filter_params(clf_ps().get_params(), kwargs)
|
|
19
|
+
func_ps = lambda X, Y, X_test: clf_ps(**params_ps).fit(X, Y).predict_proba(X_test)[:,1]
|
|
20
|
+
elif ps_model=='ensemble':
|
|
21
|
+
params_ps_rf = _filter_params(fit_rf, kwargs)
|
|
22
|
+
clf_ps = LogisticRegression
|
|
23
|
+
kwargs = {**{'fit_intercept':False, 'C':1e0, 'class_weight':'balanced', 'random_state':0}, **kwargs}
|
|
24
|
+
params_ps_lr = _filter_params(clf_ps().get_params(), kwargs)
|
|
25
|
+
params_ps = {'params_ps_rf':params_ps_rf, 'params_ps_lr':params_ps_lr}
|
|
26
|
+
func_ps = lambda X, Y, X_test:(fit_rf_ind(X, Y[:,None], X_test=X_test, **params_ps_rf)[:,0] + clf_ps(**params_ps_lr).fit(X, Y).predict_proba(X_test)[:,1])/2
|
|
27
|
+
else:
|
|
28
|
+
raise ValueError('Invalid propensity score model.')
|
|
29
|
+
|
|
30
|
+
return func_ps, params_ps
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def cross_fitting(
|
|
34
|
+
Y, A, X, X_A, family='poisson', K=1, glm_alpha=1e-4,
|
|
35
|
+
ps_model='logistic',
|
|
36
|
+
pi_hat=None, Y_hat=None, verbose=False, **kwargs):
|
|
37
|
+
'''
|
|
38
|
+
Cross-fitting for causal estimands.
|
|
39
|
+
|
|
40
|
+
Parameters
|
|
41
|
+
----------
|
|
42
|
+
Y : array
|
|
43
|
+
Outcomes.
|
|
44
|
+
A : array
|
|
45
|
+
Binary treatment indicator.
|
|
46
|
+
X : array
|
|
47
|
+
Covariates.
|
|
48
|
+
X_A : array
|
|
49
|
+
Covariates for the propensity score model.
|
|
50
|
+
family : str, optional
|
|
51
|
+
The family of the generalized linear model. The default is 'poisson'.
|
|
52
|
+
K : int, optional
|
|
53
|
+
The number of folds for cross-validation. The default is 1.
|
|
54
|
+
glm_alpha : float, optional
|
|
55
|
+
The regularization parameter for the generalized linear model. The default is 1e-4.
|
|
56
|
+
ps_model : str, optional
|
|
57
|
+
The propensity score model. The default is 'logistic'.
|
|
58
|
+
pi_hat : array, optional
|
|
59
|
+
Propensity score of shape (n, a). The default is None.
|
|
60
|
+
Y_hat : array, optional
|
|
61
|
+
Estimated potential outcome of shape (n, p, a, 2). The default is None.
|
|
62
|
+
**kwargs : dict
|
|
63
|
+
Additional arguments to pass to the model.
|
|
64
|
+
|
|
65
|
+
Returns
|
|
66
|
+
-------
|
|
67
|
+
Y_hat : array
|
|
68
|
+
Estimated potential outcome under control.
|
|
69
|
+
pi_hat : array
|
|
70
|
+
Estimated propensity score.
|
|
71
|
+
'''
|
|
72
|
+
func_ps, params_ps = _get_func_ps(ps_model, **kwargs)
|
|
73
|
+
params_glm = _filter_params(fit_glm, kwargs)
|
|
74
|
+
|
|
75
|
+
if verbose:
|
|
76
|
+
pprint.pprint(params_ps)
|
|
77
|
+
pprint.pprint(params_glm)
|
|
78
|
+
|
|
79
|
+
if K>1:
|
|
80
|
+
# Initialize KFold cross-validator
|
|
81
|
+
kf = KFold(n_splits=K, random_state=0, shuffle=True)
|
|
82
|
+
folds = kf.split(X)
|
|
83
|
+
else:
|
|
84
|
+
folds = [(np.arange(X.shape[0]), np.arange(X.shape[0]))]
|
|
85
|
+
|
|
86
|
+
# Initialize lists to store results
|
|
87
|
+
fit_pi = True if pi_hat is None else False
|
|
88
|
+
pi_hat = np.zeros_like(A, dtype=float) if fit_pi else pi_hat
|
|
89
|
+
Y_hat = np.zeros((Y.shape[0],Y.shape[1],A.shape[1],2), dtype=float)
|
|
90
|
+
|
|
91
|
+
# Perform cross-fitting
|
|
92
|
+
for train_index, test_index in folds:
|
|
93
|
+
# Split data
|
|
94
|
+
X_train, X_test = X[train_index], X[test_index]
|
|
95
|
+
XA_train, XA_test = X_A[train_index], X_A[test_index]
|
|
96
|
+
A_train, A_test = A[train_index], A[test_index]
|
|
97
|
+
Y_train, Y_test = Y[train_index], Y[test_index]
|
|
98
|
+
|
|
99
|
+
if fit_pi:
|
|
100
|
+
i_ctrl = (np.sum(A_train, axis=1) == 0.)
|
|
101
|
+
|
|
102
|
+
pi = np.zeros_like(A_test, dtype=float)
|
|
103
|
+
for j in range(A.shape[1]):
|
|
104
|
+
i_case = (A_train[:,j] == 1.)
|
|
105
|
+
i_cells = i_ctrl | i_case
|
|
106
|
+
|
|
107
|
+
if ps_model=='logistic' and XA_train.shape[1]==1 and np.all(XA_train==1):
|
|
108
|
+
prob = np.sum(i_case)/np.sum(i_cells)
|
|
109
|
+
pi[A_train[:,j] == 1., j] = prob
|
|
110
|
+
pi[A_train[:,j] == 0., j] = 1 - prob
|
|
111
|
+
else:
|
|
112
|
+
pi[:,j] = func_ps(XA_train[i_cells], A_train[i_cells][:,j], XA_test)
|
|
113
|
+
|
|
114
|
+
# Fit GLM on training data and predict on test data
|
|
115
|
+
res = fit_glm(Y_train, X_train, A_train, family=family, alpha=glm_alpha,
|
|
116
|
+
impute=X_test, **params_glm)
|
|
117
|
+
|
|
118
|
+
# Store results
|
|
119
|
+
if fit_pi:
|
|
120
|
+
pi_hat[test_index] = pi
|
|
121
|
+
|
|
122
|
+
Y_hat[test_index,:,:,0] = res[1][0]
|
|
123
|
+
Y_hat[test_index,:,:,1] = res[1][1]
|
|
124
|
+
|
|
125
|
+
pi_hat = np.clip(pi_hat, 0.01, 0.99)
|
|
126
|
+
Y_hat = np.clip(Y_hat, None, 1e5)
|
|
127
|
+
return Y_hat, pi_hat
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def AIPW_mean(Y, A, mu, pi, positive=False):
|
|
134
|
+
'''
|
|
135
|
+
Augmented inverse probability weighted estimator (AIPW)
|
|
136
|
+
|
|
137
|
+
Parameters
|
|
138
|
+
----------
|
|
139
|
+
Y : array
|
|
140
|
+
Outcomes of shape (n, p).
|
|
141
|
+
A : array
|
|
142
|
+
Binary treatment indicator of shape (n, a, 2).
|
|
143
|
+
mu : array
|
|
144
|
+
Conditional outcome distribution estimate of shape (n, p, a, 2).
|
|
145
|
+
pi : array
|
|
146
|
+
Propensity score of shape (n, a, 2).
|
|
147
|
+
positive : bool, optional
|
|
148
|
+
Whether to restrict the pseudo-outcome to be positive.
|
|
149
|
+
|
|
150
|
+
Returns
|
|
151
|
+
-------
|
|
152
|
+
tau : array
|
|
153
|
+
A point estimate of the expected potential outcome of shape (p, a, 2).
|
|
154
|
+
pseudo_y : array
|
|
155
|
+
Pseudo-outcome of shape (n, p, a, 2).
|
|
156
|
+
'''
|
|
157
|
+
|
|
158
|
+
weight = A / pi
|
|
159
|
+
weight = weight[:, None, ...]
|
|
160
|
+
Y = Y[:, :, None, None]
|
|
161
|
+
|
|
162
|
+
pseudo_y = weight * (Y - mu) + mu
|
|
163
|
+
|
|
164
|
+
if positive:
|
|
165
|
+
pseudo_y = np.clip(pseudo_y, 0, None)
|
|
166
|
+
|
|
167
|
+
tau = np.mean(pseudo_y, axis=0)
|
|
168
|
+
|
|
169
|
+
return tau, pseudo_y
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
from joblib import Parallel, delayed
|
|
181
|
+
from tqdm import tqdm
|
|
182
|
+
from sklearn_ensemble_cv import reset_random_seeds, Ensemble, ECV
|
|
183
|
+
from sklearn.tree import DecisionTreeRegressor
|
|
184
|
+
|
|
185
|
+
def fit_rf(X, y, X_test=None, sample_weight=None, M=100, M_max=1000,
|
|
186
|
+
# fixed parameters for bagging regressor
|
|
187
|
+
kwargs_ensemble={'verbose':1},
|
|
188
|
+
# fixed parameters for decision tree
|
|
189
|
+
kwargs_regr={'min_samples_leaf': 3}, # 'min_samples_split': 10, 'max_features':'sqrt'
|
|
190
|
+
# grid search parameters
|
|
191
|
+
grid_regr = {'max_depth': [11]},
|
|
192
|
+
grid_ensemble = {'random_state': 0}, #'max_samples':np.linspace(0.25, 1., 4)
|
|
193
|
+
):
|
|
194
|
+
|
|
195
|
+
# Validate integer parameters
|
|
196
|
+
M = int(M)
|
|
197
|
+
M_max = int(M_max)
|
|
198
|
+
# for kwargs in [kwargs_regr, kwargs_ensemble, grid_regr, grid_ensemble]:
|
|
199
|
+
# for param in kwargs:
|
|
200
|
+
# if param in ['max_depth', 'random_state', 'max_leaf_nodes'] and isinstance(kwargs[param], float):
|
|
201
|
+
# kwargs[param] = int(kwargs[param])
|
|
202
|
+
|
|
203
|
+
# Make sure y is 2D
|
|
204
|
+
y = y.reshape(-1, 1) if y.ndim == 1 else y
|
|
205
|
+
|
|
206
|
+
# Run ECV
|
|
207
|
+
res_ecv, info_ecv = ECV(
|
|
208
|
+
X, y, DecisionTreeRegressor, grid_regr, grid_ensemble,
|
|
209
|
+
kwargs_regr, kwargs_ensemble,
|
|
210
|
+
M=M, M0=M, M_max=M_max, return_df=True
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
# Replace the in-sample best parameter for 'n_estimators' with extrapolated best parameter
|
|
214
|
+
info_ecv['best_params_ensemble']['n_estimators'] = info_ecv['best_n_estimators_extrapolate']
|
|
215
|
+
|
|
216
|
+
# Fit the ensemble with the best CV parameters
|
|
217
|
+
regr = Ensemble(
|
|
218
|
+
estimator=DecisionTreeRegressor(**info_ecv['best_params_regr']),
|
|
219
|
+
**info_ecv['best_params_ensemble']).fit(X, y, sample_weight=sample_weight)
|
|
220
|
+
|
|
221
|
+
# Predict
|
|
222
|
+
if X_test is None:
|
|
223
|
+
X_test = X
|
|
224
|
+
return regr.predict(X_test).reshape(-1, y.shape[1])
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def fit_rf_ind(X, Y, *args, **kwargs):
|
|
229
|
+
Y_hat = Parallel(n_jobs=-1)(delayed(fit_rf)(X, Y[:,j], *args, **kwargs)
|
|
230
|
+
for j in tqdm(range(Y.shape[1])))
|
|
231
|
+
Y_pred = np.concatenate(Y_hat, axis=-1)
|
|
232
|
+
return Y_pred
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def fit_rf_ind_ps(X, Y, *args, **kwargs):
|
|
236
|
+
i_ctrl = (np.sum(Y, axis=1) == 0.)
|
|
237
|
+
|
|
238
|
+
if 'X_test' not in kwargs:
|
|
239
|
+
kwargs['X_test'] = X
|
|
240
|
+
|
|
241
|
+
def _fit(X, y, i_ctrl, *args, **kwargs):
|
|
242
|
+
i_case = (y == 1.)
|
|
243
|
+
i_cells = i_ctrl | i_case
|
|
244
|
+
sample_weight = np.ones(y.shape[0])
|
|
245
|
+
class_weight = len(y) / (2 * np.bincount(y.astype(int)))
|
|
246
|
+
for a in range(2):
|
|
247
|
+
sample_weight[y == a] = class_weight[a]
|
|
248
|
+
return fit_rf(X[i_cells], y[i_cells], sample_weight=sample_weight[i_cells], *args, **kwargs)
|
|
249
|
+
|
|
250
|
+
Y_hat = Parallel(n_jobs=-1)(delayed(_fit)(X, Y[:,j], i_ctrl, *args, **kwargs)
|
|
251
|
+
for j in tqdm(range(Y.shape[1])))
|
|
252
|
+
Y_pred = np.concatenate(Y_hat, axis=-1)
|
|
253
|
+
|
|
254
|
+
return Y_pred
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def fit_rf_ind_outcome(W, Y, A, *args, **kwargs):
|
|
258
|
+
d = W.shape[1]
|
|
259
|
+
a = A.shape[1]
|
|
260
|
+
X = np.c_[W, A]
|
|
261
|
+
X_test = np.tile(np.c_[W, np.zeros_like(A)][:,None,:], (1,1+a,1))
|
|
262
|
+
for j in range(a):
|
|
263
|
+
X_test[:,1+j,d+j] = 1
|
|
264
|
+
X_test = X_test.reshape(-1, X_test.shape[-1])
|
|
265
|
+
Y_pred = fit_rf_ind(X, Y, X_test=X_test)
|
|
266
|
+
Y_pred = Y_pred.reshape(X.shape[0],1+a,Y.shape[1])
|
|
267
|
+
Yhat_1 = Y_pred[:,1:,:].transpose(0,2,1)
|
|
268
|
+
Yhat_0 = np.tile(Y_pred[:,0,:][:,:,None], (1,1,a))
|
|
269
|
+
return Yhat_0, Yhat_1
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import scipy as sp
|
|
3
|
+
import statsmodels.api as sm
|
|
4
|
+
from scipy.stats import norm
|
|
5
|
+
from statsmodels.stats.multitest import multipletests, fdrcorrection
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def multiplier_bootstrap(resid, var_est, B):
|
|
9
|
+
'''
|
|
10
|
+
Multiplier bootstrap for inference.
|
|
11
|
+
|
|
12
|
+
Parameters
|
|
13
|
+
----------
|
|
14
|
+
resid : array-like
|
|
15
|
+
[n, p] Residuals.
|
|
16
|
+
var_est : array-like
|
|
17
|
+
[p,] Variance of the parameter estimates.
|
|
18
|
+
B : int
|
|
19
|
+
Number of bootstrap samples.
|
|
20
|
+
|
|
21
|
+
Returns
|
|
22
|
+
-------
|
|
23
|
+
z_init : array-like
|
|
24
|
+
[B, p] Bootstrap statistics for each hypothesis.
|
|
25
|
+
'''
|
|
26
|
+
n, p = resid.shape
|
|
27
|
+
B = int(B)
|
|
28
|
+
z_init = np.zeros((B,p))
|
|
29
|
+
eta = resid / np.sqrt(n * var_est[None,:])
|
|
30
|
+
for b in range(B):
|
|
31
|
+
g = np.random.normal(size=n)
|
|
32
|
+
z_init[b, :] = np.sum(eta * g[:, None], axis=0)
|
|
33
|
+
|
|
34
|
+
# g = np.random.normal(size=(B,n,1))
|
|
35
|
+
# z_init = np.sum(resid[None, :,:] * g, axis=1) / np.sqrt(n * var_est[None,:])
|
|
36
|
+
|
|
37
|
+
return z_init
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def step_down(tvalues_init, z_init, alpha):
|
|
41
|
+
'''
|
|
42
|
+
Step-down procedure for controlling FWER.
|
|
43
|
+
|
|
44
|
+
Parameters
|
|
45
|
+
----------
|
|
46
|
+
tvalues_init : array-like
|
|
47
|
+
[p,] t-values for each hypothesis.
|
|
48
|
+
z_init : array-like
|
|
49
|
+
[B, p] Bootstrap statistics for each hypothesis.
|
|
50
|
+
alpha : float
|
|
51
|
+
The significance level.
|
|
52
|
+
|
|
53
|
+
Returns
|
|
54
|
+
-------
|
|
55
|
+
V : array-like
|
|
56
|
+
[p,] Set of discoveries.
|
|
57
|
+
tvalues : array-like
|
|
58
|
+
[p,] t-values for each hypothesis.
|
|
59
|
+
z : array-like
|
|
60
|
+
[B, p] Bootstrap statistics for each hypothesis.
|
|
61
|
+
'''
|
|
62
|
+
p = z_init.shape[1]
|
|
63
|
+
V = np.zeros(p,)
|
|
64
|
+
z = z_init.copy()
|
|
65
|
+
tvalues = tvalues_init.copy()
|
|
66
|
+
while True:
|
|
67
|
+
tvalues_max = np.max(np.abs(tvalues))
|
|
68
|
+
index_temp = np.unravel_index(np.argmax(np.abs(tvalues)), tvalues.shape)
|
|
69
|
+
z_max = np.max(np.abs(z), axis=1)
|
|
70
|
+
z_max_quan = np.quantile(z_max, 1 - alpha, method='lower')
|
|
71
|
+
if tvalues_max < z_max_quan or z_max_quan == 0:
|
|
72
|
+
break
|
|
73
|
+
tvalues[index_temp] = 0
|
|
74
|
+
z[:,index_temp] = 0
|
|
75
|
+
V[index_temp] = 1
|
|
76
|
+
return V, tvalues, z
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def augmentation(V, tvalues, c):
|
|
80
|
+
'''
|
|
81
|
+
Augment the set of discoveries V.
|
|
82
|
+
|
|
83
|
+
Parameters
|
|
84
|
+
----------
|
|
85
|
+
V : array-like
|
|
86
|
+
Set of discoveries.
|
|
87
|
+
tvalues : array-like
|
|
88
|
+
t-values for each hypothesis.
|
|
89
|
+
c : float
|
|
90
|
+
The exceeding level for FDP exceedance rate.
|
|
91
|
+
|
|
92
|
+
Returns
|
|
93
|
+
-------
|
|
94
|
+
V : array-like
|
|
95
|
+
Set of discoveries.
|
|
96
|
+
'''
|
|
97
|
+
if c>0:
|
|
98
|
+
size = np.sum(V)
|
|
99
|
+
num_add = int(np.floor(c * size / (1 - c)))
|
|
100
|
+
if num_add >= 1:
|
|
101
|
+
tvalues_sorted = np.sort(np.abs(tvalues), axis=None)[::-1]
|
|
102
|
+
for i in np.arange(num_add):
|
|
103
|
+
V[np.abs(tvalues)==tvalues_sorted[i]] = 1
|
|
104
|
+
|
|
105
|
+
return V
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def fdx_control(
|
|
109
|
+
tau_est, var_est, tvalues_init, eta_est,
|
|
110
|
+
fdx, B, alpha, c,
|
|
111
|
+
min_var=1e-4, min_diff=0.1
|
|
112
|
+
):
|
|
113
|
+
'''
|
|
114
|
+
Perform FDX control.
|
|
115
|
+
|
|
116
|
+
Parameters
|
|
117
|
+
----------
|
|
118
|
+
tau_est : array-like
|
|
119
|
+
The estimated causal effect.
|
|
120
|
+
var_est : array-like
|
|
121
|
+
The estimated variance.
|
|
122
|
+
tvalues_init : array-like
|
|
123
|
+
The estimated t-values.
|
|
124
|
+
eta_est : array-like
|
|
125
|
+
The estimated influence function values.
|
|
126
|
+
fdx : bool
|
|
127
|
+
If True, perform FDX control.
|
|
128
|
+
B : int
|
|
129
|
+
Number of bootstrap samples.
|
|
130
|
+
alpha : float
|
|
131
|
+
The significance level.
|
|
132
|
+
c : float
|
|
133
|
+
Augmentation parameter.
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
Returns
|
|
137
|
+
-------
|
|
138
|
+
V : array-like
|
|
139
|
+
FDX control results, 0 for non-discoveries and 1 for discoveries.
|
|
140
|
+
'''
|
|
141
|
+
n = eta_est.shape[0]
|
|
142
|
+
|
|
143
|
+
if fdx:
|
|
144
|
+
id_test = var_est >= min_var
|
|
145
|
+
z_init = multiplier_bootstrap(eta_est, var_est, B)
|
|
146
|
+
z_init[:, ~id_test] = 0.
|
|
147
|
+
tvalues = tvalues_init.copy()
|
|
148
|
+
tvalues[~id_test] = 0.
|
|
149
|
+
V, tvalues, z = step_down(tvalues, z_init, alpha)
|
|
150
|
+
|
|
151
|
+
V[(~id_test) & (np.abs(tau_est) > min_diff)] = 1.
|
|
152
|
+
V = augmentation(V, tvalues, c)
|
|
153
|
+
else:
|
|
154
|
+
V = np.zeros(tvalues_init.shape[0])
|
|
155
|
+
|
|
156
|
+
return V
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def bh_correction(tvalues_init):
|
|
160
|
+
'''
|
|
161
|
+
Perform BH correction.
|
|
162
|
+
|
|
163
|
+
Parameters
|
|
164
|
+
----------
|
|
165
|
+
tvalues_init : array-like
|
|
166
|
+
Initial t-values.
|
|
167
|
+
|
|
168
|
+
Returns
|
|
169
|
+
-------
|
|
170
|
+
pvals : array-like
|
|
171
|
+
P-values.
|
|
172
|
+
qvals : array-like
|
|
173
|
+
Q-values.
|
|
174
|
+
pvals_adj : array-like
|
|
175
|
+
P-values after empirical null adjustment.
|
|
176
|
+
qvals_adj : array-like
|
|
177
|
+
Q-values after empirical null adjustment.
|
|
178
|
+
'''
|
|
179
|
+
idx = ~np.isnan(tvalues_init)
|
|
180
|
+
|
|
181
|
+
# BH correction
|
|
182
|
+
pvals = np.full(tvalues_init.shape, np.nan)
|
|
183
|
+
qvals = np.full(tvalues_init.shape, np.nan)
|
|
184
|
+
pvals_adj = np.full(tvalues_init.shape, np.nan)
|
|
185
|
+
qvals_adj = np.full(tvalues_init.shape, np.nan)
|
|
186
|
+
|
|
187
|
+
if np.sum(idx) > 0:
|
|
188
|
+
pvals[idx] = sp.stats.norm.sf(np.abs(tvalues_init[idx]))*2
|
|
189
|
+
qvals[idx] = multipletests(pvals[idx], alpha=0.05, method='fdr_bh')[1]
|
|
190
|
+
|
|
191
|
+
# BH correction with empirical null adjustment
|
|
192
|
+
med = np.nanmedian(tvalues_init[idx])
|
|
193
|
+
mad = sp.stats.median_abs_deviation(tvalues_init[idx], scale="normal", nan_policy='omit')
|
|
194
|
+
|
|
195
|
+
if mad>0:
|
|
196
|
+
tvalues_init_adj = (tvalues_init - med) / mad
|
|
197
|
+
|
|
198
|
+
pvals_adj[idx] = sp.stats.norm.sf(np.abs(tvalues_init_adj[idx]))*2
|
|
199
|
+
qvals_adj[idx] = multipletests(pvals_adj[idx], alpha=0.05, method='fdr_bh')[1]
|
|
200
|
+
|
|
201
|
+
return pvals, qvals, pvals_adj, qvals_adj
|