drdid 1.1.6__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.
drdid/__init__.py ADDED
File without changes
drdid/_version_.py ADDED
@@ -0,0 +1 @@
1
+ version = '1.1.6'
drdid/drdid.py ADDED
@@ -0,0 +1,275 @@
1
+ import numpy as np
2
+ from numpy import ndarray
3
+ import statsmodels.api as sm
4
+ from .utils import *
5
+
6
+ import numpy as np
7
+ import statsmodels.api as sm
8
+ def drdid_rc(y, post, D, covariates, i_weights=None):
9
+
10
+ # Convert inputs to numpy arrays
11
+ D = np.asarray(D).flatten()
12
+ n = len(D)
13
+ y = np.asarray(y).flatten()
14
+ post = np.asarray(post).flatten()
15
+
16
+ # Add constant to covariate matrix
17
+ if covariates is None:
18
+ int_cov = np.ones((n, 1))
19
+ else:
20
+ covariates = np.asarray(covariates)
21
+ if np.all(covariates[:, 0] == 1):
22
+ int_cov = covariates
23
+ else:
24
+ int_cov = np.column_stack((np.ones(n), covariates))
25
+
26
+ # Normalize weights
27
+ if i_weights is None:
28
+ i_weights = np.ones(n)
29
+ i_weights = np.asanyarray(i_weights).flatten()
30
+ i_weights = i_weights / np.mean(i_weights)
31
+
32
+ # Compute the Pscore by MLE
33
+ pscore_model = sm.GLM(D, int_cov, family=sm.families.Binomial(), freq_weights=i_weights)
34
+ pscore_results = pscore_model.fit()
35
+ if not pscore_results.converged:
36
+ print("Warning: glm algorithm did not converge")
37
+ if np.any(np.isnan(pscore_results.params)):
38
+ raise ValueError("Propensity score model coefficients have NA components. \n Multicollinearity (or lack of variation) of covariates is a likely reason.")
39
+ ps_fit = pscore_results.predict()
40
+ ps_fit = np.minimum(ps_fit, 1 - 1e-16)
41
+
42
+ # Compute the Outcome regression for the control group at the pre-treatment period
43
+ mask_cont_pre = (D == 0) & (post == 0)
44
+ reg_cont_pre = sm.WLS(y[mask_cont_pre], int_cov[mask_cont_pre], weights=i_weights[mask_cont_pre]).fit()
45
+ if np.any(np.isnan(reg_cont_pre.params)):
46
+ raise ValueError("Outcome regression model coefficients have NA components. \n Multicollinearity (or lack of variation) of covariates is a likely reason.")
47
+ out_y_cont_pre = np.dot(int_cov, reg_cont_pre.params)
48
+
49
+ # Compute the Outcome regression for the control group at the post-treatment period
50
+ mask_cont_post = (D == 0) & (post == 1)
51
+ reg_cont_post = sm.WLS(y[mask_cont_post], int_cov[mask_cont_post], weights=i_weights[mask_cont_post]).fit()
52
+ if np.any(np.isnan(reg_cont_post.params)):
53
+ raise ValueError("Outcome regression model coefficients have NA components. \n Multicollinearity (or lack of variation) of covariates is a likely reason.")
54
+ out_y_cont_post = np.dot(int_cov, reg_cont_post.params)
55
+
56
+ # Combine the ORs for control group
57
+ out_y_cont = post * out_y_cont_post + (1 - post) * out_y_cont_pre
58
+
59
+ # Compute the Outcome regression for the treated group at the pre-treatment period
60
+ mask_treat_pre = (D == 1) & (post == 0)
61
+ reg_treat_pre = sm.WLS(y[mask_treat_pre], int_cov[mask_treat_pre], weights=i_weights[mask_treat_pre]).fit()
62
+ out_y_treat_pre = np.dot(int_cov, reg_treat_pre.params)
63
+
64
+ # Compute the Outcome regression for the treated group at the post-treatment period
65
+ mask_treat_post = (D == 1) & (post == 1)
66
+ reg_treat_post = sm.WLS(y[mask_treat_post], int_cov[mask_treat_post], weights=i_weights[mask_treat_post]).fit()
67
+ out_y_treat_post = np.dot(int_cov, reg_treat_post.params)
68
+
69
+ # Compute weights
70
+ w_treat_pre = i_weights * D * (1 - post)
71
+ w_treat_post = i_weights * D * post
72
+ w_cont_pre = i_weights * ps_fit * (1 - D) * (1 - post) / (1 - ps_fit)
73
+ w_cont_post = i_weights * ps_fit * (1 - D) * post / (1 - ps_fit)
74
+ w_d = i_weights * D
75
+ w_dt1 = i_weights * D * post
76
+ w_dt0 = i_weights * D * (1 - post)
77
+
78
+ # Elements of the influence function (summands)
79
+ eta_treat_pre = w_treat_pre * (y - out_y_cont) / np.mean(w_treat_pre)
80
+ eta_treat_post = w_treat_post * (y - out_y_cont) / np.mean(w_treat_post)
81
+ eta_cont_pre = w_cont_pre * (y - out_y_cont) / np.mean(w_cont_pre)
82
+ eta_cont_post = w_cont_post * (y - out_y_cont) / np.mean(w_cont_post)
83
+
84
+ # Extra elements for the locally efficient DRDID
85
+ eta_d_post = w_d * (out_y_treat_post - out_y_cont_post) / np.mean(w_d)
86
+ eta_dt1_post = w_dt1 * (out_y_treat_post - out_y_cont_post) / np.mean(w_dt1)
87
+ eta_d_pre = w_d * (out_y_treat_pre - out_y_cont_pre) / np.mean(w_d)
88
+ eta_dt0_pre = w_dt0 * (out_y_treat_pre - out_y_cont_pre) / np.mean(w_dt0)
89
+
90
+ # Estimator of each component
91
+ att_treat_pre = np.mean(eta_treat_pre)
92
+ att_treat_post = np.mean(eta_treat_post)
93
+ att_cont_pre = np.mean(eta_cont_pre)
94
+ att_cont_post = np.mean(eta_cont_post)
95
+ att_d_post = np.mean(eta_d_post)
96
+ att_dt1_post = np.mean(eta_dt1_post)
97
+ att_d_pre = np.mean(eta_d_pre)
98
+ att_dt0_pre = np.mean(eta_dt0_pre)
99
+
100
+ # ATT estimator
101
+ dr_att = (att_treat_post - att_treat_pre) - (att_cont_post - att_cont_pre) + \
102
+ (att_d_post - att_dt1_post) - (att_d_pre - att_dt0_pre)
103
+
104
+ # Compute influence functions
105
+ weights_ols_pre = i_weights * (1 - D) * (1 - post)
106
+ weights_ols_pre = np.array(weights_ols_pre)
107
+ wols_x_pre = weights_ols_pre[:, np.newaxis] * int_cov
108
+ wols_eX_pre = weights_ols_pre[:, np.newaxis] * (y - out_y_cont_pre)[:, np.newaxis] * int_cov
109
+ XpX_inv_pre = np.linalg.inv(np.dot(wols_x_pre.T, int_cov) / n)
110
+ asy_lin_rep_ols_pre = np.dot(wols_eX_pre, XpX_inv_pre)
111
+
112
+ weights_ols_post = i_weights * (1 - D) * post
113
+
114
+ wols_x_post = weights_ols_post[:, np.newaxis] * int_cov
115
+ wols_eX_post = weights_ols_post[:, np.newaxis] * (y - out_y_cont_post)[:, np.newaxis] * int_cov
116
+ XpX_inv_post = np.linalg.inv(np.dot(wols_x_post.T, int_cov) / n)
117
+ asy_lin_rep_ols_post = np.dot(wols_eX_post, XpX_inv_post)
118
+
119
+ weights_ols_pre_treat = i_weights * D * (1 - post)
120
+ wols_x_pre_treat = weights_ols_pre_treat[:, np.newaxis] * int_cov
121
+ wols_eX_pre_treat = weights_ols_pre_treat[:, np.newaxis] * (y - out_y_treat_pre)[:, np.newaxis] * int_cov
122
+ XpX_inv_pre_treat = np.linalg.inv(np.dot(wols_x_pre_treat.T, int_cov) / n)
123
+ asy_lin_rep_ols_pre_treat = np.dot(wols_eX_pre_treat, XpX_inv_pre_treat)
124
+
125
+ weights_ols_post_treat = i_weights * D * post
126
+ wols_x_post_treat = weights_ols_post_treat[:, np.newaxis] * int_cov
127
+ wols_eX_post_treat = weights_ols_post_treat[:, np.newaxis] * (y - out_y_treat_post)[:, np.newaxis] * int_cov
128
+ XpX_inv_post_treat = np.linalg.inv(np.dot(wols_x_post_treat.T, int_cov) / n)
129
+ asy_lin_rep_ols_post_treat = np.dot(wols_eX_post_treat, XpX_inv_post_treat)
130
+
131
+ score_ps = i_weights[:, np.newaxis] * (D - ps_fit)[:, np.newaxis] * int_cov
132
+ Hessian_ps = pscore_results.cov_params() * n
133
+ asy_lin_rep_ps = np.dot(score_ps, Hessian_ps)
134
+
135
+ # Influence function components
136
+ inf_treat_pre = eta_treat_pre - w_treat_pre * att_treat_pre / np.mean(w_treat_pre)
137
+ inf_treat_post = eta_treat_post - w_treat_post * att_treat_post / np.mean(w_treat_post)
138
+
139
+ M1_post = -np.mean(w_treat_post[:, np.newaxis] * post[:, np.newaxis] * int_cov, axis=0) / np.mean(w_treat_post)
140
+ M1_pre = -np.mean(w_treat_pre[:, np.newaxis] * (1 - post)[:, np.newaxis] * int_cov, axis=0) / np.mean(w_treat_pre)
141
+
142
+ inf_treat_or_post = np.dot(asy_lin_rep_ols_post, M1_post)
143
+ inf_treat_or_pre = np.dot(asy_lin_rep_ols_pre, M1_pre)
144
+ inf_treat_or = inf_treat_or_post + inf_treat_or_pre
145
+
146
+ inf_treat = inf_treat_post - inf_treat_pre + inf_treat_or
147
+
148
+ inf_cont_pre = eta_cont_pre - w_cont_pre * att_cont_pre / np.mean(w_cont_pre)
149
+ inf_cont_post = eta_cont_post - w_cont_post * att_cont_post / np.mean(w_cont_post)
150
+
151
+ M2_pre = np.mean(w_cont_pre[:, np.newaxis] * (y - out_y_cont - att_cont_pre)[:, np.newaxis] * int_cov, axis=0) / np.mean(w_cont_pre)
152
+ M2_post = np.mean(w_cont_post[:, np.newaxis] * (y - out_y_cont - att_cont_post)[:, np.newaxis] * int_cov, axis=0) / np.mean(w_cont_post)
153
+
154
+ inf_cont_ps = np.dot(asy_lin_rep_ps, M2_post - M2_pre)
155
+
156
+ M3_post = -np.mean(w_cont_post[:, np.newaxis] * post[:, np.newaxis] * int_cov, axis=0) / np.mean(w_cont_post)
157
+ M3_pre = -np.mean(w_cont_pre[:, np.newaxis] * (1 - post)[:, np.newaxis] * int_cov, axis=0) / np.mean(w_cont_pre)
158
+
159
+ inf_cont_or_post = np.dot(asy_lin_rep_ols_post, M3_post)
160
+ inf_cont_or_pre = np.dot(asy_lin_rep_ols_pre, M3_pre)
161
+ inf_cont_or = inf_cont_or_post + inf_cont_or_pre
162
+
163
+ inf_cont = inf_cont_post - inf_cont_pre + inf_cont_ps + inf_cont_or
164
+
165
+ dr_att_inf_func1 = inf_treat - inf_cont
166
+
167
+ inf_eff1 = eta_d_post - w_d * att_d_post / np.mean(w_d)
168
+ inf_eff2 = eta_dt1_post - w_dt1 * att_dt1_post / np.mean(w_dt1)
169
+ inf_eff3 = eta_d_pre - w_d * att_d_pre / np.mean(w_d)
170
+ inf_eff4 = eta_dt0_pre - w_dt0 * att_dt0_pre / np.mean(w_dt0)
171
+ inf_eff = (inf_eff1 - inf_eff2) - (inf_eff3 - inf_eff4)
172
+
173
+ mom_post = np.mean((w_d[:, np.newaxis] / np.mean(w_d) - w_dt1[:, np.newaxis] / np.mean(w_dt1)) * int_cov, axis=0)
174
+ mom_pre = np.mean((w_d[:, np.newaxis] / np.mean(w_d) - w_dt0[:, np.newaxis] / np.mean(w_dt0)) * int_cov, axis=0)
175
+ inf_or_post = np.dot(asy_lin_rep_ols_post_treat - asy_lin_rep_ols_post, mom_post)
176
+ inf_or_pre = np.dot(asy_lin_rep_ols_pre_treat - asy_lin_rep_ols_pre, mom_pre)
177
+ inf_or = inf_or_post - inf_or_pre
178
+
179
+ dr_att_inf_func = dr_att_inf_func1 + inf_eff + inf_or
180
+ se = np.std(dr_att_inf_func, ddof=1) / np.sqrt(n)
181
+ #print(f"att: {dr_att} \t se: {se}")
182
+ return dr_att, dr_att_inf_func
183
+
184
+
185
+
186
+ def drdid_panel(y1, y0, D, covariates, i_weights=None):
187
+
188
+ # Convert inputs to numpy arrays
189
+ D = np.asarray(D).flatten()
190
+ n = len(D)
191
+ deltaY = np.asarray(y1 - y0).flatten()
192
+ i_weights = np.asarray(i_weights).flatten()
193
+
194
+ # Add constant to covariate matrix
195
+ if covariates is None:
196
+ int_cov = np.ones((n, 1))
197
+ else:
198
+ covariates = np.asarray(covariates)
199
+ if np.all(covariates[:, 0] == 1):
200
+ int_cov = covariates
201
+ else:
202
+ int_cov = np.column_stack((np.ones(n), covariates))
203
+
204
+ # Weights
205
+ if i_weights is None:
206
+ i_weights = np.ones(n)
207
+ elif np.min(i_weights) < 0:
208
+ raise ValueError("i_weights must be non-negative")
209
+
210
+ # Normalize weights
211
+ i_weights = i_weights / np.mean(i_weights)
212
+ # print(D.mean())
213
+
214
+ # Compute the Pscore by MLE
215
+ pscore_model = sm.GLM(D, int_cov, family=sm.families.Binomial(), freq_weights=i_weights)
216
+ pscore_results = pscore_model.fit()
217
+ # print(D.mean())
218
+ # print(pscore_results.summary2())
219
+ if not pscore_results.converged:
220
+ print("Warning: glm algorithm did not converge")
221
+ if np.any(np.isnan(pscore_results.params)):
222
+ raise ValueError("Propensity score model coefficients have NA components. \n Multicollinearity (or lack of variation) of covariates is a likely reason.")
223
+ ps_fit = pscore_results.predict()
224
+ ps_fit = np.minimum(ps_fit, 1 - 1e-16)
225
+ # print(ps_fit)
226
+
227
+ # Compute the Outcome regression for the control group using wols
228
+ mask = D == 0
229
+ reg_model = sm.WLS(deltaY[mask], int_cov[mask], weights=i_weights[mask])
230
+ reg_results = reg_model.fit()
231
+ if np.any(np.isnan(reg_results.params)):
232
+ raise ValueError("Outcome regression model coefficients have NA components. \n Multicollinearity (or lack of variation) of covariates is a likely reason.")
233
+ out_delta = np.dot(int_cov, reg_results.params)
234
+
235
+ # Compute Traditional Doubly Robust DiD estimators
236
+ w_treat = i_weights * D
237
+ w_cont = i_weights * ps_fit * (1 - D) / (1 - ps_fit)
238
+ dr_att_treat = w_treat * (deltaY - out_delta)
239
+ dr_att_cont = w_cont * (deltaY - out_delta)
240
+
241
+ eta_treat = np.mean(dr_att_treat) / np.mean(w_treat)
242
+ eta_cont = np.mean(dr_att_cont) / np.mean(w_cont)
243
+
244
+ dr_att = eta_treat - eta_cont
245
+
246
+ # Compute influence function
247
+ weights_ols = i_weights * (1 - D)
248
+ wols_x = weights_ols[:, np.newaxis] * int_cov
249
+ wols_eX = weights_ols[:, np.newaxis] * (deltaY - out_delta)[:, np.newaxis] * int_cov
250
+ XpX_inv = np.linalg.inv(np.dot(wols_x.T, int_cov) / n)
251
+ asy_lin_rep_wols = np.dot(wols_eX, XpX_inv)
252
+
253
+ score_ps = i_weights[:, np.newaxis] * (D - ps_fit)[:, np.newaxis] * int_cov
254
+ Hessian_ps = pscore_results.cov_params() * n
255
+ asy_lin_rep_ps = np.dot(score_ps, Hessian_ps)
256
+
257
+ inf_treat_1 = dr_att_treat - w_treat * eta_treat
258
+ M1 = np.mean(w_treat[:, np.newaxis] * int_cov, axis=0)
259
+ inf_treat_2 = np.dot(asy_lin_rep_wols, M1)
260
+ inf_treat = (inf_treat_1 - inf_treat_2) / np.mean(w_treat)
261
+
262
+ inf_cont_1 = dr_att_cont - w_cont * eta_cont
263
+ M2 = np.mean(w_cont[:, np.newaxis] * (deltaY - out_delta - eta_cont)[:, np.newaxis] * int_cov, axis=0)
264
+ inf_cont_2 = np.dot(asy_lin_rep_ps, M2)
265
+ M3 = np.mean(w_cont[:, np.newaxis] * int_cov, axis=0)
266
+ inf_cont_3 = np.dot(asy_lin_rep_wols, M3)
267
+ inf_control = (inf_cont_1 + inf_cont_2 - inf_cont_3) / np.mean(w_cont)
268
+
269
+ dr_att_inf_func = inf_treat - inf_control
270
+ se = np.std(dr_att_inf_func, ddof=1) / np.sqrt(n)
271
+ # print(f"att: {dr_att} \t se: {se}")
272
+ return dr_att, dr_att_inf_func
273
+
274
+
275
+
drdid/ipwd_did.py ADDED
@@ -0,0 +1,160 @@
1
+ from .utils import *
2
+
3
+ def std_ipw_did_panel(y1, y0, D, covariates, i_weights = None):
4
+ # print("ipw: panel")
5
+ D = np.asarray(D).flatten()
6
+ n = len(D)
7
+ delta_y = np.asarray(y1 - y0).flatten()
8
+ int_cov = np.ones((n, 1))
9
+ i_weights = np.asarray(i_weights).flatten()
10
+ if covariates is not None:
11
+ covariates = np.asarray(covariates)
12
+ if np.all(covariates[:, 0] == 1):
13
+ int_cov = covariates
14
+ else:
15
+ int_cov = np.column_stack((np.ones(n), covariates))
16
+
17
+ if i_weights is None:
18
+ i_weights = np.ones(n)
19
+ elif np.min(i_weights) < 0:
20
+ raise ValueError("i_weights must be non-negative")
21
+
22
+ i_weights = i_weights / np.mean(i_weights)
23
+ pscore_model = sm.GLM(D, int_cov, family=sm.families.Binomial(), freq_weights=i_weights)
24
+ pscore_results = pscore_model.fit()
25
+ # print(D.mean())
26
+ # print(pscore_results.summary2())
27
+ if not pscore_results.converged:
28
+ print("Warning: glm algorithm did not converge")
29
+ if np.any(np.isnan(pscore_results.params)):
30
+ raise ValueError("Propensity score model coefficients have NA components. \n Multicollinearity (or lack of variation) of covariates is a likely reason.")
31
+ ps_fit = pscore_results.predict()
32
+ ps_fit = np.minimum(ps_fit, 1 - 1e-16)
33
+
34
+ w_treat = i_weights * D
35
+ w_cont = i_weights * ps_fit * (1 - D) / (1 - ps_fit)
36
+
37
+ att_treat = w_treat * delta_y
38
+ att_cont = w_cont * delta_y
39
+
40
+ eta_treat = mean(att_treat) / mean(w_treat)
41
+ eta_cont = mean(att_cont) / mean(w_cont)
42
+
43
+ ipw_att = eta_treat - eta_cont
44
+
45
+ score_ps = i_weights[:, np.newaxis] * (D - ps_fit)[:, np.newaxis] * int_cov
46
+ Hessian_ps = pscore_results.cov_params() * n
47
+ asy_lin_rep_ps = np.dot(score_ps, Hessian_ps)
48
+
49
+ inf_treat = (att_treat - w_treat * eta_treat) / mean(w_treat)
50
+ inf_cont_1 = att_cont - w_cont * eta_cont
51
+ pre_m2 = w_cont * (delta_y - eta_cont)
52
+ M2 = np.mean(pre_m2[:, np.newaxis] * int_cov, axis = 0)
53
+ # print(M2)
54
+ inf_cont_2 = np.dot(asy_lin_rep_ps, M2)
55
+
56
+ inf_control = (inf_cont_1 + inf_cont_2) / np.mean(w_cont)
57
+ att_inf_func = inf_treat - inf_control
58
+ se = np.std(att_inf_func, ddof=1) / np.sqrt(n)
59
+ # print(f"att: {ipw_att} \t se: {se}")
60
+ # print(np.std(att_inf_func) / np.sqrt(n))
61
+ return ipw_att, att_inf_func
62
+
63
+ def std_ipw_did_rc(y, post, D, covariates, i_weights = None):
64
+ # print("ipw: rc")
65
+ D = np.asarray(D).flatten()
66
+ y = np.asarray(y).flatten()
67
+ post = np.asarray(post).flatten()
68
+ n = len(D)
69
+ if covariates is None:
70
+ int_cov = np.ones((n, 1))
71
+ else:
72
+ covariates = np.asarray(covariates)
73
+ if np.all(covariates[:, 0] == 1):
74
+ int_cov = covariates
75
+ else:
76
+ int_cov = np.column_stack((np.ones(n), covariates))
77
+
78
+ # Pesos
79
+ if i_weights is None:
80
+ i_weights = np.ones(n)
81
+ else:
82
+ i_weights = np.asarray(i_weights)
83
+ if np.min(i_weights) < 0:
84
+ raise ValueError("i_weights must be non-negative")
85
+
86
+ # Normalizar pesos
87
+ i_weights = np.asarray(i_weights).flatten()
88
+ i_weights = i_weights / np.mean(i_weights)
89
+
90
+ pscore_model = sm.GLM(D, int_cov, family=sm.families.Binomial(), freq_weights=i_weights)
91
+ pscore_results = pscore_model.fit()
92
+ if not pscore_results.converged:
93
+ print("Warning: glm algorithm did not converge")
94
+ if np.any(np.isnan(pscore_results.params)):
95
+ raise ValueError("Propensity score model coefficients have NA components. \n Multicollinearity (or lack of variation) of covariates is a likely reason.")
96
+ ps_fit = pscore_results.predict()
97
+ ps_fit = np.minimum(ps_fit, 1 - 1e-16)
98
+
99
+
100
+ w_treat_pre = i_weights * D * (1 - post)
101
+ w_treat_post = i_weights * D * post
102
+ # print(np.mean(w_treat_pre))
103
+
104
+ w_cont_pre = i_weights * ps_fit * (1 - D) * (1 - post)/(1 - ps_fit)
105
+ w_cont_post = i_weights * ps_fit * (1 - D) * post/(1 - ps_fit)
106
+
107
+ # Elements of the influence function (summands)
108
+ eta_treat_pre = w_treat_pre * y / np.mean(w_treat_pre)
109
+ eta_treat_post = w_treat_post * y / np.mean(w_treat_post)
110
+ # print(eta_treat_pre)
111
+
112
+ eta_cont_pre = w_cont_pre * y / np.mean(w_cont_pre)
113
+ eta_cont_post = w_cont_post * y / np.mean(w_cont_post)
114
+
115
+ # Estimator of each component
116
+ att_treat_pre = np.mean(eta_treat_pre)
117
+ att_treat_post = np.mean(eta_treat_post)
118
+ att_cont_pre = np.mean(eta_cont_pre)
119
+ att_cont_post = np.mean(eta_cont_post)
120
+ ipw_att = (att_treat_post - att_treat_pre) - (att_cont_post - att_cont_pre)
121
+
122
+ score_ps = (i_weights * (D - ps_fit))[:, np.newaxis] * int_cov
123
+ Hessian_ps = pscore_results.cov_params() * n
124
+ asy_lyn_rep_ps = np.dot(score_ps, Hessian_ps)
125
+
126
+ inf_treat_pre = eta_treat_pre - w_treat_pre * att_treat_pre/np.mean(w_treat_pre)
127
+ inf_treat_post = eta_treat_post - w_treat_post * att_treat_post/np.mean(w_treat_post)
128
+ inf_treat = inf_treat_post - inf_treat_pre
129
+ # Now, get the influence function of control component
130
+ # Leading term of the influence function: no estimation effect
131
+ inf_cont_pre = eta_cont_pre - w_cont_pre * att_cont_pre/np.mean(w_cont_pre)
132
+ inf_cont_post = eta_cont_post - w_cont_post * att_cont_post/np.mean(w_cont_post)
133
+ inf_cont = inf_cont_post - inf_cont_pre
134
+
135
+ # Estimation effect from gamma hat (pscore)
136
+ # Derivative matrix (k x 1 vector)
137
+
138
+ M2_pre = np.mean((w_cont_pre *(y - att_cont_pre))[:, np.newaxis] * int_cov, axis = 0)/np.mean(w_cont_pre)
139
+ M2_post = np.mean((w_cont_post *(y - att_cont_post))[:, np.newaxis] * int_cov, axis = 0)/np.mean(w_cont_post)
140
+
141
+ # Now the influence function related to estimation effect of pscores
142
+ M2 = M2_post - M2_pre
143
+ # print()
144
+
145
+ inf_cont_ps = np.dot(asy_lyn_rep_ps, M2)
146
+
147
+ # Influence function for the control component
148
+ inf_cont = inf_cont + inf_cont_ps
149
+
150
+ #get the influence function of the DR estimator (put all pieces together)
151
+ att_inf_func = inf_treat - inf_cont
152
+ # print(np.std(att_inf_func) / np.sqrt(n))
153
+ se = np.std(att_inf_func, ddof=1) / np.sqrt(n)
154
+ # print(f"att: {ipw_att} \t se: {se}")
155
+ return ipw_att, att_inf_func
156
+
157
+
158
+
159
+
160
+
drdid/reg_did.py ADDED
@@ -0,0 +1,196 @@
1
+ import numpy as np
2
+ import statsmodels.api as sm
3
+ from numpy import ndarray
4
+
5
+ from .utils import *
6
+ import numpy as np
7
+ import statsmodels.api as sm
8
+ from numpy import ndarray
9
+
10
+ from .utils import *
11
+
12
+ def asy_lin_wols(d, post, y, out_y, int_cov, i_w, n):
13
+ weigths_ols = i_w * d * post
14
+ # weigths_ols_pre
15
+ wols_x = weigths_ols[:, n_x] * int_cov
16
+ wols_ex = (weigths_ols * (y - out_y))[:, n_x] * int_cov
17
+ cr = np.dot(wols_x.T, int_cov) / n
18
+ xpx_inv = qr_solver(cr)
19
+ asy_lin_rep_ols = np.dot(wols_ex, xpx_inv)
20
+ return asy_lin_rep_ols
21
+
22
+ import numpy as np
23
+ import statsmodels.api as sm
24
+
25
+ def reg_did_panel(y1, y0, D, covariates=None, i_weights=None):
26
+ # Convert inputs to numpy arrays
27
+ # print("reg: panel")
28
+ y1 = np.array(y1)
29
+ y0 = np.array(y0)
30
+ D = np.array(D)
31
+
32
+ # Sample size
33
+ n = len(D)
34
+
35
+ # Generate deltaY
36
+ deltaY = y1 - y0
37
+
38
+ # Add constant to covariate vector
39
+ if covariates is None:
40
+ int_cov = np.ones((n, 1))
41
+ else:
42
+ covariates = np.asarray(covariates)
43
+ if np.all(covariates[:, 0] == 1):
44
+ int_cov = covariates
45
+ else:
46
+ int_cov = np.column_stack((np.ones(n), covariates))
47
+
48
+
49
+ # Weights
50
+ if i_weights is None:
51
+ i_weights = np.ones(n)
52
+ else:
53
+ i_weights = np.array(i_weights)
54
+ if np.min(i_weights) < 0:
55
+ raise ValueError("i_weights must be non-negative")
56
+
57
+ # Normalize weights
58
+ i_weights = i_weights / np.mean(i_weights)
59
+
60
+ # Compute the Outcome regression for the control group using OLS
61
+ model = sm.WLS(deltaY[D == 0], int_cov[D == 0], weights=i_weights[D == 0])
62
+ results = model.fit()
63
+ reg_coeff = results.params
64
+ # print(reg_coeff)
65
+ if np.any(np.isnan(reg_coeff)):
66
+ raise ValueError("Outcome regression model coefficients have NA components. Multicollinearity (or lack of variation) of covariates is probably the reason for it.")
67
+
68
+ out_delta = np.dot(int_cov, reg_coeff)
69
+ # print(out_delta)
70
+
71
+ # Compute the OR-DiD estimator
72
+ w_treat = i_weights * D
73
+ w_cont = i_weights * D
74
+ reg_att_treat = w_treat * deltaY
75
+ reg_att_cont = w_cont * out_delta
76
+ eta_treat = np.mean(reg_att_treat) / np.mean(w_treat)
77
+ eta_cont = np.mean(reg_att_cont) / np.mean(w_cont)
78
+ # print(np.mean(reg_att_cont), np.mean(w_cont))
79
+ reg_att = eta_treat - eta_cont
80
+
81
+ # Compute influence function
82
+ weights_ols = i_weights * (1 - D)
83
+ wols_x = weights_ols[:, np.newaxis] * int_cov
84
+ wols_eX = weights_ols[:, np.newaxis] * (deltaY - out_delta)[:, np.newaxis] * int_cov
85
+ XpX_inv = np.linalg.inv(np.dot(wols_x.T, int_cov) / n)
86
+ asy_lin_rep_ols = np.dot(wols_eX, XpX_inv)
87
+
88
+ inf_treat = (reg_att_treat - w_treat * eta_treat) / np.mean(w_treat)
89
+
90
+ inf_cont_1 = reg_att_cont - w_cont * eta_cont
91
+ M1 = np.mean(w_cont[:, np.newaxis] * int_cov, axis=0)
92
+ inf_cont_2 = np.dot(asy_lin_rep_ols, M1)
93
+ inf_control = (inf_cont_1 + inf_cont_2) / np.mean(w_cont)
94
+
95
+ reg_att_inf_func = inf_treat - inf_control
96
+ se = np.std(reg_att_inf_func, ddof=1) / np.sqrt(n)
97
+ # print(f"att: {reg_att} \t se: {se}")
98
+
99
+ return reg_att, reg_att_inf_func
100
+
101
+
102
+
103
+
104
+ def reg_did_rc(y, post, D, covariates, i_weights=None):
105
+ # print("reg_rc")
106
+ D = np.asarray(D).flatten()
107
+ post = np.asarray(post).flatten()
108
+ n = len(D)
109
+ y = np.asarray(y).flatten()
110
+ i_weights = np.asarray(i_weights).flatten()
111
+ int_cov = np.ones((n, 1))
112
+
113
+ if covariates is not None:
114
+ covariates = np.asarray(covariates)
115
+ if np.all(covariates[:, 0] == 1):
116
+ int_cov = covariates
117
+ else:
118
+ int_cov = np.column_stack((np.ones(n), covariates))
119
+
120
+ if i_weights is None:
121
+ i_weights = np.ones(n)
122
+ elif np.min(i_weights) < 0:
123
+ raise ValueError("i_weights must be non-negative")
124
+
125
+ i_weights = i_weights / np.mean(i_weights)
126
+
127
+ # Pre-treatment regression
128
+ mask_pre = (D == 0) & (post == 0)
129
+ X_pre = int_cov[mask_pre]
130
+ y_pre = y[mask_pre]
131
+ w_pre = i_weights[mask_pre]
132
+ model_pre = sm.WLS(y_pre, X_pre, weights=w_pre)
133
+ results_pre = model_pre.fit()
134
+ reg_coeff_pre = results_pre.params
135
+
136
+ if np.any(np.isnan(reg_coeff_pre)):
137
+ raise ValueError("Outcome regression model coefficients have NA components. \n Multicollinearity of covariates is probably the reason for it.")
138
+
139
+ out_y_pre = np.dot(int_cov, reg_coeff_pre)
140
+
141
+ # Post-treatment regression
142
+ mask_post = (D == 0) & (post == 1)
143
+ X_post = int_cov[mask_post]
144
+ y_post = y[mask_post]
145
+ w_post = i_weights[mask_post]
146
+ model_post = sm.WLS(y_post, X_post, weights=w_post)
147
+ results_post = model_post.fit()
148
+ reg_coeff_post = results_post.params
149
+
150
+ if np.any(np.isnan(reg_coeff_post)):
151
+ raise ValueError("Outcome regression model coefficients have NA components. \n Multicollinearity (or lack of variation) of covariates is probably the reason for it.")
152
+
153
+ out_y_post = np.dot(int_cov, reg_coeff_post)
154
+
155
+ w_treat_pre = i_weights * D * (1 - post)
156
+ w_treat_post = i_weights * D * post
157
+ w_cont = i_weights * D
158
+ reg_att_treat_pre = w_treat_pre * y
159
+ reg_att_treat_post = w_treat_post * y
160
+ reg_att_cont = w_cont * (out_y_post - out_y_pre)
161
+ eta_treat_pre = np.mean(reg_att_treat_pre) / np.mean(w_treat_pre)
162
+ eta_treat_post = np.mean(reg_att_treat_post) / np.mean(w_treat_post)
163
+ eta_cont = np.mean(reg_att_cont) / np.mean(w_cont)
164
+ reg_att = (eta_treat_post - eta_treat_pre) - eta_cont
165
+
166
+ weights_ols_pre = i_weights * (1 - D) * (1 - post)
167
+ # print(weights_ols_pre.reshape((n, 1)))
168
+ wols_x_pre = weights_ols_pre[:, np.newaxis] * int_cov
169
+ wols_eX_pre = weights_ols_pre[:, np.newaxis] * (y - out_y_pre)[:, np.newaxis] * int_cov
170
+ XpX_inv_pre = np.linalg.inv(np.dot(wols_x_pre.T, int_cov) / n)
171
+ asy_lin_rep_ols_pre = np.dot(wols_eX_pre, XpX_inv_pre)
172
+
173
+ weights_ols_post = i_weights * (1 - D) * post
174
+ wols_x_post = weights_ols_post[:, np.newaxis] * int_cov
175
+ wols_eX_post = weights_ols_post[:, np.newaxis] * (y - out_y_post)[:, np.newaxis] * int_cov
176
+ XpX_inv_post = np.linalg.inv(np.dot(wols_x_post.T, int_cov) / n)
177
+ asy_lin_rep_ols_post = np.dot(wols_eX_post, XpX_inv_post)
178
+
179
+ inf_treat_pre = (reg_att_treat_pre - w_treat_pre * eta_treat_pre) / np.mean(w_treat_pre)
180
+ inf_treat_post = (reg_att_treat_post - w_treat_post * eta_treat_post) / np.mean(w_treat_post)
181
+ inf_treat = inf_treat_post - inf_treat_pre
182
+
183
+ inf_cont_1 = (reg_att_cont - w_cont * eta_cont)
184
+ M1 = np.mean(w_cont[:, np.newaxis] * int_cov, axis=0)
185
+ inf_cont_2_post = np.dot(asy_lin_rep_ols_post, M1)
186
+ inf_cont_2_pre = np.dot(asy_lin_rep_ols_pre, M1)
187
+ inf_control = (inf_cont_1 + inf_cont_2_post - inf_cont_2_pre) / np.mean(w_cont)
188
+
189
+ reg_att_inf_func = (inf_treat - inf_control)
190
+ # se_reg_att = np.std(reg_att_inf_func) / np.sqrt(n)
191
+ se = np.std(reg_att_inf_func, ddof=1) / np.sqrt(n)
192
+ # print(f"att: {reg_att} \t se: {se}")
193
+
194
+ return reg_att, reg_att_inf_func
195
+
196
+
drdid/utils.py ADDED
@@ -0,0 +1,94 @@
1
+ import numpy as np
2
+ import statsmodels.api as sm
3
+
4
+ lm = sm.WLS
5
+ glm = sm.GLM
6
+ n_x = np.newaxis
7
+ qr_solver = np.linalg.pinv
8
+ binomial = sm.families.Binomial()
9
+ mean = np.mean
10
+
11
+ def has_intercept(covariates, n):
12
+ int_cov = np.ones(n)
13
+ if covariates is not None:
14
+ if np.all(covariates[:, 0] == int_cov):
15
+ int_cov = covariates
16
+ else:
17
+ int_cov = np.concatenate((np.ones((n, 1)), covariates), axis=1)
18
+
19
+ return int_cov
20
+
21
+ def has_weights(weights, n):
22
+ if weights is None:
23
+ weights = np.ones(n)
24
+ elif np.min(weights) < 0:
25
+ raise ValueError('weights must be non-negative')
26
+
27
+ def asy_lin_wols(w, d, x, y, out_y, pst = 1):
28
+ n = len(y)
29
+ w_ols = w * d * pst
30
+ wols_x = w_ols[:, n_x] * x
31
+ w_diff = w_ols * (y - out_y)
32
+ wols_ex = w_diff[:, n_x] * x
33
+ cr = np.dot(wols_x.T, x) / n
34
+ xpx_inv = qr_solver(cr)
35
+ asy_lin = np.dot(wols_ex, xpx_inv)
36
+ return asy_lin
37
+
38
+ def out_wols(y, d, x, rows, wgts, pst=1):
39
+ ols_coef = lm(
40
+ y[rows], x[rows], weights=wgts[rows]
41
+ ).fit().params
42
+ out_y = np.dor(ols_coef, x.T)
43
+
44
+ ### asy_lin_rep
45
+ asy_lin_rep = asy_lin_wols(
46
+ wgts, d, x, y, out_y, pst
47
+ )
48
+
49
+
50
+ return out_y, asy_lin_rep
51
+
52
+ def w_tc_val(w, d, pst=1):
53
+ return w * d * pst
54
+
55
+ def eta_val(att, w_tc=1, y=None):
56
+ if y is None:
57
+ eta_r = mean(att) / mean(w_tc)
58
+ else:
59
+ eta_r = att * y / mean(att)
60
+
61
+
62
+ def inf_treat_f(att, w, eta):
63
+ inf_f_u = att - w * eta
64
+ inf_f = inf_f_u / mean(w)
65
+ return inf_f
66
+
67
+ def bstrap_se():
68
+ # replace with mboot
69
+ pass
70
+
71
+ def fit_ps(d, x, w, post = 1):
72
+ n = len(d)
73
+ glm_fit = glm(
74
+ d, x, family=binomial, freq_weights=w
75
+ ).fit()
76
+ ps_fit = glm_fit.fittedvalues
77
+ ps_fit = np.minimum(ps_fit, 1 - 1e-16)
78
+
79
+ rest_cont = w * ps_fit * (1 - d)
80
+ post1 = (1 - post)
81
+ ps_fit1 = (1 - ps_fit)
82
+ # Rc
83
+ w_cont_pre = rest_cont * post1 / ps_fit1
84
+ w_cont_post = rest_cont * post / ps_fit
85
+ # panel
86
+ w_cont = rest_cont / ps_fit1
87
+
88
+ w_ps = w * (d - ps_fit)
89
+ score_ps = w_ps[:, n_x] * x
90
+ hessian = glm_fit.cov_params() * n
91
+
92
+ asy_lin_ps = np.dot(score_ps, hessian)
93
+
94
+ return (ps_fit, w_cont_pre, w_cont, w_cont_post, 0, asy_lin_ps)
@@ -0,0 +1,26 @@
1
+ Metadata-Version: 2.4
2
+ Name: drdid
3
+ Version: 1.1.6
4
+ Summary: Double Robust Difference in Difference y python
5
+ Home-page: https://github.com/d2cml-ai/drdid
6
+ Author: Jhon Flores
7
+ Author-email: fr.jhonk@gmail.com
8
+ License: MIT
9
+ License-File: LICENSE
10
+ Requires-Dist: pandas
11
+ Requires-Dist: numpy
12
+ Requires-Dist: statsmodels
13
+ Dynamic: author
14
+ Dynamic: author-email
15
+ Dynamic: description
16
+ Dynamic: home-page
17
+ Dynamic: license
18
+ Dynamic: license-file
19
+ Dynamic: requires-dist
20
+ Dynamic: summary
21
+
22
+
23
+ Implementation of drdid for Python, R-like syntax with return destructuring using optimized Python code.
24
+
25
+ See the original [R package](https://github.com/pedrohcgs/DRDID/tree/master)
26
+
@@ -0,0 +1,11 @@
1
+ drdid/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ drdid/_version_.py,sha256=dh8J4El9b4iqphBgXLgnpBBahiEA-LUwKZYLjWS-x3c,17
3
+ drdid/drdid.py,sha256=Ii5qQ0lInYHLSsGXEEpobhlMq1BFUK3LiZP73lHBifw,13156
4
+ drdid/ipwd_did.py,sha256=vrnEyCg9jsZabsjcp_S8KzPuRt-eaXOlF_72TFavZcE,6273
5
+ drdid/reg_did.py,sha256=IX5x7MLXsL7qhCtcBL3bVDPuPemN5QEVW50soEQ3G5k,7039
6
+ drdid/utils.py,sha256=DLz3zfaJBtkQH72t_Ks5dUeZCqUf5HAz--iDkPIh5OY,2154
7
+ drdid-1.1.6.dist-info/licenses/LICENSE,sha256=074-9zp7UvJPkvubrgLWw_JB4EgZiYCjAtzpgi9es_I,1142
8
+ drdid-1.1.6.dist-info/METADATA,sha256=nT8P01tCuh9uwMNrDABO1pVTcCt3VaAnO6etBj8GlfY,690
9
+ drdid-1.1.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
10
+ drdid-1.1.6.dist-info/top_level.txt,sha256=xRabzMBAiGz5Aks1Wcebo_WtauCRbYgkHYo3DQ-G1eM,6
11
+ drdid-1.1.6.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Pedro Sant’Anna, Carlos Guevara, Jhon Flores, Alexander Quispe
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ drdid