From e1f560f82fa94145b2d913897162b2827109f51c Mon Sep 17 00:00:00 2001 From: John Ryan Date: Mon, 24 Feb 2025 00:38:39 -0600 Subject: [PATCH 1/5] find_eti solves ODE --- iot/inverse_optimal_tax.py | 47 ++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 25 deletions(-) diff --git a/iot/inverse_optimal_tax.py b/iot/inverse_optimal_tax.py index a0b1275..50ab358 100644 --- a/iot/inverse_optimal_tax.py +++ b/iot/inverse_optimal_tax.py @@ -386,41 +386,38 @@ def sw_weights(self): return g_z, g_z_numerical -def find_eti(iot1, iot2, g_z_type="g_z"): +def find_eti(self, g_z = None, eti_0 = 0.25): """ This function solves for the ETI that would result in the - policy represented via MTRs in iot2 be consistent with the - social welfare function inferred from the policies of iot1. + policy represented via MTRs being consistent with the + social welfare function supplied. It solves a first order + ordinary differential equation. .. math:: - \varepsilon_{z} = \frac{(1-T'(z))}{T'(z)}\frac{(1-F(z))}{zf(z)}\int_{z}^{\infty}\frac{1-g_{\tilde{z}}{1-F(y)}dF(\tilde{z}) + \varepsilon'(z)\left[\frac{zT'(z)}{1-T'(z)}\right] + \varepsilon(z)\left[\theta_z \frac{T'(z)}{1-T'(z)} +\frac{zT''(z)}{(1-T'(z))^2}\right]+ (1-g(z)) Args: - iot1 (IOT): IOT class instance representing baseline policy - iot2 (IOT): IOT class instance representing reform policy - g_z_type (str): type of social welfare function to use - Options are: - * 'g_z' for the analytical formula - * 'g_z_numerical' for the numerical approximation + g_z (None or array_like): vector of social welfare weights + eti_0 (scalar): guess for ETI at z=0 Returns: eti_beliefs (array-like): vector of ETI beliefs over z """ - if g_z_type == "g_z": - g_z = iot1.g_z - else: - g_z = iot1.g_z_numerical - # The equation below is a simplication of the above to make the integration easier - eti_beliefs_lw = ((1 - iot2.mtr) / (iot2.z * iot2.f * iot2.mtr)) * ( - 1 - iot2.F - (g_z.sum() - np.cumsum(g_z)) - ) - # derivation from JJZ analytical solution that doesn't involved integration - eti_beliefs_jjz = (g_z - 1) / ( - (iot2.theta_z * (iot2.mtr / (1 - iot2.mtr))) - + (iot2.z * (iot2.mtr_prime / (1 - iot2.mtr) ** 2)) - ) - - return eti_beliefs_lw, eti_beliefs_jjz + + if g_z is None: + g_z = self.g_z + + # we solve an ODE of the form f'(z) + P(z)f(z) = Q(z) + P_z = 1/self.z + self.f_prime/self.f + self.mtr_prime/(self.mtr * (1-self.mtr)) + # integrating factor for ODE: mu(z) * f'(z) + mu(z) * P(z) * f(z) = mu(z) * Q(z) + mu_z = np.exp(np.cumsum(P_z)) + Q_z = (g_z - 1) * (1 - self.mtr) / (self.mtr * self.z) + # integrate Q(z) * mu(z), as we integrate both sides of the ODE + int_mu_Q = np.cumsum(mu_z * Q_z) + + eti_beliefs = (eti_0 + int_mu_Q) / mu_z + + return eti_beliefs def wm(value, weight): From cb56419a66c716cd0549cbf08f4030393bba0140 Mon Sep 17 00:00:00 2001 From: John Ryan Date: Mon, 24 Feb 2025 00:40:55 -0600 Subject: [PATCH 2/5] Update some doc strings --- iot/inverse_optimal_tax.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/iot/inverse_optimal_tax.py b/iot/inverse_optimal_tax.py index 50ab358..bebf882 100644 --- a/iot/inverse_optimal_tax.py +++ b/iot/inverse_optimal_tax.py @@ -30,6 +30,8 @@ class IOT: parametric, if None, then non-parametric bin weights mtr_smoother (None or str): method used to smooth our mtr function, if None, then use bin average mtrs + mtr_smooth_param (scalar): parameter for mtr_smoother + kreg_bw (array_like): bandwidth for kernel regression Returns: class instance: IOT @@ -122,6 +124,8 @@ def compute_mtr_dist( weight_var (str): name of weight measure from data to use mtr_smoother (None or str): method used to smooth our mtr function, if None, then use bin average mtrs + mtr_smooth_param (scalar): parameter for mtr_smoother + kreg_bw (array_like): bandwidth for kernel regression Returns: tuple: @@ -207,6 +211,7 @@ def compute_income_dist( weight_var (str): name of weight measure from data to use dist_type (None or str): type of distribution to use if parametric, if None, then non-parametric bin weights + kde_bw (array_like): bandwidth for kernel regression Returns: tuple: From a3e521ef64fec26af4c60457bc5845a47b426030 Mon Sep 17 00:00:00 2001 From: John Ryan Date: Tue, 25 Feb 2025 10:19:12 -0600 Subject: [PATCH 3/5] Fix find_eti errors --- iot/inverse_optimal_tax.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/iot/inverse_optimal_tax.py b/iot/inverse_optimal_tax.py index bebf882..ff5508b 100644 --- a/iot/inverse_optimal_tax.py +++ b/iot/inverse_optimal_tax.py @@ -372,7 +372,7 @@ def sw_weights(self): + ((self.theta_z * self.eti * self.mtr) / (1 - self.mtr)) + ((self.eti * self.z * self.mtr_prime) / (1 - self.mtr) ** 2) ) - integral = np.trapz(g_z * self.f, self.z) + integral = np.trapz(g_z * self.f, self.z) # renormalize to integrate to 1 g_z = g_z / integral # use Lockwood and Weinzierl formula, which should be equivalent but using numerical differentiation @@ -391,10 +391,10 @@ def sw_weights(self): return g_z, g_z_numerical -def find_eti(self, g_z = None, eti_0 = 0.25): +def find_eti(iot, g_z = None, eti_0 = 0.25): """ This function solves for the ETI that would result in the - policy represented via MTRs being consistent with the + policy represented via MTRs in IOT being consistent with the social welfare function supplied. It solves a first order ordinary differential equation. @@ -402,6 +402,7 @@ def find_eti(self, g_z = None, eti_0 = 0.25): \varepsilon'(z)\left[\frac{zT'(z)}{1-T'(z)}\right] + \varepsilon(z)\left[\theta_z \frac{T'(z)}{1-T'(z)} +\frac{zT''(z)}{(1-T'(z))^2}\right]+ (1-g(z)) Args: + iot (IOT): instance of the I g_z (None or array_like): vector of social welfare weights eti_0 (scalar): guess for ETI at z=0 @@ -410,13 +411,13 @@ def find_eti(self, g_z = None, eti_0 = 0.25): """ if g_z is None: - g_z = self.g_z + g_z = iot.g_z # we solve an ODE of the form f'(z) + P(z)f(z) = Q(z) - P_z = 1/self.z + self.f_prime/self.f + self.mtr_prime/(self.mtr * (1-self.mtr)) + P_z = 1/iot.z + iot.f_prime/iot.f + iot.mtr_prime/(iot.mtr * (1-iot.mtr)) # integrating factor for ODE: mu(z) * f'(z) + mu(z) * P(z) * f(z) = mu(z) * Q(z) mu_z = np.exp(np.cumsum(P_z)) - Q_z = (g_z - 1) * (1 - self.mtr) / (self.mtr * self.z) + Q_z = (g_z - 1) * (1 - iot.mtr) / (iot.mtr * iot.z) # integrate Q(z) * mu(z), as we integrate both sides of the ODE int_mu_Q = np.cumsum(mu_z * Q_z) From cef53210e1aaee612017dc59848dcaa185929d9d Mon Sep 17 00:00:00 2001 From: John Ryan Date: Tue, 25 Feb 2025 10:19:42 -0600 Subject: [PATCH 4/5] Adjust plots for trump-clinton ETI and add utilitarian ETI plot --- examples/Simulate_all_policies.ipynb | 210 ++++++++++++++++++++++----- 1 file changed, 170 insertions(+), 40 deletions(-) diff --git a/examples/Simulate_all_policies.ipynb b/examples/Simulate_all_policies.ipynb index 4c9690a..08db114 100644 --- a/examples/Simulate_all_policies.ipynb +++ b/examples/Simulate_all_policies.ipynb @@ -10,7 +10,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -71,9 +71,33 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "https://raw.githubusercontent.com/jdebacker/examples/pres_proposals/psl_examples/taxcalc/Obama2015.json\n", + "['https://raw.githubusercontent.com/PSLmodels/examples/main/psl_examples/taxcalc/2017_law.json']\n", + "https://raw.githubusercontent.com/jdebacker/examples/pres_proposals/psl_examples/taxcalc/Romney2012.json\n", + "['https://raw.githubusercontent.com/PSLmodels/examples/main/psl_examples/taxcalc/2017_law.json']\n", + "https://raw.githubusercontent.com/jdebacker/examples/pres_proposals/psl_examples/taxcalc/Clinton2016.json\n", + "['https://raw.githubusercontent.com/PSLmodels/examples/main/psl_examples/taxcalc/2017_law.json']\n", + "https://raw.githubusercontent.com/PSLmodels/examples/main/psl_examples/taxcalc/Trump2016.json\n", + "['https://raw.githubusercontent.com/PSLmodels/examples/main/psl_examples/taxcalc/2017_law.json']\n", + "https://raw.githubusercontent.com/PSLmodels/examples/main/psl_examples/taxcalc/Biden2020.json\n", + "['https://raw.githubusercontent.com/PSLmodels/examples/main/psl_examples/taxcalc/2017_law.json', 'https://raw.githubusercontent.com/PSLmodels/examples/main/psl_examples/taxcalc/TCJA.json']\n", + "['https://raw.githubusercontent.com/PSLmodels/examples/main/psl_examples/taxcalc/2017_law.json', 'https://raw.githubusercontent.com/PSLmodels/examples/main/psl_examples/taxcalc/TCJA.json']\n", + "https://raw.githubusercontent.com/PSLmodels/examples/main/psl_examples/taxcalc/TCJA.json\n", + "['https://raw.githubusercontent.com/PSLmodels/examples/main/psl_examples/taxcalc/2017_law.json']\n", + "https://raw.githubusercontent.com/PSLmodels/examples/main/psl_examples/taxcalc/TCJA.json\n", + "['https://raw.githubusercontent.com/PSLmodels/examples/main/psl_examples/taxcalc/2017_law.json']\n", + "https://raw.githubusercontent.com/PSLmodels/examples/main/psl_examples/taxcalc/TCJA.json\n", + "['https://raw.githubusercontent.com/PSLmodels/examples/main/psl_examples/taxcalc/2017_law.json']\n" + ] + } + ], "source": [ "# Create IOT objects for each candidate platform\n", "policies = []\n", @@ -617,7 +641,109 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 17, + "metadata": {}, + "outputs": [], + "source": [ + "eti_dict = {\n", + " \"eti_values\": [0.18, 0.106, 0.567, 1.83, 1.9],\n", + " \"knot_points\": [30000, 75000, 250000, 2000000, 10000000]\n", + "}\n", + "# ETI values from Gruber and Saez (2002) (Table 3) and Saez (2004) (Tables 2, 4, 5)\n", + "# Compute MTR schedule under current law\n", + "iot_2023 = iot_user.iot_comparison(\n", + " policies=[{}],\n", + " baseline_policies=[None],\n", + " labels=[\"2023 Law\"],\n", + " years=[2023],\n", + " data=\"CPS\",\n", + " eti=eti_dict\n", + " )\n", + "fig = px.line(\n", + " x=iot_2023.iot[0].df().z,\n", + " y=iot_2023.iot[0].df().mtr\n", + " )\n", + "fig.update_layout(\n", + " template=template,\n", + " xaxis_title=\"Wages and Salaries\",\n", + " yaxis_title=r\"$T'(z)$\",\n", + ")\n", + "fig.write_image(\n", + " os.path.join(path, \"MTR_2023.png\"),\n", + " scale=4\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Thought experiment: What beliefs about ETI do the candidates need to \n", + "# justify their policies if we assume they are utilitarian? \n", + "# If the elasticities are wildly counterfactual, we can reject this hypothesis\n", + "\n", + "eti_utilitarian = np.zeros((len(iot_all.iot), len(iot_all.iot[0].df().z)))\n", + "for i in range(len(iot_all.iot)):\n", + " eti_utilitarian[i, :] = iot.inverse_optimal_tax.find_eti(\n", + " iot_all.iot[i], g_z = np.ones(len(iot_all.iot[i].df().z))\n", + " )\n", + "\n", + "# Convert the 2D array to a DataFrame for plotting\n", + "eti_df = pd.DataFrame(eti_utilitarian.T, columns=labels)\n", + "\n", + "fig_eti = px.line(\n", + " eti_df,\n", + " x=iot_all.iot[0].df().z,\n", + " y=eti_df.columns,\n", + " labels={\"x\": \"Wages and Salaries\", \"value\": r\"$\\varepsilon$\", \"variable\": \"Candidate\"},\n", + ")\n", + "fig_eti.update_layout(\n", + " template=template,\n", + ")\n", + "\n", + "fig_eti.update_traces(\n", + " line=dict(dash=\"dot\", color=\"blue\"),\n", + " selector=dict(name=\"Obama 2015\")\n", + ")\n", + "fig_eti.update_traces(\n", + " line=dict(dash=\"dot\", color=\"red\"),\n", + " selector=dict(name=\"Romney 2012\")\n", + ")\n", + "fig_eti.update_traces(\n", + " line=dict(dash=\"dash\", color=\"blue\"),\n", + " selector=dict(name=\"Clinton 2016\")\n", + ")\n", + "fig_eti.update_traces(\n", + " line=dict(dash=\"dash\", color=\"red\"),\n", + " selector=dict(name=\"Trump 2016\")\n", + ")\n", + "fig_eti.update_traces(\n", + " line=dict(dash=\"dashdot\", color=\"blue\"),\n", + " selector=dict(name=\"Biden 2020\")\n", + ")\n", + "fig_eti.update_traces(\n", + " line=dict(dash=\"dashdot\", color=\"red\"),\n", + " selector=dict(name=\"Trump 2020\")\n", + ")\n", + "fig_eti.update_traces(\n", + " line=dict(dash=\"solid\", color=\"blue\"),\n", + " selector=dict(name=\"Harris 2024\")\n", + ")\n", + "fig_eti.update_traces(\n", + " line=dict(dash=\"solid\", color=\"red\"),\n", + " selector=dict(name=\"Trump 2024\")\n", + ")\n", + "fig_eti.write_image(\n", + " os.path.join(path, \"eti_utilitarian.png\"),\n", + " scale=4\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -626,6 +752,7 @@ "# one plot with epsilon(z) for each candidate\n", "# Will pick Trump and Clinton (2016) for example\n", "\n", + "\n", "# First, plot just their g(z)\n", "fig = px.line(\n", " x=iot_all.iot[2].df().z[10:],\n", @@ -649,56 +776,59 @@ " os.path.join(path, \"trump_clinton_g_z_numerical.png\"),\n", " scale=4\n", " )\n", - "# Now find the epsilon(z) that would give Trump's policies the same g(z) as Clinton\n", - "eti_beliefs_lw, eti_beliefs_jjz = iot.inverse_optimal_tax.find_eti(iot_all.iot[2], iot_all.iot[3], g_z_type=\"g_z\")\n", - "idx = np.where(np.absolute(eti_beliefs_jjz[1:]) < 10)[0]\n", - "fig2 = px.line(\n", - " x=iot_all.iot[2].df().z[idx],\n", - " y=eti_beliefs_jjz[idx],\n", - " labels={\"x\": \"Wages and Salaries\", \"y\": r\"$\\text{Implied } \\varepsilon$\"},\n", + "\n", + "\n", + "# What ETI does Clinton need to justify Trump's SWW given her policy?\n", + "eti_clinton = iot.inverse_optimal_tax.find_eti(\n", + " iot_all.iot[2], \n", + " g_z=iot_all.iot[3].df().g_z)\n", + "\n", + "fig_eti_clinton = px.line(\n", + " x=iot_all.iot[2].df().z,\n", + " y=eti_clinton,\n", + " labels={\"x\": \"Wages and Salaries\", \"y\": r\"$\\varepsilon$\"},\n", " )\n", - "fig2.update_layout(\n", + "fig_eti_clinton.update_layout(\n", " template=template,\n", ")\n", - "fig2.write_image(\n", - " os.path.join(path, \"trump_eti.png\"),\n", + "fig_eti_clinton.update_traces(\n", + " line=dict(dash=\"dash\", color=\"blue\"),\n", + " selector=dict(name=\"Clinton 2016\")\n", + ")\n", + "fig_eti_clinton.write_image(\n", + " os.path.join(path, \"eti_clinton.png\"),\n", " scale=4\n", - " )" + " )\n" ] }, { "cell_type": "code", - "execution_count": 17, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "eti_dict = {\n", - " \"eti_values\": [0.18, 0.106, 0.567, 1.83, 1.9],\n", - " \"knot_points\": [30000, 75000, 250000, 2000000, 10000000]\n", - "}\n", - "# ETI values from Gruber and Saez (2002) (Table 3) and Saez (2004) (Tables 2, 4, 5)\n", - "# Compute MTR schedule under current law\n", - "iot_2023 = iot_user.iot_comparison(\n", - " policies=[{}],\n", - " baseline_policies=[None],\n", - " labels=[\"2023 Law\"],\n", - " years=[2023],\n", - " data=\"CPS\",\n", - " eti=eti_dict\n", - " )\n", - "fig = px.line(\n", - " x=iot_2023.iot[0].df().z,\n", - " y=iot_2023.iot[0].df().mtr\n", + "\n", + "# Converse: What elasticty does Trump need to justify Clinton's weights?\n", + "eti_trump = iot.inverse_optimal_tax.find_eti(\n", + " iot_all.iot[3], \n", + " g_z=iot_all.iot[2].df().g_z)\n", + "\n", + "fig_eti_trump = px.line(\n", + " x=iot_all.iot[2].df().z,\n", + " y=eti_trump,\n", + " labels={\"x\": \"Wages and Salaries\", \"y\": r\"$\\varepsilon$\"},\n", " )\n", - "fig.update_layout(\n", + "fig_eti_trump.update_layout(\n", " template=template,\n", - " xaxis_title=\"Wages and Salaries\",\n", - " yaxis_title=r\"$T'(z)$\",\n", ")\n", - "fig.write_image(\n", - " os.path.join(path, \"MTR_2023.png\"),\n", + "fig_eti_trump.update_traces(\n", + " line=dict(dash=\"dash\", color=\"red\"),\n", + " selector=dict(name=\"Trump 2016\")\n", + ")\n", + "fig_eti_trump.write_image(\n", + " os.path.join(path, \"eti_trump.png\"),\n", " scale=4\n", - " )" + " )\n" ] } ], @@ -718,7 +848,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.3" + "version": "3.11.7" } }, "nbformat": 4, From bcaf193a878214de07a10f8d91e0d86fafaebf1b Mon Sep 17 00:00:00 2001 From: John Ryan Date: Tue, 25 Feb 2025 10:22:44 -0600 Subject: [PATCH 5/5] Remove cell output --- examples/Simulate_all_policies.ipynb | 28 ++-------------------------- 1 file changed, 2 insertions(+), 26 deletions(-) diff --git a/examples/Simulate_all_policies.ipynb b/examples/Simulate_all_policies.ipynb index 08db114..ec8628d 100644 --- a/examples/Simulate_all_policies.ipynb +++ b/examples/Simulate_all_policies.ipynb @@ -71,33 +71,9 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "https://raw.githubusercontent.com/jdebacker/examples/pres_proposals/psl_examples/taxcalc/Obama2015.json\n", - "['https://raw.githubusercontent.com/PSLmodels/examples/main/psl_examples/taxcalc/2017_law.json']\n", - "https://raw.githubusercontent.com/jdebacker/examples/pres_proposals/psl_examples/taxcalc/Romney2012.json\n", - "['https://raw.githubusercontent.com/PSLmodels/examples/main/psl_examples/taxcalc/2017_law.json']\n", - "https://raw.githubusercontent.com/jdebacker/examples/pres_proposals/psl_examples/taxcalc/Clinton2016.json\n", - "['https://raw.githubusercontent.com/PSLmodels/examples/main/psl_examples/taxcalc/2017_law.json']\n", - "https://raw.githubusercontent.com/PSLmodels/examples/main/psl_examples/taxcalc/Trump2016.json\n", - "['https://raw.githubusercontent.com/PSLmodels/examples/main/psl_examples/taxcalc/2017_law.json']\n", - "https://raw.githubusercontent.com/PSLmodels/examples/main/psl_examples/taxcalc/Biden2020.json\n", - "['https://raw.githubusercontent.com/PSLmodels/examples/main/psl_examples/taxcalc/2017_law.json', 'https://raw.githubusercontent.com/PSLmodels/examples/main/psl_examples/taxcalc/TCJA.json']\n", - "['https://raw.githubusercontent.com/PSLmodels/examples/main/psl_examples/taxcalc/2017_law.json', 'https://raw.githubusercontent.com/PSLmodels/examples/main/psl_examples/taxcalc/TCJA.json']\n", - "https://raw.githubusercontent.com/PSLmodels/examples/main/psl_examples/taxcalc/TCJA.json\n", - "['https://raw.githubusercontent.com/PSLmodels/examples/main/psl_examples/taxcalc/2017_law.json']\n", - "https://raw.githubusercontent.com/PSLmodels/examples/main/psl_examples/taxcalc/TCJA.json\n", - "['https://raw.githubusercontent.com/PSLmodels/examples/main/psl_examples/taxcalc/2017_law.json']\n", - "https://raw.githubusercontent.com/PSLmodels/examples/main/psl_examples/taxcalc/TCJA.json\n", - "['https://raw.githubusercontent.com/PSLmodels/examples/main/psl_examples/taxcalc/2017_law.json']\n" - ] - } - ], + "outputs": [], "source": [ "# Create IOT objects for each candidate platform\n", "policies = []\n",