causarray 0.0.4__tar.gz → 0.0.6__tar.gz
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-0.0.4 → causarray-0.0.6}/.gitignore +19 -1
- {causarray-0.0.4 → causarray-0.0.6}/PKG-INFO +33 -6
- {causarray-0.0.4 → causarray-0.0.6}/README.md +30 -3
- {causarray-0.0.4 → causarray-0.0.6}/causarray/DR_estimation.py +110 -37
- {causarray-0.0.4 → causarray-0.0.6}/causarray/DR_inference.py +9 -3
- causarray-0.0.6/causarray/DR_learner.py +728 -0
- causarray-0.0.6/causarray/__about__.py +1 -0
- causarray-0.0.6/causarray/__init__.py +31 -0
- causarray-0.0.6/causarray/gcate.py +609 -0
- {causarray-0.0.4 → causarray-0.0.6}/causarray/gcate_glm.py +204 -7
- {causarray-0.0.4 → causarray-0.0.6}/causarray/gcate_opt.py +188 -21
- causarray-0.0.6/causarray/nb_glm_fast.py +777 -0
- {causarray-0.0.4 → causarray-0.0.6}/causarray/utils.py +102 -9
- {causarray-0.0.4 → causarray-0.0.6}/pyproject.toml +3 -3
- causarray-0.0.4/causarray/DR_learner.py +0 -326
- causarray-0.0.4/causarray/__about__.py +0 -1
- causarray-0.0.4/causarray/__init__.py +0 -21
- causarray-0.0.4/causarray/gcate.py +0 -249
- {causarray-0.0.4 → causarray-0.0.6}/LICENSE +0 -0
- {causarray-0.0.4 → causarray-0.0.6}/causarray/gcate_likelihood.py +0 -0
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
# Planning documents
|
|
2
|
+
plan/
|
|
3
|
+
|
|
1
4
|
# Byte-compiled / optimized / DLL files
|
|
2
5
|
__pycache__/
|
|
3
6
|
*.py[cod]
|
|
@@ -160,5 +163,20 @@ cython_debug/
|
|
|
160
163
|
#.idea/
|
|
161
164
|
|
|
162
165
|
|
|
163
|
-
|
|
166
|
+
# Generic module tests under tests/ ARE tracked (regression coverage for
|
|
167
|
+
# the causarray API). Exclude only benchmark / scratch files that are
|
|
168
|
+
# not part of the unit-test suite — add new exclusions here when
|
|
169
|
+
# benchmark or tutorial-coupled test files appear.
|
|
170
|
+
tests/benchmark_adamson.py
|
|
171
|
+
|
|
164
172
|
causarray/___*.py
|
|
173
|
+
/docs/source/tutorial/adamson
|
|
174
|
+
|
|
175
|
+
# Replogle tutorial: large data files (regenerate with prep_tutorial_data.py or
|
|
176
|
+
# download from the project data repository; replogle_subset.h5ad is ~2 GB)
|
|
177
|
+
docs/source/tutorial/replogle/replogle_subset.h5ad
|
|
178
|
+
docs/source/tutorial/replogle/replogle_normed.h5ad
|
|
179
|
+
docs/source/tutorial/replogle/replogle_results.h5
|
|
180
|
+
|
|
181
|
+
# Unrelated tutorial directory (not part of causarray package docs)
|
|
182
|
+
docs/source/tutorial/scp/
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: causarray
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.6
|
|
4
4
|
Summary: causarray is a Python module for simultaneous causal inference with an array of outcomes.
|
|
5
|
-
Author-email: Jin-Hong Du <jinhongd@
|
|
6
|
-
Maintainer-email: Jin-Hong Du <jinhongd@
|
|
5
|
+
Author-email: Jin-Hong Du <jinhongd@hku.hk>, Maya Shen <myshen@andrew.cmu.edu>, Hansruedi Mathys <mathysh@pitt.edu>, Kathryn Roeder <jinhongd@hku.hk>
|
|
6
|
+
Maintainer-email: Jin-Hong Du <jinhongd@hku.hk>
|
|
7
7
|
License: MIT License
|
|
8
8
|
License-File: LICENSE
|
|
9
9
|
Classifier: Intended Audience :: Developers
|
|
@@ -49,17 +49,44 @@ The module can be installed via PyPI:
|
|
|
49
49
|
pip install causarray
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
+
For optimal parallel performance, we recommend installing `llvm-openmp` if using conda:
|
|
53
|
+
```cmd
|
|
54
|
+
conda install -c conda-forge llvm-openmp
|
|
55
|
+
```
|
|
56
|
+
|
|
52
57
|
For `R` users, `reticulate` can be used to call `causarray` from `R`.
|
|
53
58
|
The documentation and tutorials using both `Python` and `R` are available at [causarray.readthedocs.io](https://causarray.readthedocs.io/en/latest/).
|
|
54
59
|
|
|
55
60
|
|
|
56
61
|
|
|
57
|
-
##
|
|
62
|
+
## Batch fitting for large-scale screens
|
|
63
|
+
|
|
64
|
+
For screens with hundreds to thousands of perturbations, use the batch API
|
|
65
|
+
so that peak memory is bounded by one batch at a time:
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from causarray import gcate_lfc_batch
|
|
69
|
+
|
|
70
|
+
df_res = gcate_lfc_batch(
|
|
71
|
+
Y, X, A, r,
|
|
72
|
+
batch_size=10, # perturbations per batch (or use n_batches= for a fixed count)
|
|
73
|
+
max_cells=2000, # max pert cells per batch (ctrl added on top)
|
|
74
|
+
n_ctrl=2000, # fixed ctrl subsample shared across batches
|
|
75
|
+
cache_path='results.h5', # resume if interrupted
|
|
76
|
+
verbose=True,
|
|
77
|
+
)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
See the [Replogle-E-K562 tutorial](https://causarray.readthedocs.io/en/latest/)
|
|
81
|
+
for a demonstration on 200 perturbations from a genome-wide CRISPRi screen.
|
|
82
|
+
|
|
83
|
+
## Changelog
|
|
58
84
|
|
|
59
85
|
- [x] (2025-01-30) Python package released on PyPI
|
|
60
|
-
- [x] (2025-02-01)
|
|
86
|
+
- [x] (2025-02-01) Code for reproducing figures in paper
|
|
61
87
|
- [x] (2025-02-02) Tutorial for Python and R
|
|
62
|
-
- [
|
|
88
|
+
- [x] (2026-05-31) Batch fitting API (`gcate_lfc_batch`) for large-scale screens
|
|
89
|
+
- [x] (2026-05-31) Documentation at [causarray.readthedocs.io](https://causarray.readthedocs.io/en/latest/)
|
|
63
90
|
|
|
64
91
|
|
|
65
92
|
<!--
|
|
@@ -24,17 +24,44 @@ The module can be installed via PyPI:
|
|
|
24
24
|
pip install causarray
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
+
For optimal parallel performance, we recommend installing `llvm-openmp` if using conda:
|
|
28
|
+
```cmd
|
|
29
|
+
conda install -c conda-forge llvm-openmp
|
|
30
|
+
```
|
|
31
|
+
|
|
27
32
|
For `R` users, `reticulate` can be used to call `causarray` from `R`.
|
|
28
33
|
The documentation and tutorials using both `Python` and `R` are available at [causarray.readthedocs.io](https://causarray.readthedocs.io/en/latest/).
|
|
29
34
|
|
|
30
35
|
|
|
31
36
|
|
|
32
|
-
##
|
|
37
|
+
## Batch fitting for large-scale screens
|
|
38
|
+
|
|
39
|
+
For screens with hundreds to thousands of perturbations, use the batch API
|
|
40
|
+
so that peak memory is bounded by one batch at a time:
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
from causarray import gcate_lfc_batch
|
|
44
|
+
|
|
45
|
+
df_res = gcate_lfc_batch(
|
|
46
|
+
Y, X, A, r,
|
|
47
|
+
batch_size=10, # perturbations per batch (or use n_batches= for a fixed count)
|
|
48
|
+
max_cells=2000, # max pert cells per batch (ctrl added on top)
|
|
49
|
+
n_ctrl=2000, # fixed ctrl subsample shared across batches
|
|
50
|
+
cache_path='results.h5', # resume if interrupted
|
|
51
|
+
verbose=True,
|
|
52
|
+
)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
See the [Replogle-E-K562 tutorial](https://causarray.readthedocs.io/en/latest/)
|
|
56
|
+
for a demonstration on 200 perturbations from a genome-wide CRISPRi screen.
|
|
57
|
+
|
|
58
|
+
## Changelog
|
|
33
59
|
|
|
34
60
|
- [x] (2025-01-30) Python package released on PyPI
|
|
35
|
-
- [x] (2025-02-01)
|
|
61
|
+
- [x] (2025-02-01) Code for reproducing figures in paper
|
|
36
62
|
- [x] (2025-02-02) Tutorial for Python and R
|
|
37
|
-
- [
|
|
63
|
+
- [x] (2026-05-31) Batch fitting API (`gcate_lfc_batch`) for large-scale screens
|
|
64
|
+
- [x] (2026-05-31) Documentation at [causarray.readthedocs.io](https://causarray.readthedocs.io/en/latest/)
|
|
38
65
|
|
|
39
66
|
|
|
40
67
|
<!--
|
|
@@ -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])))
|
|
@@ -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
|