1111def 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
160170def 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