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.
@@ -0,0 +1,196 @@
1
+ from csdid.utils.bmisc import TorF
2
+ from csdid.utils.mboot import mboot
3
+
4
+ import numpy as np
5
+ import scipy.stats as stats
6
+ import pandas as pd
7
+
8
+ def wif(keepers, pg, weights_ind, G, group):
9
+ # note: weights are all of the form P(G=g|cond)/sum_cond(P(G=g|cond))
10
+ # this is equal to P(G=g)/sum_cond(P(G=g)) which simplifies things here
11
+ pg = np.array(pg)
12
+ group = np.array(group)
13
+
14
+ # effect of estimating weights in the numerator
15
+ if1 = np.empty((len(weights_ind), len(keepers)))
16
+ for i, k in enumerate(keepers):
17
+ numerator = (weights_ind * 1 * TorF(G == group[k])) - pg[k]
18
+ # denominator = sum(np.array(pg)[keepers]) )[:, None]
19
+ denominator = np.sum(pg[keepers])
20
+
21
+ result = numerator[:, None] / denominator
22
+ if1[:, i] = result.squeeze()
23
+
24
+ # effect of estimating weights in the denominator
25
+ if2 = np.empty((len(weights_ind), len(keepers)))
26
+ for i, k in enumerate(keepers):
27
+ numerator = ( weights_ind * 1 * TorF(G == group[k]) ) - pg[k]
28
+ # result = numerator.to_numpy()[:, None] @ multipler[:, None].T
29
+ if2[:, i] = numerator.squeeze()
30
+ if2 = np.sum(if2, axis=1)
31
+ multiplier = ( pg[keepers] / sum( pg[keepers] ) ** 2 )
32
+ if2 = np.outer( if2 , multiplier)
33
+
34
+ # if1 = [((weights_ind * 1*TorF(G==group[k])) - pg[k]) / sum(pg[keepers]) for k in keepers]
35
+ # if2 = np.dot(np.array([weights_ind*1*TorF(G==group[k]) - pg[k] for k in keepers]).T, pg[keepers]/(sum(pg[keepers])**2))
36
+ wif_factor = if1 - if2
37
+ return wif_factor
38
+
39
+ def get_agg_inf_func(att, inffunc, whichones, weights_agg, wif=None):
40
+ # enforce weights are in matrix form
41
+ weights_agg = np.asarray(weights_agg)
42
+
43
+ # multiplies influence function times weights and sums to get vector of weighted IF (of length n)
44
+ thisinffunc = np.dot(inffunc[:, whichones], weights_agg)
45
+
46
+ # Incorporate influence function of the weights
47
+ if wif is not None:
48
+ thisinffunc = thisinffunc + np.dot(wif, np.array(att[whichones]))
49
+
50
+ # return influence function
51
+ return thisinffunc
52
+
53
+
54
+ def get_se(thisinffunc, DIDparams=None):
55
+ alpha = 0.05
56
+ bstrap = False
57
+ if DIDparams is not None:
58
+ bstrap = DIDparams['bstrap']
59
+ alpha = DIDparams['alp']
60
+ cband = DIDparams['cband']
61
+ n = len(thisinffunc)
62
+
63
+ if bstrap:
64
+ bout = mboot(thisinffunc, DIDparams)
65
+ return bout['se']
66
+ else:
67
+ return np.sqrt(np.mean((thisinffunc)**2) / n)
68
+
69
+ def AGGTEobj(overall_att=None,
70
+ overall_se=None,
71
+ typec="simple",
72
+ egt=None,
73
+ att_egt=None,
74
+ se_egt=None,
75
+ crit_val_egt=None,
76
+ inf_function=None,
77
+ min_e=None,
78
+ max_e=None,
79
+ balance_e=None,
80
+ call=None,
81
+ DIDparams=None):
82
+
83
+ out = {
84
+ "overall_att": overall_att,
85
+ "overall_se": overall_se,
86
+ "type": typec,
87
+ "egt": egt,
88
+ "att_egt": att_egt,
89
+ "se_egt": se_egt,
90
+ "crit_val_egt": crit_val_egt,
91
+ "inf_function": inf_function,
92
+ "min_e": min_e,
93
+ "max_e": max_e,
94
+ "balance_e": balance_e,
95
+ "call": call,
96
+ "DIDparams": DIDparams
97
+ }
98
+
99
+
100
+ # Overall estimates
101
+ alp = out["DIDparams"]["alp"]
102
+ pointwise_cval = stats.norm.ppf(1 - alp / 2)
103
+ overall_cband_upper = out["overall_att"] + pointwise_cval * out["overall_se"]
104
+ overall_cband_lower = out["overall_att"] - pointwise_cval * out["overall_se"]
105
+ out1 = np.column_stack((out["overall_att"], out["overall_se"], overall_cband_lower, overall_cband_upper))
106
+ out1 = np.round(out1, 4)
107
+ overall_sig = (overall_cband_upper < 0) | (overall_cband_lower > 0)
108
+ overall_sig[np.isnan(overall_sig)] = False
109
+ overall_sig_text = np.where(overall_sig, "*", "")
110
+ out1 = np.column_stack((out1, overall_sig_text))
111
+
112
+ print("\n")
113
+ if out["type"] == "dynamic":
114
+ print("Overall summary of ATT's based on event-study/dynamic aggregation:")
115
+ elif out["type"] == "group":
116
+ print("Overall summary of ATT's based on group/cohort aggregation:")
117
+ elif out["type"] == "calendar":
118
+ print("Overall summary of ATT's based on calendar time aggregation:")
119
+ colnames = ["ATT", "Std. Error", f"[{100 * (1 - out['DIDparams']['alp'])}%"," Conf. Int.]", ""]
120
+ print(pd.DataFrame(out1, columns=colnames).to_string(index=False))
121
+ print("\n")
122
+
123
+ # Handle cases depending on type
124
+ if out["type"] in ["group", "dynamic", "calendar"]:
125
+ if out["type"] == "dynamic":
126
+ c1name = "Event time"
127
+ print("Dynamic Effects:")
128
+ elif out["type"] == "group":
129
+ c1name = "Group"
130
+ print("Group Effects:")
131
+ elif out["type"] == "calendar":
132
+ c1name = "Time"
133
+ print("Time Effects (calendar):")
134
+
135
+ cband_text1a = f"{100 * (1 - out['DIDparams']['alp'])}% "
136
+ cband_text1b = "Simult. " if out["DIDparams"]["bstrap"] else "Pointwise "
137
+ cband_text1 = f"[{cband_text1a}{cband_text1b}"
138
+
139
+ cband_lower = out["att_egt"] - out["crit_val_egt"] * out["se_egt"]
140
+ cband_upper = out["att_egt"] + out["crit_val_egt"] * out["se_egt"]
141
+
142
+ sig = (cband_upper < 0) | (cband_lower > 0)
143
+ sig[np.isnan(sig)] = False
144
+ sig_text = np.where(sig, "*", "").T
145
+
146
+ out2 = pd.DataFrame([out["egt"],
147
+ out["att_egt"],
148
+ out["se_egt"].flatten(),
149
+ np.hstack(cband_lower),
150
+ np.hstack(cband_upper)]).T
151
+
152
+ out2 = out2.round(4)
153
+ out2[0] = out2[0].astype(int)
154
+ out2 = pd.concat([out2, pd.DataFrame(sig_text, columns=['sig_text']) ], axis=1)
155
+
156
+ out2.columns = [c1name, "Estimate", "Std. Error", cband_text1, "Conf. Band", ""]
157
+ print(out2)
158
+
159
+
160
+
161
+
162
+
163
+ print("---")
164
+ print("Signif. codes: `*' confidence band does not cover 0")
165
+
166
+ # Set control group text
167
+ control_group = out["DIDparams"]["control_group"]
168
+ control_group_text = None
169
+ if control_group == "nevertreated":
170
+ control_group_text = "Never Treated"
171
+ elif control_group == "notyettreated":
172
+ control_group_text = "Not Yet Treated"
173
+
174
+ if control_group:
175
+ print("Control Group: ", control_group_text, ", ")
176
+
177
+ # Anticipation periods
178
+ print("Anticipation Periods: ", out["DIDparams"]["anticipation"])
179
+
180
+ # Estimation method text
181
+ est_method = out["DIDparams"]["est_method"]
182
+ if isinstance(est_method, str):
183
+ est_method_text = est_method
184
+ if est_method == "dr":
185
+ est_method_text = "Doubly Robust"
186
+ elif est_method == "ipw":
187
+ est_method_text = "Inverse Probability Weighting"
188
+ elif est_method == "reg":
189
+ est_method_text = "Outcome Regression"
190
+
191
+ print("Estimation Method: ", est_method_text)
192
+ print("\n")
193
+
194
+ return out
195
+
196
+
csdid/att_gt.py ADDED
@@ -0,0 +1,245 @@
1
+ # from aggte import AGGte
2
+ from csdid.aggte_fnc.aggte import aggte as agg_te
3
+
4
+ from csdid.attgt_fnc.preprocess_did import pre_process_did
5
+ from csdid.attgt_fnc.compute_att_gt import compute_att_gt
6
+
7
+ from csdid.utils.mboot import mboot
8
+
9
+ from csdid.plots.gplot import gplot, splot
10
+
11
+
12
+ import matplotlib.pyplot as plt
13
+
14
+ import warnings
15
+
16
+ import numpy as np, pandas as pd
17
+
18
+ # class ATTgt(AGGte):
19
+ class ATTgt:
20
+ def __init__(self, yname, tname, idname, gname, data, control_group = ['nevertreated', 'notyettreated'],
21
+ xformla: str = None, panel = True, allow_unbalanced_panel = True,
22
+ clustervar = None, weights_name = None, anticipation = 0,
23
+ cband = False, biters = 1000, alp = 0.05
24
+ ):
25
+ dp = pre_process_did(
26
+ yname=yname, tname = tname, idname=idname, gname = gname,
27
+ data = data, control_group=control_group, anticipation=anticipation,
28
+ xformla=xformla, panel=panel, allow_unbalanced_panel=allow_unbalanced_panel, cband=cband, clustervar=None, weights_name=None
29
+ )
30
+
31
+ dp['biters'] = biters
32
+ dp['alp'] = alp
33
+ dp['true_repeated_cross_sections'] = dp['true_rep_cross_section']
34
+ dp['cband'] = cband
35
+ dp['panel'] = panel
36
+ self.dp = dp
37
+
38
+ def fit(self, est_method = 'dr', base_period = 'varying', bstrap = True):
39
+ # print(self.dp)
40
+ dp = self.dp
41
+ result, inffunc = compute_att_gt(dp, est_method = est_method, base_period = base_period)
42
+ att = result['att']
43
+ n_len = list(map(len, inffunc))
44
+ crit_val, se, V = (
45
+ 1.96,
46
+ np.std(inffunc, axis=1, ddof = 1) / np.sqrt(n_len),
47
+ np.zeros(len(att)),
48
+ )
49
+ if bstrap:
50
+ ref_se = mboot(inffunc.T, dp)
51
+ crit_val, se = ref_se['crit_val'], ref_se['se']
52
+ V = ref_se['V']
53
+
54
+ ############# aggte input
55
+ group = result['group']
56
+ att = result['att']
57
+ tt = result['year']
58
+ inf_fnc = {'inffunc': inffunc.T}
59
+
60
+ dp['bstrap'] = bstrap
61
+ dp['est_method'] = est_method
62
+ dp['base_period'] = base_period
63
+ self.dp = dp
64
+ n = dp['n']
65
+
66
+ mp = {
67
+ 'group': group, 'att': att, 't': tt,
68
+ 'DIDparams': dp, 'inffunc': inf_fnc,
69
+ 'n': n
70
+ }
71
+ self.MP = mp
72
+
73
+
74
+ cband_lower = att - crit_val * se
75
+ cband_upper = att + crit_val * se
76
+ sig = (cband_upper < 0) | (cband_lower > 0)
77
+ sig[np.isnan(sig)] = False
78
+ sig_text = np.where(sig, "*", "")
79
+
80
+ result.update(
81
+ {
82
+ 'se': se, 'l_se': cband_lower,
83
+ 'c': crit_val,
84
+ 'u_se': cband_upper, 'sig': sig_text
85
+ })
86
+
87
+ self.results = result
88
+
89
+ rst = result
90
+ did_object = {
91
+ 'group': mp['group'],
92
+ 't': mp['t'],
93
+ 'att': rst['att'],
94
+ 'se': rst['se'],
95
+ 'c': rst['c'],
96
+ }
97
+ self.did_object = did_object
98
+ return self
99
+ def summ_attgt(self, n = 4):
100
+ result = self.results
101
+ att_gt = pd.DataFrame(result)
102
+ att_gt = att_gt.drop('c', axis=1)
103
+ name_attgt_df = ['Group', 'Time', 'ATT(g, t)', 'Post', "Std. Error", "[95% Pointwise", 'Conf. Band]', '']
104
+ att_gt.columns = name_attgt_df
105
+ att_gt = att_gt.round(n)
106
+ self.summary2 = att_gt
107
+ return self
108
+
109
+ def aggte(
110
+ self,
111
+ typec = "group",
112
+ balance_e = None,
113
+ min_e = float('-inf'),
114
+ max_e = float('inf'),
115
+ na_rm = False,
116
+ bstrap = None,
117
+ biters = None,
118
+ cband = None,
119
+ alp = None,
120
+ clustervars = None,
121
+ ):
122
+ mp = self.MP
123
+ did_object = self.did_object
124
+
125
+ did_object.update({
126
+ 'type': typec
127
+ }
128
+ )
129
+
130
+
131
+ atte = agg_te(
132
+ mp, typec=typec, balance_e=balance_e,
133
+ min_e=min_e, max_e=max_e, na_rm=na_rm, bstrap=bstrap,
134
+ biters=biters, cband=cband, alp=alp, clustervars=clustervars
135
+ )
136
+
137
+ self.atte = atte
138
+ return self
139
+ def plot_attgt(self, ylim=None,
140
+ xlab=None,
141
+ ylab=None,
142
+ title="Group",
143
+ xgap=1,
144
+ ncol=1,
145
+ legend=True,
146
+ group=None,
147
+ ref_line=0,
148
+ theming=True,
149
+ grtitle="Group"
150
+ ):
151
+
152
+ did_object = self.did_object
153
+
154
+ grp = did_object['group']
155
+ t_i = did_object['t']
156
+
157
+ G = len(np.unique(grp))
158
+ Y = len(np.unique(t_i))
159
+ g = np.unique(grp)[np.argsort(np.unique(grp))].astype(int)
160
+ y = np.unique(t_i)
161
+
162
+ results = pd.DataFrame({'year': np.tile(y, G)})
163
+ results['group'] = np.repeat(g, Y)
164
+ results['grtitle'] = grtitle + ' ' + results['group'].astype(str)
165
+ results['att'] = did_object['att']
166
+ results['att_se'] = did_object['se']
167
+ results['post'] = np.where(results['year'] >= grp, 1, 0)
168
+ results['year'] = results['year']
169
+ results['c'] = did_object['c']
170
+
171
+ self.results_plot_df_attgt = results
172
+
173
+ if group is None:
174
+ group = g
175
+ if any(group not in g for group in group):
176
+ raise ValueError("Some of the specified groups do not exist in the data. Reporting all available groups.")
177
+
178
+
179
+ legend_1 = False # for multiple subplots, legend outside
180
+ fig, axes = plt.subplots(nrows=len(group), ncols=1, figsize=(10, 5)) # Adjust the figsize as needed
181
+ handles = []
182
+ labels = []
183
+ for i, group_cat in enumerate(group):
184
+ group_data = results.loc[results['group'] == group_cat]
185
+ title = group_data['grtitle'].unique()[0]
186
+ ax = axes[i]
187
+ ax = gplot(group_data, ax, ylim, xlab, ylab, title, xgap, legend_1, ref_line, theming)
188
+ plt.tight_layout()
189
+ if legend is True:
190
+ handles_ax, labels_ax = ax.get_legend_handles_labels()
191
+ handles.extend(handles_ax)
192
+ labels.extend(labels_ax)
193
+ fig.legend(handles, labels, loc='lower center', fontsize='small', bbox_to_anchor=(0.545, -0.075), ncol=2)
194
+
195
+ plt.show()
196
+ return fig
197
+
198
+ def plot_aggte(self, ylim=None,
199
+ xlab=None,
200
+ ylab=None,
201
+ title="",
202
+ xgap=1,
203
+ legend=True,
204
+ ref_line=0,
205
+ theming=True,
206
+ **kwargs):
207
+
208
+ did_object = self.atte
209
+
210
+ post_treat = 1 * (np.asarray(did_object["egt"]).astype(int) >= 0)
211
+
212
+ results = {
213
+ "year": list(map(int, did_object["egt"])),
214
+ "att": did_object["att_egt"],
215
+ "att_se": did_object["se_egt"][0],
216
+ "post": post_treat
217
+ }
218
+
219
+ results = pd.DataFrame(results)
220
+ self.results_plot_df_aggte = results
221
+
222
+ if did_object['crit_val_egt'] is None:
223
+ results['c'] = abs(norm.ppf(0.025))
224
+ else:
225
+ results['c'] = did_object['crit_val_egt']
226
+
227
+ if title == "":
228
+ title = "Average Effect by Group" if\
229
+ did_object["type"] == "group" else\
230
+ "Average Effect by Length of Exposure"
231
+
232
+
233
+ if did_object["type"] == "group":
234
+ fig, ax = plt.subplots(figsize=(10, 5))
235
+ p = splot(results, ax, ylim, xlab, ylab, title, legend, ref_line, theming)
236
+ plt.tight_layout()
237
+ plt.show()
238
+
239
+ else:
240
+ fig, ax = plt.subplots(figsize=(10, 5))
241
+ p = gplot(results, ax, ylim, xlab, ylab, title, xgap, legend, ref_line, theming)
242
+ plt.tight_layout()
243
+ plt.show()
244
+
245
+ return p
File without changes