Skip to content

Commit 4d86863

Browse files
authored
Merge pull request #31 from john-p-ryan/main
Add HSV tax estimation
2 parents 8599cfb + 8f9e24a commit 4d86863

File tree

1 file changed

+42
-23
lines changed

1 file changed

+42
-23
lines changed

iot/inverse_optimal_tax.py

Lines changed: 42 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import scipy
55
from statsmodels.nonparametric.kernel_regression import KernelReg
66
from scipy.interpolate import UnivariateSpline
7+
from scipy.linalg import lstsq
78

89

910
class IOT:
@@ -127,33 +128,49 @@ def compute_mtr_dist(
127128
* mtr_prime (array_like): rate of change in marginal tax rates
128129
for each income bin
129130
"""
130-
bins = 1000 # number of equal-width bins
131-
data.loc[:, ["z_bin"]] = pd.cut(
132-
data[income_measure], bins, include_lowest=True
133-
)
134-
binned_data = pd.DataFrame(
135-
data[["mtr", income_measure, "z_bin", weight_var]]
136-
.groupby(["z_bin"], observed=False)
137-
.apply(lambda x: wm(x[["mtr", income_measure]], x[weight_var]))
138-
)
139-
# make column 0 into two columns
140-
binned_data[["mtr", income_measure]] = pd.DataFrame(
141-
binned_data[0].tolist(), index=binned_data.index
142-
)
143-
binned_data.drop(columns=0, inplace=True)
144-
binned_data.reset_index(inplace=True)
131+
145132
if mtr_smoother == "kreg":
133+
bins = 1000 # number of equal-width bins
134+
data.loc[:, ["z_bin"]] = pd.cut(
135+
data[income_measure], bins, include_lowest=True
136+
)
137+
binned_data = pd.DataFrame(
138+
data[["mtr", income_measure, "z_bin", weight_var]]
139+
.groupby(["z_bin"], observed=False)
140+
.apply(lambda x: wm(x[["mtr", income_measure]], x[weight_var]))
141+
)
142+
# make column 0 into two columns
143+
binned_data[["mtr", income_measure]] = pd.DataFrame(
144+
binned_data[0].tolist(), index=binned_data.index
145+
)
146+
binned_data.drop(columns=0, inplace=True)
147+
binned_data.reset_index(inplace=True)
146148
mtr_function = KernelReg(
147149
binned_data["mtr"].dropna(),
148150
binned_data[income_measure].dropna(),
149151
var_type="c",
150152
reg_type="ll",
151153
)
152154
mtr, _ = mtr_function.fit(self.z)
155+
mtr_prime = np.gradient(mtr, edge_order=2)
156+
elif mtr_smoother == "HSV":
157+
# estimate the HSV function on mtrs via weighted least squares
158+
X = np.log(data[income_measure].values)
159+
X = np.column_stack((np.ones(len(X)), X))
160+
w = np.array(data[weight_var].values)
161+
w_sqrt = np.sqrt(w)
162+
y = np.log(1-data["mtr"].values)
163+
X_weighted = X * w_sqrt[:, np.newaxis]
164+
y_weighted = y * w_sqrt
165+
coef, _, _, _ = lstsq(X_weighted, y_weighted)
166+
tau = -coef[1]
167+
lambda_param = np.exp(coef[0]) / (1-tau)
168+
mtr = 1 - lambda_param * (1-tau) * self.z ** (-tau)
169+
mtr_prime = lambda_param * tau * (1-tau) * self.z ** (-tau - 1)
153170
else:
154171
print("Please enter a value mtr_smoother method")
155172
assert False
156-
mtr_prime = np.gradient(mtr, edge_order=2)
173+
157174

158175
return mtr, mtr_prime
159176

@@ -336,20 +353,22 @@ def sw_weights(self):
336353
+ ((self.theta_z * self.eti * self.mtr) / (1 - self.mtr))
337354
+ ((self.eti * self.z * self.mtr_prime) / (1 - self.mtr) ** 2)
338355
)
339-
integral = np.trapz(g_z, self.z)
340-
# g_z = g_z / integral
356+
integral = np.trapz(g_z * self.f, self.z)
357+
g_z = g_z / integral
358+
341359
# use Lockwood and Weinzierl formula, which should be equivalent but using numerical differentiation
342360
bracket_term = (
343361
1
344362
- self.F
345363
- (self.mtr / (1 - self.mtr)) * self.eti * self.z * self.f
346364
)
347-
# d_dz_bracket = np.gradient(bracket_term, edge_order=2)
348-
d_dz_bracket = np.diff(bracket_term) / np.diff(self.z)
349-
d_dz_bracket = np.append(d_dz_bracket, d_dz_bracket[-1])
365+
d_dz_bracket = np.gradient(bracket_term, edge_order=2)
366+
# d_dz_bracket = np.diff(bracket_term) / np.diff(self.z)
367+
# d_dz_bracket = np.append(d_dz_bracket, d_dz_bracket[-1])
350368
g_z_numerical = -(1 / self.f) * d_dz_bracket
351-
integral = np.trapz(g_z_numerical, self.z)
352-
# g_z_numerical = g_z_numerical / integral
369+
integral = np.trapz(g_z_numerical * self.f, self.z)
370+
g_z_numerical = g_z_numerical / integral
371+
353372
return g_z, g_z_numerical
354373

355374

0 commit comments

Comments
 (0)