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

New growth rate handles #383

Merged
merged 17 commits into from
Oct 14, 2015
1 change: 1 addition & 0 deletions taxcalc/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from .calculate import *
from .policy import *
from .behavior import *
from .growth import *
from .records import *
from .utils import *
from .decorators import *
Expand Down
14 changes: 13 additions & 1 deletion taxcalc/calculate.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from .policy import Policy
from .records import Records
from .behavior import Behavior
from .growth import *
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggest:

from .growth import adjustment, target

(and any other functions needed here)


all_cols = set()

Expand All @@ -26,18 +27,24 @@ def add_df(alldfs, df):
class Calculator(object):

def __init__(self, policy=None, records=None,
sync_years=True, behavior=None, **kwargs):
sync_years=True, behavior=None, growth=None, **kwargs):

if isinstance(policy, Policy):
self._policy = policy
else:
msg = 'Must supply tax parameters as a Policy object'
raise ValueError(msg)

if isinstance(behavior, Behavior):
self.behavior = behavior
else:
self.behavior = Behavior(start_year=policy.start_year)

if isinstance(growth, Growth):
self.growth = growth
else:
self.growth = Growth(start_year=policy.start_year)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm.. this idiom seems bad to me (used for both Growth and Behavior). If someone passes a Growth object for growth, then we set it to self.growth. If they pass anything else (a string, some other kind of object, whatever), we just silently make a new Growth object and assign it to self.growth. Basically the user could assume he/she is sending in an object for growth but actually ending up with something else (with no error/warning message). That seems bad. How about something like this?

if growth:
    if isinstance(growth, Growth):
        self.growth = growth
    else:
        raise ValueError("Must supply growth as a Growth object")
else:
    self.growth = Growth(start_year=policy.start_year


if isinstance(records, Records):
self._records = records
elif isinstance(records, str):
Expand Down Expand Up @@ -139,6 +146,11 @@ def calc_all_test(self):
return totaldf

def increment_year(self):
adjustment(self, self.growth.factor_adjustment,
self.policy.current_year + 1)
target(self, self.growth._factor_target,
self.policy._inflation_rates,
self.policy.current_year + 1)
self.records.increment_year()
self.policy.set_year(self.policy.current_year + 1)
self.behavior.set_year(self.policy.current_year)
Expand Down
26 changes: 26 additions & 0 deletions taxcalc/growth.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Amy-Xu could you take a crack at filling these in? Here is a suggestion, although please feel free to modify these.

long_name: "Deviation from CBO forecast of baseline economic growth (percentage point)"

description: "The data underlying this model are extrapolated to roughly match the CBO's projection of the economy's development over the 10-year federal budget window, with each type of economic data extrapolated at a different growth rate. This parameter allows a factor to be subtracted or added to those growth rates for the construction of the economic baseline. For example if you supply .02 (or 2%), then 0.02 will be added to the wage and salary growth rate, interest income growth rate, dividend growth rate, schedule E income growth rate, and all other growth rates used to extrapolate the underlying dataset."

long_name: "Replacement for CBO real GDP growth in economic baseline (percent)".

description: "The data underlying this model are extrapolated to roughly match the CBO's projection of the economy's development over the 10-year federal budget window, with each type of economic data extrapolated at a different growth rate. One of the growth rates taken from the CBO is GDP growth. This parameter allows you to specify a real GDP growth rate, and all other rates will be modified to maintain the distance between them and GDP growth in the CBO baseline. For example, if the CBO growth rate for one year is 0.02 and the user enters 0.018 for this parameter, then 0.002 will be subtracted from every growth rate in the construction of the economic baseline, including wage and salary growth, interest income growth, dividend growth, and many others.

cc @feenberg @martinholmer

"_factor_adjustment":{
"long_name": "",
"description": "",
"irs_ref": "",
"start_year": 2013,
"col_var": "",
"row_var": "",
"row_label": ["2013"],
"cpi_inflated": false,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't need the cpi_inflated attribute for this param.

"col_label": "",
"value": [[0.0]]
},
"_factor_target":{
"long_name": "",
"description": "",
"irs_ref": "",
"start_year": 2013,
"col_var": "",
"row_var": "",
"row_label": ["2013"],
"cpi_inflated": false,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't need the cpi_inflated attribute for this param.

"col_label": "",
"value": [[0.0]]
}
}
97 changes: 97 additions & 0 deletions taxcalc/growth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import copy
import json
import os
import numpy as np
from .policy import Policy
from .parameters_base import ParametersBase


class Growth(ParametersBase):

JSON_START_YEAR = Policy.JSON_START_YEAR
DEFAULTS_FILENAME = 'growth.json'
DEFAULT_NUM_YEARS = Policy.DEFAULT_NUM_YEARS
DEFAULT_INFLATION_RATES = {2013: 0.015, 2014: 0.020, 2015: 0.022,
2016: 0.020, 2017: 0.021, 2018: 0.022,
2019: 0.023, 2020: 0.024, 2021: 0.024,
2022: 0.024, 2023: 0.024, 2024: 0.024}

def __init__(self, growth_dict=None,
start_year=JSON_START_YEAR,
num_years=DEFAULT_NUM_YEARS,
inflation_rates=DEFAULT_INFLATION_RATES):
if growth_dict:
if not isinstance(growth_dict, dict):
raise ValueError('growth_dict is not a dictionary')
self._vals = growth_dict
else: # if None, read defaults
self._vals = self._params_dict_from_json_file()

self.initialize(start_year, num_years)

def update_economic_growth(self, reform):
'''Update economic growth rates/targets
for a reform, a dictionary
consisting of year: modification dictionaries. For example:
{2014: {'_BE_inc': [0.4, 0.3]}}'''
self.set_default_vals()
if self.current_year != self.start_year:
self.set_year(self.start_year)

for year in reform:
if year != self.start_year:
self.set_year(year)
self._update({year: reform[year]})


def adjustment(calc, percentage, year):
records = calc.records
records.BF.AGDPN[year] += percentage * abs(records.BF.AGDPN[year] - 1)
records.BF.ATXPY[year] += percentage * abs(records.BF.ATXPY[year] - 1)
records.BF.AWAGE[year] += percentage * abs(records.BF.AWAGE[year] - 1)
records.BF.ASCHCI[year] += percentage * abs(records.BF.ASCHCI[year] - 1)
records.BF.ASCHCL[year] += percentage * abs(records.BF.ASCHCL[year] - 1)
records.BF.ASCHF[year] += percentage * abs(records.BF.ASCHF[year] - 1)
records.BF.AINTS[year] += percentage * abs(records.BF.AINTS[year] - 1)
records.BF.ADIVS[year] += percentage * abs(records.BF.ADIVS[year] - 1)
records.BF.ACGNS[year] += percentage * abs(records.BF.ACGNS[year] - 1)
records.BF.ASCHEI[year] += percentage * abs(records.BF.ASCHEI[year] - 1)
records.BF.ASCHEL[year] += percentage * abs(records.BF.ASCHEL[year] - 1)
records.BF.ABOOK[year] += percentage * abs(records.BF.ABOOK[year] - 1)
records.BF.ACPIU[year] += percentage * abs(records.BF.ACPIU[year] - 1)
records.BF.ACPIM[year] += percentage * abs(records.BF.ACPIM[year] - 1)
records.BF.ASOCSEC[year] += percentage * abs(records.BF.ASOCSEC[year] - 1)
records.BF.AUCOMP[year] += percentage * abs(records.BF.AUCOMP[year] - 1)
records.BF.AIPD[year] += percentage * abs(records.BF.AIPD[year] - 1)


def target(calc, target, inflation, year):
# 2013 is the start year of all parameter arrays. Hard coded for now.
# Need to be fixed later
records = calc.records
default_year = calc.policy.JSON_START_YEAR
if year >= default_year and target[year - default_year] != 0:
# user inputs theoretically should be based on GDP
g = abs(records.BF.AGDPN[year] - 1)
ratio = (target[year - default_year] +
inflation[year - default_year]) / g

# apply this ratio to all the dollar amount factors
records.BF.AGDPN[year] = ratio * abs(records.BF.AGDPN[year] - 1) + 1
records.BF.ATXPY[year] = ratio * abs(records.BF.ATXPY[year] - 1) + 1
records.BF.AWAGE[year] = ratio * abs(records.BF.AWAGE[year] - 1) + 1
records.BF.ASCHCI[year] = ratio * abs(records.BF.ASCHCI[year] - 1) + 1
records.BF.ASCHCL[year] = ratio * abs(records.BF.ASCHCL[year] - 1) + 1
records.BF.ASCHF[year] = ratio * abs(records.BF.ASCHF[year] - 1) + 1
records.BF.AINTS[year] = ratio * abs(records.BF.AINTS[year] - 1) + 1
records.BF.ADIVS[year] = ratio * abs(records.BF.ADIVS[year] - 1) + 1
records.BF.ACGNS[year] = ratio * abs(records.BF.ACGNS[year] - 1) + 1
records.BF.ASCHEI[year] = ratio * abs(records.BF.ASCHEI[year] - 1) + 1
records.BF.ASCHEL[year] = ratio * abs(records.BF.ASCHEL[year] - 1) + 1
records.BF.ABOOK[year] = ratio * abs(records.BF.ABOOK[year] - 1) + 1
records.BF.ACPIU[year] = ratio * abs(records.BF.ACPIU[year] - 1) + 1
records.BF.ACPIM[year] = ratio * abs(records.BF.ACPIM[year] - 1) + 1
records.BF.ASOCSEC[year] = (ratio *
abs(records.BF.ASOCSEC[year] - 1) + 1)
records.BF.AUCOMP[year] = ratio * abs(records.BF.AUCOMP[year] - 1) + 1
records.BF.AIPD[year] = ratio * abs(records.BF.AIPD[year] - 1) + 1
5 changes: 2 additions & 3 deletions taxcalc/parameters_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,10 @@ def set_default_vals(self):
for name, data in self._vals.items():
cpi_inflated = data.get('cpi_inflated', False)
values = data['value']
i_rates = getattr(self, '_inflation_rates', None)
setattr(self, name,
self.expand_array(values, inflate=cpi_inflated,
inflation_rates=(getattr(self,
'_inflation_rates',
None)),
inflation_rates=i_rates,
num_years=self._num_years))
self.set_year(self._start_year)

Expand Down
2 changes: 1 addition & 1 deletion taxcalc/tests/test_behavior.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
CUR_PATH = os.path.abspath(os.path.dirname(__file__))
sys.path.append(os.path.join(CUR_PATH, "../../"))
import pandas as pd
from taxcalc import Policy, Records, Calculator, behavior, Behavior
from taxcalc import Policy, Records, Calculator, behavior, Behavior, Growth


WEIGHTS_FILENAME = "../../WEIGHTS_testing.csv"
Expand Down
2 changes: 1 addition & 1 deletion taxcalc/tests/test_calculate.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import pandas as pd
import tempfile
import pytest
from taxcalc import Policy, Records, Calculator
from taxcalc import Policy, Records, Calculator, Growth
from taxcalc import create_distribution_table, create_difference_table


Expand Down