csdid 0.2.7__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.
csdid/__init__.py ADDED
File without changes
csdid/_version.py ADDED
@@ -0,0 +1 @@
1
+ __version = '0.2.7'
File without changes
@@ -0,0 +1,113 @@
1
+ #' @title Aggregate Group-Time Average Treatment Effects
2
+ #'
3
+ #' @description A function to take group-time average treatment effects
4
+ #' and aggregate them into a smaller number of parameters. There are
5
+ #' several possible aggregations including "simple", "dynamic", "group",
6
+ #' and "calendar."
7
+ #'
8
+ #' @param MP an MP object (i.e., the results of the [att_gt()] method)
9
+ #' @param type Which type of aggregated treatment effect parameter to compute.
10
+ #' One option is "simple" (this just computes a weighted average of all
11
+ #' group-time average treatment effects with weights proportional to group
12
+ #' size). Other options are "dynamic" (this computes average effects across
13
+ #' different lengths of exposure to the treatment and is similar to an
14
+ #' "event study"; here the overall effect averages the effect of the
15
+ #' treatment across all positive lengths of exposure); "group" (this
16
+ #' is the default option and
17
+ #' computes average treatment effects across different groups; here
18
+ #' the overall effect averages the effect across different groups); and
19
+ #' "calendar" (this computes average treatment effects across different
20
+ #' time periods; here the overall effect averages the effect across each
21
+ #' time period).
22
+ #' @param balance_e If set (and if one computes dynamic effects), it balances
23
+ #' the sample with respect to event time. For example, if `balance.e=2`,
24
+ #' `aggte` will drop groups that are not exposed to treatment for
25
+ #' at least three periods. (the initial period when `e=0` as well as the
26
+ #' next two periods when `e=1` and the `e=2`). This ensures that
27
+ #' the composition of groups does not change when event time changes.
28
+ #' @param min_e For event studies, this is the smallest event time to compute
29
+ #' dynamic effects for. By default, `min_e = -Inf` so that effects at
30
+ #' all lengths of exposure are computed.
31
+ #' @param max_e For event studies, this is the largest event time to compute
32
+ #' dynamic effects for. By default, `max_e = Inf` so that effects at
33
+ #' all lengths of exposure are computed.
34
+ #' @param na.rm Logical value if we are to remove missing Values from analyses. Defaults is FALSE.
35
+ #' @param bstrap Boolean for whether or not to compute standard errors using
36
+ #' the multiplier bootstrap. If standard errors are clustered, then one
37
+ #' must set `bstrap=TRUE`. Default is value set in the MP object. If bstrap is `FALSE`, then analytical
38
+ #' standard errors are reported.
39
+ #' @param biters The number of bootstrap iterations to use. The default is the value set in the MP object,
40
+ #' and this is only applicable if `bstrap=TRUE`.
41
+ #'
42
+ #' @param cband Boolean for whether or not to compute a uniform confidence
43
+ #' band that covers all of the group-time average treatment effects
44
+ #' with fixed probability `1-alp`. In order to compute uniform confidence
45
+ #' bands, `bstrap` must also be set to `TRUE`. The default is
46
+ #' the value set in the MP object
47
+ #' @param alp the significance level, default is value set in the MP object.
48
+ #' @param clustervars A vector of variables to cluster on. At most, there
49
+ #' can be two variables (otherwise will throw an error) and one of these
50
+ #' must be the same as idname which allows for clustering at the individual
51
+ #' level. Default is the variables set in the MP object
52
+
53
+ #'
54
+ #' @return An [`AGGTEobj`] object that holds the results from the
55
+ #' aggregation
56
+ #'
57
+ #' @section Examples:
58
+ #'
59
+ #
60
+ #' Initial ATT(g,t) estimates from [att_gt()]
61
+ #'
62
+ #'
63
+ #' You can aggregate the ATT(g,t) in many ways.
64
+ #'
65
+ #' **Overall ATT:**
66
+ #' aggte(out, type = "simple")
67
+ #' ```
68
+ #'
69
+ #' **Dynamic ATT (Event-Study):**
70
+ #' aggte(out, type = "dynamic")
71
+ #' ```
72
+ #'
73
+ #' **ATT for each group:**
74
+ #' aggte(out, type = "group")
75
+ #' ```
76
+ #'
77
+ #' **ATT for each calendar year:**
78
+ #' aggte(out, type = "calendar")
79
+ #' ```
80
+ #'
81
+ #'
82
+ #'
83
+ from csdid.aggte_fnc.compute_aggte import compute_aggte
84
+ import inspect
85
+
86
+ def aggte(MP,
87
+ typec="group",
88
+ balance_e=None,
89
+ min_e=float('-inf'),
90
+ max_e=float('inf'),
91
+ na_rm=False,
92
+ bstrap=None,
93
+ biters=None,
94
+ cband=None,
95
+ alp=None,
96
+ clustervars=None):
97
+ call = inspect.currentframe().f_back.f_locals.copy()
98
+
99
+ return compute_aggte(MP=MP,
100
+ typec=typec,
101
+ balance_e=balance_e,
102
+ min_e=min_e,
103
+ max_e=max_e,
104
+ na_rm=na_rm,
105
+ bstrap=bstrap,
106
+ biters=biters,
107
+ cband=cband,
108
+ alp=alp,
109
+ clustervars=clustervars,
110
+ call=call)
111
+
112
+
113
+
@@ -0,0 +1,495 @@
1
+ import pickle
2
+ import pandas as pd
3
+ import numpy as np
4
+ from scipy.stats import norm
5
+
6
+
7
+ from csdid.aggte_fnc.utils import get_agg_inf_func, get_se, wif, AGGTEobj
8
+ from csdid.utils.mboot import mboot
9
+ import warnings
10
+
11
+ def compute_aggte(MP,
12
+ typec = "group",
13
+ balance_e = None,
14
+ min_e = float('-inf'),
15
+ max_e = float('inf'),
16
+ na_rm = False,
17
+ bstrap = None,
18
+ biters = None,
19
+ cband = None,
20
+ alp = None,
21
+ clustervars = None,
22
+ call = None):
23
+
24
+ # =============================================================================
25
+ # unpack MP object
26
+ # =============================================================================
27
+ group = np.array( MP['group'] )
28
+ t = np.array( MP['t'] )
29
+ att = np.array( MP['att'] )
30
+ dp = MP['DIDparams']
31
+ tlist = np.array( dp['tlist'] )
32
+ glist = np.array( dp['glist'] )
33
+ data = dp['data']
34
+ inffunc = MP['inffunc']['inffunc']
35
+ n = MP['n']
36
+ gname = dp['gname']
37
+ tname = dp['tname']
38
+ idname = dp['idname']
39
+ # typec = MP['type']
40
+ panel = dp['panel']
41
+
42
+
43
+
44
+ if clustervars is None:
45
+ clustervars = dp['clustervars']
46
+ if bstrap is None:
47
+ bstrap = dp['bstrap']
48
+ if biters is None:
49
+ biters = dp['biters']
50
+ if alp is None:
51
+ alp = dp['alp']
52
+ if cband is None:
53
+ cband = dp['cband']
54
+
55
+
56
+
57
+ # Overwrite MP objects (to compute bootstrap)
58
+ MP['DIDparams']['clustervars'] = clustervars
59
+ MP['DIDparams']['bstrap'] = bstrap
60
+ MP['DIDparams']['biters'] = biters
61
+ MP['DIDparams']['alp'] = alp
62
+ MP['DIDparams']['cband'] = cband
63
+
64
+ # =============================================================================
65
+ # Treat data
66
+ # =============================================================================
67
+
68
+ if typec not in ["simple", "dynamic", "group", "calendar"]:
69
+ raise ValueError("`typec` must be one of ['simple', 'dynamic', 'group', 'calendar']")
70
+ # Removing missing values
71
+ if na_rm:
72
+ notna = ~np.isnan(att)
73
+ group = group[notna]
74
+ t = t[notna]
75
+ att = att[notna]
76
+ inffunc = inffunc[:, notna]
77
+ glist = np.sort(np.unique(group))
78
+
79
+ if typec == "group":
80
+ gnotna = []
81
+ for g in glist:
82
+ indices = np.where((group == g) & (g <= t))
83
+ is_not_na = np.any(~np.isnan(att[indices]))
84
+ gnotna.append(is_not_na)
85
+
86
+ gnotna = np.array(gnotna)
87
+ glist = glist[gnotna]
88
+ not_all_na = np.isin(group, glist)
89
+ group = group[not_all_na]
90
+ t = t[not_all_na]
91
+ att = att[not_all_na]
92
+ inffunc = inffunc[:, not_all_na]
93
+ glist = np.sort(np.unique(group))
94
+
95
+
96
+ if (not na_rm) and np.any(np.isnan(att)):
97
+ raise ValueError("Missing values at att_gt found. If you want to remove these, set `na_rm = True`.")
98
+
99
+ if panel:
100
+ dta = data[data[tname] == tlist[0]]
101
+ else:
102
+ dta = data.groupby(idname).mean().reset_index()
103
+ dta = dta.iloc[:, 1:]
104
+
105
+ # =============================================================================
106
+ # Treat data 2
107
+ # =============================================================================
108
+
109
+
110
+ originalt = t
111
+ originalgroup = group
112
+ originalglist = glist
113
+ originaltlist = tlist
114
+ # In case g's are not part of tlist
115
+ originalgtlist = np.sort(np.unique(np.concatenate((originaltlist, originalglist))))
116
+ uniquet = list(range(1, len(originalgtlist) + 1))
117
+
118
+ # Function to switch from "new" t values to original t values
119
+ def t2orig(t):
120
+ return originalgtlist[uniquet.index(t) if t in uniquet else -1]
121
+
122
+ # Function to switch between "original" t values and new t values
123
+ def orig2t(orig):
124
+ new_t = [uniquet[i] for i in range(len(originalgtlist)) if originalgtlist[i] == orig]
125
+ out = new_t[0] if new_t else None
126
+ return out
127
+
128
+ t = [orig2t(orig) for orig in originalt]
129
+ group = [orig2t(orig) for orig in originalgroup]
130
+ glist = [orig2t(orig) for orig in originalglist]
131
+ tlist = np.asarray(list(set(t)))
132
+ maxT = max(t)
133
+
134
+ # Set the weights
135
+ # return data.columns
136
+ weights_ind = dta['w1'].to_numpy()
137
+
138
+ # We can work in overall probabilities because conditioning will cancel out
139
+ # since it shows up in numerator and denominator
140
+ pg = np.array([np.mean(weights_ind * (dta[gname].to_numpy() == g)) for g in originalglist])
141
+
142
+ # Length of this is equal to the number of groups
143
+ pgg = pg
144
+
145
+ # Same but length is equal to the number of ATT(g,t)
146
+ pg = [pg[glist.index(g)] for g in group]
147
+
148
+ # Which group time average treatment effects are post-treatment
149
+ keepers = [i for i in range(len(group)) if group[i] <= t[i] <= (group[i] + max_e)] ### added second condition to allow for limit on longest period included in att
150
+
151
+ # n x 1 vector of group variable
152
+ G = [orig2t(g) for g in dta[gname].to_numpy()]
153
+
154
+ # =============================================================================
155
+ # simple
156
+ # =============================================================================
157
+
158
+
159
+ if typec == "simple":
160
+ # Simple ATT
161
+ # Averages all post-treatment ATT(g,t) with weights given by group size
162
+ pg = np.array(pg)
163
+ simple_att = np.sum(att[keepers] * pg[keepers]) / np.sum(pg[keepers])
164
+ if np.isnan(simple_att):
165
+ simple_att = None
166
+
167
+ # Get the part of the influence function coming from estimated weights
168
+ simple_wif = wif(keepers, pg, weights_ind, G, group)
169
+
170
+ # Get the overall influence function
171
+ simple_if = get_agg_inf_func(att = att ,
172
+ inffunc = inffunc ,
173
+ whichones = keepers ,
174
+ weights_agg = np.array(pg)[keepers]/np.sum(np.array(pg)[keepers]) ,
175
+ wif = simple_wif )[:, None]
176
+
177
+ # Get standard errors from the overall influence function
178
+ simple_se = get_se(simple_if, dp)
179
+
180
+ if simple_se is not None:
181
+ if simple_se <= np.sqrt(np.finfo(float).eps) * 10:
182
+ simple_se = None
183
+
184
+ AGGTEobj_print = AGGTEobj(overall_att=simple_att,
185
+ overall_se=simple_se,
186
+ typec=typec,
187
+ inf_function={'simple_att': simple_if},
188
+ call=call, DIDparams=dp)
189
+
190
+ return AGGTEobj_print
191
+
192
+
193
+ # =============================================================================
194
+ # GRoup
195
+ # =============================================================================
196
+
197
+ if typec == "group":
198
+ group = np.array(group)
199
+ t = np.array(t)
200
+ pg = np.array(pg)
201
+ selective_att_g = [np.mean(att[( group== g) & (t >= g) & (t <= (group + max_e))]) for g in glist]
202
+ selective_att_g = np.asarray(selective_att_g)
203
+ selective_att_g[np.isnan(selective_att_g)] = None
204
+
205
+ selective_se_inner = [None] * len(glist)
206
+ for i, g in enumerate(glist):
207
+ whichg = np.where(np.logical_and.reduce((group == g, g <= t, t <= (group + max_e))))[0]
208
+ weightsg = pg[whichg] / np.sum(pg[whichg])
209
+ inf_func_g = get_agg_inf_func(att = att ,
210
+ inffunc = inffunc ,
211
+ whichones = whichg ,
212
+ weights_agg = weightsg ,
213
+ wif = None)[:, None]
214
+ se_g = get_se(inf_func_g, dp)
215
+ selective_se_inner[i] = {'inf_func': inf_func_g, 'se': se_g}
216
+
217
+ # recover standard errors separately by group
218
+ selective_se_g = np.asarray([item['se'] for item in selective_se_inner]).T
219
+
220
+ selective_se_g[selective_se_g <= np.sqrt(np.finfo(float).eps) * 10] = None
221
+
222
+ selective_inf_func_g = np.column_stack([elem["inf_func"] for elem in selective_se_inner])
223
+
224
+ # use multiplier bootstrap (across groups) to get critical value
225
+ # for constructing uniform confidence bands
226
+ selective_crit_val = norm.ppf(1 - alp/2)
227
+
228
+ if dp['cband']:
229
+ if not dp['bstrap']:
230
+ print("Used bootstrap procedure to compute simultaneous confidence band")
231
+
232
+ selective_crit_val = mboot(selective_inf_func_g, dp)['crit_val']
233
+
234
+ if np.isnan(selective_crit_val) or np.isinf(selective_crit_val):
235
+ print("Simultaneous critical value is NA. This probably happened because we cannot compute t-statistic (std errors are NA). We then report pointwise conf. intervals.")
236
+ selective_crit_val = norm.ppf(1 - alp/2)
237
+ dp['cband'] = False
238
+
239
+ if selective_crit_val < norm.ppf(1 - alp/2):
240
+ print("Simultaneous conf. band is somehow smaller than pointwise one using normal approximation. Since this is unusual, we are reporting pointwise confidence intervals")
241
+ selective_crit_val = norm.ppf(1 - alp/2)
242
+ dp['cband'] = False
243
+
244
+ if selective_crit_val >= 7:
245
+ print("Simultaneous critical value is arguably 'too large' to be reliable. This usually happens when the number of observations per group is small and/or there is not much variation in outcomes.")
246
+
247
+ # get overall att under selective treatment timing
248
+ # (here use pgg instead of pg because we can just look at each group)
249
+ selective_att = np.sum(selective_att_g * pgg) / np.sum(pgg)
250
+
251
+ # account for having to estimate pgg in the influence function
252
+ selective_wif = wif(keepers = np.arange(1, len(glist)+1)-1,
253
+ pg = pgg,
254
+ weights_ind = weights_ind,
255
+ G = G,
256
+ group = group)
257
+
258
+ # get overall influence function
259
+ selective_inf_func = get_agg_inf_func(att = selective_att_g,
260
+ inffunc = selective_inf_func_g,
261
+ whichones = np.arange(1, len(glist)+1)-1,
262
+ weights_agg = pgg/np.sum(pgg),
263
+ wif = selective_wif)[:, None]
264
+
265
+ # get overall standard error
266
+ selective_se = get_se(selective_inf_func, dp)
267
+ if not np.isnan(selective_se):
268
+ if selective_se <= np.sqrt(np.finfo(float).eps) * 10:
269
+ selective_se = None
270
+
271
+ AGGTEobj_print = AGGTEobj(overall_att = selective_att,
272
+ overall_se = selective_se,
273
+ typec = typec,
274
+ egt = originalglist,
275
+ att_egt = selective_att_g,
276
+ se_egt = selective_se_g,
277
+ crit_val_egt = selective_crit_val,
278
+ inf_function = {'selective_inf_func_g': selective_inf_func_g,
279
+ 'selective_inf_func': selective_inf_func},
280
+ call = call,
281
+ DIDparams = dp)
282
+
283
+ return AGGTEobj_print
284
+
285
+
286
+ # =============================================================================
287
+ # Dynamic
288
+ # =============================================================================
289
+
290
+ if typec == "dynamic":
291
+ # event times
292
+ # this looks at all available event times
293
+ # note: event times can be negative here.
294
+ # note: event time = 0 corresponds to "on impact"
295
+ eseq = np.unique(np.array(originalt) - np.array(originalgroup) ) # Subtract corresponding elements and convert to NumPy array
296
+ eseq = np.sort(eseq) # Sort the unique values in ascending order
297
+
298
+ # if the user specifies balance_e, then we are going to
299
+ # drop some event times and some groups; if not, we just
300
+ # keep everything (that is what this variable is for)
301
+ originalt = np.array(originalt)
302
+ originalgroup = np.array(originalgroup)
303
+ pg = np.array(pg)
304
+ include_balanced_gt = np.repeat(True, len(originalgroup))
305
+
306
+ if balance_e is not None:
307
+ include_balanced_gt = (t2orig(maxT) - originalgroup >= balance_e)
308
+ eseq = np.unique(originalt[include_balanced_gt] - originalgroup[include_balanced_gt])
309
+ eseq = np.sort(eseq)
310
+ eseq = eseq[(eseq <= balance_e) & (eseq >= balance_e - t2orig(maxT) + t2orig(1))]
311
+ eseq = eseq[(eseq >= min_e) & (eseq <= max_e)]
312
+
313
+ dynamic_att_e = []
314
+ for e in eseq:
315
+ whiche = np.where((originalt - originalgroup == e) & include_balanced_gt)
316
+ atte = att[whiche]
317
+ pge = pg[whiche] / np.sum(pg[whiche])
318
+ dynamic_att_e.append(np.sum(atte * pge))
319
+
320
+ dynamic_se_inner = []
321
+ for e in eseq:
322
+ whiche = np.where((originalt - originalgroup == e) & (include_balanced_gt) )[0]
323
+ pge = pg[whiche] / sum(pg[whiche])
324
+ wif_e = wif(whiche,
325
+ pg,
326
+ weights_ind,
327
+ G,
328
+ group)
329
+ inf_func_e = get_agg_inf_func(att = att,
330
+ inffunc = inffunc,
331
+ whichones = whiche,
332
+ weights_agg = pge,
333
+ wif = wif_e)[:, None]
334
+ se_e = get_se(inf_func_e, dp)
335
+ dynamic_se_inner.append({'inf_func': inf_func_e, 'se': se_e})
336
+
337
+ dynamic_se_e = np.array([item['se'] for item in dynamic_se_inner]).T
338
+
339
+ dynamic_se_e[dynamic_se_e <= np.sqrt(np.finfo(float).eps) * 10] = np.nan
340
+
341
+ dynamic_inf_func_e = np.column_stack([item['inf_func'] for item in dynamic_se_inner])
342
+
343
+ dynamic_crit_val = norm.ppf(1 - alp/2)
344
+ if dp['cband']:
345
+ if not dp['bstrap']:
346
+ print('Used bootstrap procedure to compute simultaneous confidence band')
347
+ dynamic_crit_val = mboot(dynamic_inf_func_e, dp)['crit_val']
348
+
349
+ if np.isnan(dynamic_crit_val) or np.isinf(dynamic_crit_val):
350
+ print('Simultaneous critical value is NA. This probably happened because we cannot compute t-statistic (std errors are NA). We then report pointwise conf. intervals.')
351
+ dynamic_crit_val = norm.ppf(1 - alp/2)
352
+ dp['cband'] = False
353
+
354
+ if dynamic_crit_val < norm.ppf(1 - alp/2):
355
+ print('Simultaneous conf. band is somehow smaller than pointwise one using normal approximation. Since this is unusual, we are reporting pointwise confidence intervals')
356
+ dynamic_crit_val = norm.ppf(1 - alp/2)
357
+ dp['cband'] = False
358
+
359
+ if dynamic_crit_val >= 7:
360
+ print("Simultaneous critical value is arguably 'too large' to be reliable. This usually happens when the number of observations per group is small and/or there is not much variation in outcomes.")
361
+
362
+ epos = eseq >= 0
363
+ dynamic_att = np.mean(np.array(dynamic_att_e)[epos])
364
+ dynamic_inf_func = get_agg_inf_func(att = np.array(dynamic_att_e)[epos],
365
+ inffunc = np.array(dynamic_inf_func_e[:, epos]),
366
+ whichones = np.arange(1, np.sum(epos)+1)-1,
367
+ weights_agg = np.repeat(1 / np.sum(epos), np.sum(epos)),
368
+ wif=None)[:, None]
369
+
370
+ dynamic_se = get_se(dynamic_inf_func, dp)
371
+ if not np.isnan(dynamic_se):
372
+ if dynamic_se <= np.sqrt(np.finfo(float).eps) * 10:
373
+ dynamic_se = np.nan
374
+
375
+ AGGTEobj_print = AGGTEobj(overall_att=dynamic_att,
376
+ overall_se=dynamic_se,
377
+ typec=typec,
378
+ egt=eseq,
379
+ att_egt=dynamic_att_e,
380
+ se_egt=dynamic_se_e,
381
+ crit_val_egt=dynamic_crit_val,
382
+ inf_function={'dynamic_inf_func_e': dynamic_inf_func_e,
383
+ 'dynamic_inf_func': dynamic_inf_func},
384
+ call=call,
385
+ min_e=min_e,
386
+ max_e=max_e,
387
+ balance_e=balance_e,
388
+ DIDparams=dp)
389
+
390
+ return AGGTEobj_print
391
+
392
+
393
+
394
+ # =============================================================================
395
+ # Calendar
396
+ # =============================================================================
397
+
398
+ # np.array(group)
399
+ if typec == "calendar":
400
+ minG = min(group)
401
+ calendar_tlist = tlist[tlist >= minG]
402
+ pg = np.array(pg)
403
+ calendar_att_t = []
404
+ group = np.array(group)
405
+ t = np.array(t)
406
+ for t1 in calendar_tlist:
407
+ whicht = np.where((t == t1) & (group <= t))[0]
408
+ attt = att[whicht]
409
+ pgt = pg[whicht] / np.sum(pg[whicht])
410
+ calendar_att_t.append(np.sum(pgt * attt))
411
+
412
+ # get standard errors and influence functions
413
+ # for each time specific att
414
+ calendar_se_inner = []
415
+ for t1 in calendar_tlist:
416
+ which_t = np.where((t == t1) & (group <= t))[0]
417
+ pgt = pg[which_t] / np.sum(pg[which_t])
418
+ wif_t = wif(keepers=which_t,
419
+ pg=pg,
420
+ weights_ind=weights_ind,
421
+ G=G,
422
+ group=group)
423
+ inf_func_t = get_agg_inf_func(att=att,
424
+ inffunc=inffunc,
425
+ whichones=which_t,
426
+ weights_agg=pgt,
427
+ wif=wif_t)[:, None]
428
+ se_t = get_se(inf_func_t, dp)
429
+ calendar_se_inner.append({"inf_func": inf_func_t, "se": se_t})
430
+
431
+
432
+
433
+ # recover standard errors separately by time
434
+ calendar_se_t = np.array([se["se"] for se in calendar_se_inner]).T
435
+ calendar_se_t[calendar_se_t <= np.sqrt(np.finfo(float).eps) * 10] = np.nan
436
+
437
+ # recover influence function separately by time
438
+ calendar_inf_func_t = np.column_stack([se["inf_func"] for se in calendar_se_inner])
439
+
440
+ # use multiplier boostrap (across groups) to get critical value
441
+ # for constructing uniform confidence bands
442
+ calendar_crit_val = norm.ppf(1 - alp/2)
443
+
444
+ if dp['cband']:
445
+ if not dp['bstrap']:
446
+ warnings.warn('Used bootstrap procedure to compute simultaneous confidence band')
447
+
448
+ # mboot function is not provided, please define it separately
449
+ calendar_crit_val = mboot(calendar_inf_func_t, dp)['crit_val']
450
+
451
+ if np.isnan(calendar_crit_val) or np.isinf(calendar_crit_val):
452
+ warnings.warn('Simultaneous critical value is NA. This probably happened because we cannot compute t-statistic (std errors are NA). We then report pointwise conf. intervals.')
453
+ calendar_crit_val = norm.ppf(1 - alp/2)
454
+ dp['cband'] = False
455
+
456
+ if calendar_crit_val < norm.ppf(1 - alp/2):
457
+ warnings.warn('Simultaneous conf. band is somehow smaller than pointwise one using normal approximation. Since this is unusual, we are reporting pointwise confidence intervals.')
458
+ calendar_crit_val = norm.ppf(1 - alp/2)
459
+ dp['cband'] = False
460
+
461
+ if calendar_crit_val >= 7:
462
+ warnings.warn("Simultaneous critical value is arguably 'too large' to be reliable. This usually happens when the number of observations per group is small and/or there is not much variation in outcomes.")
463
+
464
+
465
+ # get overall att under calendar time effects
466
+ # this is just average over all time periods
467
+ calendar_att = np.mean(calendar_att_t)
468
+
469
+ # get overall influence function
470
+ calendar_inf_func = get_agg_inf_func(att=calendar_att_t,
471
+ inffunc=calendar_inf_func_t,
472
+ whichones=range(len(calendar_tlist)),
473
+ weights_agg=np.repeat(1/len(calendar_tlist), len(calendar_tlist)),
474
+ wif=None)[:, None]
475
+ calendar_inf_func = np.array(calendar_inf_func)
476
+
477
+ # get overall standard error
478
+ calendar_se = get_se(calendar_inf_func, dp)
479
+ if not np.isnan(calendar_se):
480
+ if calendar_se <= np.sqrt(np.finfo(float).eps) * 10:
481
+ calendar_se = np.nan
482
+
483
+ AGGTEobj_print = AGGTEobj(overall_att=calendar_att,
484
+ overall_se=calendar_se,
485
+ typec=typec,
486
+ egt=list(map(t2orig, calendar_tlist)),
487
+ att_egt=calendar_att_t,
488
+ se_egt=calendar_se_t,
489
+ crit_val_egt=calendar_crit_val,
490
+ inf_function={"calendar_inf_func_t": calendar_inf_func_t,
491
+ "calendar_inf_func": calendar_inf_func},
492
+ call=call,
493
+ DIDparams=dp)
494
+
495
+ return AGGTEobj_print