def test_make_calculator_with_multiyear_reform(cps_subsample): """ Test Calculator class ctor with multi-year policy reform. """ rec = Records.cps_constructor(data=cps_subsample) year = rec.current_year # create a Policy object and apply a policy reform pol = Policy() reform = {2015: {}, 2016: {}} reform[2015]['_II_em'] = [5000, 6000] # reform values for 2015 and 2016 reform[2015]['_II_em_cpi'] = False reform[2016]['_STD_Aged'] = [[1600, 1300, 1600, 1300, 1600]] pol.implement_reform(reform) # create a Calculator object using this policy-reform calc = Calculator(policy=pol, records=rec) # check that Policy object embedded in Calculator object is correct assert pol.num_years == Policy.DEFAULT_NUM_YEARS assert calc.current_year == year assert calc.policy_param('II_em') == 3950 exp_II_em = [3900, 3950, 5000] + [6000] * (Policy.DEFAULT_NUM_YEARS - 3) assert np.allclose(calc.policy_param('_II_em'), np.array(exp_II_em)) calc.increment_year() calc.increment_year() assert calc.current_year == 2016 assert np.allclose(calc.policy_param('STD_Aged'), np.array([1600, 1300, 1600, 1300, 1600]))
def test_calculator_mtr_when_PT_rates_differ(): """ Test Calculator mtr method in special case. """ reform = {2013: {'_II_rt1': [0.40], '_II_rt2': [0.40], '_II_rt3': [0.40], '_II_rt4': [0.40], '_II_rt5': [0.40], '_II_rt6': [0.40], '_II_rt7': [0.40], '_PT_rt1': [0.30], '_PT_rt2': [0.30], '_PT_rt3': [0.30], '_PT_rt4': [0.30], '_PT_rt5': [0.30], '_PT_rt6': [0.30], '_PT_rt7': [0.30]}} funit = ( u'RECID,MARS,FLPDYR,e00200,e00200p,e00900,e00900p,extraneous\n' u'1, 1, 2009, 200000,200000, 100000,100000, 9999999999\n' ) rec = Records(pd.read_csv(StringIO(funit))) pol = Policy() calc1 = Calculator(policy=pol, records=rec) (_, mtr1, _) = calc1.mtr(variable_str='p23250') pol.implement_reform(reform) calc2 = Calculator(policy=pol, records=rec) (_, mtr2, _) = calc2.mtr(variable_str='p23250') assert np.allclose(mtr1, mtr2, rtol=0.0, atol=1e-06)
def test_Calculator_mtr_when_PT_rates_differ(): reform = {2013: {'_II_rt1': [0.40], '_II_rt2': [0.40], '_II_rt3': [0.40], '_II_rt4': [0.40], '_II_rt5': [0.40], '_II_rt6': [0.40], '_II_rt7': [0.40], '_PT_rt1': [0.30], '_PT_rt2': [0.30], '_PT_rt3': [0.30], '_PT_rt4': [0.30], '_PT_rt5': [0.30], '_PT_rt6': [0.30], '_PT_rt7': [0.30]}} funit = ( u'RECID,MARS,FLPDYR,e00200,e00200p,e00900,e00900p\n' u'1, 1, 2015, 200000,200000, 100000,100000\n' ) pol1 = Policy() rec1 = Records(pd.read_csv(StringIO(funit))) calc1 = Calculator(policy=pol1, records=rec1) (_, mtr1, _) = calc1.mtr(variable_str='p23250') pol2 = Policy() pol2.implement_reform(reform) rec2 = Records(pd.read_csv(StringIO(funit))) calc2 = Calculator(policy=pol2, records=rec2) (_, mtr2, _) = calc2.mtr(variable_str='p23250') assert np.allclose(mtr1, mtr2, rtol=0.0, atol=1e-06)
def test_read_json_reform_file_and_implement_reform_a(reform_file): """ Test reading and translation of reform file into a reform dictionary that is then used to call implement_reform method. NOTE: implement_reform called when policy.current_year == policy.start_year """ reform = Policy.read_json_reform_file(reform_file.name) policy = Policy() policy.implement_reform(reform) syr = policy.start_year amt_tthd = policy._AMT_tthd assert amt_tthd[2015 - syr] == 200000 assert amt_tthd[2016 - syr] > 200000 assert amt_tthd[2017 - syr] == 300000 assert amt_tthd[2018 - syr] > 300000 ii_em = policy._II_em assert ii_em[2016 - syr] == 6000 assert ii_em[2017 - syr] == 6000 assert ii_em[2018 - syr] == 7500 assert ii_em[2019 - syr] > 7500 assert ii_em[2020 - syr] == 9000 assert ii_em[2021 - syr] > 9000 amt_em = policy._AMT_em assert amt_em[2016 - syr, 0] > amt_em[2015 - syr, 0] assert amt_em[2017 - syr, 0] > amt_em[2016 - syr, 0] assert amt_em[2018 - syr, 0] == amt_em[2017 - syr, 0] assert amt_em[2019 - syr, 0] == amt_em[2017 - syr, 0] assert amt_em[2020 - syr, 0] == amt_em[2017 - syr, 0] assert amt_em[2021 - syr, 0] > amt_em[2020 - syr, 0] assert amt_em[2022 - syr, 0] > amt_em[2021 - syr, 0]
def diff_in_revenue(reform_on_II, orig_reform): policy_func = Policy() puf = pd.read_csv("./puf.csv") records_func = Records(puf) calc_func = Calculator(policy = policy_func, records = records_func) policy_bench = Policy() records_bench = Records(puf) calc_bench = Calculator(policy = policy_bench, records = records_bench) reform = { CURRENT_YEAR:{ "_II_rt1":[max(policy_bench._II_rt1[0] *(1 - reform_on_II),0.0)], "_II_rt2":[max(policy_bench._II_rt2[0] *(1 - reform_on_II),0.0)], "_II_rt3":[max(policy_bench._II_rt3[0] *(1 - reform_on_II),0.0)], "_II_rt4":[max(policy_bench._II_rt4[0] *(1 - reform_on_II),0.0)], "_II_rt5":[max(policy_bench._II_rt5[0] *(1 - reform_on_II),0.0)], "_II_rt6":[max(policy_bench._II_rt6[0] *(1 - reform_on_II),0.0)], "_II_rt7":[max(policy_bench._II_rt7[0] *(1 - reform_on_II),0.0)]} } policy_func.implement_reform(reform) policy_func.implement_reform(orig_reform) calc_func.advance_to_year(CURRENT_YEAR) calc_bench.advance_to_year(CURRENT_YEAR) calc_func.calc_all() calc_bench.calc_all() ans = ((calc_bench.records._combined*calc_bench.records.s006).sum()-(calc_func.records._combined*calc_func.records.s006).sum()) print("diff in revenue is ", ans) return ans
def test_Policy_reform_in_start_year(): ppo = Policy(start_year=2013) reform = {2013: {'_STD_Aged': [[1400, 1100, 1100, 1400, 1400, 1199]]}} ppo.implement_reform(reform) assert_allclose(ppo.STD_Aged, np.array([1400, 1100, 1100, 1400, 1400, 1199]), atol=0.01, rtol=0.0)
def test_strip_Nones(): x = [None, None] assert Policy.strip_Nones(x) == [] x = [1, 2, None] assert Policy.strip_Nones(x) == [1, 2] x = [[1, 2, 3], [4, None, None]] assert Policy.strip_Nones(x) == [[1, 2, 3], [4, -1, -1]]
def test_make_calculator_with_policy_reform(cps_subsample): """ Test Calculator class ctor with policy reform. """ rec = Records.cps_constructor(data=cps_subsample) year = rec.current_year # create a Policy object and apply a policy reform pol = Policy() reform = {2013: {'_II_em': [4000], '_II_em_cpi': False, '_STD_Aged': [[1600, 1300, 1300, 1600, 1600]], '_STD_Aged_cpi': False}} pol.implement_reform(reform) # create a Calculator object using this policy reform calc = Calculator(policy=pol, records=rec) # check that Policy object embedded in Calculator object is correct assert calc.current_year == year assert calc.policy_param('II_em') == 4000 assert np.allclose(calc.policy_param('_II_em'), np.array([4000] * Policy.DEFAULT_NUM_YEARS)) exp_STD_Aged = [[1600, 1300, 1300, 1600, 1600]] * Policy.DEFAULT_NUM_YEARS assert np.allclose(calc.policy_param('_STD_Aged'), np.array(exp_STD_Aged)) assert np.allclose(calc.policy_param('STD_Aged'), np.array([1600, 1300, 1300, 1600, 1600]))
def test_dec_graph_plots(cps_subsample): pol = Policy() rec = Records.cps_constructor(data=cps_subsample) calc1 = Calculator(policy=pol, records=rec) year = 2020 calc1.advance_to_year(year) reform = { 'SS_Earnings_c': {year: 9e99}, # OASDI FICA tax on all earnings 'FICA_ss_trt': {year: 0.107484} # lower rate to keep revenue unchanged } pol.implement_reform(reform) calc2 = Calculator(policy=pol, records=rec) calc2.advance_to_year(year) assert calc1.current_year == calc2.current_year calc1.calc_all() calc2.calc_all() fig = calc1.decile_graph(calc2) assert fig dt1, dt2 = calc1.distribution_tables(calc2, 'weighted_deciles') dta = dec_graph_data(dt1, dt2, year, include_zero_incomes=True, include_negative_incomes=False) assert isinstance(dta, dict) dta = dec_graph_data(dt1, dt2, year, include_zero_incomes=False, include_negative_incomes=True) assert isinstance(dta, dict) dta = dec_graph_data(dt1, dt2, year, include_zero_incomes=False, include_negative_incomes=False) assert isinstance(dta, dict)
def test_make_Calculator_with_reform_after_start_year(): # create Policy object using custom indexing rates irates = {2013: 0.01, 2014: 0.01, 2015: 0.02, 2016: 0.01, 2017: 0.03} parm = Policy(start_year=2013, num_years=len(irates), inflation_rates=irates) # specify reform in 2015, which is two years after Policy start_year reform = {2015: {}, 2016: {}} reform[2015]['_STD_Aged'] = [[1600, 1300, 1600, 1300, 1600, 1300]] reform[2015]['_II_em'] = [5000] reform[2016]['_II_em'] = [6000] reform[2016]['_II_em_cpi'] = False parm.implement_reform(reform) recs = Records(data=TAX_DTA, weights=WEIGHTS, start_year=2009) calc = Calculator(policy=parm, records=recs) # compare actual and expected parameter values over all years exp_STD_Aged = np.array([[1500, 1200, 1200, 1500, 1500, 1200], [1550, 1200, 1200, 1550, 1550, 1200], [1600, 1300, 1600, 1300, 1600, 1300], [1632, 1326, 1632, 1326, 1632, 1326], [1648, 1339, 1648, 1339, 1648, 1339]]) exp_II_em = np.array([3900, 3950, 5000, 6000, 6000]) assert_array_equal(calc.policy._STD_Aged, exp_STD_Aged) assert_array_equal(calc.policy._II_em, exp_II_em) # compare actual and expected values for 2015 calc.increment_year() calc.increment_year() assert calc.current_year == 2015 exp_2015_II_em = 5000 assert_array_equal(calc.policy.II_em, exp_2015_II_em) exp_2015_STD_Aged = np.array([1600, 1300, 1600, 1300, 1600, 1300]) assert_array_equal(calc.policy.STD_Aged, exp_2015_STD_Aged)
def test_Policy_reform_makes_no_changes_before_year(): ppo = Policy(start_year=2013) reform = {2015: {'_II_em': [4400], '_II_em_cpi': True}} ppo.implement_reform(reform) ppo.set_year(2015) assert_array_equal(ppo._II_em[:3], np.array([3900, 3950, 4400])) assert ppo.II_em == 4400
def test_Policy_reform_after_start_year(): ppo = Policy(start_year=2013) reform = {2015: {'_STD_Aged': [[1400, 1100, 1100, 1400, 1400, 1199]]}} ppo.implement_reform(reform) ppo.set_year(2015) assert_array_equal(ppo.STD_Aged, np.array([1400, 1100, 1100, 1400, 1400, 1199]))
def test_make_Calculator_increment_years_first(): # create Policy object with custom indexing rates and policy reform irates = {2013: 0.01, 2014: 0.01, 2015: 0.02, 2016: 0.01, 2017: 0.03} policy = Policy(start_year=2013, inflation_rates=irates, num_years=len(irates)) reform = {2015: {}, 2016: {}} reform[2015]['_STD_Aged'] = [[1600, 1300, 1600, 1300, 1600, 1300]] reform[2015]['_II_em'] = [5000] reform[2016]['_II_em'] = [6000] reform[2016]['_II_em_cpi'] = False policy.implement_reform(reform) # create Records object by reading 1991 data and saying it is 2009 data tax_dta = pd.read_csv(TAX_DTA_PATH, compression='gzip') puf = Records(tax_dta, weights=WEIGHTS, start_year=2009) # create Calculator object with Policy object as modified by reform calc = Calculator(policy=policy, records=puf) # compare expected policy parameter values with those embedded in calc exp_STD_Aged = np.array([[1500, 1200, 1200, 1500, 1500, 1200], [1550, 1200, 1200, 1550, 1550, 1200], [1600, 1300, 1600, 1300, 1600, 1300], [1632, 1326, 1632, 1326, 1632, 1326], [1648, 1339, 1648, 1339, 1648, 1339]]) exp_II_em = np.array([3900, 3950, 5000, 6000, 6000]) assert_array_equal(calc.policy._STD_Aged, exp_STD_Aged) assert_array_equal(calc.policy._II_em, exp_II_em)
def test_make_calculator_increment_years_first(cps_subsample): """ Test Calculator inflation indexing of policy parameters. """ # pylint: disable=too-many-locals # create Policy object with policy reform pol = Policy() reform = {2015: {}, 2016: {}} std5 = 2000 reform[2015]['_STD_Aged'] = [[std5, std5, std5, std5, std5]] reform[2015]['_II_em'] = [5000] reform[2016]['_II_em'] = [6000] reform[2016]['_II_em_cpi'] = False pol.implement_reform(reform) # create Calculator object with Policy object as modified by reform rec = Records.cps_constructor(data=cps_subsample) calc = Calculator(policy=pol, records=rec) # compare expected policy parameter values with those embedded in calc irates = pol.inflation_rates() syr = Policy.JSON_START_YEAR irate2015 = irates[2015 - syr] irate2016 = irates[2016 - syr] std6 = std5 * (1.0 + irate2015) std7 = std6 * (1.0 + irate2016) exp_STD_Aged = np.array([[1500, 1200, 1200, 1500, 1500], [1550, 1200, 1200, 1550, 1550], [std5, std5, std5, std5, std5], [std6, std6, std6, std6, std6], [std7, std7, std7, std7, std7]]) act_STD_Aged = calc.policy_param('_STD_Aged') assert np.allclose(act_STD_Aged[:5], exp_STD_Aged) exp_II_em = np.array([3900, 3950, 5000, 6000, 6000]) act_II_em = calc.policy_param('_II_em') assert np.allclose(act_II_em[:5], exp_II_em)
def test_reform_pkey_year(): with pytest.raises(ValueError): rdict = Policy._reform_pkey_year({4567: {2013: [40000]}}) with pytest.raises(ValueError): rdict = Policy._reform_pkey_year({'_II_em': 40000}) with pytest.raises(ValueError): rdict = Policy._reform_pkey_year({'_II_em': {'2013': [40000]}})
def test_calculator_using_nonstd_input(rawinputfile): """ Test Calculator using non-standard input records. """ # check Calculator handling of raw, non-standard input data with no aging pol = Policy() pol.set_year(RAWINPUTFILE_YEAR) # set policy params to input data year nonstd = Records(data=rawinputfile.name, gfactors=None, # keeps raw data unchanged weights=None, start_year=RAWINPUTFILE_YEAR) # set raw input data year assert nonstd.array_length == RAWINPUTFILE_FUNITS calc = Calculator(policy=pol, records=nonstd, sync_years=False) # keeps raw data unchanged assert calc.current_year == RAWINPUTFILE_YEAR calc.calc_all() assert calc.weighted_total('e00200') == 0 assert calc.total_weight() == 0 varlist = ['RECID', 'MARS'] dframe = calc.dataframe(varlist) assert isinstance(dframe, pd.DataFrame) assert dframe.shape == (RAWINPUTFILE_FUNITS, len(varlist)) mars = calc.array('MARS') assert isinstance(mars, np.ndarray) assert mars.shape == (RAWINPUTFILE_FUNITS,) exp_iitax = np.zeros((nonstd.array_length,)) assert np.allclose(calc.array('iitax'), exp_iitax) mtr_ptax, _, _ = calc.mtr(wrt_full_compensation=False) exp_mtr_ptax = np.zeros((nonstd.array_length,)) exp_mtr_ptax.fill(0.153) assert np.allclose(mtr_ptax, exp_mtr_ptax)
def test_make_Calculator_with_reform_after_start_year(records_2009): # create Policy object using custom indexing rates irates = {2013: 0.01, 2014: 0.01, 2015: 0.02, 2016: 0.01, 2017: 0.03} parm = Policy(start_year=2013, num_years=len(irates), inflation_rates=irates) # specify reform in 2015, which is two years after Policy start_year reform = {2015: {}, 2016: {}} reform[2015]['_STD_Aged'] = [[1600, 1300, 1600, 1300, 1600, 1300]] reform[2015]['_II_em'] = [5000] reform[2016]['_II_em'] = [6000] reform[2016]['_II_em_cpi'] = False parm.implement_reform(reform) calc = Calculator(policy=parm, records=records_2009) # compare actual and expected parameter values over all years assert np.allclose(calc.policy._STD_Aged, np.array([[1500, 1200, 1200, 1500, 1500, 1200], [1550, 1200, 1200, 1550, 1550, 1200], [1600, 1300, 1600, 1300, 1600, 1300], [1632, 1326, 1632, 1326, 1632, 1326], [1648, 1339, 1648, 1339, 1648, 1339]]), atol=0.5, rtol=0.0) # handles rounding to dollars assert np.allclose(calc.policy._II_em, np.array([3900, 3950, 5000, 6000, 6000])) # compare actual and expected values for 2015 calc.increment_year() calc.increment_year() assert calc.current_year == 2015 assert np.allclose(calc.policy.II_em, 5000) assert np.allclose(calc.policy.STD_Aged, np.array([1600, 1300, 1600, 1300, 1600, 1300]))
def test_read_json_param_with_suffixes_and_errors(): """ Test interaction of policy parameter suffixes and reform errors (fails without 0.10.2 bug fix as reported by Hank Doupe in PB PR#641) """ reform = { u'policy': { u'_II_brk4_separate': {u'2017': [5000.0]}, u'_STD_separate': {u'2017': [8000.0]}, u'_STD_single': {u'2018': [1000.0]}, u'_II_brk2_headhousehold': {u'2017': [1000.0]}, u'_II_brk4_single': {u'2017': [500.0]}, u'_STD_joint': {u'2017': [10000.0], u'2020': [150.0]}, u'_II_brk2_separate': {u'2017': [1000.0]}, u'_II_brk2_single': {u'2017': [1000.0]}, u'_II_brk2_joint': {u'2017': [1000.0]}, u'_FICA_ss_trt': {u'2017': [-1.0], u'2019': [0.1]}, u'_II_brk4_headhousehold': {u'2017': [500.0]}, u'_STD_headhousehold': {u'2017': [10000.0], u'2020': [150.0]}, u'_ID_Medical_frt': {u'2019': [0.06]}, u'_II_brk4_joint': {u'2017': [500.0]}, u'_ID_BenefitSurtax_Switch_medical': {u'2017': [True]} } } json_reform = json.dumps(reform) params = Calculator.read_json_param_objects(json_reform, None) assert isinstance(params, dict) pol = Policy() pol.ignore_reform_errors() pol.implement_reform(params['policy'], print_warnings=False, raise_errors=False) assert pol.parameter_errors assert pol.parameter_warnings
def test_convert(self): values = {"II_brk2_0": [36000., 38000., 40000.], "II_brk2_1": [72250., 74000.], "II_brk2_2": [36500.] } ans = package_up_vars(values, first_budget_year=FBY) pp = Policy(start_year=2013) pp.set_year(FBY) # irates are rates for 2015, 2016, and 2017 irates = pp.indexing_rates_for_update(param_name='II_brk2', calyear=FBY, num_years_to_expand=3) # User choices propagate through to all future years # The user has specified part of the parameter up to 2017. # So, we choose to fill in the propagated value, which is # either inflated or not. f2_2016 = int(36500 * (1.0 + irates[0])) f3_2016 = int(50200 * (1.0 + irates[0])) f4_2016 = int(74900 * (1.0 + irates[0])) f5_2016 = int(37450 * (1.0 + irates[0])) f1_2017 = int(74000 * (1.0 + irates[1])) f2_2017 = int(f2_2016 * (1.0 + irates[1])) exp = [[36000, 72250, 36500, 50200, 74900, 37450], [38000, 74000, f2_2016, 50400, 75300, 37650], [40000, f1_2017, f2_2017, None, None, None]] assert ans['_II_brk2'] == exp assert len(ans) == 1
def test_create_parameters_from_file(policyfile): with open(policyfile.name) as pfile: policy = json.load(pfile) ppo = Policy(parameter_dict=policy) irates = Policy.default_inflation_rates() inf_rates = [irates[ppo.start_year + i] for i in range(0, ppo.num_years)] assert_allclose(ppo._almdep, Policy.expand_array(np.array([7150, 7250, 7400]), inflate=True, inflation_rates=inf_rates, num_years=ppo.num_years), atol=0.01, rtol=0.0) assert_allclose(ppo._almsep, Policy.expand_array(np.array([40400, 41050]), inflate=True, inflation_rates=inf_rates, num_years=ppo.num_years), atol=0.01, rtol=0.0) assert_allclose(ppo._rt5, Policy.expand_array(np.array([0.33]), inflate=False, inflation_rates=inf_rates, num_years=ppo.num_years), atol=0.01, rtol=0.0) assert_allclose(ppo._rt7, Policy.expand_array(np.array([0.396]), inflate=False, inflation_rates=inf_rates, num_years=ppo.num_years), atol=0.01, rtol=0.0)
def test_distribution_tables(cps_subsample): """ Test distribution_tables method. """ pol = Policy() recs = Records.cps_constructor(data=cps_subsample) calc1 = Calculator(policy=pol, records=recs) assert calc1.current_year == 2014 calc1.calc_all() dt1, dt2 = calc1.distribution_tables(None, 'weighted_deciles') assert isinstance(dt1, pd.DataFrame) assert dt2 is None dt1, dt2 = calc1.distribution_tables(calc1, 'weighted_deciles') assert isinstance(dt1, pd.DataFrame) assert isinstance(dt2, pd.DataFrame) reform = {2014: {'_UBI_u18': [1000], '_UBI_1820': [1000], '_UBI_21': [1000]}} pol.implement_reform(reform) assert not pol.parameter_errors calc2 = Calculator(policy=pol, records=recs) calc2.calc_all() dt1, dt2 = calc1.distribution_tables(calc2, 'weighted_deciles') assert isinstance(dt1, pd.DataFrame) assert isinstance(dt2, pd.DataFrame)
def test_parameters_get_default_start_year(): paramdata = Policy.default_data(metadata=True, start_year=2015) # 1D data, has 2015 values meta_II_em = paramdata['_II_em'] assert meta_II_em['start_year'] == 2015 assert meta_II_em['row_label'] == ['2015', '2016'] assert meta_II_em['value'] == [4000, 4050] # 2D data, has 2015 values meta_std_aged = paramdata['_STD_Aged'] assert meta_std_aged['start_year'] == 2015 assert meta_std_aged['row_label'] == ['2015', '2016'] assert meta_std_aged['value'] == [[1550, 1250, 1250, 1550, 1550, 1250], [1550, 1250, 1250, 1550, 1550, 1250]] # 1D data, doesn't have 2015 values, is CPI inflated meta_amt_thd_marrieds = paramdata['_AMT_thd_MarriedS'] assert meta_amt_thd_marrieds['start_year'] == 2015 assert meta_amt_thd_marrieds['row_label'] == ['2015'] # Take the 2014 parameter value and multiply by inflation for that year should_be = 41050 * (1.0 + Policy.default_inflation_rates()[2014]) meta_amt_thd_marrieds['value'] == should_be # 1D data, doesn't have 2015 values, is not CPI inflated meta_kt_c_age = paramdata['_KT_c_Age'] assert meta_kt_c_age['start_year'] == 2015 assert meta_kt_c_age['row_label'] == ['2015'] assert meta_kt_c_age['value'] == [24]
def taxcalc_results(start_year, reform_dict, itax_clp, ptax_clp): """ Use taxcalc package on this computer to compute aggregate income tax and payroll tax revenue difference (between reform and current-law policy) for ten years beginning with the specified start_year using the specified reform_dict dictionary and the two specified current-law-policy results dictionaries. Return two aggregate tax revenue difference dictionaries indexed by calendar year. """ pol = Policy() pol.implement_reform(reform_dict) calc = Calculator(policy=pol, records=Records(data=PUF_PATH), verbose=False) calc.advance_to_year(start_year) nyears = NUMBER_OF_YEARS adts = list() for iyr in range(-1, nyears - 1): calc.calc_all() adts.append(create_diagnostic_table(calc)) if iyr < nyears: calc.increment_year() adt = pd.concat(adts, axis=1) # note that adt is Pandas DataFrame object itax_ref = adt.xs('Ind Income Tax ($b)').to_dict() ptax_ref = adt.xs('Payroll Taxes ($b)').to_dict() itax_diff = {} ptax_diff = {} for year in itax_ref: itax_diff[year] = round(itax_ref[year] - itax_clp[year], 1) ptax_diff[year] = round(ptax_ref[year] - ptax_clp[year], 1) return (itax_diff, ptax_diff)
def test_Policy_reform_with_default_cpi_flags(): ppo = Policy(start_year=2013) reform = {2015: {'_II_em': [4300]}} ppo.implement_reform(reform) # '_II_em' has a default cpi_flag of True, so # in 2016 its value should be greater than 4300 ppo.set_year(2016) assert ppo.II_em > 4300
def test_reform_with_warning(): """ Try to use warned out-of-range parameter value in reform. """ pol = Policy() reform = {'ID_Medical_frt': {2020: 0.05}} pol.implement_reform(reform) assert pol.parameter_warnings
def test_implement_reform_raises_on_no_year(): """ Test that implement_reform raises error for missing year. """ reform = {'STD_Aged': [1400, 1200, 1400, 1400, 1400]} ppo = Policy() with pytest.raises(ValueError): ppo.implement_reform(reform)
def test_reform_with_out_of_range_error(): """ Try to use out-of-range values versus other parameter values in a reform. """ pol = Policy() reform = {'SS_thd85': {2020: [20000, 20000, 20000, 20000, 20000]}} pol.implement_reform(reform, raise_errors=False) assert pol.parameter_errors
def test_implement_reform_raises_on_early_year(): """ Test that implement_reform raises error for early year. """ ppo = Policy() reform = {'STD_Aged': {2010: [1400, 1100, 1100, 1400, 1400]}} with pytest.raises(ValueError): ppo.implement_reform(reform)
def test_indexing_rates_for_update(): """ Check private _indexing_rates_for_update method. """ pol = Policy() wgrates = pol._indexing_rates_for_update('_SS_Earnings_c', 2017, 10) pirates = pol._indexing_rates_for_update('_II_em', 2017, 10) assert len(wgrates) == len(pirates)
def test_Calculator_current_law_version(records_2009): policy = Policy() reform = {2013: {'_II_rt7': [0.45]}} policy.implement_reform(reform) calc = Calculator(policy=policy, records=records_2009) calc_clp = calc.current_law_version() assert isinstance(calc_clp, Calculator) assert calc.policy.II_rt6 == calc_clp.policy.II_rt6 assert calc.policy.II_rt7 != calc_clp.policy.II_rt7
def test_read_json_param_and_implement_reform(reform_file, set_year): """ Test reading and translation of reform file into a reform dictionary that is then used to call implement_reform method. NOTE: implement_reform called when policy.current_year == policy.start_year """ policy = Policy() if set_year: policy.set_year(2015) param_dict = Calculator.read_json_param_objects(reform_file.name, None) policy.implement_reform(param_dict['policy']) syr = policy.start_year amt_brk1 = policy._AMT_brk1 assert amt_brk1[2015 - syr] == 200000 assert amt_brk1[2016 - syr] > 200000 assert amt_brk1[2017 - syr] == 300000 assert amt_brk1[2018 - syr] > 300000 ii_em = policy._II_em assert ii_em[2016 - syr] == 6000 assert ii_em[2017 - syr] == 6000 assert ii_em[2018 - syr] == 7500 assert ii_em[2019 - syr] > 7500 assert ii_em[2020 - syr] == 9000 assert ii_em[2021 - syr] > 9000 amt_em = policy._AMT_em assert amt_em[2016 - syr, 0] > amt_em[2015 - syr, 0] assert amt_em[2017 - syr, 0] > amt_em[2016 - syr, 0] assert amt_em[2018 - syr, 0] == amt_em[2017 - syr, 0] assert amt_em[2019 - syr, 0] == amt_em[2017 - syr, 0] assert amt_em[2020 - syr, 0] == amt_em[2017 - syr, 0] assert amt_em[2021 - syr, 0] > amt_em[2020 - syr, 0] assert amt_em[2022 - syr, 0] > amt_em[2021 - syr, 0] add4aged = policy._ID_Medical_frt_add4aged assert add4aged[2015 - syr] == -0.025 assert add4aged[2016 - syr] == -0.025 assert add4aged[2017 - syr] == 0.0 assert add4aged[2022 - syr] == 0.0
def test_parameters_get_default(): paramdata = Policy.default_data() assert paramdata['_CDCC_ps'] == [15000]
def test_reform_json_and_output(tests_path): """ Check that each JSON reform file can be converted into a reform dictionary that can then be passed to the Policy class implement_reform method that generates no reform_errors. Then use each reform to generate static tax results for small set of filing units in a single tax_year and compare those results with expected results from a text file. """ # pylint: disable=too-many-statements,too-many-locals used_dist_stats = [ 'c00100', # AGI 'c04600', # personal exemptions 'standard', # standard deduction 'c04800', # regular taxable income 'c05800', # income tax before credits 'iitax', # income tax after credits 'payrolltax', # payroll taxes 'aftertax_income' ] # aftertax expanded income unused_dist_stats = set(DIST_TABLE_COLUMNS) - set(used_dist_stats) renamed_columns = { 'c00100': 'AGI', 'c04600': 'pexempt', 'standard': 'stdded', 'c04800': 'taxinc', 'c05800': 'tax-wo-credits', 'iitax': 'inctax', 'payrolltax': 'paytax', 'aftertax_income': 'ataxinc' } # embedded function used only in test_reform_json_and_output def write_distribution_table(calc, resfilename): """ Write abbreviated distribution table calc to file with resfilename. """ dist, _ = calc.distribution_tables(None, groupby='large_income_bins') for stat in unused_dist_stats: del dist[stat] dist = dist[used_dist_stats] dist.rename(mapper=renamed_columns, axis='columns', inplace=True) pd.options.display.float_format = '{:7.0f}'.format with open(resfilename, 'w') as resfile: dist.to_string(resfile) # embedded function used only in test_reform_json_and_output def res_and_out_are_same(base): """ Return True if base.res and base.out file contents are the same; return False if base.res and base.out file contents differ. """ with open(base + '.res') as resfile: act_res = resfile.read() with open(base + '.out') as outfile: exp_res = outfile.read() # check to see if act_res & exp_res have differences return not nonsmall_diffs(act_res.splitlines(True), exp_res.splitlines(True)) # specify Records object containing cases data tax_year = 2020 cases_path = os.path.join(tests_path, '..', 'reforms', 'cases.csv') cases = Records( data=cases_path, gfactors=None, # keeps raw data unchanged weights=None, adjust_ratios=None, start_year=tax_year) # set raw input data year # specify list of reform failures failures = list() # specify current-law-policy Calculator object calc1 = Calculator(policy=Policy(), records=cases, verbose=False) calc1.advance_to_year(tax_year) calc1.calc_all() res_path = cases_path.replace('cases.csv', 'clp.res') write_distribution_table(calc1, res_path) if res_and_out_are_same(res_path.replace('.res', '')): os.remove(res_path) else: failures.append(res_path) # read 2017_law.json reform file and specify its parameters dictionary pre_tcja_jrf = os.path.join(tests_path, '..', 'reforms', '2017_law.json') pre_tcja = Calculator.read_json_param_objects(pre_tcja_jrf, None) # check reform file contents and reform results for each reform reforms_path = os.path.join(tests_path, '..', 'reforms', '*.json') json_reform_files = glob.glob(reforms_path) for jrf in json_reform_files: # determine reform's baseline by reading contents of jrf with open(jrf, 'r') as rfile: jrf_text = rfile.read() pre_tcja_baseline = 'Reform_Baseline: 2017_law.json' in jrf_text # implement the reform relative to its baseline reform = Calculator.read_json_param_objects(jrf_text, None) pol = Policy() # current-law policy if pre_tcja_baseline: pol.implement_reform(pre_tcja['policy']) pol.implement_reform(reform['policy']) assert not pol.reform_errors calc2 = Calculator(policy=pol, records=cases, verbose=False) calc2.advance_to_year(tax_year) calc2.calc_all() res_path = jrf.replace('.json', '.res') write_distribution_table(calc2, res_path) if res_and_out_are_same(res_path.replace('.res', '')): os.remove(res_path) else: failures.append(res_path) if failures: msg = 'Following reforms have res-vs-out differences:\n' for ref in failures: msg += '{}\n'.format(os.path.basename(ref)) raise ValueError(msg)
def test_behavioral_response(use_puf_not_cps, puf_subsample, cps_fullsample): """ Test that behavioral-response results are the same when generated from standard Tax-Calculator calls and when generated from tbi.run_nth_year_taxcalc_model() calls """ # specify reform and assumptions reform_json = """ {"policy": { "_II_rt5": {"2020": [0.25]}, "_II_rt6": {"2020": [0.25]}, "_II_rt7": {"2020": [0.25]}, "_PT_rt5": {"2020": [0.25]}, "_PT_rt6": {"2020": [0.25]}, "_PT_rt7": {"2020": [0.25]}, "_II_em": {"2020": [1000]} }} """ assump_json = """ {"behavior": {"_BE_sub": {"2013": [0.25]}}, "growdiff_baseline": {}, "growdiff_response": {}, "consumption": {}, "growmodel": {} } """ params = Calculator.read_json_param_objects(reform_json, assump_json) # specify keyword arguments used in tbi function call kwargs = { 'start_year': 2019, 'year_n': 0, 'use_puf_not_cps': use_puf_not_cps, 'use_full_sample': False, 'user_mods': { 'policy': params['policy'], 'behavior': params['behavior'], 'growdiff_baseline': params['growdiff_baseline'], 'growdiff_response': params['growdiff_response'], 'consumption': params['consumption'], 'growmodel': params['growmodel'] }, 'return_dict': False } # generate aggregate results two ways: using tbi and standard calls num_years = 9 std_res = dict() tbi_res = dict() if use_puf_not_cps: rec = Records(data=puf_subsample) else: # IMPORTANT: must use same subsample as used in test_cpscsv.py because # that is the subsample used by run_nth_year_taxcalc_model std_cps_subsample = cps_fullsample.sample(frac=0.03, random_state=180) rec = Records.cps_constructor(data=std_cps_subsample) for using_tbi in [True, False]: for year in range(0, num_years): cyr = year + kwargs['start_year'] if using_tbi: kwargs['year_n'] = year tables = run_nth_year_taxcalc_model(**kwargs) tbi_res[cyr] = dict() for tbl in ['aggr_1', 'aggr_2', 'aggr_d']: tbi_res[cyr][tbl] = tables[tbl] else: pol = Policy() calc1 = Calculator(policy=pol, records=rec) pol.implement_reform(params['policy']) assert not pol.parameter_errors beh = Behavior() beh.update_behavior(params['behavior']) calc2 = Calculator(policy=pol, records=rec, behavior=beh) assert calc2.behavior_has_response() calc1.advance_to_year(cyr) calc2.advance_to_year(cyr) calc2 = Behavior.response(calc1, calc2) std_res[cyr] = dict() for tbl in ['aggr_1', 'aggr_2', 'aggr_d']: if tbl.endswith('_1'): itax = calc1.weighted_total('iitax') ptax = calc1.weighted_total('payrolltax') ctax = calc1.weighted_total('combined') elif tbl.endswith('_2'): itax = calc2.weighted_total('iitax') ptax = calc2.weighted_total('payrolltax') ctax = calc2.weighted_total('combined') elif tbl.endswith('_d'): itax = (calc2.weighted_total('iitax') - calc1.weighted_total('iitax')) ptax = (calc2.weighted_total('payrolltax') - calc1.weighted_total('payrolltax')) ctax = (calc2.weighted_total('combined') - calc1.weighted_total('combined')) cols = ['0_{}'.format(year)] rows = ['ind_tax', 'payroll_tax', 'combined_tax'] datalist = [itax, ptax, ctax] std_res[cyr][tbl] = pd.DataFrame(data=datalist, index=rows, columns=cols) for col in std_res[cyr][tbl].columns: val = std_res[cyr][tbl][col] * 1e-9 std_res[cyr][tbl][col] = round(val, 3) # compare the two sets of results # NOTE that the PUF tbi results have been "fuzzed" for privacy reasons, # so there is no expectation that those results should be identical. no_diffs = True cps_dump = False # setting to True produces dump output and test failure if use_puf_not_cps: reltol = 0.004 # std and tbi differ if more than 0.4 percent different dataset = 'PUF' dumping = False else: # CPS results are not "fuzzed", so reltol = 1e-9 # std and tbi should be virtually identical dataset = 'CPS' dumping = cps_dump for year in range(0, num_years): cyr = year + kwargs['start_year'] do_dump = bool(dumping and cyr >= 2019 and cyr <= 2020) col = '0_{}'.format(year) for tbl in ['aggr_1', 'aggr_2', 'aggr_d']: tbi = tbi_res[cyr][tbl][col] if do_dump: txt = 'DUMP of {} {} table for year {}:' print(txt.format(dataset, tbl, cyr)) print(tbi) std = std_res[cyr][tbl][col] if not np.allclose(tbi, std, atol=0.0, rtol=reltol): no_diffs = False txt = '***** {} diff in {} table for year {} (year_n={}):' print(txt.format(dataset, tbl, cyr, year)) print('TBI RESULTS:') print(tbi) print('STD RESULTS:') print(std) assert no_diffs assert not dumping
def test_agg(tests_path, puf_fullsample): """ Test Tax-Calculator aggregate taxes with no policy reform using the full-sample puf.csv and a small sub-sample of puf.csv """ # pylint: disable=too-many-locals,too-many-statements nyrs = 10 # create a baseline Policy object containing 2017_law.json parameters pre_tcja_jrf = os.path.join(tests_path, '..', 'reforms', '2017_law.json') pre_tcja = Calculator.read_json_param_objects(pre_tcja_jrf, None) baseline_policy = Policy() baseline_policy.implement_reform(pre_tcja['policy']) # create a Records object (rec) containing all puf.csv input records rec = Records(data=puf_fullsample) # create a Calculator object using baseline policy and puf records calc = Calculator(policy=baseline_policy, records=rec) calc_start_year = calc.current_year # create aggregate diagnostic table (adt) as a Pandas DataFrame object adt = calc.diagnostic_table(nyrs) taxes_fullsample = adt.loc["Combined Liability ($b)"] # convert adt results to a string with a trailing EOL character adtstr = adt.to_string() + '\n' # create actual and expected lists of diagnostic table lines actual = adtstr.splitlines(True) aggres_path = os.path.join(tests_path, 'pufcsv_agg_expect.txt') with open(aggres_path, 'r') as expected_file: txt = expected_file.read() expected_results = txt.rstrip('\n\t ') + '\n' # cleanup end of file txt expect = expected_results.splitlines(True) # ensure actual and expect lines have differences no more than small value if sys.version_info.major == 2: small = 0.0 # tighter test for Python 2.7 else: small = 0.1 # looser test for Python 3.6 diffs = nonsmall_diffs(actual, expect, small) if diffs: new_filename = '{}{}'.format(aggres_path[:-10], 'actual.txt') with open(new_filename, 'w') as new_file: new_file.write(adtstr) msg = 'PUFCSV AGG RESULTS DIFFER FOR FULL-SAMPLE\n' msg += '-------------------------------------------------\n' msg += '--- NEW RESULTS IN pufcsv_agg_actual.txt FILE ---\n' msg += '--- if new OK, copy pufcsv_agg_actual.txt to ---\n' msg += '--- pufcsv_agg_expect.txt ---\n' msg += '--- and rerun test. ---\n' msg += '-------------------------------------------------\n' raise ValueError(msg) # create aggregate diagnostic table using unweighted sub-sample of records fullsample = puf_fullsample rn_seed = 180 # to ensure sub-sample is always the same subfrac = 0.05 # sub-sample fraction subsample = fullsample.sample(frac=subfrac, random_state=rn_seed) rec_subsample = Records(data=subsample) calc_subsample = Calculator(policy=baseline_policy, records=rec_subsample) adt_subsample = calc_subsample.diagnostic_table(nyrs) # compare combined tax liability from full and sub samples for each year taxes_subsample = adt_subsample.loc["Combined Liability ($b)"] reltol = 0.01 # maximum allowed relative difference in tax liability if not np.allclose(taxes_subsample, taxes_fullsample, atol=0.0, rtol=reltol): msg = 'PUFCSV AGG RESULTS DIFFER IN SUB-SAMPLE AND FULL-SAMPLE\n' msg += 'WHEN subfrac={:.3f}, rtol={:.4f}, seed={}\n'.format(subfrac, reltol, rn_seed) it_sub = np.nditer(taxes_subsample, flags=['f_index']) it_all = np.nditer(taxes_fullsample, flags=['f_index']) while not it_sub.finished: cyr = it_sub.index + calc_start_year tax_sub = float(it_sub[0]) tax_all = float(it_all[0]) reldiff = abs(tax_sub - tax_all) / abs(tax_all) if reldiff > reltol: msgstr = ' year,sub,full,reldiff= {}\t{:.2f}\t{:.2f}\t{:.4f}\n' msg += msgstr.format(cyr, tax_sub, tax_all, reldiff) it_sub.iternext() it_all.iternext() raise ValueError(msg)
def test_mtr(tests_path, puf_path): """ Test Tax-Calculator marginal tax rates with no policy reform using puf.csv Compute histograms for each marginal tax rate income type using sample input from the puf.csv file and writing output to a string, which is then compared for differences with EXPECTED_MTR_RESULTS. """ # pylint: disable=too-many-locals,too-many-statements assert len(PTAX_MTR_BIN_EDGES) == len(ITAX_MTR_BIN_EDGES) # construct actual results string, res res = '' if MTR_NEG_DIFF: res += 'MTR computed using NEGATIVE finite_diff ' else: res += 'MTR computed using POSITIVE finite_diff ' res += 'for tax year {}\n'.format(MTR_TAX_YEAR) # create a Policy object (clp) containing current-law policy parameters clp = Policy() clp.set_year(MTR_TAX_YEAR) # create a Records object (puf) containing puf.csv input records puf = Records(data=puf_path) recid = puf.RECID # pylint: disable=no-member # create a Calculator object using clp policy and puf records calc = Calculator(policy=clp, records=puf) res += '{} = {}\n'.format('Total number of data records', puf.array_length) res += 'PTAX mtr histogram bin edges:\n' res += ' {}\n'.format(PTAX_MTR_BIN_EDGES) res += 'ITAX mtr histogram bin edges:\n' res += ' {}\n'.format(ITAX_MTR_BIN_EDGES) variable_header = 'PTAX and ITAX mtr histogram bin counts for' # compute marginal tax rate (mtr) histograms for each mtr variable for var_str in Calculator.MTR_VALID_VARIABLES: zero_out = (var_str == 'e01400') (mtr_ptax, mtr_itax, _) = calc.mtr(variable_str=var_str, negative_finite_diff=MTR_NEG_DIFF, zero_out_calculated_vars=zero_out, wrt_full_compensation=False) if zero_out: # check that calculated variables are consistent assert np.allclose((calc.array('iitax') + calc.array('payrolltax')), calc.array('combined')) assert np.allclose((calc.array('ptax_was') + calc.array('setax') + calc.array('ptax_amc')), calc.array('payrolltax')) assert np.allclose(calc.array('c21060') - calc.array('c21040'), calc.array('c04470')) assert np.allclose(calc.array('taxbc') + calc.array('c09600'), calc.array('c05800')) assert np.allclose((calc.array('c05800') + calc.array('othertaxes') - calc.array('c07100')), calc.array('c09200')) assert np.allclose(calc.array('c09200') - calc.array('refund'), calc.array('iitax')) if var_str == 'e00200s': # only MARS==2 filing units have valid MTR values mtr_ptax = mtr_ptax[calc.array('MARS') == 2] mtr_itax = mtr_itax[calc.array('MARS') == 2] res += '{} {}:\n'.format(variable_header, var_str) res += mtr_bin_counts(mtr_ptax, PTAX_MTR_BIN_EDGES, recid) res += mtr_bin_counts(mtr_itax, ITAX_MTR_BIN_EDGES, recid) # check for differences between actual and expected results mtrres_path = os.path.join(tests_path, 'pufcsv_mtr_expect.txt') with open(mtrres_path, 'r') as expected_file: txt = expected_file.read() expected_results = txt.rstrip('\n\t ') + '\n' # cleanup end of file txt if nonsmall_diffs(res.splitlines(True), expected_results.splitlines(True)): new_filename = '{}{}'.format(mtrres_path[:-10], 'actual.txt') with open(new_filename, 'w') as new_file: new_file.write(res) msg = 'PUFCSV MTR RESULTS DIFFER\n' msg += '-------------------------------------------------\n' msg += '--- NEW RESULTS IN pufcsv_mtr_actual.txt FILE ---\n' msg += '--- if new OK, copy pufcsv_mtr_actual.txt to ---\n' msg += '--- pufcsv_mtr_expect.txt ---\n' msg += '--- and rerun test. ---\n' msg += '-------------------------------------------------\n' raise ValueError(msg)
def test_2017_law_reform(): """ Check that policy parameter values in a future year under current-law policy and under the reform specified in the 2017_law.json file are sensible. """ # create pre metadata dictionary for 2017_law.json reform in fyear pol = Policy() reform_file = os.path.join(CUR_PATH, '..', 'taxcalc', '2017_law.json') with open(reform_file, 'r') as rfile: rtext = rfile.read() pol.implement_reform(Policy.read_json_reform(rtext)) assert not pol.parameter_warnings pol.set_year(2018) pre_mdata = dict(pol.items()) # check some policy parameter values against expected values under 2017 law pre_expect = { # relation '<' implies asserting that actual < expect # relation '>' implies asserting that actual > expect # ... parameters not affected by TCJA and that are not indexed 'AMEDT_ec': { 'relation': '=', 'value': 200000 }, 'SS_thd85': { 'relation': '=', 'value': 34000 }, # ... parameters not affected by TCJA and that are indexed 'STD_Dep': { 'relation': '>', 'value': 1050 }, 'CG_brk2': { 'relation': '>', 'value': 425800 }, 'AMT_CG_brk1': { 'relation': '>', 'value': 38600 }, 'AMT_brk1': { 'relation': '>', 'value': 191100 }, 'EITC_c': { 'relation': '>', 'value': 519 }, 'EITC_ps': { 'relation': '>', 'value': 8490 }, 'EITC_ps_MarriedJ': { 'relation': '>', 'value': 5680 }, 'EITC_InvestIncome_c': { 'relation': '>', 'value': 3500 }, # ... parameters affected by TCJA and that are not indexed 'ID_Charity_crt_all': { 'relation': '=', 'value': 0.5 }, 'II_rt3': { 'relation': '=', 'value': 0.25 }, # ... parameters affected by TCJA and that are indexed 'II_brk3': { 'relation': '>', 'value': 91900 }, 'STD': { 'relation': '<', 'value': 7000 }, 'II_em': { 'relation': '>', 'value': 4050 }, 'AMT_em_pe': { 'relation': '<', 'value': 260000 } } assert isinstance(pre_expect, dict) assert set(pre_expect.keys()).issubset(set(pre_mdata.keys())) for name in pre_expect: aval = pre_mdata[name] if aval.ndim == 2: act = aval[0][0] # comparing only first item in a vector parameter else: act = aval[0] exp = pre_expect[name]['value'] if pre_expect[name]['relation'] == '<': assert act < exp, '{} a={} !< e={}'.format(name, act, exp) elif pre_expect[name]['relation'] == '>': assert act > exp, '{} a={} !> e={}'.format(name, act, exp) elif pre_expect[name]['relation'] == '=': assert act == exp, '{} a={} != e={}'.format(name, act, exp)
def test_round_trip_tcja_reform(): """ Check that current-law policy has the same policy parameter values in a future year as does a compound reform that first implements the reform specified in the 2017_law.json file and then implements the reform specified in the TCJA.json file. This test checks that the future-year parameter values for current-law policy (which incorporates TCJA) are the same as future-year parameter values for the compound round-trip reform. Doing this check ensures that the 2017_law.json and TCJA.json reform files are specified in a consistent manner. """ # pylint: disable=too-many-locals fyear = 2020 # create clp metadata dictionary for current-law policy in fyear pol = Policy() pol.set_year(fyear) clp_mdata = dict(pol.items()) # create rtr metadata dictionary for round-trip reform in fyear pol = Policy() reform_file = os.path.join(CUR_PATH, '..', 'taxcalc', '2017_law.json') with open(reform_file, 'r') as rfile: rtext = rfile.read() pol.implement_reform(Policy.read_json_reform(rtext)) assert not pol.parameter_warnings assert not pol.errors reform_file = os.path.join(CUR_PATH, '..', 'taxcalc', 'TCJA.json') with open(reform_file, 'r') as rfile: rtext = rfile.read() pol.implement_reform(Policy.read_json_reform(rtext)) assert not pol.parameter_warnings assert not pol.errors pol.set_year(fyear) rtr_mdata = dict(pol.items()) # compare fyear policy parameter values assert clp_mdata.keys() == rtr_mdata.keys() fail_dump = False if fail_dump: rtr_fails = open('fails_rtr', 'w') clp_fails = open('fails_clp', 'w') fail_params = list() msg = '\nRound-trip-reform and current-law-policy param values differ for:' for pname in clp_mdata.keys(): rtr_val = rtr_mdata[pname] clp_val = clp_mdata[pname] if not np.allclose(rtr_val, clp_val): fail_params.append(pname) msg += '\n {} in {} : rtr={} clp={}'.format( pname, fyear, rtr_val, clp_val) if fail_dump: rtr_fails.write('{} {} {}\n'.format(pname, fyear, rtr_val)) clp_fails.write('{} {} {}\n'.format(pname, fyear, clp_val)) if fail_dump: rtr_fails.close() clp_fails.close() if fail_params: raise ValueError(msg)
def test_make_Calculator_deepcopy(records_2009): parm = Policy() calc1 = Calculator(policy=parm, records=records_2009) calc2 = copy.deepcopy(calc1) assert isinstance(calc2, Calculator)
def test_benefits(tests_path, cps_fullsample): """ Test CPS benefits. """ # pylint: disable=too-many-locals benefit_names = [ 'ssi', 'mcare', 'mcaid', 'snap', 'wic', 'tanf', 'vet', 'housing' ] # write benefits_actual.csv file recs = Records.cps_constructor(data=cps_fullsample) start_year = recs.current_year calc = Calculator(policy=Policy(), records=recs, verbose=False) assert calc.current_year == start_year year_list = list() bname_list = list() benamt_list = list() bencnt_list = list() benavg_list = list() for year in range(start_year, Policy.LAST_BUDGET_YEAR + 1): calc.advance_to_year(year) size = calc.array('XTOT') wght = calc.array('s006') # compute benefit aggregate amounts and head counts and average benefit # (head counts include all members of filing unit receiving a benefit, # which means benavg is f.unit benefit amount divided by f.unit size) for bname in benefit_names: ben = calc.array('{}_ben'.format(bname)) benamt = round((ben * wght).sum() * 1e-9, 3) bencnt = round((size[ben > 0] * wght[ben > 0]).sum() * 1e-6, 3) benavg = round(benamt / bencnt, 1) year_list.append(year) bname_list.append(bname) benamt_list.append(benamt) bencnt_list.append(bencnt) benavg_list.append(benavg) adict = { 'year': year_list, 'bname': bname_list, 'benamt': benamt_list, 'bencnt': bencnt_list, 'benavg': benavg_list } adf = pd.DataFrame(data=adict, columns=['year', 'bname', 'benamt', 'bencnt', 'benavg']) ben_act_path = os.path.join(tests_path, 'benefits_actual.csv') adf.to_csv(ben_act_path, index=False) # read benefits_expect.csv file ben_exp_path = os.path.join(tests_path, 'benefits_expect.csv') edf = pd.read_csv(ben_exp_path) # compare benefit information atol = 0.0001 rtol = 0.0 diffs = False for col in ['benamt', 'bencnt', 'benavg']: if not np.allclose(adf[col], edf[col], atol=atol, rtol=rtol): diffs = True if diffs: msg = 'CPS BENEFITS RESULTS DIFFER\n' msg += '-------------------------------------------------\n' msg += '--- NEW RESULTS IN benefits_actual.txt FILE ---\n' msg += '--- if new OK, copy benefits_actual.txt to ---\n' msg += '--- benefits_expect.txt ---\n' msg += '--- and rerun test. ---\n' msg += '-------------------------------------------------\n' raise ValueError(msg) else: os.remove(ben_act_path)
def test_validate_param_values_warnings_errors(): """ Check detection of out_of_range policy parameters in reforms. """ pol1 = Policy() ref1 = {2020: {'_ID_Medical_frt': [0.05]}} pol1.implement_reform(ref1) assert len(pol1.reform_warnings) > 0 pol2 = Policy() ref2 = {2021: {'_ID_Charity_crt_all': [0.61]}} pol2.implement_reform(ref2) assert len(pol2.reform_warnings) > 0 pol3 = Policy() ref3 = {2024: {'_II_brk4': [[0, 0, 0, 0, 0]]}} pol3.implement_reform(ref3) assert len(pol3.reform_errors) > 0 pol4 = Policy() ref4 = {2024: {'_II_brk4': [[0, 9e9, 0, 0, 0]]}} pol4.implement_reform(ref4) assert len(pol4.reform_errors) > 0 pol5 = Policy() ref5 = {2025: {'_ID_BenefitSurtax_Switch': [[False, True, 0, 1, 0, 1, 0]]}} pol5.implement_reform(ref5) assert len(pol5.reform_errors) == 0 pol6 = Policy() ref6 = {2013: {'_STD': [[20000, 25000, 20000, 20000, 25000]]}} pol6.implement_reform(ref6) assert pol6.reform_errors == '' assert pol6.reform_warnings == ''
def test_validate_param_names_types_errors(): """ Check detection of invalid policy parameter names and types in reforms. """ pol0 = Policy() ref0 = {2020: {'_STD_cpi': 2}} with pytest.raises(ValueError): pol0.implement_reform(ref0) pol1 = Policy() ref1 = {2020: {'_badname_cpi': True}} with pytest.raises(ValueError): pol1.implement_reform(ref1) pol2 = Policy() ref2 = {2020: {'_II_em_cpi': 5}} with pytest.raises(ValueError): pol2.implement_reform(ref2) pol3 = Policy() ref3 = {2020: {'_badname': [0.4]}} with pytest.raises(ValueError): pol3.implement_reform(ref3) pol4 = Policy() ref4 = {2020: {'_EITC_MinEligAge': [21.4]}} with pytest.raises(ValueError): pol4.implement_reform(ref4) pol5 = Policy() ref5 = {2025: {'_ID_BenefitSurtax_Switch': [[False, True, 0, 2, 0, 1, 0]]}} with pytest.raises(ValueError): pol5.implement_reform(ref5) pol6 = Policy() ref6 = {2021: {'_II_em': ['not-a-number']}} with pytest.raises(ValueError): pol6.implement_reform(ref6)
def test_correct_Policy_instantiation(): pol = Policy() assert pol pol.implement_reform({}) with pytest.raises(ValueError): pol.implement_reform(list()) with pytest.raises(ValueError): pol.implement_reform({2099: {'_II_em': [99000]}}) pol.set_year(2019) with pytest.raises(ValueError): pol.implement_reform({2018: {'_II_em': [99000]}})
def test_multi_year_reform(): """ Test multi-year reform involving 1D and 2D parameters. """ # specify dimensions of policy Policy object syr = 2013 nyrs = Policy.DEFAULT_NUM_YEARS pol = Policy(start_year=syr) iratelist = pol.inflation_rates() ifactor = {} for i in range(0, nyrs): ifactor[syr + i] = 1.0 + iratelist[i] wratelist = pol.wage_growth_rates() wfactor = {} for i in range(0, nyrs): wfactor[syr + i] = 1.0 + wratelist[i] # confirm that parameters have current-law values assert_allclose(getattr(pol, '_EITC_c'), Policy._expand_array(np.array( [[487, 3250, 5372, 6044], [496, 3305, 5460, 6143], [503, 3359, 5548, 6242], [506, 3373, 5572, 6269], [510, 3400, 5616, 6318]], dtype=np.float64), False, inflate=True, inflation_rates=iratelist, num_years=nyrs), atol=0.01, rtol=0.0) assert_allclose(getattr(pol, '_STD_Dep'), Policy._expand_array(np.array( [1000, 1000, 1050, 1050, 1050], dtype=np.float64), False, inflate=True, inflation_rates=iratelist, num_years=nyrs), atol=0.01, rtol=0.0) assert_allclose(getattr(pol, '_CTC_c'), Policy._expand_array(np.array([1000] * 5 + [1400] * 4 + [1500] * 3 + [1600] + [1000], dtype=np.float64), False, inflate=False, inflation_rates=iratelist, num_years=nyrs), atol=0.01, rtol=0.0) # this parameter uses a different indexing rate assert_allclose(getattr(pol, '_SS_Earnings_c'), Policy._expand_array(np.array( [113700, 117000, 118500, 118500, 127200], dtype=np.float64), False, inflate=True, inflation_rates=wratelist, num_years=nyrs), atol=0.01, rtol=0.0) # specify multi-year reform using a dictionary of year_provisions dicts reform = { 2015: { '_CTC_c': [2000] }, 2016: { '_EITC_c': [[900, 5000, 8000, 9000]], '_II_em': [7000], '_SS_Earnings_c': [300000] }, 2017: { '_SS_Earnings_c': [500000], '_SS_Earnings_c_cpi': False }, 2019: { '_EITC_c': [[1200, 7000, 10000, 12000]], '_II_em': [9000], '_SS_Earnings_c': [700000], '_SS_Earnings_c_cpi': True } } # implement multi-year reform pol.implement_reform(reform) assert pol.current_year == syr # move policy Policy object forward in time so current_year is syr+2 # Note: this would be typical usage because the first budget year # is greater than Policy start_year. pol.set_year(pol.start_year + 2) assert pol.current_year == syr + 2 # confirm that actual parameters have expected post-reform values check_eitc_c(pol, reform, ifactor) check_ii_em(pol, reform, ifactor) check_ss_earnings_c(pol, reform, wfactor) check_ctc_c(pol, reform)
def run(): """ Execute Calculator.calc_all() method using 'puf.csv' input and writing truncated ouput to a CSV file named 'results_puf.csv'. """ # create a Policy object containing current-law policy (clp) parameters clp = Policy() # create a Records object (puf) containing puf.csv input records puf = Records() # create a Calculator object using clp policy and puf records calc = Calculator(policy=clp, records=puf) calc.calc_all() rshape = calc.records.e00100.shape dataf = pd.DataFrame() for attr in dir(calc.records): value = getattr(calc.records, attr) if hasattr(value, "shape"): if value.shape == rshape: dataf[attr] = value # truncate the outputs col_names = ['EICYB1', 'EICYB2', 'EICYB3', 'NIIT', '_addamt', '_addtax', '_agep', '_ages', '_agierr', '_alminc', '_amed', '_amt15pc', '_amt20pc', '_amt25pc', '_amt5pc', '_amtfei', '_amtsepadd', '_amtstd', '_avail', '_cglong', '_cmbtp', '_comb', '_combined', '_ctc1', '_ctc2', '_ctcagi', '_ctctax', '_dclim', '_dwks12', '_dwks16', '_dwks17', '_dwks21', '_dwks25', '_dwks26', '_dwks28', '_dwks31', '_dwks5', '_dwks9', '_dy', '_earned', '_eitc', '_exocrd', '_expanded_income', '_feided', '_feitax', '_fica', '_hasgain', '_ieic', 'c03260', '_limitratio', '_line17', '_line19', '_line22', '_line30', '_line31', '_line32', '_line33', '_line34', '_line35', '_line36', '_modagi', '_nctcr', '_ncu13', '_ngamty', '_noncg', '_nonlimited', '_num', '_numextra', '_oldfei', '_othadd', '_othded', '_othertax', '_othtax', '_parents', '_phase2_i', '_posagi', '_precrd', '_preeitc', '_prexmp', '_refund', '_regcrd', '_s1291', '_sep', '_sey', 'c09400', 'c03260', '_seywage', '_standard', '_statax', '_tamt2', '_taxbc', '_taxinc', '_taxspecial', '_tratio', '_txpyers', '_val_rtbase', '_val_rtless', '_val_ymax', '_xyztax', '_ymod', '_ymod1', '_ymod2', '_ymod3', '_ywossbc', '_ywossbe', 'c00100', 'c01000', 'c02500', 'c02650', 'c02700', 'c02900', 'c04100', 'c04200', 'c04470', 'c04500', 'c04600', 'c04800', 'c05100', 'c05200', 'c05700', 'c05750', 'c05800', 'c07100', 'c07150', 'c07180', 'c07220', 'c07230', 'c07240', 'c07300', 'c07600', 'c07970', 'c08795', 'c08800', 'c09200', 'c09600', 'c10300', 'c10950', 'c10960', 'c11055', 'c11070', 'c15100', 'c15200', 'c17000', 'c17750', 'c18300', 'c19200', 'c19700', 'c20400', 'c20500', 'c20750', 'c20800', 'c21040', 'c21060', 'c23650', 'c24505', 'c24510', 'c24516', 'c24517', 'c24520', 'c24530', 'c24534', 'c24540', 'c24550', 'c24560', 'c24570', 'c24580', 'c24597', 'c24598', 'c24610', 'c24615', 'c32800', 'c32840', 'c32880', 'c32890', 'c33000', 'c33200', 'c33400', 'c33465', 'c33470', 'c33475', 'c33480', 'c37703', 'c59430', 'c59450', 'c59460', 'c59485', 'c59490', 'c59560', 'c59660', 'c59680', 'c59700', 'c59720', 'c60000', 'c60130', 'c60200', 'c60220', 'c60240', 'c60260', 'c62100', 'c62100_everyone', 'c62600', 'c62700', 'c62720', 'c62730', 'c62740', 'c62745', 'c62747', 'c62755', 'c62760', 'c62770', 'c62780', 'c62800', 'c62900', 'c63000', 'c63100', 'c82880', 'c82885', 'c82890', 'c82900', 'c82905', 'c82910', 'c82915', 'c82920', 'c82925', 'c82930', 'c82935', 'c82937', 'c82940', 'c87482', 'c87483', 'c87487', 'c87488', 'c87492', 'c87493', 'c87497', 'c87498', 'c87521', 'c87530', 'c87540', 'c87550', 'c87560', 'c87570', 'c87580', 'c87590', 'c87600', 'c87610', 'c87620', 'c87654', 'c87656', 'c87658', 'c87660', 'c87662', 'c87664', 'c87666', 'c87668', 'c87681', 'e00650', 'e02500', 'e08795', 'h82880', 'x04500', 'x07100', 'y07100', 'y62745'] dataf_truncated = dataf[col_names] # write test output to csv file named 'results_puf.csv' dataf_truncated.to_csv('results_puf.csv', float_format='%1.3f', sep=',', header=True, index=False)
def test_implement_reform_Policy_raises_on_no_year(): reform = {'_STD_Aged': [[1400, 1200]]} ppo = Policy() with pytest.raises(ValueError): ppo.implement_reform(reform)
def test_implement_reform_Policy_raises_on_future_year(): ppo = Policy(start_year=2013) with pytest.raises(ValueError): reform = {2010: {'_STD_Aged': [[1400, 1100, 1100, 1400, 1400, 1199]]}} ppo.implement_reform(reform)
def test_Calculator_attr_access_to_policy(records_2009): policy = Policy() calc = Calculator(policy=policy, records=records_2009) assert hasattr(calc.records, 'c01000') assert hasattr(calc.policy, '_AMT_Child_em') assert hasattr(calc, 'policy')
def test_reform_json_and_output(): """ Check that each JSON reform file can be converted into a reform dictionary that can then be passed to the Policy class implement_reform method that generates no parameter_errors. Then use each reform to generate static tax results for small set of filing units in a single tax_year and compare those results with expected results from a CSV-formatted file. """ # pylint: disable=too-many-statements,too-many-locals # embedded function used only in test_reform_json_and_output def write_res_file(calc, resfilename): """ Write calc output to CSV-formatted file with resfilename. """ varlist = [ 'RECID', 'c00100', 'standard', 'c04800', 'iitax', 'payrolltax' ] # varnames AGI STD TaxInc ITAX PTAX stats = calc.dataframe(varlist) stats['RECID'] = stats['RECID'].astype(int) with open(resfilename, 'w') as resfile: stats.to_csv(resfile, index=False, float_format='%.2f') # embedded function used only in test_reform_json_and_output def res_and_out_are_same(base): """ Return True if base.res.csv and base.out.csv file contents are same; return False if base.res.csv and base.out.csv file contents differ. """ resdf = pd.read_csv(base + '.res.csv') outdf = pd.read_csv(base + '.out.csv') diffs = False for col in resdf: if col in outdf: if not np.allclose(resdf[col], outdf[col]): diffs = True else: diffs = True return not diffs # specify Records object containing cases data tax_year = 2020 cases_path = os.path.join(CUR_PATH, '..', 'taxcalc', 'cases.csv') cases = Records( data=cases_path, start_year=tax_year, # set raw input data year gfactors=None, # keeps raw data unchanged weights=None, adjust_ratios=None) # specify list of reform failures failures = list() # specify current-law-policy Calculator object calc = Calculator(policy=Policy(), records=cases, verbose=False) calc.advance_to_year(tax_year) calc.calc_all() res_path = cases_path.replace('cases.csv', 'clp.res.csv') write_res_file(calc, res_path) if res_and_out_are_same(res_path.replace('.res.csv', '')): os.remove(res_path) else: failures.append(res_path) del calc # read 2017_law.json reform file and specify its parameters dictionary pre_tcja_jrf = os.path.join(CUR_PATH, '..', 'taxcalc', '2017_law.json') pre_tcja = Policy.read_json_reform(pre_tcja_jrf) # check reform file contents and reform results for each reform reforms_path = os.path.join(CUR_PATH, '..', 'taxcalc', '*.json') json_reform_files = glob.glob(reforms_path) for jrf in json_reform_files: # determine reform's baseline by reading contents of jrf with open(jrf, 'r') as rfile: jrf_text = rfile.read() pre_tcja_baseline = 'Reform_Baseline: 2017_law.json' in jrf_text # implement the reform relative to its baseline reform = Policy.read_json_reform(jrf_text) pol = Policy() # current-law policy if pre_tcja_baseline: pol.implement_reform(pre_tcja) assert not pol.parameter_errors pol.implement_reform(reform) assert not pol.parameter_errors calc = Calculator(policy=pol, records=cases, verbose=False) calc.advance_to_year(tax_year) calc.calc_all() res_path = jrf.replace('.json', '.res.csv') write_res_file(calc, res_path) if res_and_out_are_same(res_path.replace('.res.csv', '')): os.remove(res_path) else: failures.append(res_path) del calc if failures: msg = 'Following reforms have res-vs-out differences:\n' for ref in failures: msg += '{}\n'.format(os.path.basename(ref)) raise ValueError(msg)
def test_Calculator_create_diagnostic_table(records_2009): calc = Calculator(policy=Policy(), records=records_2009) calc.calc_all() adt = create_diagnostic_table(calc) assert isinstance(adt, pd.DataFrame)
def fixture_baseline_2017_law(): """ Read ../taxcalc/2017_law.json and return its policy dictionary. """ pre_tcja_jrf = os.path.join(CUR_PATH, '..', 'taxcalc', '2017_law.json') return Policy.read_json_reform(pre_tcja_jrf)
def calculator_objects(year_n, start_year, use_puf_not_cps, use_full_sample, user_mods, behavior_allowed): """ This function assumes that the specified user_mods is a dictionary returned by the Calculator.read_json_param_objects() function. This function returns (calc1, calc2) where calc1 is pre-reform Calculator object calculated for year_n, and calc2 is post-reform Calculator object calculated for year_n. Set behavior_allowed to False when generating static results or set behavior_allowed to True when generating dynamic results. """ # pylint: disable=too-many-arguments,too-many-locals # pylint: disable=too-many-branches,too-many-statements check_user_mods(user_mods) # specify Consumption instance consump = Consumption() consump_assumptions = user_mods['consumption'] consump.update_consumption(consump_assumptions) # specify growdiff_baseline and growdiff_response growdiff_baseline = GrowDiff() growdiff_response = GrowDiff() growdiff_base_assumps = user_mods['growdiff_baseline'] growdiff_resp_assumps = user_mods['growdiff_response'] growdiff_baseline.update_growdiff(growdiff_base_assumps) growdiff_response.update_growdiff(growdiff_resp_assumps) # create pre-reform and post-reform GrowFactors instances growfactors_pre = GrowFactors() growdiff_baseline.apply_to(growfactors_pre) growfactors_post = GrowFactors() growdiff_baseline.apply_to(growfactors_post) growdiff_response.apply_to(growfactors_post) # create sample pd.DataFrame from specified input file and sampling scheme tbi_path = os.path.abspath(os.path.dirname(__file__)) if use_puf_not_cps: # first try TaxBrain deployment path input_path = 'puf.csv.gz' if not os.path.isfile(input_path): # otherwise try local Tax-Calculator deployment path input_path = os.path.join(tbi_path, '..', '..', 'puf.csv') sampling_frac = 0.05 sampling_seed = 2222 else: # if using cps input not puf input # first try Tax-Calculator code path input_path = os.path.join(tbi_path, '..', 'cps.csv.gz') if not os.path.isfile(input_path): # otherwise read from taxcalc package "egg" input_path = None # pragma: no cover full_sample = read_egg_csv('cps.csv.gz') # pragma: no cover sampling_frac = 0.03 sampling_seed = 180 if input_path: full_sample = pd.read_csv(input_path) if use_full_sample: sample = full_sample else: sample = full_sample.sample(frac=sampling_frac, random_state=sampling_seed) # create pre-reform Calculator instance if use_puf_not_cps: recs1 = Records(data=sample, gfactors=growfactors_pre) else: recs1 = Records.cps_constructor(data=sample, gfactors=growfactors_pre) policy1 = Policy(gfactors=growfactors_pre) calc1 = Calculator(policy=policy1, records=recs1, consumption=consump) while calc1.current_year < start_year: calc1.increment_year() calc1.calc_all() assert calc1.current_year == start_year # specify Behavior instance behv = Behavior() behavior_assumps = user_mods['behavior'] behv.update_behavior(behavior_assumps) # always prevent both behavioral response and growdiff response if behv.has_any_response() and growdiff_response.has_any_response(): msg = 'BOTH behavior AND growdiff_response HAVE RESPONSE' raise ValueError(msg) # optionally prevent behavioral response if behv.has_any_response() and not behavior_allowed: msg = 'A behavior RESPONSE IS NOT ALLOWED' raise ValueError(msg) # create post-reform Calculator instance if use_puf_not_cps: recs2 = Records(data=sample, gfactors=growfactors_post) else: recs2 = Records.cps_constructor(data=sample, gfactors=growfactors_post) policy2 = Policy(gfactors=growfactors_post) policy_reform = user_mods['policy'] policy2.implement_reform(policy_reform) calc2 = Calculator(policy=policy2, records=recs2, consumption=consump, behavior=behv) while calc2.current_year < start_year: calc2.increment_year() assert calc2.current_year == start_year # delete objects now embedded in calc1 and calc2 del sample del full_sample del consump del growdiff_baseline del growdiff_response del growfactors_pre del growfactors_post del behv del recs1 del recs2 del policy1 del policy2 # increment Calculator objects for year_n years and calculate for _ in range(0, year_n): calc1.increment_year() calc2.increment_year() calc1.calc_all() if calc2.behavior_has_response(): calc2 = Behavior.response(calc1, calc2) else: calc2.calc_all() # return calculated Calculator objects return (calc1, calc2)
def test_mtr_pt_active(puf_subsample): """ Test whether including wages in active income causes MTRs on e00900p and e26270 to be less than -1 (i.e., -100%) """ # pylint: disable=too-many-locals rec = Records(data=puf_subsample) reform_year = 2018 # create current-law Calculator object, calc1 pol1 = Policy() calc1 = Calculator(policy=pol1, records=rec) calc1.advance_to_year(reform_year) calc1.calc_all() mtr1_e00900p = calc1.mtr('e00900p')[2] mtr1_e26270 = calc1.mtr('e26270')[2] assert min(mtr1_e00900p) > -1 assert min(mtr1_e26270) > -1 # change PT rates, calc2 reform2 = {reform_year: {'_PT_rt7': [0.35]}} pol2 = Policy() pol2.implement_reform(reform2) calc2 = Calculator(policy=pol2, records=rec) calc2.advance_to_year(reform_year) calc2.calc_all() mtr2_e00900p = calc2.mtr('e00900p')[2] mtr2_e26270 = calc2.mtr('e26270')[2] assert min(mtr2_e00900p) > -1 assert min(mtr2_e26270) > -1 # change PT_wages_active_income reform3 = {reform_year: {'_PT_wages_active_income': [True]}} pol3 = Policy() pol3.implement_reform(reform3) calc3 = Calculator(policy=pol3, records=rec) calc3.advance_to_year(reform_year) calc3.calc_all() mtr3_e00900p = calc3.mtr('e00900p')[2] mtr3_e26270 = calc3.mtr('e26270')[2] assert min(mtr3_e00900p) > -1 assert min(mtr3_e26270) > -1 # change PT rates and PT_wages_active_income reform4 = {reform_year: {'_PT_wages_active_income': [True], '_PT_rt7': [0.35]}} pol4 = Policy() pol4.implement_reform(reform4) calc4 = Calculator(policy=pol4, records=rec) calc4.advance_to_year(reform_year) calc4.calc_all() mtr4_e00900p = calc4.mtr('e00900p')[2] mtr4_e26270 = calc4.mtr('e26270')[2] assert min(mtr4_e00900p) > -1 assert min(mtr4_e26270) > -1
def convert_defaults(pcl): ''' Function to convert default Tax-Calculator policy parameters to ParamTools compliant parameters. ''' type_map = { "real": "float", "boolean": "bool", "integer": "int", "string": "str", } new_pcl = defaultdict(dict) new_pcl["schema"] = POLICY_SCHEMA LAST_YEAR = TC_LAST_YEAR pol = Policy() pol.set_year(TC_LAST_YEAR) for param, item in pcl.items(): values = [] pol_val = getattr(pol, f"_{param}").tolist() min_year = min(item["value_yrs"]) if isinstance(pol_val[0], list): for year in range(len(pol_val)): if min_year + year > LAST_YEAR: break for dim1 in range(len(pol_val[0])): values.append({ "year": min_year + year, item["vi_name"]: item["vi_vals"][dim1], "value": pol_val[year][dim1] }) else: for year in range(len(pol_val)): if min_year + year > LAST_YEAR: break values.append({ "year": min_year + year, "value": pol_val[year] }) new_pcl[param]['value'] = values new_pcl[param]['title'] = pcl[param]["long_name"] new_pcl[param]['type'] = type_map[pcl[param]["value_type"]] new_pcl[param]["validators"] = {"range": pcl[param]["valid_values"]} # checkbox if indexable if item["indexable"]: if item["indexed"]: new_pcl[param]["checkbox"] = True else: new_pcl[param]["checkbox"] = False to_keep = list(POLICY_SCHEMA["additional_members"].keys()) + [ "description", "notes", ] for k in to_keep: if k in pcl[param]: new_pcl[param][k] = pcl[param][k] return new_pcl
def test_agg(tests_path, cps_fullsample): """ Test current-law aggregate taxes using cps.csv file. """ # pylint: disable=too-many-statements,too-many-locals nyrs = 10 # create a baseline Policy object containing current law policy parameters baseline_policy = Policy() # create a Records object (rec) containing all cps.csv input records recs = Records.cps_constructor(data=cps_fullsample) # create a Calculator object using baseline policy and cps records calc = Calculator(policy=baseline_policy, records=recs) calc_start_year = calc.current_year # create aggregate diagnostic table (adt) as a Pandas DataFrame object adt = calc.diagnostic_table(nyrs) taxes_fullsample = adt.loc["Combined Liability ($b)"] # convert adt to a string with a trailing EOL character actual_results = adt.to_string() + '\n' # read expected results from file aggres_path = os.path.join(tests_path, 'cpscsv_agg_expect.txt') with open(aggres_path, 'r') as expected_file: txt = expected_file.read() expected_results = txt.rstrip('\n\t ') + '\n' # cleanup end of file txt # ensure actual and expected results have no nonsmall differences diffs = nonsmall_diffs(actual_results.splitlines(True), expected_results.splitlines(True)) if diffs: new_filename = '{}{}'.format(aggres_path[:-10], 'actual.txt') with open(new_filename, 'w') as new_file: new_file.write(actual_results) msg = 'CPSCSV AGG RESULTS DIFFER\n' msg += '-------------------------------------------------\n' msg += '--- NEW RESULTS IN cpscsv_agg_actual.txt FILE ---\n' msg += '--- if new OK, copy cpscsv_agg_actual.txt to ---\n' msg += '--- cpscsv_agg_expect.txt ---\n' msg += '--- and rerun test. ---\n' msg += '-------------------------------------------------\n' raise ValueError(msg) # create aggregate diagnostic table using unweighted sub-sample of records rn_seed = 180 # to ensure sub-sample is always the same subfrac = 0.03 # sub-sample fraction subsample = cps_fullsample.sample(frac=subfrac, random_state=rn_seed) recs_subsample = Records.cps_constructor(data=subsample) calc_subsample = Calculator(policy=baseline_policy, records=recs_subsample) adt_subsample = calc_subsample.diagnostic_table(nyrs) # compare combined tax liability from full and sub samples for each year taxes_subsample = adt_subsample.loc["Combined Liability ($b)"] msg = '' for cyr in range(calc_start_year, calc_start_year + nyrs): if cyr == calc_start_year: reltol = 0.014 else: reltol = 0.006 if not np.allclose( taxes_subsample[cyr], taxes_fullsample[cyr], atol=0.0, rtol=reltol): reldiff = (taxes_subsample[cyr] / taxes_fullsample[cyr]) - 1. line1 = '\nCPSCSV AGG SUB-vs-FULL RESULTS DIFFER IN {}' line2 = '\n when subfrac={:.3f}, rtol={:.4f}, seed={}' line3 = '\n with sub={:.3f}, full={:.3f}, rdiff={:.4f}' msg += line1.format(cyr) msg += line2.format(subfrac, reltol, rn_seed) msg += line3.format(taxes_subsample[cyr], taxes_fullsample[cyr], reldiff) if msg: raise ValueError(msg)
def test_multi_year_reform(): """ Test multi-year reform involving 1D and 2D parameters. """ # specify dimensions of policy Policy object syr = 2013 nyrs = 10 # specify assumed inflation rates irates = { 2013: 0.02, 2014: 0.02, 2015: 0.02, 2016: 0.03, 2017: 0.03, 2018: 0.04, 2019: 0.04, 2020: 0.04, 2021: 0.04, 2022: 0.04 } ifactor = {} for i in range(0, nyrs): ifactor[syr + i] = 1.0 + irates[syr + i] iratelist = [irates[syr + i] for i in range(0, nyrs)] # specify assumed inflation rates wrates = { 2013: 0.0276, 2014: 0.0419, 2015: 0.0465, 2016: 0.0498, 2017: 0.0507, 2018: 0.0481, 2019: 0.0451, 2020: 0.0441, 2021: 0.0437, 2022: 0.0435 } wfactor = {} for i in range(0, nyrs): wfactor[syr + i] = 1.0 + wrates[syr + i] wratelist = [wrates[syr + i] for i in range(0, nyrs)] # instantiate policy Policy object ppo = Policy(start_year=syr, num_years=nyrs, inflation_rates=irates, wage_growth_rates=wrates) # confirm that parameters have current-law values assert_allclose(getattr(ppo, '_AMT_thd_MarriedS'), Policy.expand_array(np.array([40400, 41050]), inflate=True, inflation_rates=iratelist, num_years=nyrs), atol=0.01, rtol=0.0) assert_allclose(getattr(ppo, '_EITC_c'), Policy.expand_array(np.array([[487, 3250, 5372, 6044], [496, 3305, 5460, 6143], [503, 3359, 5548, 6242], [506, 3373, 5572, 6269]]), inflate=True, inflation_rates=iratelist, num_years=nyrs), atol=0.01, rtol=0.0) assert_allclose(getattr(ppo, '_II_em'), Policy.expand_array(np.array([3900, 3950, 4000, 4050]), inflate=True, inflation_rates=iratelist, num_years=nyrs), atol=0.01, rtol=0.0) assert_allclose(getattr(ppo, '_CTC_c'), Policy.expand_array(np.array([1000]), inflate=False, inflation_rates=iratelist, num_years=nyrs), atol=0.01, rtol=0.0) # this parameter uses a different inflating rate assert_allclose(getattr(ppo, '_SS_Earnings_c'), Policy.expand_array(np.array( [113700, 117000, 118500, 118500]), inflate=True, inflation_rates=wratelist, num_years=nyrs), atol=0.01, rtol=0.0) # specify multi-year reform using a dictionary of year_provisions dicts reform = { 2015: { '_AMT_thd_MarriedS': [60000], '_CTC_c': [2000] }, 2016: { '_EITC_c': [[900, 5000, 8000, 9000]], '_II_em': [7000], '_SS_Earnings_c': [300000] }, 2017: { '_AMT_thd_MarriedS': [80000], '_SS_Earnings_c': [500000], '_SS_Earnings_c_cpi': False }, 2019: { '_EITC_c': [[1200, 7000, 10000, 12000]], '_II_em': [9000], '_SS_Earnings_c': [700000], '_SS_Earnings_c_cpi': True } } # implement multi-year reform ppo.implement_reform(reform) assert ppo.current_year == syr # move policy Policy object forward in time so current_year is syr+2 # Note: this would be typical usage because the first budget year # is greater than Policy start_year. ppo.set_year(ppo.start_year + 2) assert ppo.current_year == syr + 2 # confirm that actual parameters have expected post-reform values check_amt_thd_marrieds(ppo, reform, ifactor) check_eitc_c(ppo, reform, ifactor) check_ii_em(ppo, reform, ifactor) check_ss_earnings_c(ppo, reform, wfactor) check_ctc_c(ppo, reform)
def test_behavioral_response(puf_subsample): """ Test that behavioral-response results are the same when generated from standard Tax-Calculator calls and when generated from tbi.run_nth_year_taxcalc_model() calls """ # specify reform and assumptions reform_json = """ {"policy": { "_II_rt5": {"2020": [0.25]}, "_II_rt6": {"2020": [0.25]}, "_II_rt7": {"2020": [0.25]}, "_PT_rt5": {"2020": [0.25]}, "_PT_rt6": {"2020": [0.25]}, "_PT_rt7": {"2020": [0.25]}, "_II_em": {"2020": [1000]} }} """ assump_json = """ {"behavior": {"_BE_sub": {"2013": [0.25]}}, "growdiff_baseline": {}, "growdiff_response": {}, "consumption": {}, "growmodel": {} } """ params = Calculator.read_json_param_objects(reform_json, assump_json) # specify keyword arguments used in tbi function call kwargs = { 'start_year': 2019, 'year_n': 0, 'use_puf_not_cps': True, 'use_full_sample': False, 'user_mods': { 'policy': params['policy'], 'behavior': params['behavior'], 'growdiff_baseline': params['growdiff_baseline'], 'growdiff_response': params['growdiff_response'], 'consumption': params['consumption'], 'growmodel': params['growmodel'] }, 'return_dict': False } # generate aggregate results two ways: using tbi and standard calls num_years = 9 std_res = dict() tbi_res = dict() for using_tbi in [True, False]: for year in range(0, num_years): cyr = year + kwargs['start_year'] if using_tbi: kwargs['year_n'] = year tables = run_nth_year_taxcalc_model(**kwargs) tbi_res[cyr] = dict() for tbl in ['aggr_1', 'aggr_2', 'aggr_d']: tbi_res[cyr][tbl] = tables[tbl] else: rec = Records(data=puf_subsample) pol = Policy() calc1 = Calculator(policy=pol, records=rec) pol.implement_reform(params['policy']) assert not pol.parameter_errors beh = Behavior() beh.update_behavior(params['behavior']) calc2 = Calculator(policy=pol, records=rec, behavior=beh) assert calc2.behavior_has_response() calc1.advance_to_year(cyr) calc2.advance_to_year(cyr) calc2 = Behavior.response(calc1, calc2) std_res[cyr] = dict() for tbl in ['aggr_1', 'aggr_2', 'aggr_d']: if tbl.endswith('_1'): itax = calc1.weighted_total('iitax') ptax = calc1.weighted_total('payrolltax') ctax = calc1.weighted_total('combined') elif tbl.endswith('_2'): itax = calc2.weighted_total('iitax') ptax = calc2.weighted_total('payrolltax') ctax = calc2.weighted_total('combined') elif tbl.endswith('_d'): itax = (calc2.weighted_total('iitax') - calc1.weighted_total('iitax')) ptax = (calc2.weighted_total('payrolltax') - calc1.weighted_total('payrolltax')) ctax = (calc2.weighted_total('combined') - calc1.weighted_total('combined')) cols = ['0_{}'.format(year)] rows = ['ind_tax', 'payroll_tax', 'combined_tax'] datalist = [itax, ptax, ctax] std_res[cyr][tbl] = pd.DataFrame(data=datalist, index=rows, columns=cols) # compare the two sets of results # NOTE that the tbi results have been "fuzzed" for PUF privacy reasons, # so there is no expectation that the results should be identical. no_diffs = True reltol = 0.004 # std and tbi differ if more than 0.4 percent different for year in range(0, num_years): cyr = year + kwargs['start_year'] col = '0_{}'.format(year) for tbl in ['aggr_1', 'aggr_2', 'aggr_d']: tbi = tbi_res[cyr][tbl][col] std = std_res[cyr][tbl][col] if not np.allclose(tbi, std, atol=0.0, rtol=reltol): no_diffs = False print('**** DIFF for year {} (year_n={}):'.format(cyr, year)) print('TBI RESULTS:') print(tbi) print('STD RESULTS:') print(std) assert no_diffs
def test_create_parameters(): p = Policy() assert p
def calculators(year_n, start_year, use_puf_not_cps, use_full_sample, user_mods): """ This function assumes that the specified user_mods is a dictionary returned by the Calculator.read_json_param_objects() function. This function returns (calc1, calc2) where calc1 is pre-reform Calculator object for year_n, and calc2 is post-reform Calculator object for year_n. Neither Calculator object has had the calc_all() method executed. """ # pylint: disable=too-many-locals,too-many-branches,too-many-statements check_user_mods(user_mods) # specify Consumption instance consump = Consumption() consump_assumptions = user_mods['consumption'] consump.update_consumption(consump_assumptions) # specify growdiff_baseline and growdiff_response growdiff_baseline = GrowDiff() growdiff_response = GrowDiff() growdiff_base_assumps = user_mods['growdiff_baseline'] growdiff_resp_assumps = user_mods['growdiff_response'] growdiff_baseline.update_growdiff(growdiff_base_assumps) growdiff_response.update_growdiff(growdiff_resp_assumps) # create pre-reform and post-reform GrowFactors instances growfactors_pre = GrowFactors() growdiff_baseline.apply_to(growfactors_pre) growfactors_post = GrowFactors() growdiff_baseline.apply_to(growfactors_post) growdiff_response.apply_to(growfactors_post) # create sample pd.DataFrame from specified input file and sampling scheme tbi_path = os.path.abspath(os.path.dirname(__file__)) if use_puf_not_cps: # first try TaxBrain deployment path input_path = 'puf.csv.gz' if not os.path.isfile(input_path): # otherwise try local Tax-Calculator deployment path input_path = os.path.join(tbi_path, '..', '..', 'puf.csv') sampling_frac = 0.05 sampling_seed = 2222 else: # if using cps input not puf input # first try Tax-Calculator code path input_path = os.path.join(tbi_path, '..', 'cps.csv.gz') if not os.path.isfile(input_path): # otherwise read from taxcalc package "egg" input_path = None # pragma: no cover full_sample = read_egg_csv('cps.csv.gz') # pragma: no cover sampling_frac = 0.03 sampling_seed = 180 if input_path: full_sample = pd.read_csv(input_path) if use_full_sample: sample = full_sample else: sample = full_sample.sample(frac=sampling_frac, random_state=sampling_seed) # create pre-reform Calculator instance if use_puf_not_cps: recs1 = Records(data=sample, gfactors=growfactors_pre) else: recs1 = Records.cps_constructor(data=sample, gfactors=growfactors_pre) policy1 = Policy(gfactors=growfactors_pre) calc1 = Calculator(policy=policy1, records=recs1, consumption=consump) while calc1.current_year < start_year: calc1.increment_year() assert calc1.current_year == start_year # create post-reform Calculator instance if use_puf_not_cps: recs2 = Records(data=sample, gfactors=growfactors_post) else: recs2 = Records.cps_constructor(data=sample, gfactors=growfactors_post) policy2 = Policy(gfactors=growfactors_post) policy_reform = user_mods['policy'] policy2.implement_reform(policy_reform) calc2 = Calculator(policy=policy2, records=recs2, consumption=consump) while calc2.current_year < start_year: calc2.increment_year() assert calc2.current_year == start_year # delete objects now embedded in calc1 and calc2 del sample del full_sample del consump del growdiff_baseline del growdiff_response del growfactors_pre del growfactors_post del recs1 del recs2 del policy1 del policy2 # increment Calculator objects for year_n years for _ in range(0, year_n): calc1.increment_year() calc2.increment_year() # return Calculator objects return (calc1, calc2)
def propagate_user_list(x, name, defaults, cpi, first_budget_year, multi_param_idx=-1): """ Dispatch to either expand_1D or expand2D depending on the dimension of x Parameters: ----------- x : list from user to propagate forward in time. The first value is for year 'first_budget_year'. The value at index i is the value for budget year first_budget_year + i. defaults: list of default values; our result must be at least this long name: the parameter name for looking up the indexing rate cpi: Bool first_budget_year: int multi_param_idx: int, optional. If this parameter is multi-valued, this is the index for which the values for 'x' apply. So, for exampe, if multi_param_idx=0, the values for x are typically for the 'single' filer status. -1 indidcates that this is not a multi-valued parameter Returns: -------- list of length 'num_years'. if 'cpi'==True, the values will be inflated based on the last value the user specified """ # x must have a real first value assert len(x) > 0 assert x[0] not in ("", None) num_years = max(len(defaults), len(x)) is_rate = any([i < 1.0 for i in x]) current_policy = Policy(start_year=2013) current_policy.set_year(first_budget_year) # irates are rates for 2015, 2016, and 2017 if cpi: irates = current_policy._indexing_rates_for_update( param_name=name, calyear=first_budget_year, num_years_to_expand=num_years) else: irates = [0.0] * num_years ans = [None] * num_years for i in range(num_years): if i < len(x): if is_wildcard(x[i]): if multi_param_idx > -1: ans[i] = defaults[i][multi_param_idx] else: ans[i] = defaults[i] else: ans[i] = x[i] if ans[i] is not None: continue else: newval = ans[i - 1] * (1.0 + irates[i - 1]) ans[i] = newval if is_rate else int(newval) return ans