Skip to content

Commit 4d66859

Browse files
committed
Add mask and usevar augment
1 parent 4cc5d1c commit 4d66859

1 file changed

Lines changed: 41 additions & 14 deletions

File tree

causarray/DR_learner.py

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
def compute_causal_estimand(
1212
estimand,
1313
Y, W, A, W_A=None, family='nb', offset=False,
14-
Y_hat=None, pi_hat=None, eps_var=1e-3,
14+
Y_hat=None, pi_hat=None, mask=None,
1515
fdx=False, fdx_B=1000, fdx_alpha=0.05, fdx_c=0.1,
1616
verbose=False, random_state=0, **kwargs):
1717
'''
@@ -39,6 +39,10 @@ def compute_causal_estimand(
3939
Predicted outcomes under treatment of shape (n, p, a, 2).
4040
pi_hat : array, optional
4141
Predicted propensity scores of shape (n, a).
42+
mask : array, optional
43+
Boolean mask of shape (n, a) for the treatment, indicating which samples are used for
44+
the estimation of the estimand. This does not affect the estimation of pseudo-outcomes
45+
and propensity scores.
4246
4347
fdx : bool
4448
Whether to use FDX control, P(FDP > c) < alpha.
@@ -87,6 +91,12 @@ def compute_causal_estimand(
8791
W_A = W
8892
elif isinstance(W_A, pd.DataFrame):
8993
W_A = W_A.values
94+
95+
if mask is not None:
96+
mask = np.array(mask).astype(bool)
97+
if len(mask.shape) == 1: mask = mask.reshape(-1,1)
98+
if mask.shape != A.shape:
99+
raise ValueError('Mask must have the same shape as the treatment matrix')
90100

91101

92102
if verbose:
@@ -117,18 +127,18 @@ def compute_causal_estimand(
117127
# normalize the influence function values
118128
etas /= size_factors[:,None,None,None]
119129

120-
121-
i_ctrl = (np.sum(A, axis=1) == 0.)
122-
123130
res = []
124131
iters = range(A.shape[1]) if A.shape[1]==1 else tqdm(range(A.shape[1]))
125132
for j in iters:
126-
i_case = (A[:,j] == 1.)
127-
i_cells = i_ctrl | i_case
128-
n_cells = np.sum(i_cells)
129-
eta_est, tau_est, var_est = estimand(etas[i_cells,:,j], **kwargs)
133+
if mask is not None:
134+
i_cells = mask[:, j]
135+
else:
136+
i_ctrl = (np.sum(A, axis=1) == 0.)
137+
i_case = (A[:,j] == 1.)
138+
i_cells = i_ctrl | i_case
139+
eta_est, tau_est, var_est = estimand(etas[i_cells,:,j], A[i_cells,j], **kwargs)
130140

131-
std_est = np.sqrt((var_est + eps_var)/ n_cells)
141+
std_est = np.sqrt(var_est)
132142
tvalues_init = tau_est / std_est
133143

134144
# Multiple testing procedure
@@ -159,7 +169,7 @@ def compute_causal_estimand(
159169

160170
def LFC(
161171
Y, W, A, W_A=None, family='nb', offset=False,
162-
Y_hat=None, pi_hat=None, cross_est=False,
172+
Y_hat=None, pi_hat=None, cross_est=False, mask=None, usevar='pooled',
163173
thres_min=1e-4, thres_diff=1e-6, eps_var=1e-3,
164174
fdx=False, fdx_alpha=0.05, fdx_c=0.1,
165175
verbose=False, **kwargs):
@@ -187,12 +197,18 @@ def LFC(
187197
Predicted propensity scores of shape (n, a).
188198
cross_est : bool
189199
Whether to use cross-estimation.
200+
mask : array, optional
201+
Boolean mask of shape (n, a) for the treatment, indicating which samples are used for
202+
the estimation of the estimand. This does not affect the estimation of pseudo-outcomes
203+
and propensity scores.
190204
191205
thres_min : float
192206
The minimum threshold for the treatment effect.
193207
thres_diff : float
194208
The minimum threshold for the difference in treatment effect.
195-
209+
eps_var : float
210+
The minimum threshold for the variance of treatment.
211+
196212
fdx : bool
197213
Whether to use FDX control, P(FDP > c) < alpha.
198214
fdx_alpha : float
@@ -211,15 +227,26 @@ def LFC(
211227
Dataframe of test results.
212228
'''
213229

214-
def estimand(etas, **kwargs):
230+
def estimand(etas, A, **kwargs):
215231
eta_0, eta_1 = etas[..., 0], etas[..., 1]
216232
tau_0, tau_1 = np.mean(eta_0, axis=0), np.mean(eta_1, axis=0)
217233

218234
tau_1 = np.clip(tau_1, thres_diff, None)
219235
tau_0 = np.clip(tau_0, thres_diff, None)
220236
tau_est = np.log(tau_1/tau_0)
221237
eta_est = eta_1 / tau_1[None,:] - eta_0 / tau_0[None,:]
222-
var_est = np.var(eta_est, axis=0, ddof=1)
238+
239+
if usevar == 'pooled':
240+
var_est = (np.var(eta_est, axis=0, ddof=1) + eps_var) / eta_est.shape[0]
241+
elif usevar == 'unequal':
242+
# Estimate the variance using Welch's t-test
243+
var_0 = np.var(eta_est[A==0], axis=0, ddof=1)
244+
var_1 = np.var(eta_est[A==1], axis=0, ddof=1)
245+
n_0 = np.sum(A==0)
246+
n_1 = np.sum(A==1)
247+
var_est = (var_0 + eps_var) / n_0 + (var_1 + eps_var) / n_1
248+
else:
249+
raise ValueError('usevar must be either "pooled" or "unequal"')
223250

224251
# filter out low-expressed genes
225252
idx = (np.maximum(tau_0,tau_1)<thres_min) & ((tau_1-tau_0)<thres_diff)
@@ -229,7 +256,7 @@ def estimand(etas, **kwargs):
229256

230257
return compute_causal_estimand(
231258
estimand, Y, W, A, W_A, family, offset,
232-
Y_hat=Y_hat, pi_hat=pi_hat,
259+
Y_hat=Y_hat, pi_hat=pi_hat, mask=mask,
233260
fdx=fdx, fdx_alpha=fdx_alpha, fdx_c=fdx_c, verbose=verbose, **kwargs)
234261

235262

0 commit comments

Comments
 (0)