Skip to content

Commit

Permalink
Fix GDP elasticity logic to use lagged average MTR
Browse files Browse the repository at this point in the history
  • Loading branch information
martinholmer committed Oct 6, 2017
1 parent 70a8ece commit 8ee99a3
Show file tree
Hide file tree
Showing 8 changed files with 123 additions and 57 deletions.
7 changes: 7 additions & 0 deletions taxcalc/calculate.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,13 @@ def current_year(self):
"""
return self.policy.current_year

@property
def data_year(self):
"""
Calculator class initial (i.e., first) records data year property.
"""
return self.records.data_year

MTR_VALID_VARIABLES = ['e00200p', 'e00200s',
'e00900p', 'e00300',
'e00400', 'e00600',
Expand Down
74 changes: 48 additions & 26 deletions taxcalc/macro_elasticity.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,54 +5,76 @@
# pep8 --ignore=E402 macro_elasticity.py
# pylint --disable=locally-disabled macro_elasticity.py

from taxcalc.policy import Policy

def proportional_change_gdp(calc1, calc2, elasticity):

def proportional_change_in_gdp(year, calc1, calc2, elasticity):
'''
This function harnesses econometric estimates of the historic relationship
between tax policy and the macroeconomy to predict the effect of tax
reforms on growth.
between tax policy and the macro economy to predict the effect of tax
reforms on economic growth.
In particular, this model relies on estimates of how GDP responds to
changes in the average after tax rate on wage income across all taxpayers
(one minus the average marginal tax rate, or 1-AMTR). These estimates are
derived from calculations of income-weighted marginal tax rates under the
baseline and reform.
baseline and reform. The reform-induced change in GDP in year t is
assumed to be equal to the assumed elasticity times the absolute (not
proportional) change in one minus the average marginal tax rate in
year t-1. In other words, the current-year change in GDP is assumed to
be related to the prior-year change in the average marginal tax rate.
Empirical evidence on this elasticity can be found in Robert Barro
and Charles Redlick, "Macroeconomic Effects from Government Purchases
and Taxes" (2011 Quarterly Journal of Economics). In particular,
Barro and Redlick find that a 1 percentage point decrease in the AMTR
leads to a 0.54 percent increase in GDP. Evaluated at the sample mean,
this translates to an elasticity of GDP with respect to the average
after-tax marginal rate of about 0.36.
and Taxes" (2011 Quarterly Journal of Economics). A pre-publication
version of this paper is available at the following URL:
<siteresources.worldbank.org/INTMACRO/Resources/BarroBRedlickBpaper.pdf>.
In particular, Barro and Redlick find that a 1 percentage point decrease
in the AMTR leads to a 0.54 percent increase in GDP. Evaluated at the
sample mean, this translates to an elasticity of GDP with respect to the
average after-tax marginal rate of about 0.36.
A more recent paper by Karel Mertens and Jose L. Montiel Olea,
entitled "Marginal Tax Rates and Income: New Time Series Evidence",
NBER working paper 19171 (June 2013 with September 2017 revisions)
<http://www.nber.org/papers/w19171.pdf>,
contains additional empirical evidence suggesting the elasticity is
no less than the 0.36 Barro-Redlick estimate and perhaps somewhat
higher (see section 4.6).
<www.nber.org/papers/w19171.pdf>, contains additional empirical
evidence suggesting the elasticity is no less than the 0.36 Barro-
Redlick estimate and perhaps somewhat higher (see section 4.6).
Their summary of the Barro and Redlick findings (on page 5) are
as follows: "Barro and Redlick (2011) however find that a one
percentage point cut in the AMTR raises per capita GDP by around
0.5% in the following year. This estimate is statistically
significant and amounts to a short run GDP elasticity to the
net-of-tax rate of 0.36".
Parameters
----------
calc1 : Calculator object for the pre-reform baseline
calc2 : Calculator object for the policy reform
year : calendar year of the reform-induced proportion change in GDP
calc1 : Calculator object for the pre-reform baseline for prior year
calc2 : Calculator object for the policy reform for prior year
elasticity: Float estimate of elasticity of GDP wrt 1-AMTR
Returns
-------
Float estimate of proportional GDP impact of the reform.
Float estimate of proportional change in GDP induced by the reform
Note that proportional means a relative change but it is not expressed
in percentage terms
'''
assert elasticity >= 0.0
assert calc1.current_year == calc2.current_year
assert calc1.data_year == calc2.data_year
if year <= max(Policy.JSON_START_YEAR, calc1.data_year):
return 0.0 # because Calculator cannot simulate taxes in year-1
if calc1.current_year != (year - 1):
msg = 'calc.current_year={} must be one less than year={}'
raise ValueError(msg.format(calc1.current_year, year))
_, _, mtr_combined1 = calc1.mtr()
_, _, mtr_combined2 = calc2.mtr()
avg_one_mtr1 = (1.0 - (mtr_combined1 * calc1.records.c00100 *
calc1.records.s006).sum() /
(calc1.records.c00100 * calc1.records.s006).sum())
avg_one_mtr2 = (1.0 - (mtr_combined2 * calc2.records.c00100 *
calc2.records.s006).sum() /
(calc2.records.c00100 * calc2.records.s006).sum())
diff_avg_one_mtr = avg_one_mtr2 - avg_one_mtr1
proportional_diff_mtr = diff_avg_one_mtr / avg_one_mtr1
gdp_effect_of_reform = proportional_diff_mtr * elasticity
return gdp_effect_of_reform
avg_mtr1 = ((mtr_combined1 * calc1.records.c00100 *
calc1.records.s006).sum() /
(calc1.records.c00100 * calc1.records.s006).sum())
avg_mtr2 = ((mtr_combined2 * calc2.records.c00100 *
calc2.records.s006).sum() /
(calc2.records.c00100 * calc2.records.s006).sum())
# Note: (avg_mtr1 - avg_mtr2) == ((1.0 - avg_mtr2) - (1.0 - avg_mtr1))
return elasticity * (avg_mtr1 - avg_mtr2)
9 changes: 4 additions & 5 deletions taxcalc/records.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,6 @@
from taxcalc.utils import read_egg_csv, read_egg_json


PUFCSV_YEAR = 2009
CPSCSV_YEAR = 2014


class Records(object):
"""
Constructor for the tax-filing-unit Records class.
Expand Down Expand Up @@ -100,6 +96,9 @@ class instance: Records
# suppress pylint warnings about too many class instance attributes:
# pylint: disable=too-many-instance-attributes

PUFCSV_YEAR = 2009
CPSCSV_YEAR = 2014

CUR_PATH = os.path.abspath(os.path.dirname(__file__))
PUF_WEIGHTS_FILENAME = 'puf_weights.csv'
PUF_RATIOS_FILENAME = 'puf_ratios.csv'
Expand Down Expand Up @@ -191,7 +190,7 @@ def cps_constructor(data=None,
gfactors=gfactors,
weights=Records.CPS_WEIGHTS_FILENAME,
adjust_ratios=Records.CPS_RATIOS_FILENAME,
start_year=CPSCSV_YEAR)
start_year=Records.CPSCSV_YEAR)

@property
def data_year(self):
Expand Down
34 changes: 22 additions & 12 deletions taxcalc/tbi/tbi.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
"""
The public API of the TaxBrain Interface (tbi).
The tbi functions are used by TaxBrain to call Tax-Calculator in order
to do distributed processing of TaxBrain runs and in order to maintain
the privacy of the IRS-SOI PUF data being used by TaxBrain. Maintaining
Expand All @@ -16,12 +18,14 @@
import time
import numpy as np
import pandas as pd
from taxcalc.tbi.tbi_utils import (calculate,
from taxcalc.tbi.tbi_utils import (check_years_return_first_year,
calculate,
random_seed,
summary,
AGGR_ROW_NAMES)
from taxcalc import (results, DIST_TABLE_LABELS,
proportional_change_gdp, Growdiff, Growfactors, Policy)
proportional_change_in_gdp,
Growdiff, Growfactors, Policy)


# specify constants
Expand Down Expand Up @@ -99,6 +103,7 @@ def run_nth_year_tax_calc_model(year_n, start_year,
start_time = time.time()

# create calc1 and calc2 calculated for year_n and mask
check_years_return_first_year(year_n, start_year, use_puf_not_cps)
(calc1, calc2, mask) = calculate(year_n, start_year,
use_puf_not_cps, use_full_sample,
user_mods,
Expand Down Expand Up @@ -178,17 +183,22 @@ def run_nth_year_gdp_elast_model(year_n, start_year,
Setting use_full_sample=False implies use sub-sample of input file;
otherwsie, use the complete sample.
"""
# pylint: disable=too-many-arguments

# create calc1 and calc2 calculated for year_n
(calc1, calc2, _) = calculate(year_n, start_year,
use_puf_not_cps,
use_full_sample,
user_mods,
behavior_allowed=False)
# pylint: disable=too-many-arguments,too-many-locals

# compute GDP effect given specified gdp_elasticity
gdp_effect = proportional_change_gdp(calc1, calc2, gdp_elasticity)
# calculate gdp_effect
fyear = check_years_return_first_year(year_n, start_year, use_puf_not_cps)
if (start_year + year_n) > fyear:
# create calc1 and calc2 calculated for year_n - 1
(calc1, calc2, _) = calculate((year_n - 1), start_year,
use_puf_not_cps,
use_full_sample,
user_mods,
behavior_allowed=False)
# compute GDP effect given specified gdp_elasticity
gdp_effect = proportional_change_in_gdp((start_year + year_n),
calc1, calc2, gdp_elasticity)
else:
gdp_effect = 0.0

# return gdp_effect results
if return_dict:
Expand Down
19 changes: 13 additions & 6 deletions taxcalc/tbi/tbi_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,28 @@
WEBAPP_INCOME_BINS, read_egg_csv)


def check_years(start_year, year_n):
def check_years_return_first_year(year_n, start_year, use_puf_not_cps):
"""
Ensure start_year and year_n values are consistent with Policy constants.
Ensure year_n and start_year values are valid given input data used.
Return value of first year, which is maximum of first records data year
and first policy parameter year.
"""
if start_year < Policy.JSON_START_YEAR:
msg = 'start_year={} < Policy.JSON_START_YEAR={}'
raise ValueError(msg.format(start_year, Policy.JSON_START_YEAR))
if year_n < 0:
msg = 'year_n={} < 0'
raise ValueError(msg.format(year_n))
if use_puf_not_cps:
first_data_year = Records.PUFCSV_YEAR
else:
first_data_year = Records.CPSCSV_YEAR
first_year = max(Policy.JSON_START_YEAR, first_data_year)
if start_year < first_year:
msg = 'start_year={} < first_year={}'
raise ValueError(msg.format(start_year, first_year))
if (start_year + year_n) > Policy.LAST_BUDGET_YEAR:
msg = '(start_year={} + year_n={}) > Policy.LAST_BUDGET_YEAR={}'
raise ValueError(msg.format(start_year, year_n,
Policy.LAST_BUDGET_YEAR))
return first_year


def check_user_mods(user_mods):
Expand Down Expand Up @@ -70,7 +78,6 @@ def calculate(year_n, start_year,
# pylint: disable=too-many-arguments,too-many-locals
# pylint: disable=too-many-branches,too-many-statements

check_years(start_year, year_n)
check_user_mods(user_mods)

# specify Consumption instance
Expand Down
31 changes: 26 additions & 5 deletions taxcalc/tests/test_macro_elasticity.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,34 @@
from taxcalc import Policy, Records, Calculator, proportional_change_gdp
"""
Tests of proportional_change_in_gdp function.
"""
# CODING-STYLE CHECKS:
# pep8 --ignore=E402 test_macro_elasticity.py
# pylint --disable=locally-disabled test_macro_elasticity.py

import pytest
from taxcalc import (Policy, Records, # pylint: disable=import-error
Calculator, proportional_change_in_gdp)

def test_proportional_change_gdp(cps_subsample):

def test_proportional_change_in_gdp(cps_subsample):
"""
Test correct and incorrect calls to proportional_change_in_gdp function.
"""
rec1 = Records.cps_constructor(data=cps_subsample)
calc1 = Calculator(policy=Policy(), records=rec1)
rec2 = Records.cps_constructor(data=cps_subsample)
pol2 = Policy()
reform = {2013: {'_II_em': [0.0]}} # reform increases taxes and MTRs
reform = {2015: {'_II_em': [0.0]}} # reform increases taxes and MTRs
pol2.implement_reform(reform)
calc2 = Calculator(policy=pol2, records=rec2)
gdp_diff = proportional_change_gdp(calc1, calc2, elasticity=0.36)
assert gdp_diff < 0. # higher MTRs imply negative GDP effect
assert calc1.current_year == 2014 # because using CPS data
gdpc = proportional_change_in_gdp(2015, calc1, calc2, elasticity=0.36)
assert gdpc == 0.0 # no effect in first year of reform
calc1.increment_year()
calc2.increment_year()
assert calc1.current_year == 2015
gdpc = proportional_change_in_gdp(2016, calc1, calc2, elasticity=0.36)
assert gdpc < 0.0 # higher average MTR implies reduction in GDP
# don't increment year to 2016
with pytest.raises(ValueError):
gdpc = proportional_change_in_gdp(2017, calc1, calc2, elasticity=0.36)
4 changes: 2 additions & 2 deletions taxcalc/tests/test_records.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import pandas as pd
import pytest
from io import StringIO
from taxcalc import Growfactors, Policy, Records, Calculator, CPSCSV_YEAR
from taxcalc import Growfactors, Policy, Records, Calculator


def test_incorrect_Records_instantiation(cps_subsample):
Expand Down Expand Up @@ -42,7 +42,7 @@ def test_correct_Records_instantiation(cps_subsample):
gfactors=Growfactors(),
weights=wghts_df,
adjust_ratios=ratio_df,
start_year=CPSCSV_YEAR)
start_year=Records.CPSCSV_YEAR)
assert rec2
assert np.all(rec2.MARS != 0)
assert rec2.current_year == rec2.data_year
Expand Down
2 changes: 1 addition & 1 deletion taxcalc/tests/test_tbi.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
(2017, 10)])
def test_check_years_errors(start_year, year_n):
with pytest.raises(ValueError):
check_years(start_year, year_n)
check_years_return_first_year(year_n, start_year, use_puf_not_cps=True)


def test_check_user_mods_errors():
Expand Down

0 comments on commit 8ee99a3

Please sign in to comment.