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
causarray/DR_learner.py
ADDED
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pandas as pd
|
|
3
|
+
from causarray.DR_estimation import AIPW_mean, cross_fitting
|
|
4
|
+
from causarray.gcate_glm import loess_fit, ls_fit
|
|
5
|
+
from causarray.DR_inference import fdx_control, bh_correction
|
|
6
|
+
from causarray.utils import reset_random_seeds, pprint, tqdm, comp_size_factor, _filter_params
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def compute_causal_estimand(
|
|
12
|
+
estimand,
|
|
13
|
+
Y, W, A, W_A=None, family='nb', offset=False,
|
|
14
|
+
Y_hat=None, pi_hat=None, eps_var=1e-3,
|
|
15
|
+
fdx=False, fdx_B=1000, fdx_alpha=0.05, fdx_c=0.1,
|
|
16
|
+
verbose=False, random_state=0, **kwargs):
|
|
17
|
+
'''
|
|
18
|
+
Estimate the log-fold chanegs of treatment effects (LFCs) using AIPW.
|
|
19
|
+
|
|
20
|
+
Parameters
|
|
21
|
+
----------
|
|
22
|
+
estimand : function
|
|
23
|
+
The causal estimand to estimate, it takes the estimated influence function values (eta_0, eta_1)
|
|
24
|
+
of ATE as input and returns the estimated treatment effect and the estimated influence function (tau, eta).
|
|
25
|
+
Y : array
|
|
26
|
+
n x p matrix of outcomes.
|
|
27
|
+
W : array
|
|
28
|
+
n x d matrix of covariates.
|
|
29
|
+
A : array
|
|
30
|
+
n x 1 vector of treatments.
|
|
31
|
+
W_A : array, optional
|
|
32
|
+
n x d_A matrix of covariates for treatment. If None, W is used.
|
|
33
|
+
family : str
|
|
34
|
+
The distribution of the outcome. The default is 'poisson'.
|
|
35
|
+
offset : array-like, optional
|
|
36
|
+
Offset for the model.
|
|
37
|
+
|
|
38
|
+
Y_hat : array, optional
|
|
39
|
+
Predicted outcomes under treatment of shape (n, p, a, 2).
|
|
40
|
+
pi_hat : array, optional
|
|
41
|
+
Predicted propensity scores of shape (n, a).
|
|
42
|
+
|
|
43
|
+
fdx : bool
|
|
44
|
+
Whether to use FDX control, P(FDP > c) < alpha.
|
|
45
|
+
fdx_B : int
|
|
46
|
+
Number of bootstrap samples for FDX control.
|
|
47
|
+
fdx_alpha : float
|
|
48
|
+
The significance level for FDX control.
|
|
49
|
+
fdx_c : float
|
|
50
|
+
The augmentation parameter for FDX control.
|
|
51
|
+
|
|
52
|
+
verbose : bool
|
|
53
|
+
Whether to print the model information.
|
|
54
|
+
**kwargs : dict
|
|
55
|
+
Additional arguments to pass to fit_glm.
|
|
56
|
+
|
|
57
|
+
Returns
|
|
58
|
+
-------
|
|
59
|
+
df_res : DataFrame
|
|
60
|
+
Dataframe of test results.
|
|
61
|
+
'''
|
|
62
|
+
reset_random_seeds(random_state)
|
|
63
|
+
|
|
64
|
+
kwargs = {k:v for k,v in kwargs.items() if k not in
|
|
65
|
+
['kwargs_ls_1', 'kwargs_ls_2', 'kwargs_es_1', 'kwargs_es_2', 'c1', 'num_d']
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if isinstance(Y, pd.DataFrame):
|
|
69
|
+
gene_names = Y.columns
|
|
70
|
+
Y = Y.values
|
|
71
|
+
else:
|
|
72
|
+
gene_names = range(Y.shape[1])
|
|
73
|
+
n, p = Y.shape
|
|
74
|
+
|
|
75
|
+
if len(A.shape) == 1:
|
|
76
|
+
A = A.reshape(-1,1)
|
|
77
|
+
if isinstance(A, pd.DataFrame):
|
|
78
|
+
trt_names = A.columns
|
|
79
|
+
A = A.values
|
|
80
|
+
else:
|
|
81
|
+
trt_names = range(A.shape[1])
|
|
82
|
+
|
|
83
|
+
if isinstance(W, pd.DataFrame):
|
|
84
|
+
cov_names = W.columns
|
|
85
|
+
W = W.values
|
|
86
|
+
if W_A is None:
|
|
87
|
+
W_A = W
|
|
88
|
+
elif isinstance(W_A, pd.DataFrame):
|
|
89
|
+
W_A = W_A.values
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
if verbose:
|
|
93
|
+
d_A = W_A.shape[1]
|
|
94
|
+
pprint.pprint('Estimating LFC...')
|
|
95
|
+
pprint.pprint({'estimands':'LFC','n':n,'p':p,'d':W.shape[1], 'd_A':d_A, 'a':A.shape[1]}, compact=True)
|
|
96
|
+
|
|
97
|
+
if offset is not None and offset is not False:
|
|
98
|
+
if type(offset)==bool and offset is True:
|
|
99
|
+
size_factors = comp_size_factor(Y, **_filter_params(comp_size_factor, kwargs))
|
|
100
|
+
offset = np.log(size_factors)
|
|
101
|
+
else:
|
|
102
|
+
size_factors = np.exp(offset)
|
|
103
|
+
else:
|
|
104
|
+
offset = None
|
|
105
|
+
size_factors = np.ones(n)
|
|
106
|
+
|
|
107
|
+
Y = Y.astype('float')
|
|
108
|
+
Y_hat, pi_hat = cross_fitting(Y, A, W, W_A, family=family, offset=offset,
|
|
109
|
+
Y_hat=Y_hat, pi_hat=pi_hat, random_state=random_state, verbose=verbose, **kwargs)
|
|
110
|
+
pi_hat = pi_hat.reshape(*A.shape)
|
|
111
|
+
|
|
112
|
+
# point estimation of the treatment effect
|
|
113
|
+
_, etas = AIPW_mean(Y, np.stack([1-A, A], axis=-1),
|
|
114
|
+
Y_hat, np.stack([1-pi_hat, pi_hat], axis=-1), positive=True)
|
|
115
|
+
|
|
116
|
+
# normalize the influence function values
|
|
117
|
+
etas /= size_factors[:,None,None,None]
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
i_ctrl = (np.sum(A, axis=1) == 0.)
|
|
121
|
+
|
|
122
|
+
res = []
|
|
123
|
+
iters = range(A.shape[1]) if A.shape[1]==1 else tqdm(range(A.shape[1]))
|
|
124
|
+
for j in iters:
|
|
125
|
+
i_case = (A[:,j] == 1.)
|
|
126
|
+
i_cells = i_ctrl | i_case
|
|
127
|
+
n_cells = np.sum(i_cells)
|
|
128
|
+
eta_est, tau_est, var_est = estimand(etas[i_cells,:,j], **kwargs)
|
|
129
|
+
|
|
130
|
+
std_est = np.sqrt((var_est + eps_var)/ n_cells)
|
|
131
|
+
tvalues_init = tau_est / std_est
|
|
132
|
+
|
|
133
|
+
# Multiple testing procedure
|
|
134
|
+
V = fdx_control(tau_est, var_est, tvalues_init, eta_est, fdx, fdx_B, fdx_alpha, fdx_c)
|
|
135
|
+
|
|
136
|
+
# BH correction
|
|
137
|
+
tvalues_init[np.isinf(var_est)] = np.nan
|
|
138
|
+
pvals, qvals, pvals_adj, qvals_adj = bh_correction(tvalues_init)
|
|
139
|
+
|
|
140
|
+
df_res = pd.DataFrame({
|
|
141
|
+
'gene_names': gene_names,
|
|
142
|
+
'tau': tau_est,
|
|
143
|
+
'std': std_est,
|
|
144
|
+
'stat': tvalues_init,
|
|
145
|
+
'rej': V,
|
|
146
|
+
'pvalue': pvals,
|
|
147
|
+
'padj': qvals,
|
|
148
|
+
'pvalue_emp_null_adj': pvals_adj,
|
|
149
|
+
'padj_emp_null_adj': qvals_adj,
|
|
150
|
+
})
|
|
151
|
+
if A.shape[1]>1:
|
|
152
|
+
df_res['trt'] = trt_names[j]
|
|
153
|
+
res.append(df_res)
|
|
154
|
+
df_res = pd.concat(res, axis=0).reset_index(drop=True)
|
|
155
|
+
estimation = {**{'pi_hat':pi_hat, 'Y_hat':Y_hat, 'offset':offset, 'size_factors':size_factors}, **kwargs}
|
|
156
|
+
return df_res, estimation
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def LFC(
|
|
160
|
+
Y, W, A, W_A=None, family='nb', offset=False,
|
|
161
|
+
Y_hat=None, pi_hat=None, cross_est=False,
|
|
162
|
+
thres_min=1e-4, thres_diff=1e-6, eps_var=1e-3,
|
|
163
|
+
fdx=False, fdx_alpha=0.05, fdx_c=0.1,
|
|
164
|
+
verbose=False, **kwargs):
|
|
165
|
+
'''
|
|
166
|
+
Estimate the log-fold chanegs of treatment effects (LFCs) using AIPW.
|
|
167
|
+
|
|
168
|
+
Parameters
|
|
169
|
+
----------
|
|
170
|
+
Y : array
|
|
171
|
+
n x p matrix of outcomes.
|
|
172
|
+
W : array
|
|
173
|
+
n x d matrix of covariates.
|
|
174
|
+
A : array
|
|
175
|
+
n x 1 vector of treatments.
|
|
176
|
+
W_A : array, optional
|
|
177
|
+
n x d_A matrix of covariates for treatment. If None, W is used.
|
|
178
|
+
family : str
|
|
179
|
+
The distribution of the outcome. The default is 'poisson'.
|
|
180
|
+
offset : array-like, optional
|
|
181
|
+
Offset for the model.
|
|
182
|
+
|
|
183
|
+
Y_hat : array, optional
|
|
184
|
+
Predicted outcomes under treatment of shape (n, p, a, 2).
|
|
185
|
+
pi_hat : array, optional
|
|
186
|
+
Predicted propensity scores of shape (n, a).
|
|
187
|
+
cross_est : bool
|
|
188
|
+
Whether to use cross-estimation.
|
|
189
|
+
|
|
190
|
+
thres_min : float
|
|
191
|
+
The minimum threshold for the treatment effect.
|
|
192
|
+
thres_diff : float
|
|
193
|
+
The minimum threshold for the difference in treatment effect.
|
|
194
|
+
|
|
195
|
+
fdx : bool
|
|
196
|
+
Whether to use FDX control, P(FDP > c) < alpha.
|
|
197
|
+
fdx_alpha : float
|
|
198
|
+
The significance level for FDX control.
|
|
199
|
+
fdx_c : float
|
|
200
|
+
The augmentation parameter for FDX control.
|
|
201
|
+
|
|
202
|
+
verbose : bool
|
|
203
|
+
Whether to print the model information.
|
|
204
|
+
kwargs : dict
|
|
205
|
+
Additional arguments to pass to fit_glm.
|
|
206
|
+
|
|
207
|
+
Returns
|
|
208
|
+
-------
|
|
209
|
+
df_res : DataFrame
|
|
210
|
+
Dataframe of test results.
|
|
211
|
+
'''
|
|
212
|
+
|
|
213
|
+
def estimand(etas, **kwargs):
|
|
214
|
+
eta_0, eta_1 = etas[..., 0], etas[..., 1]
|
|
215
|
+
tau_0, tau_1 = np.mean(eta_0, axis=0), np.mean(eta_1, axis=0)
|
|
216
|
+
|
|
217
|
+
tau_1 = np.clip(tau_1, thres_diff, None)
|
|
218
|
+
tau_0 = np.clip(tau_0, thres_diff, None)
|
|
219
|
+
tau_est = np.log(tau_1/tau_0)
|
|
220
|
+
eta_est = eta_1 / tau_1[None,:] - eta_0 / tau_0[None,:]
|
|
221
|
+
var_est = np.var(eta_est, axis=0, ddof=1)
|
|
222
|
+
|
|
223
|
+
# filter out low-expressed genes
|
|
224
|
+
idx = (np.maximum(tau_0,tau_1)<thres_min) & ((tau_1-tau_0)<thres_diff)
|
|
225
|
+
tau_est[idx] = 0.; eta_est[:,idx] = 0.; var_est[idx] = np.inf
|
|
226
|
+
|
|
227
|
+
return eta_est, tau_est, var_est
|
|
228
|
+
|
|
229
|
+
return compute_causal_estimand(
|
|
230
|
+
estimand, Y, W, A, W_A, family, offset,
|
|
231
|
+
Y_hat=Y_hat, pi_hat=pi_hat,
|
|
232
|
+
fdx=fdx, fdx_alpha=fdx_alpha, fdx_c=fdx_c, verbose=verbose, **kwargs)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def VIM(eta_est, X, id_covs, **kwargs):
|
|
239
|
+
'''
|
|
240
|
+
Estimate the variable importance measure (VIM) using AIPW.
|
|
241
|
+
|
|
242
|
+
Parameters
|
|
243
|
+
----------
|
|
244
|
+
eta_est : array
|
|
245
|
+
n x p matrix of influence function values.
|
|
246
|
+
'''
|
|
247
|
+
if len(X.shape)==1:
|
|
248
|
+
X = X[:,None]
|
|
249
|
+
|
|
250
|
+
n, p = eta_est.shape
|
|
251
|
+
d = X.shape[1]
|
|
252
|
+
if id_covs is None:
|
|
253
|
+
id_covs = range(d)
|
|
254
|
+
if np.isscalar(id_covs):
|
|
255
|
+
id_covs = range(id_covs)
|
|
256
|
+
|
|
257
|
+
n_covs = len(id_covs)
|
|
258
|
+
|
|
259
|
+
emp_VTE = (eta_est - np.mean(eta_est, axis=0, keepdims=True))**2
|
|
260
|
+
VTE = np.mean(emp_VTE, axis=0)
|
|
261
|
+
VIM_mean = np.zeros((n_covs, p))
|
|
262
|
+
VIM_sd = np.zeros((n_covs, p))
|
|
263
|
+
emp_CVTE = np.zeros((n_covs, n, p))
|
|
264
|
+
CVTE = np.zeros((n_covs, p))
|
|
265
|
+
CATE = np.zeros((n_covs, n, p))
|
|
266
|
+
CATE_lower = np.zeros((n_covs, n, p))
|
|
267
|
+
CATE_upper = np.zeros((n_covs, n, p))
|
|
268
|
+
|
|
269
|
+
for j,i in enumerate(id_covs):
|
|
270
|
+
print(j,i)
|
|
271
|
+
# regression eta_est on X to get predicted values
|
|
272
|
+
if np.all(np.modf(X[:,i:i+1])[0] == 0):
|
|
273
|
+
CATE[j], CATE_lower[j], CATE_upper[i] = ls_fit(eta_est, X[:,i], **kwargs)
|
|
274
|
+
else:
|
|
275
|
+
CATE[j], CATE_lower[j], CATE_upper[j] = loess_fit(eta_est, X[:,i], **kwargs)
|
|
276
|
+
# compute the variance of treatment effect
|
|
277
|
+
_emp_CVTE = (eta_est - CATE[j])**2
|
|
278
|
+
_CVTE = np.nanmean(_emp_CVTE, axis=0)
|
|
279
|
+
emp_CVTE[j] = _emp_CVTE
|
|
280
|
+
CVTE[j] = _CVTE
|
|
281
|
+
|
|
282
|
+
VIM_mean[j] = _CVTE / VTE - 1
|
|
283
|
+
VIM_sd[j] = np.nanstd((emp_VTE - _emp_CVTE), axis=0, ddof=1)/VTE
|
|
284
|
+
|
|
285
|
+
estimation = {
|
|
286
|
+
'CATE': CATE,
|
|
287
|
+
'CATE_lower': CATE_lower,
|
|
288
|
+
'CATE_upper': CATE_upper,
|
|
289
|
+
'emp_VTE': emp_VTE,
|
|
290
|
+
'VTE': VTE,
|
|
291
|
+
'emp_CVTE' : emp_CVTE,
|
|
292
|
+
'CVTE' :CVTE,
|
|
293
|
+
'VIM_mean' : VIM_mean,
|
|
294
|
+
'VIM_sd' : VIM_sd
|
|
295
|
+
}
|
|
296
|
+
return estimation
|
causarray/__about__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.0.1"
|
causarray/__init__.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
'LFC', #'ATE', 'SATE', 'FC',
|
|
3
|
+
'fit_glm', 'reset_random_seeds', 'fit_gcate'
|
|
4
|
+
]
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
from causarray.DR_learner import LFC # ATE, SATE, FC
|
|
8
|
+
from causarray.gcate_glm import fit_glm
|
|
9
|
+
from causarray.utils import prep_causarray_data, reset_random_seeds, comp_size_factor
|
|
10
|
+
|
|
11
|
+
from causarray.gcate import *
|
|
12
|
+
from causarray.__about__ import __version__
|
|
13
|
+
|
|
14
|
+
__license__ = "MIT"
|
|
15
|
+
|
|
16
|
+
__author__ = "Jin-Hong Du, Maya Shen, Hansruedi Mathys, and Kathryn Roeder"
|
|
17
|
+
__maintainer__ = "Jin-Hong Du"
|
|
18
|
+
__maintainer_email__ = "jinhongd@andrew.cmu.edu"
|
|
19
|
+
__description__ = ("Causarray: A Python package for simultaneous causal inference"
|
|
20
|
+
" with an array of outcomes."
|
|
21
|
+
)
|
causarray/gcate.py
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
from causarray.gcate_opt import *
|
|
2
|
+
import pandas as pd
|
|
3
|
+
from causarray.utils import comp_size_factor, _filter_params
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def _check_input(Y, X, family, disp_glm, disp_family, offset, c1, **kwargs):
|
|
7
|
+
if not (X.ndim == 2 and Y.ndim == 2):
|
|
8
|
+
raise ValueError("Input must have ndim of 2. Y.ndim: {}, X.ndim: {}.".format(Y.ndim, X.ndim))
|
|
9
|
+
|
|
10
|
+
if not np.allclose(Y, Y.astype(int)):
|
|
11
|
+
warnings.warn("Y is not integer-valued. It will be rounded to the nearest integer.")
|
|
12
|
+
Y = np.round(Y)
|
|
13
|
+
|
|
14
|
+
if np.sum(np.any(Y!=0., axis=0))<Y.shape[1]:
|
|
15
|
+
raise ValueError("Y contains non-expressed features.")
|
|
16
|
+
|
|
17
|
+
if np.linalg.svd(X, compute_uv=False)[-1] < 1e-3:
|
|
18
|
+
raise ValueError("The covariate matrix is near singular.")
|
|
19
|
+
|
|
20
|
+
Y = np.asarray(Y).astype(type_f)
|
|
21
|
+
n, p = Y.shape
|
|
22
|
+
|
|
23
|
+
kwargs_glm = {}
|
|
24
|
+
kwargs_glm['family'] = family
|
|
25
|
+
|
|
26
|
+
if offset is not None:
|
|
27
|
+
if type(offset)==bool and offset is True:
|
|
28
|
+
size_factor = comp_size_factor(Y, **_filter_params(comp_size_factor, kwargs))
|
|
29
|
+
kwargs_glm['size_factor'] = size_factor
|
|
30
|
+
offset = np.log(size_factor)
|
|
31
|
+
else:
|
|
32
|
+
offset = np.asarray(offset)
|
|
33
|
+
else:
|
|
34
|
+
offset = None
|
|
35
|
+
if kwargs_glm['family']=='nb':
|
|
36
|
+
if disp_family is None:
|
|
37
|
+
disp_family = 'poisson'
|
|
38
|
+
disp_glm = estimate_disp(Y, X, offset=offset, disp_family=disp_family, maxiter=1000, **kwargs)
|
|
39
|
+
if disp_glm is not None:
|
|
40
|
+
kwargs_glm['disp_glm'] = disp_glm
|
|
41
|
+
|
|
42
|
+
kwargs_glm = {**{'family':'gaussian', 'disp_glm':np.ones((1,p)), 'size_factor':np.ones((n,1))
|
|
43
|
+
}, **kwargs_glm}
|
|
44
|
+
|
|
45
|
+
c1 = 0.05 if c1 is None else c1
|
|
46
|
+
lam1 = c1 #* a #* np.sqrt(np.log(p)/n)
|
|
47
|
+
|
|
48
|
+
return Y, kwargs_glm, lam1
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def fit_gcate(Y, X, A, r, family='nb', disp_glm=None, disp_family=None, offset=True,
|
|
52
|
+
kwargs_ls_1={}, kwargs_ls_2={}, kwargs_es_1={}, kwargs_es_2={},
|
|
53
|
+
c1=None, **kwargs
|
|
54
|
+
):
|
|
55
|
+
'''
|
|
56
|
+
Parameters
|
|
57
|
+
----------
|
|
58
|
+
Y : array-like, shape (n, p)
|
|
59
|
+
The response variable.
|
|
60
|
+
X : array-like, shape (n, d)
|
|
61
|
+
The covariate matrix.
|
|
62
|
+
A : array-like, shape (n, a)
|
|
63
|
+
The treatment matrix.
|
|
64
|
+
r : int
|
|
65
|
+
The number of unmeasured confounders.
|
|
66
|
+
family : str
|
|
67
|
+
The family of the GLM. Default is 'poisson'.
|
|
68
|
+
disp_glm : array-like, shape (p, ) or None
|
|
69
|
+
The dispersion parameter for the negative binomial distribution.
|
|
70
|
+
offset : array-like, shape (p, ) or None
|
|
71
|
+
The offset parameter.
|
|
72
|
+
kwargs_ls_1 : dict
|
|
73
|
+
Keyword arguments for the line search solver in the first phrase.
|
|
74
|
+
kwargs_ls_2 : dict
|
|
75
|
+
Keyword arguments for the line search solver in the second phrase.
|
|
76
|
+
kwargs_es_1 : dict
|
|
77
|
+
Keyword arguments for the early stopper in the first phrase.
|
|
78
|
+
kwargs_es_2 : dict
|
|
79
|
+
Keyword arguments for the early stopper in the second phrase.
|
|
80
|
+
c1 : float
|
|
81
|
+
The regularization constant in the first phrase. Default is 0.1.
|
|
82
|
+
kwargs : dict
|
|
83
|
+
Additional keyword arguments.
|
|
84
|
+
'''
|
|
85
|
+
|
|
86
|
+
X = np.hstack((X, A))
|
|
87
|
+
a = A.shape[1]
|
|
88
|
+
Y, kwargs_glm, lam1 = _check_input(Y, X, family, disp_glm, disp_family, offset, c1, **kwargs)
|
|
89
|
+
|
|
90
|
+
r = int(r)
|
|
91
|
+
|
|
92
|
+
res_1, res_2 = estimate(Y, X, r, a,
|
|
93
|
+
lam1, kwargs_glm, kwargs_ls_1, kwargs_es_1, kwargs_ls_2, kwargs_es_2, **kwargs)
|
|
94
|
+
|
|
95
|
+
return res_1, res_2
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def estimate(Y, X, r, a, lam1,
|
|
99
|
+
kwargs_glm, kwargs_ls_1, kwargs_es_1, kwargs_ls_2, kwargs_es_2, **kwargs):
|
|
100
|
+
'''
|
|
101
|
+
Two-stage estimation of the GCATE model.
|
|
102
|
+
|
|
103
|
+
Parameters
|
|
104
|
+
----------
|
|
105
|
+
Y : array-like, shape (n, p)
|
|
106
|
+
Response matrix.
|
|
107
|
+
X : array-like, shape (n, d+a)
|
|
108
|
+
Observed covariate matrix.
|
|
109
|
+
r : int
|
|
110
|
+
Number of latent variables.
|
|
111
|
+
a : int
|
|
112
|
+
The number of columns to be regularized. Assume the last 'a' columns of the covariates are the regularized coefficients. If 'a' is None, it is set to be 'd' by default.
|
|
113
|
+
lam1 : float
|
|
114
|
+
Regularization parameter for the first optimization problem.
|
|
115
|
+
kwargs_glm : dict
|
|
116
|
+
Keyword arguments for the GLM.
|
|
117
|
+
kwargs_ls_1 : dict
|
|
118
|
+
Keyword arguments of the line search algorithm for the first optimization problem.
|
|
119
|
+
kwargs_ls_2 : dict
|
|
120
|
+
Keyword arguments of the line search algorithm for the second optimization problem.
|
|
121
|
+
kwargs_es_1 : dict
|
|
122
|
+
Keyword arguments of the early stopping monitor for the first optimization problem.
|
|
123
|
+
kwargs_es_2 : dict
|
|
124
|
+
Keyword arguments of the early stopping monitor for the second optimization problem.
|
|
125
|
+
kwargs : dict
|
|
126
|
+
Additional keyword arguments.
|
|
127
|
+
|
|
128
|
+
Returns
|
|
129
|
+
-------
|
|
130
|
+
res_1 : dict
|
|
131
|
+
The results of the first optimization problem.
|
|
132
|
+
res_2 : dict
|
|
133
|
+
The results of the second optimization problem, including
|
|
134
|
+
'X_U': the matrix (n, d+a+r) for the covariate and updated latent factors.
|
|
135
|
+
'B_Gamma': the matrix (p, d+a+r) for the updated covariate and latent coefficients.
|
|
136
|
+
'''
|
|
137
|
+
|
|
138
|
+
p = Y.shape[1]
|
|
139
|
+
|
|
140
|
+
valid_params = _filter_params(alter_min, kwargs)
|
|
141
|
+
|
|
142
|
+
res_1 = alter_min(
|
|
143
|
+
Y, r, X=X, P1=True,
|
|
144
|
+
kwargs_glm=kwargs_glm, kwargs_ls=kwargs_ls_1, kwargs_es=kwargs_es_1, **valid_params)
|
|
145
|
+
Q, _ = sp.linalg.qr(res_1['B_Gamma'][:,-r:], mode='economic')
|
|
146
|
+
P_Gamma = np.identity(p) - Q @ Q.T
|
|
147
|
+
|
|
148
|
+
if lam1 == 0.:
|
|
149
|
+
res_2 = {'X_U': res_1['X_U'], 'B_Gamma': res_1['B_Gamma']}
|
|
150
|
+
else:
|
|
151
|
+
res_2 = alter_min(
|
|
152
|
+
Y, r, X=X, P2=P_Gamma, A=res_1['X_U'].copy(), B=res_1['B_Gamma'].copy(), lam=lam1, a=a,
|
|
153
|
+
kwargs_glm=res_1['kwargs_glm'], kwargs_ls=kwargs_ls_2, kwargs_es=kwargs_es_2, **valid_params)
|
|
154
|
+
|
|
155
|
+
return res_1, res_2
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def estimate_r(Y, X, A, r_max, c=1.,
|
|
159
|
+
family='nb', disp_glm=None, disp_family='poisson', offset=True,
|
|
160
|
+
kwargs_ls_1={}, kwargs_ls_2={}, kwargs_es_1={}, kwargs_es_2={},
|
|
161
|
+
**kwargs
|
|
162
|
+
):
|
|
163
|
+
'''
|
|
164
|
+
Estimate the number of latent factors for the GCATE model.
|
|
165
|
+
|
|
166
|
+
Parameters
|
|
167
|
+
----------
|
|
168
|
+
Y : array-like, shape (n, p)
|
|
169
|
+
Response matrix.
|
|
170
|
+
X : array-like, shape (n, d)
|
|
171
|
+
Observed covariate matrix.
|
|
172
|
+
A : array-like, shape (n, a)
|
|
173
|
+
Treatment matrix.
|
|
174
|
+
r_max : int
|
|
175
|
+
Number of latent variables.
|
|
176
|
+
c : float
|
|
177
|
+
The constant factor for the complexity term.
|
|
178
|
+
family : str
|
|
179
|
+
The family of the GLM. Default is 'poisson'.
|
|
180
|
+
disp_glm : array-like, shape (1, p) or None
|
|
181
|
+
The dispersion parameter for the negative binomial distribution.
|
|
182
|
+
kwargs_glm : dict
|
|
183
|
+
Keyword arguments for the GLM.
|
|
184
|
+
kwargs_ls_1 : dict
|
|
185
|
+
Keyword arguments of the line search algorithm for the first optimization problem.
|
|
186
|
+
kwargs_ls_2 : dict
|
|
187
|
+
Keyword arguments of the line search algorithm for the second optimization problem.
|
|
188
|
+
kwargs_es_1 : dict
|
|
189
|
+
Keyword arguments of the early stopping monitor for the first optimization problem.
|
|
190
|
+
kwargs_es_2 : dict
|
|
191
|
+
Keyword arguments of the early stopping monitor for the second optimization problem.
|
|
192
|
+
|
|
193
|
+
Returns
|
|
194
|
+
-------
|
|
195
|
+
df_r : DataFrame
|
|
196
|
+
Results of the number of latent factors.
|
|
197
|
+
'''
|
|
198
|
+
X = np.hstack((X, A))
|
|
199
|
+
a, d = A.shape[1], X.shape[1]
|
|
200
|
+
n, p = Y.shape
|
|
201
|
+
|
|
202
|
+
Y, kwargs_glm, _ = _check_input(Y, X, family, disp_glm, disp_family, offset, None, **kwargs)
|
|
203
|
+
|
|
204
|
+
family, nuisance, size_factor = kwargs_glm['family'], kwargs_glm['disp_glm'], kwargs_glm['size_factor']
|
|
205
|
+
nuisance = nuisance.reshape(1,-1)
|
|
206
|
+
size_factor = size_factor.reshape(-1,1)
|
|
207
|
+
|
|
208
|
+
res = []
|
|
209
|
+
if np.isscalar(r_max):
|
|
210
|
+
r_list = np.arange(1, int(r_max)+1)
|
|
211
|
+
else:
|
|
212
|
+
r_list = np.array(r_max, dtype=int)
|
|
213
|
+
|
|
214
|
+
for r in r_list:
|
|
215
|
+
res_1, res_2 = estimate(Y, X, r, a,
|
|
216
|
+
0, kwargs_glm, kwargs_ls_1, kwargs_es_1, kwargs_ls_2, kwargs_es_2, **kwargs)
|
|
217
|
+
A01, A02, A1, A2 = res_1['X_U'], res_1['B_Gamma'], res_2['X_U'], res_2['B_Gamma']
|
|
218
|
+
|
|
219
|
+
logh = log_h(Y, family, nuisance)
|
|
220
|
+
|
|
221
|
+
if r==1:
|
|
222
|
+
ll = 2 * (
|
|
223
|
+
nll(Y, A01, A02, family, nuisance, size_factor) / p
|
|
224
|
+
- np.sum(logh) / (n*p) )
|
|
225
|
+
nu = (d+a) * np.maximum(n,p) * np.log(n * p / np.maximum(n,p)) / (n*p)
|
|
226
|
+
jic = ll + c * nu
|
|
227
|
+
res.append([0, ll, nu, jic])
|
|
228
|
+
|
|
229
|
+
ll = 2 * (
|
|
230
|
+
nll(Y, A1, A2, family, nuisance, size_factor) / p
|
|
231
|
+
- np.sum(logh) / (n*p) )
|
|
232
|
+
nu = (d + a + r) * np.maximum(n,p) * np.log(n * p / np.maximum(n,p)) / (n*p)
|
|
233
|
+
jic = ll + c * nu
|
|
234
|
+
res.append([r, ll, nu, jic])
|
|
235
|
+
|
|
236
|
+
df_r = pd.DataFrame(res, columns=['r', 'deviance', 'nu', 'JIC'])
|
|
237
|
+
return df_r
|