Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update decomposition plot to take numerical or analytical weights #30

Merged
merged 5 commits into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/build_and_test.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Build and test [Python 3.9, 3.10, 3.11]
name: Build and test [Python 3.10, 3.11]

on: [push, pull_request]

Expand All @@ -7,7 +7,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.9, "3.10", "3.11"]
python-version: ["3.10", "3.11"]

steps:
- name: Checkout
Expand Down
5 changes: 3 additions & 2 deletions iot/inverse_optimal_tax.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ def df(self):
dict_out = {
"z": self.z,
"f": self.f,
"F": self.F,
"f_prime": self.f_prime,
"mtr": self.mtr,
"mtr_prime": self.mtr_prime,
Expand Down Expand Up @@ -336,7 +337,7 @@ def sw_weights(self):
+ ((self.eti * self.z * self.mtr_prime) / (1 - self.mtr) ** 2)
)
integral = np.trapz(g_z, self.z)
g_z = g_z / integral
# g_z = g_z / integral
# use Lockwood and Weinzierl formula, which should be equivalent but using numerical differentiation
bracket_term = (
1
Expand All @@ -348,7 +349,7 @@ def sw_weights(self):
d_dz_bracket = np.append(d_dz_bracket, d_dz_bracket[-1])
g_z_numerical = -(1 / self.f) * d_dz_bracket
integral = np.trapz(g_z_numerical, self.z)
g_z_numerical = g_z_numerical / integral
# g_z_numerical = g_z_numerical / integral
return g_z, g_z_numerical


Expand Down
92 changes: 75 additions & 17 deletions iot/iot_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,9 @@ def __init__(
income_measure=income_measure,
weight_var=weight_var,
eti=eti,
bandwidth=bandwidth,
lower_bound=lower_bound,
upper_bound=upper_bound,
# bandwidth=bandwidth,
# lower_bound=lower_bound,
# upper_bound=upper_bound,
dist_type=dist_type,
kde_bw=kde_bw,
mtr_smoother=mtr_smoother,
Expand Down Expand Up @@ -202,39 +202,74 @@ def SaezFig2(self, DS2011=False, upper_bound=None):
)
return fig

def JJZFig4(self, policy="Current Law"):
def JJZFig4(self, policy="Current Law", var="g_z"):
"""
Function to plot a decomposition of the political weights, `g_z`

Args:
policy (str): policy to plot
var (str): variable to plot against income
Variable options are:
* 'g_z' for analytically derived weights
* 'g_z_numeric' for numerically derived weights

Returns:
fig (plotly.express figure): figure with the decomposition
"""
k = self.labels.index(policy)
df = self.iot[k].df()
if var == "g_z":
g_weights = df.g_z
else:
g_weights = df.g_z_numerical

# g1 with mtr_prime = 0
g1 = (
0
+ ((df.theta_z * self.iot[k].eti * df.mtr) / (1 - df.mtr))
1
+ +((df.theta_z * self.iot[k].eti * df.mtr) / (1 - df.mtr))
+ ((self.iot[k].eti * df.z * 0) / (1 - df.mtr) ** 2)
)
# g2 with theta_z = 0
g2 = (
0
+ ((0 * self.iot[k].eti * df.mtr) / (1 - df.mtr))
1
+ +((0 * self.iot[k].eti * df.mtr) / (1 - df.mtr))
+ ((self.iot[k].eti * df.z * df.mtr_prime) / (1 - df.mtr) ** 2)
)
integral = np.trapz(g1, df.z)
g1 = g1 / integral
integral = np.trapz(g2, df.z)
plot_df = pd.DataFrame(
{
self.income_measure: df.z,
"Overall weight": df.g_z,
"Tax Base Elasticity": df.g_z - g1,
"Nonconstant MTRs": df.g_z - g1 - g2,
"Overall weight": g_weights,
"Tax Base Elasticity": g1,
"Nonconstant MTRs": g2,
}
)
fig = go.Figure()
# add a line at y = 1
fig.add_trace(
go.Scatter(
x=plot_df[self.income_measure],
y=plot_df["Overall weight"],
fill=None,
x=[
plot_df[self.income_measure].min(),
plot_df[self.income_measure].max(),
],
y=[1, 1],
mode="lines",
name="Overall weight",
line=dict(color="black", width=1, dash="dash"),
showlegend=False,
)
)
# fig.add_trace(
# go.Scatter(
# x=plot_df[self.income_measure],
# y=plot_df["Nonconstant MTRs"],
# fill="tonexty", # fill area between trace1 and trace2
# # fill="tozeroy",
# mode="lines",
# name="Nonconstant MTRs",
# )
# )
fig.add_trace(
go.Scatter(
x=plot_df[self.income_measure],
Expand All @@ -247,12 +282,35 @@ def JJZFig4(self, policy="Current Law"):
fig.add_trace(
go.Scatter(
x=plot_df[self.income_measure],
y=plot_df["Nonconstant MTRs"],
fill="tonexty", # fill area between trace1 and trace2
y=plot_df["Overall weight"],
fill="tonexty",
mode="lines",
name="Nonconstant MTRs",
)
)
# add a line at y=0
fig.add_trace(
go.Scatter(
x=plot_df[self.income_measure],
y=plot_df["Overall weight"],
mode="lines",
line=dict(color="black", width=1, dash="solid"),
name="Overall weight",
showlegend=False,
)
)
# add a line at y=0
fig.add_trace(
go.Scatter(
x=[
plot_df[self.income_measure].min(),
plot_df[self.income_measure].max(),
],
y=[0, 0],
mode="lines",
line=dict(color="black", width=1, dash="dash"),
)
)
fig.update_layout(
xaxis_title=OUTPUT_LABELS[self.income_measure],
yaxis_title=r"$g_z$",
Expand Down
Loading