def test_response_json(tests_path): """ Check that each JSON file can be converted into dictionaries that can be used to construct objects needed for a Calculator object. """ responses_path = os.path.join(tests_path, '..', 'responses', '*.json') for jpf in glob.glob(responses_path): # read contents of jpf (JSON parameter filename) jfile = open(jpf, 'r') jpf_text = jfile.read() # check that jpf_text can be used to construct objects response_file = ('"consumption"' in jpf_text and '"behavior"' in jpf_text and '"growdiff_baseline"' in jpf_text and '"growdiff_response"' in jpf_text) if response_file: # pylint: disable=protected-access (con, beh, gdiff_base, gdiff_resp) = Calculator._read_json_econ_assump_text(jpf_text) cons = Consumption() cons.update_consumption(con) behv = Behavior() behv.update_behavior(beh) growdiff_baseline = Growdiff() growdiff_baseline.update_growdiff(gdiff_base) growdiff_response = Growdiff() growdiff_response.update_growdiff(gdiff_resp) else: # jpf_text is not a valid JSON response assumption file print('test-failing-filename: ' + jpf) assert False
def test_correct_update_behavior(): beh = Behavior(start_year=2013) beh.update_behavior({ 2014: { '_BE_sub': [0.5] }, 2015: { '_BE_cg': [-1.2] }, 2016: { '_BE_charity': [[-0.5, -0.5, -0.5]] } }) should_be = np.full((Behavior.DEFAULT_NUM_YEARS, ), 0.5) should_be[0] = 0.0 assert np.allclose(beh._BE_sub, should_be, rtol=0.0) assert np.allclose(beh._BE_inc, np.zeros((Behavior.DEFAULT_NUM_YEARS, )), rtol=0.0) beh.set_year(2017) assert beh.current_year == 2017 assert beh.BE_sub == 0.5 assert beh.BE_inc == 0.0 assert beh.BE_cg == -1.2 assert beh.BE_charity.tolist() == [-0.5, -0.5, -0.5]
def test_taxbrain_json(taxbrain_path): # pylint: disable=redefined-outer-name """ Check that each JSON parameter file can be converted into dictionaries that can be used to construct objects needed for a Calculator object. """ for jpf in glob.glob(taxbrain_path): # read contents of jpf (JSON parameter filename) jfile = open(jpf, 'r') jpf_text = jfile.read() # check that jpf_text can be used to construct objects if '"policy"' in jpf_text: pol = Calculator.read_json_policy_reform_text(jpf_text) policy = Policy() policy.implement_reform(pol) elif ('"consumption"' in jpf_text and '"behavior"' in jpf_text and '"growdiff_baseline"' in jpf_text and '"growdiff_response"' in jpf_text): (con, beh, gdiff_base, gdiff_resp) = Calculator.read_json_econ_assump_text(jpf_text) cons = Consumption() cons.update_consumption(con) behv = Behavior() behv.update_behavior(beh) growdiff_baseline = Growdiff() growdiff_baseline.update_growdiff(gdiff_base) growdiff_response = Growdiff() growdiff_response.update_growdiff(gdiff_resp) else: # jpf_text is not a valid JSON parameter file print('test-failing-filename: ' + jpf) assert False
def run_reform(name, reform, behave): puf = pd.read_csv("../tax-calculator/puf.csv") policy_base = Policy(start_year=2013) records_base = Records(puf) policy_reform = Policy() records_reform = Records(puf) bhv = Behavior() calcbase = Calculator(policy=policy_base, records=records_base) calcreform = Calculator(policy=policy_reform, records=records_reform, behavior=bhv) policy_reform.implement_reform(reform) calcbase.advance_to_year(CURRENT_YEAR) calcreform.advance_to_year(CURRENT_YEAR) calcbase.calc_all() calcreform.calc_all() bhv.update_behavior(behave) calc_behav = Behavior.response(calcbase, calcreform) calc_behav.calc_all() base_list = multiyear_diagnostic_table(calcbase, 10) reform_list = multiyear_diagnostic_table(calc_behav, 10) difflist = (reform_list.iloc[18] - base_list.iloc[18]) return difflist
def test_Calculator_diagnostic_table(): puf = Records(data=TAXDATA, weights=WEIGHTS, start_year=Records.PUF_YEAR) beh = Behavior() beh.update_behavior({2013: {'_BE_sub': [0.4]}}) assert beh.has_response() calc = Calculator(policy=Policy(), records=puf, behavior=beh) calc.diagnostic_table(base_calc=calc)
def test_incorrect_update_behavior(): with pytest.raises(ValueError): Behavior().update_behavior([]) with pytest.raises(ValueError): Behavior().update_behavior({2013: {'_BE_inc': [+0.2]}}) with pytest.raises(ValueError): Behavior().update_behavior({2013: {'_BE_sub': [-0.2]}}) with pytest.raises(ValueError): Behavior().update_behavior({2013: {'_BE_cg': [+0.8]}}) with pytest.raises(ValueError): Behavior().update_behavior({2013: {'_BE_xx': [0.0]}}) with pytest.raises(ValueError): Behavior().update_behavior({2013: {'_BE_xx_cpi': [True]}}) # year in update must be greater than or equal start year with pytest.raises(ValueError): Behavior(start_year=2014).update_behavior({2013: {'_BE_inc': [-0.2]}}) # year in update must be greater than or equal to current year with pytest.raises(ValueError): behv = Behavior(start_year=2014) behv.set_year(2015) behv.update_behavior({2014: {'_BE_inc': [-0.2]}}) # start year greater than start_year + DEFAULT_NUM_YEARS with pytest.raises(ValueError): Behavior().update_behavior({2040: {'_BE_inc': [-0.2]}}) # invalid start year with pytest.raises(ValueError): Behavior().update_behavior({'notayear': {'_BE_inc': [-0.2]}})
def test_validate_param_names_types_errors(): behv0 = Behavior() specs0 = {2020: {'_BE_bad': [-1.0]}} with pytest.raises(ValueError): behv0.update_behavior(specs0) behv1 = Behavior() specs1 = {2019: {'_BE_inc': [True]}} with pytest.raises(ValueError): behv1.update_behavior(specs1)
def reform_warnings_errors(user_mods, using_puf): """ The reform_warnings_errors function assumes user_mods is a dictionary returned by the Calculator.read_json_param_objects() function. This function returns a dictionary containing five STR:STR subdictionaries, where the dictionary keys are: 'policy', 'behavior', consumption', 'growdiff_baseline' and 'growdiff_response'; and the subdictionaries are: {'warnings': '<empty-or-message(s)>', 'errors': '<empty-or-message(s)>'}. Note that non-policy parameters have no warnings, so the 'warnings' string for the non-policy parameters is always empty. """ rtn_dict = {'policy': {'warnings': '', 'errors': ''}, 'behavior': {'warnings': '', 'errors': ''}, 'consumption': {'warnings': '', 'errors': ''}, 'growdiff_baseline': {'warnings': '', 'errors': ''}, 'growdiff_response': {'warnings': '', 'errors': ''}} # create GrowDiff objects gdiff_baseline = GrowDiff() try: gdiff_baseline.update_growdiff(user_mods['growdiff_baseline']) except ValueError as valerr_msg: rtn_dict['growdiff_baseline']['errors'] = valerr_msg.__str__() gdiff_response = GrowDiff() try: gdiff_response.update_growdiff(user_mods['growdiff_response']) except ValueError as valerr_msg: rtn_dict['growdiff_response']['errors'] = valerr_msg.__str__() # create Growfactors object growfactors = GrowFactors() gdiff_baseline.apply_to(growfactors) gdiff_response.apply_to(growfactors) # create Policy object pol = Policy(gfactors=growfactors) try: pol.implement_reform(user_mods['policy'], print_warnings=False, raise_errors=False) if using_puf: rtn_dict['policy']['warnings'] = pol.parameter_warnings rtn_dict['policy']['errors'] = pol.parameter_errors except ValueError as valerr_msg: rtn_dict['policy']['errors'] = valerr_msg.__str__() # create Behavior object behv = Behavior() try: behv.update_behavior(user_mods['behavior']) except ValueError as valerr_msg: rtn_dict['behavior']['errors'] = valerr_msg.__str__() # create Consumption object consump = Consumption() try: consump.update_consumption(user_mods['consumption']) except ValueError as valerr_msg: rtn_dict['consumption']['errors'] = valerr_msg.__str__() # return composite dictionary of warnings/errors return rtn_dict
def reform_warnings_errors(user_mods): """ The reform_warnings_errors function assumes user_mods is a dictionary returned by the Calculator.read_json_param_objects() function. This function returns a dictionary containing two STR:STR pairs: {'warnings': '<empty-or-message(s)>', 'errors': '<empty-or-message(s)>'} In each pair the second string is empty if there are no messages. Any returned messages are generated using current_law_policy.json information on known policy parameter names and parameter value ranges. Note that this function will return one or more error messages if the user_mods['policy'] dictionary contains any unknown policy parameter names or if any *_cpi parameters have values other than True or False. These situations prevent implementing the policy reform specified in user_mods, and therefore, no range-related warnings or errors will be returned in this case. """ rtn_dict = { 'policy': { 'warnings': '', 'errors': '' }, 'behavior': { 'warnings': '', 'errors': '' } } # create Growfactors object gdiff_baseline = Growdiff() gdiff_baseline.update_growdiff(user_mods['growdiff_baseline']) gdiff_response = Growdiff() gdiff_response.update_growdiff(user_mods['growdiff_response']) growfactors = Growfactors() gdiff_baseline.apply_to(growfactors) gdiff_response.apply_to(growfactors) # create Policy object and implement reform pol = Policy(gfactors=growfactors) try: pol.implement_reform(user_mods['policy'], print_warnings=False, raise_errors=False) rtn_dict['policy']['warnings'] = pol.parameter_warnings rtn_dict['policy']['errors'] = pol.parameter_errors except ValueError as valerr_msg: rtn_dict['policy']['errors'] = valerr_msg.__str__() # create Behavior object and implement revisions # Note that Behavior does not have a `parameter_warnings` # attribute. behv = Behavior() try: behv.update_behavior(user_mods['behavior']) rtn_dict['behavior']['errors'] = behv.parameter_errors except ValueError as valerr_msg: rtn_dict['behavior']['errors'] = valerr_msg.__str__() return rtn_dict
def test_future_update_behavior(): behv = Behavior() assert behv.current_year == behv.start_year assert behv.has_response() is False cyr = 2020 behv.set_year(cyr) behv.update_behavior({cyr: {'_BE_cg': [-1.0]}}) assert behv.current_year == cyr assert behv.has_response() is True behv.set_year(cyr - 1) assert behv.has_response() is False
def test_future_update_behavior(): behv = Behavior() assert behv.current_year == behv.start_year assert behv.has_response() is False cyr = 2020 behv.set_year(cyr) behv.update_behavior({cyr: {'_BE_cg': [1.0]}}) assert behv.current_year == cyr assert behv.has_response() is True behv.set_year(cyr - 1) assert behv.has_response() is False
def test_incorrect_update_behavior(): behv = Behavior() with pytest.raises(ValueError): behv.update_behavior({2013: {'_BE_inc': [+0.2]}}) with pytest.raises(ValueError): behv.update_behavior({2013: {'_BE_sub': [-0.2]}}) with pytest.raises(ValueError): behv.update_behavior({2013: {'_BE_cg': [+0.8]}}) with pytest.raises(ValueError): behv.update_behavior({2013: {'_BE_xx': [0.0]}}) with pytest.raises(ValueError): behv.update_behavior({2013: {'_BE_xx_cpi': [True]}})
def test_incorrect_update_behavior(): behv = Behavior() with pytest.raises(ValueError): behv.update_behavior({2013: {'_BE_inc': [+0.2]}}) with pytest.raises(ValueError): behv.update_behavior({2013: {'_BE_sub': [-0.2]}}) with pytest.raises(ValueError): behv.update_behavior({2013: {'_BE_cg': [-0.8]}}) with pytest.raises(ValueError): behv.update_behavior({2013: {'_BE_xx': [0.0]}}) with pytest.raises(ValueError): behv.update_behavior({2013: {'_BE_xx_cpi': [True]}})
def reform_results(reform_dict, puf_data, reform_2017_law): """ Return actual results of the reform specified in reform_dict. """ # pylint: disable=too-many-locals rec = Records(data=puf_data) # create baseline Calculator object, calc1 pol = Policy() if reform_dict['baseline'] == '2017_law.json': pol.implement_reform(reform_2017_law) elif reform_dict['baseline'] == 'current_law_policy.json': pass else: msg = 'illegal baseline value {}' raise ValueError(msg.format(reform_dict['baseline'])) calc1 = Calculator(policy=pol, records=rec, verbose=False, behavior=None) # create reform Calculator object, calc2, with possible behavioral response start_year = reform_dict['start_year'] beh = Behavior() if '_BE_cg' in reform_dict['value']: elasticity = reform_dict['value']['_BE_cg'] del reform_dict['value']['_BE_cg'] # in order to have a valid reform beh_assump = {start_year: {'_BE_cg': elasticity}} beh.update_behavior(beh_assump) reform = {start_year: reform_dict['value']} pol.implement_reform(reform) calc2 = Calculator(policy=pol, records=rec, verbose=False, behavior=beh) # increment both Calculator objects to reform's start_year calc1.advance_to_year(start_year) calc2.advance_to_year(start_year) # calculate prereform and postreform output for several years output_type = reform_dict['output_type'] num_years = 4 results = list() for _ in range(0, num_years): calc1.calc_all() prereform = calc1.array(output_type) if calc2.behavior_has_response(): calc2_br = Behavior.response(calc1, calc2) postreform = calc2_br.array(output_type) else: calc2.calc_all() postreform = calc2.array(output_type) diff = postreform - prereform weighted_sum_diff = (diff * calc1.array('s006')).sum() * 1.0e-9 results.append(weighted_sum_diff) calc1.increment_year() calc2.increment_year() # write actual results to actual_str actual_str = 'Tax-Calculator' for iyr in range(0, num_years): actual_str += ',{:.1f}'.format(results[iyr]) return actual_str
def test_correct_update_behavior(): beh = Behavior(start_year=2013) beh.update_behavior({2014: {"_BE_sub": [0.5]}, 2015: {"_BE_cg": [-1.2]}}) should_be = np.full((Behavior.DEFAULT_NUM_YEARS,), 0.5) should_be[0] = 0.0 assert np.allclose(beh._BE_sub, should_be, rtol=0.0) assert np.allclose(beh._BE_inc, np.zeros((Behavior.DEFAULT_NUM_YEARS,)), rtol=0.0) beh.set_year(2015) assert beh.current_year == 2015 assert beh.BE_sub == 0.5 assert beh.BE_inc == 0.0 assert beh.BE_cg == -1.2
def test_multiyear_diagnostic_table(records_2009): behv = Behavior() calc = Calculator(policy=Policy(), records=records_2009, behavior=behv) with pytest.raises(ValueError): adt = multiyear_diagnostic_table(calc, 0) with pytest.raises(ValueError): adt = multiyear_diagnostic_table(calc, 20) adt = multiyear_diagnostic_table(calc, 3) assert isinstance(adt, DataFrame) behv.update_behavior({2013: {"_BE_sub": [0.3]}}) assert calc.behavior.has_response() adt = multiyear_diagnostic_table(calc, 3) assert isinstance(adt, DataFrame)
def test_multiyear_diagnostic_table(records_2009): behv = Behavior() calc = Calculator(policy=Policy(), records=records_2009, behavior=behv) with pytest.raises(ValueError): adt = multiyear_diagnostic_table(calc, 0) with pytest.raises(ValueError): adt = multiyear_diagnostic_table(calc, 20) adt = multiyear_diagnostic_table(calc, 3) assert isinstance(adt, DataFrame) behv.update_behavior({2013: {'_BE_sub': [0.3]}}) assert calc.behavior.has_response() adt = multiyear_diagnostic_table(calc, 3) assert isinstance(adt, DataFrame)
def reform_results(reform_dict, puf_data): """ Return actual results of the reform specified in reform_dict. """ # pylint: disable=too-many-locals # create current-law-policy Calculator object pol1 = Policy() rec1 = Records(data=puf_data) calc1 = Calculator(policy=pol1, records=rec1, verbose=False, behavior=None) # create reform Calculator object with possible behavioral responses start_year = reform_dict['start_year'] beh2 = Behavior() if '_BE_cg' in reform_dict['value']: elasticity = reform_dict['value']['_BE_cg'] del reform_dict['value']['_BE_cg'] # in order to have a valid reform beh_assump = {start_year: {'_BE_cg': elasticity}} beh2.update_behavior(beh_assump) reform = {start_year: reform_dict['value']} pol2 = Policy() pol2.implement_reform(reform) rec2 = Records(data=puf_data) calc2 = Calculator(policy=pol2, records=rec2, verbose=False, behavior=beh2) # increment both calculators to reform's start_year calc1.advance_to_year(start_year) calc2.advance_to_year(start_year) # calculate prereform and postreform output for several years output_type = reform_dict['output_type'] num_years = 4 results = list() for _ in range(0, num_years): calc1.calc_all() prereform = getattr(calc1.records, output_type) if calc2.behavior.has_response(): calc_clp = calc2.current_law_version() calc2_br = Behavior.response(calc_clp, calc2) postreform = getattr(calc2_br.records, output_type) else: calc2.calc_all() postreform = getattr(calc2.records, output_type) diff = postreform - prereform weighted_sum_diff = (diff * calc1.records.s006).sum() * 1.0e-9 results.append(weighted_sum_diff) calc1.increment_year() calc2.increment_year() # write actual results to actual_str reform_description = reform_dict['name'] actual_str = '{}\n'.format(reform_description) actual_str += 'Tax-Calculator' for iyr in range(0, num_years): actual_str += ',{:.1f}'.format(results[iyr]) return actual_str
def test_update_behavior(): beh = Behavior(start_year=2013) beh.update_behavior({2014: {'_BE_sub': [0.5]}}) policy = Policy() should_be = np.full((Behavior.DEFAULT_NUM_YEARS,), 0.5) should_be[0] = 0.0 assert np.allclose(beh._BE_sub, should_be, rtol=0.0) assert np.allclose(beh._BE_inc, np.zeros((Behavior.DEFAULT_NUM_YEARS,)), rtol=0.0) beh.set_year(2015) assert beh.current_year == 2015 assert beh.BE_sub == 0.5 assert beh.BE_inc == 0.0
def main(reform_year, calc_year, sub_elasticity, inc_elasticity, cg_elasticity): """ Highest-level logic of behavior.py script that produces Tax-Calculator behavioral-response results running the taxcalc package on this computer. """ # pylint: disable=too-many-locals # pylint: disable=protected-access if not os.path.isfile(PUFCSV_PATH): sys.stderr.write("ERROR: file {} does not exist\n".format(PUFCSV_PATH)) return 1 # specify policy reform reform_dict = { reform_year: { "_SS_Earnings_c": [1.0e99], "_CG_rt1": [0.01], # clp ==> 0.00 "_CG_rt2": [0.16], # clp ==> 0.15 "_CG_rt3": [0.21], } } # clp ==> 0.20 msg = "REFORM: pop-the-cap + cg-rate-up-one-percent in {}\n" sys.stdout.write(msg.format(reform_year)) # create reform-policy object ref = Policy() ref.implement_reform(reform_dict) # create behavioral-response object behv = Behavior() # create reform-policy Calculator object with behavioral responses calc_ref = Calculator(policy=ref, verbose=False, behavior=behv, records=Records(data=PUFCSV_PATH)) cyr = calc_year # (a) with all behavioral-reponse parameters set to zero assert not calc_ref.behavior.has_response() itax_s, ptax_s, ltcg_s = results(cyr, calc_ref) # (b) with behavioral-reponse parameters set to those specified in call behv_params = { behv.start_year: {"_BE_sub": [sub_elasticity], "_BE_inc": [inc_elasticity], "_BE_cg": [cg_elasticity]} } behv.update_behavior(behv_params) # now used by calc_ref object itax_d, ptax_d, ltcg_d = results(cyr, calc_ref) # dynamic analysis # write results to stdout bhv = "{},SUB_ELAST,INC_ELAST,CG_ELAST= {} {} {}\n" yridx = cyr - behv.start_year sys.stdout.write(bhv.format(cyr, behv._BE_sub[yridx], behv._BE_inc[yridx], behv._BE_cg[yridx])) res = "{},{},{}_STATIC(S),{}_DYNAMIC(D),D-S= {:.1f} {:.1f} {:.1f}\n" sys.stdout.write(res.format(cyr, "ITAX", "REV", "REV", itax_s, itax_d, itax_d - itax_s)) sys.stdout.write(res.format(cyr, "PTAX", "REV", "REV", ptax_s, ptax_d, ptax_d - ptax_s)) sys.stdout.write(res.format(cyr, "LTCG", "AGG", "AGG", ltcg_s, ltcg_d, ltcg_d - ltcg_s)) # return no-error exit code return 0
def test_multiyear_diagnostic_table(cps_subsample): rec = Records.cps_constructor(data=cps_subsample) pol = Policy() beh = Behavior() calc = Calculator(policy=pol, records=rec, behavior=beh) with pytest.raises(ValueError): multiyear_diagnostic_table(calc, 0) with pytest.raises(ValueError): multiyear_diagnostic_table(calc, 20) adt = multiyear_diagnostic_table(calc, 3) assert isinstance(adt, pd.DataFrame) beh.update_behavior({2013: {'_BE_sub': [0.3]}}) calc = Calculator(policy=pol, records=rec, behavior=beh) assert calc.behavior.has_response() adt = multiyear_diagnostic_table(calc, 3) assert isinstance(adt, pd.DataFrame)
def main(reform_year, calc_year, sub_elasticity, inc_elasticity): """ Highest-level logic of behavior.py script that produces Tax-Calculator behavioral-response results running the taxcalc package on this computer. """ # pylint: disable=too-many-locals # pylint: disable=protected-access if not os.path.isfile(PUFCSV_PATH): sys.stderr.write('ERROR: file {} does not exist\n'.format(PUFCSV_PATH)) return 1 # create current-law-policy object cur = Policy() # specify policy reform reform_dict = {reform_year: {'_SS_Earnings_c': [1.0e99]}} sys.stdout.write('REFORM: pop-the-cap in {}\n'.format(reform_year)) # create reform-policy object ref = Policy() ref.implement_reform(reform_dict) # create behavioral-response object behv = Behavior() # default object has all response parameters set to zero # create current-law-policy Calculator object calc_cur = Calculator(policy=cur, verbose=False, records=Records(data=PUFCSV_PATH)) # create reform-policy Calculator object with behavioral responses calc_ref = Calculator(policy=ref, verbose=False, behavior=behv, records=Records(data=PUFCSV_PATH)) # compute behavorial-reponse effect on income and fica tax revenues cyr = calc_year # (a) with all behavioral-reponse parameters set to zero itax_s, fica_s = revenue(cyr, calc_ref, None) # static analysis itax_d, fica_d = revenue(cyr, calc_cur, calc_ref) # dynamic analysis assert itax_d == itax_s assert fica_d == fica_s # (b) with both substitution- and income-effect behavioral-reponse params behv_params = {behv.start_year: {'_BE_sub': [sub_elasticity], '_BE_inc': [inc_elasticity]}} behv.update_behavior(behv_params) itax_s, fica_s = revenue(cyr, calc_ref, None) # static analysis itax_d, fica_d = revenue(cyr, calc_cur, calc_ref) # dynamic analysis bhv = '{},SUB_ELASTICITY,INC_ELASTICITY= {} {}\n' yridx = cyr - behv.start_year sys.stdout.write(bhv.format(cyr, behv._BE_sub[yridx], behv._BE_inc[yridx])) res = '{},{},REV_STATIC(S),REV_DYNAMIC(D),D-S= {:.1f} {:.1f} {:.1f}\n' sys.stdout.write(res.format(cyr, 'ITAX', itax_s, itax_d, itax_d - itax_s)) sys.stdout.write(res.format(cyr, 'FICA', fica_s, fica_d, fica_d - fica_s)) # return no-error exit code return 0
def test_multiyear_diagnostic_table_w_behv(records_2009): pol = Policy() behv = Behavior() calc = Calculator(policy=pol, records=records_2009, behavior=behv) assert calc.current_year == 2013 reform = {2013: {"_II_rt7": [0.33], "_PT_rt7": [0.33]}} pol.implement_reform(reform) reform_be = {2013: {"_BE_sub": [0.4], "_BE_cg": [-3.67]}} behv.update_behavior(reform_be) calc_clp = calc.current_law_version() calc_behv = Behavior.response(calc_clp, calc) calc_behv.calc_all() liabilities_x = (calc_behv.records._combined * calc_behv.records.s006).sum() adt = multiyear_diagnostic_table(calc_behv, 1) # extract combined liabilities as a float and # adopt units of the raw calculator data in liabilities_x liabilities_y = adt.iloc[18].tolist()[0] * 1000000000 npt.assert_almost_equal(liabilities_x, liabilities_y, 2)
def test_myr_diag_table_w_behv(cps_subsample): pol = Policy() rec = Records.cps_constructor(data=cps_subsample) year = rec.current_year beh = Behavior() calc = Calculator(policy=pol, records=rec, behavior=beh) assert calc.current_year == year reform = {year: {'_II_rt7': [0.33], '_PT_rt7': [0.33]}} pol.implement_reform(reform) reform_behav = {year: {'_BE_sub': [0.4], '_BE_cg': [-3.67]}} beh.update_behavior(reform_behav) calc_clp = calc.current_law_version() calc_beh = Behavior.response(calc_clp, calc) calc_beh.calc_all() liabilities_x = (calc_beh.records.combined * calc_beh.records.s006).sum() adt = multiyear_diagnostic_table(calc_beh, 1) # extract combined liabilities as a float and # adopt units of the raw calculator data in liabilities_x liabilities_y = adt.iloc[19].tolist()[0] * 1e9 assert np.allclose(liabilities_x, liabilities_y, atol=0.01, rtol=0.0)
def test_make_behavioral_Calculator(): # create Records objects records_x = Records(data=TAXDATA, weights=WEIGHTS, start_year=2009) records_y = Records(data=TAXDATA, weights=WEIGHTS, start_year=2009) # create Policy objects policy_x = Policy() policy_y = Policy() # implement policy_y reform reform = {2013: {"_II_rt7": [0.496]}} policy_y.implement_reform(reform) # create two Calculator objects behavior_y = Behavior() calc_x = Calculator(policy=policy_x, records=records_x) calc_y = Calculator(policy=policy_y, records=records_y, behavior=behavior_y) # create behavioral calculators and vary substitution and income effects behavior1 = {2013: {"_BE_sub": [0.4], "_BE_inc": [0.15]}} behavior_y.update_behavior(behavior1) calc_y_behavior1 = behavior(calc_x, calc_y) behavior2 = {2013: {"_BE_sub": [0.5], "_BE_inc": [0.15]}} behavior_y.update_behavior(behavior2) calc_y_behavior2 = behavior(calc_x, calc_y) behavior3 = {2013: {"_BE_sub": [0.4], "_BE_inc": [0.0]}} behavior_y.update_behavior(behavior3) calc_y_behavior3 = behavior(calc_x, calc_y) # check that total income tax liability differs across the three behaviors assert (calc_y_behavior1.records._iitax.sum() != calc_y_behavior2.records._iitax.sum() != calc_y_behavior3.records._iitax.sum())
def test_update_behavior(): beh = Behavior(start_year=2013) beh.update_behavior({ 2014: { '_BE_sub': [0.5] }, 2015: { '_BE_CG_per': [1.2] } }) policy = Policy() should_be = np.full((Behavior.DEFAULT_NUM_YEARS, ), 0.5) should_be[0] = 0.0 assert np.allclose(beh._BE_sub, should_be, rtol=0.0) assert np.allclose(beh._BE_inc, np.zeros((Behavior.DEFAULT_NUM_YEARS, )), rtol=0.0) beh.set_year(2015) assert beh.current_year == 2015 assert beh.BE_sub == 0.5 assert beh.BE_inc == 0.0 assert beh.BE_CG_per == 1.2
def test_behavioral_response_Calculator(puf_1991, weights_1991): # create Records objects records_x = Records(data=puf_1991, weights=weights_1991, start_year=2009) records_y = Records(data=puf_1991, weights=weights_1991, start_year=2009) # create Policy objects policy_x = Policy() policy_y = Policy() # implement policy_y reform reform = {2013: {'_II_rt7': [0.496], '_PT_rt7': [0.496]}} policy_y.implement_reform(reform) # create two Calculator objects behavior_y = Behavior() calc_x = Calculator(policy=policy_x, records=records_x) calc_y = Calculator(policy=policy_y, records=records_y, behavior=behavior_y) # test incorrect use of Behavior._mtr_xy method with pytest.raises(ValueError): behv = Behavior._mtr_xy(calc_x, calc_y, mtr_of='e00200p', tax_type='nonsense') # vary substitution and income effects in calc_y behavior1 = {2013: {'_BE_sub': [0.3], '_BE_cg': [0.0]}} behavior_y.update_behavior(behavior1) assert behavior_y.has_response() is True assert behavior_y.BE_sub == 0.3 assert behavior_y.BE_inc == 0.0 assert behavior_y.BE_cg == 0.0 calc_y_behavior1 = Behavior.response(calc_x, calc_y) behavior2 = {2013: {'_BE_sub': [0.5], '_BE_cg': [-0.8]}} behavior_y.update_behavior(behavior2) calc_y_behavior2 = Behavior.response(calc_x, calc_y) behavior3 = {2013: {'_BE_inc': [-0.2], '_BE_cg': [-0.8]}} behavior_y.update_behavior(behavior3) calc_y_behavior3 = Behavior.response(calc_x, calc_y) behavior4 = {2013: {'_BE_cg': [-0.8]}} behavior_y.update_behavior(behavior4) calc_y_behavior4 = Behavior.response(calc_x, calc_y) # check that total income tax liability differs across the # four sets of behavioral-response elasticities assert (calc_y_behavior1.records._iitax.sum() != calc_y_behavior2.records._iitax.sum() != calc_y_behavior3.records._iitax.sum() != calc_y_behavior4.records._iitax.sum()) # test incorrect _mtr_xy() usage with pytest.raises(ValueError): Behavior._mtr_xy(calc_x, calc_y, mtr_of='e00200p', tax_type='?')
def test_myr_diag_table_w_behv(records_2009): pol = Policy() behv = Behavior() calc = Calculator(policy=pol, records=records_2009, behavior=behv) assert calc.current_year == 2013 reform = { 2013: { '_II_rt7': [0.33], '_PT_rt7': [0.33], } } pol.implement_reform(reform) reform_be = {2013: {'_BE_sub': [0.4], '_BE_cg': [-3.67]}} behv.update_behavior(reform_be) calc_clp = calc.current_law_version() calc_behv = Behavior.response(calc_clp, calc) calc_behv.calc_all() liabilities_x = (calc_behv.records.combined * calc_behv.records.s006).sum() adt = multiyear_diagnostic_table(calc_behv, 1) # extract combined liabilities as a float and # adopt units of the raw calculator data in liabilities_x liabilities_y = adt.iloc[19].tolist()[0] * 1000000000 assert_almost_equal(liabilities_x, liabilities_y, 2)
def test_validate_param_names_types_errors(): behv0 = Behavior() specs0 = {2020: {'_BE_bad': [-1.0]}} with pytest.raises(ValueError): behv0.update_behavior(specs0) behv1 = Behavior() specs1 = {2019: {'_BE_inc': [True]}} with pytest.raises(ValueError): behv1.update_behavior(specs1) behv2 = Behavior() specs2 = {2020: {'_BE_charity': ['not-a-number']}} with pytest.raises(ValueError): behv2.update_behavior(specs2) behv3 = Behavior() specs3 = {2020: {'_BE_subinc_wrt_earnings': [0.3]}} with pytest.raises(ValueError): behv3.update_behavior(specs3)
def test_behavioral_response_Calculator(puf_1991, weights_1991): # create Records objects records_x = Records(data=puf_1991, weights=weights_1991, start_year=2009) records_y = Records(data=puf_1991, weights=weights_1991, start_year=2009) # create Policy objects policy_x = Policy() policy_y = Policy() # implement policy_y reform reform = {2013: {"_II_rt7": [0.496], "_PT_rt7": [0.496]}} policy_y.implement_reform(reform) # create two Calculator objects behavior_y = Behavior() calc_x = Calculator(policy=policy_x, records=records_x) calc_y = Calculator(policy=policy_y, records=records_y, behavior=behavior_y) # test incorrect use of Behavior._mtr_xy method with pytest.raises(ValueError): behv = Behavior._mtr_xy(calc_x, calc_y, mtr_of="e00200p", tax_type="nonsense") # vary substitution and income effects in calc_y behavior1 = {2013: {"_BE_sub": [0.3], "_BE_cg": [0.0]}} behavior_y.update_behavior(behavior1) assert behavior_y.has_response() is True assert behavior_y.BE_sub == 0.3 assert behavior_y.BE_inc == 0.0 assert behavior_y.BE_cg == 0.0 calc_y_behavior1 = Behavior.response(calc_x, calc_y) behavior2 = {2013: {"_BE_sub": [0.5], "_BE_cg": [-0.8]}} behavior_y.update_behavior(behavior2) calc_y_behavior2 = Behavior.response(calc_x, calc_y) behavior3 = {2013: {"_BE_inc": [-0.2], "_BE_cg": [-0.8]}} behavior_y.update_behavior(behavior3) calc_y_behavior3 = Behavior.response(calc_x, calc_y) behavior4 = {2013: {"_BE_cg": [-0.8]}} behavior_y.update_behavior(behavior4) calc_y_behavior4 = Behavior.response(calc_x, calc_y) # check that total income tax liability differs across the # four sets of behavioral-response elasticities assert ( calc_y_behavior1.records._iitax.sum() != calc_y_behavior2.records._iitax.sum() != calc_y_behavior3.records._iitax.sum() != calc_y_behavior4.records._iitax.sum() ) # test incorrect _mtr_xy() usage with pytest.raises(ValueError): Behavior._mtr_xy(calc_x, calc_y, mtr_of="e00200p", tax_type="?")
def test_validate_param_values_errors(): behv0 = Behavior() specs0 = {2020: {'_BE_cg': [0.2]}} with pytest.raises(ValueError): behv0.update_behavior(specs0) behv1 = Behavior() specs1 = {2022: {'_BE_sub': [-0.2]}} with pytest.raises(ValueError): behv1.update_behavior(specs1) behv2 = Behavior() specs2 = {2020: {'_BE_cg': [-0.2], '_BE_sub': [0.3]}} behv2.update_behavior(specs2)
def test_behavioral_response_Calculator(): # create Records objects records_x = Records(data=TAXDATA, weights=WEIGHTS, start_year=2009) records_y = Records(data=TAXDATA, weights=WEIGHTS, start_year=2009) # create Policy objects policy_x = Policy() policy_y = Policy() # implement policy_y reform reform = { 2013: { "_II_rt7": [0.496] } } policy_y.implement_reform(reform) # create two Calculator objects behavior_y = Behavior() calc_x = Calculator(policy=policy_x, records=records_x) calc_y = Calculator(policy=policy_y, records=records_y, behavior=behavior_y) # vary substitution and income effects in calc_y behavior1 = { 2013: { "_BE_sub": [0.4], "_BE_inc": [-0.15] } } behavior_y.update_behavior(behavior1) assert behavior_y.has_response() assert behavior_y.BE_sub == 0.4 assert behavior_y.BE_inc == -0.15 calc_y_behavior1 = calc_y.behavior.response(calc_x, calc_y) behavior2 = { 2013: { "_BE_sub": [0.5], "_BE_inc": [-0.15] } } behavior_y.update_behavior(behavior2) calc_y_behavior2 = calc_y.behavior.response(calc_x, calc_y) behavior3 = { 2013: { "_BE_sub": [0.4], "_BE_inc": [0.0] } } behavior_y.update_behavior(behavior3) calc_y_behavior3 = calc_y.behavior.response(calc_x, calc_y) # check that total income tax liability differs across the # three sets of behavioral-response elasticities assert (calc_y_behavior1.records._iitax.sum() != calc_y_behavior2.records._iitax.sum() != calc_y_behavior3.records._iitax.sum())
def test_make_behavioral_Calculator(): # create Records objects records_x = Records(data=TAXDATA, weights=WEIGHTS, start_year=2009) records_y = Records(data=TAXDATA, weights=WEIGHTS, start_year=2009) # create Policy objects policy_x = Policy() policy_y = Policy() # implement policy_y reform reform = { 2013: { "_II_rt7": [0.496] } } policy_y.implement_reform(reform) # create two Calculator objects behavior_y = Behavior() calc_x = Calculator(policy=policy_x, records=records_x) calc_y = Calculator(policy=policy_y, records=records_y, behavior=behavior_y) # create behavioral calculators and vary substitution and income effects behavior1 = { 2013: { "_BE_sub": [0.4], "_BE_inc": [0.15] } } behavior_y.update_behavior(behavior1) calc_y_behavior1 = behavior(calc_x, calc_y) behavior2 = { 2013: { "_BE_sub": [0.5], "_BE_inc": [0.15] } } behavior_y.update_behavior(behavior2) calc_y_behavior2 = behavior(calc_x, calc_y) behavior3 = { 2013: { "_BE_sub": [0.4], "_BE_inc": [0.0] } } behavior_y.update_behavior(behavior3) calc_y_behavior3 = behavior(calc_x, calc_y) # check that total income tax liability differs across the three behaviors assert (calc_y_behavior1.records._iitax.sum() != calc_y_behavior2.records._iitax.sum() != calc_y_behavior3.records._iitax.sum())
def test_validate_param_values_errors(): behv0 = Behavior() specs0 = {2020: {'_BE_cg': [0.2]}} behv0.update_behavior(specs0, raise_errors=False) assert len(behv0.parameter_errors) > 0 behv1 = Behavior() specs1 = {2022: {'_BE_sub': [-0.2]}} behv1.update_behavior(specs1, raise_errors=False) assert len(behv1.parameter_errors) > 0 behv2 = Behavior() specs2 = { 2020: { '_BE_subinc_wrt_earnings': [True], '_BE_cg': [-0.2], '_BE_sub': [0.3] } } behv2.update_behavior(specs2, raise_errors=False) assert len(behv2.parameter_errors) == 0 behv3 = Behavior() specs3 = {2022: {'_BE_sub': [-0.2]}} with pytest.raises(ValueError): behv3.update_behavior(specs1, raise_errors=True)
def test_behavioral_response_Calculator(): # create Records objects records_x = Records(data=TAXDATA, weights=WEIGHTS, start_year=2009) records_y = Records(data=TAXDATA, weights=WEIGHTS, start_year=2009) # create Policy objects policy_x = Policy() policy_y = Policy() # implement policy_y reform reform = {2013: {'_II_rt7': [0.496]}} policy_y.implement_reform(reform) # create two Calculator objects behavior_y = Behavior() calc_x = Calculator(policy=policy_x, records=records_x) calc_y = Calculator(policy=policy_y, records=records_y, behavior=behavior_y) # test incorrect use of Behavior._mtr_xy method with pytest.raises(ValueError): behv = Behavior._mtr_xy(calc_x, calc_y, mtr_of='e00200p', tax_type='nonsense') # vary substitution and income effects in calc_y behavior1 = {2013: {'_BE_sub': [0.4], '_BE_inc': [-0.1]}} behavior_y.update_behavior(behavior1) assert behavior_y.has_response() is True assert behavior_y.BE_sub == 0.4 assert behavior_y.BE_inc == -0.1 calc_y_behavior1 = Behavior.response(calc_x, calc_y) behavior2 = {2013: {'_BE_sub': [0.5], '_BE_cg': [0.8]}} behavior_y.update_behavior(behavior2) calc_y_behavior2 = Behavior.response(calc_x, calc_y) behavior3 = {2013: {'_BE_inc': [-0.2], '_BE_cg': [0.6]}} behavior_y.update_behavior(behavior3) calc_y_behavior3 = Behavior.response(calc_x, calc_y) # check that total income tax liability differs across the # three sets of behavioral-response elasticities assert (calc_y_behavior1.records._iitax.sum() != calc_y_behavior2.records._iitax.sum() != calc_y_behavior3.records._iitax.sum()) # test incorrect _mtr_xy() usage with pytest.raises(ValueError): Behavior._mtr_xy(calc_x, calc_y, mtr_of='e00200p', tax_type='?')
def calculate_baseline_and_reform(year_n, start_year, taxrec_df, user_mods): """ calculate_baseline_and_reform function assumes specified user_mods is a dictionary returned by the Calculator.read_json_parameter_files() function with an extra key:value pair that is specified as 'gdp_elasticity': {'value': <float_value>}. """ # 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 pre-reform Calculator instance recs1 = Records(data=taxrec_df.copy(deep=True), 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 # Create pre-reform Calculator instance with extra income recs1p = Records(data=taxrec_df.copy(deep=True), gfactors=growfactors_pre) # add one dollar to total wages and salaries of each filing unit recs1p.e00200 += 1.0 # pylint: disable=no-member policy1p = Policy(gfactors=growfactors_pre) calc1p = Calculator(policy=policy1p, records=recs1p, consumption=consump) while calc1p.current_year < start_year: calc1p.increment_year() calc1p.calc_all() assert calc1p.current_year == start_year # Construct mask to show which of the calc1 and calc1p results differ soit1 = results(calc1) soit1p = results(calc1p) mask = (soit1._iitax != soit1p._iitax) # pylint: disable=protected-access # Specify Behavior instance behv = Behavior() behavior_assumps = user_mods['behavior'] behv.update_behavior(behavior_assumps) # 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) # Create post-reform Calculator instance with behavior recs2 = Records(data=taxrec_df.copy(deep=True), 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() calc2.calc_all() assert calc2.current_year == start_year # Seed random number generator with a seed value based on user_mods seed = random_seed(user_mods) print('seed={}'.format(seed)) np.random.seed(seed) # pylint: disable=no-member # 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 results and mask soit1 = results(calc1) soit2 = results(calc2) return soit1, soit2, mask
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_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 main(reform_year, calc_year, sub_elasticity, inc_elasticity, cg_elasticity): """ Highest-level logic of behavior.py script that produces Tax-Calculator behavioral-response results running the taxcalc package on this computer. """ # pylint: disable=too-many-locals # pylint: disable=protected-access if not os.path.isfile(PUFCSV_PATH): sys.stderr.write('ERROR: file {} does not exist\n'.format(PUFCSV_PATH)) return 1 # specify policy reform reform_dict = { reform_year: { '_SS_Earnings_c': [1.0e99], '_CG_rt1': [0.01], # clp ==> 0.00 '_CG_rt2': [0.16], # clp ==> 0.15 '_CG_rt3': [0.21] } } # clp ==> 0.20 msg = 'REFORM: pop-the-cap + cg-rate-up-one-percent in {}\n' sys.stdout.write(msg.format(reform_year)) # create reform-policy object ref = Policy() ref.implement_reform(reform_dict) # create behavioral-response object behv = Behavior() # create reform-policy Calculator object with behavioral responses calc_ref = Calculator(policy=ref, verbose=False, behavior=behv, records=Records(data=PUFCSV_PATH)) cyr = calc_year # (a) with all behavioral-reponse parameters set to zero assert not calc_ref.behavior.has_response() itax_s, ptax_s, ltcg_s = results(cyr, calc_ref) # (b) with behavioral-reponse parameters set to those specified in call behv_params = { behv.start_year: { '_BE_sub': [sub_elasticity], '_BE_inc': [inc_elasticity], '_BE_cg': [cg_elasticity] } } behv.update_behavior(behv_params) # now used by calc_ref object itax_d, ptax_d, ltcg_d = results(cyr, calc_ref) # dynamic analysis # write results to stdout bhv = '{},SUB_ELAST,INC_ELAST,CG_ELAST= {} {} {}\n' yridx = cyr - behv.start_year sys.stdout.write( bhv.format(cyr, behv._BE_sub[yridx], behv._BE_inc[yridx], behv._BE_cg[yridx])) res = '{},{},{}_STATIC(S),{}_DYNAMIC(D),D-S= {:.1f} {:.1f} {:.1f}\n' sys.stdout.write( res.format(cyr, 'ITAX', 'REV', 'REV', itax_s, itax_d, itax_d - itax_s)) sys.stdout.write( res.format(cyr, 'PTAX', 'REV', 'REV', ptax_s, ptax_d, ptax_d - ptax_s)) sys.stdout.write( res.format(cyr, 'LTCG', 'AGG', 'AGG', ltcg_s, ltcg_d, ltcg_d - ltcg_s)) # return no-error exit code return 0
NUM_YEARS = 4 for i in range(1, NUM_REFORMS + 1): # create current-law-policy calculator pol1 = Policy() rec1 = Records(data=PUF_DATA) calc1 = Calculator(policy=pol1, records=rec1, verbose=False, behavior=None) # create reform calculator with possible behavioral responses this_reform = "r" + str(i) start_year = REFORMS_JSON.get(this_reform).get("start_year") beh2 = Behavior() if "_BE_cg" in REFORMS_JSON.get(this_reform).get("value"): elasticity = REFORMS_JSON[this_reform]["value"]["_BE_cg"] del REFORMS_JSON[this_reform]["value"]["_BE_cg"] # to not break reform beh_assump = {start_year: {"_BE_cg": elasticity}} beh2.update_behavior(beh_assump) reform = {start_year: REFORMS_JSON.get(this_reform).get("value")} pol2 = Policy() pol2.implement_reform(reform) rec2 = Records(data=PUF_DATA) calc2 = Calculator(policy=pol2, records=rec2, verbose=False, behavior=beh2) output_type = REFORMS_JSON.get(this_reform).get("output_type") reform_name = REFORMS_JSON.get(this_reform).get("name") # increment both calculators to reform's start_year calc1.advance_to_year(start_year) calc2.advance_to_year(start_year) # calculate prereform and postreform for num_years reform_results = [] for _ in range(0, NUM_YEARS):
def test_behavioral_response_calculator(cps_subsample): # create Records object rec = Records.cps_constructor(data=cps_subsample) year = rec.current_year # create Policy object pol = Policy() # create current-law Calculator object calc1 = Calculator(policy=pol, records=rec) # implement policy reform reform = {year: {'_II_rt7': [0.496], '_PT_rt7': [0.496]}} pol.implement_reform(reform) # create reform Calculator object with no behavioral response behv = Behavior() calc2 = Calculator(policy=pol, records=rec, behavior=behv) # test incorrect use of Behavior._mtr_xy method with pytest.raises(ValueError): Behavior._mtr_xy(calc1, calc2, mtr_of='e00200p', tax_type='nonsense') # vary substitution and income effects in Behavior object behavior0 = {year: {'_BE_sub': [0.0], '_BE_cg': [0.0], '_BE_charity': [[0.0, 0.0, 0.0]]}} behv0 = Behavior() behv0.update_behavior(behavior0) calc2 = Calculator(policy=pol, records=rec, behavior=behv0) assert calc2.behavior.has_response() is False calc2_behv0 = Behavior.response(calc1, calc2) behavior1 = {year: {'_BE_sub': [0.3], '_BE_inc': [-0.1], '_BE_cg': [0.0], '_BE_subinc_wrt_earnings': [True]}} behv1 = Behavior() behv1.update_behavior(behavior1) calc2 = Calculator(policy=pol, records=rec, behavior=behv1) assert calc2.behavior.has_response() is True epsilon = 1e-9 assert abs(calc2.behavior.BE_sub - 0.3) < epsilon assert abs(calc2.behavior.BE_inc - -0.1) < epsilon assert abs(calc2.behavior.BE_cg - 0.0) < epsilon calc2_behv1 = Behavior.response(calc1, calc2) behavior2 = {year: {'_BE_sub': [0.5], '_BE_cg': [-0.8]}} behv2 = Behavior() behv2.update_behavior(behavior2) calc2 = Calculator(policy=pol, records=rec, behavior=behv2) assert calc2.behavior.has_response() is True calc2_behv2 = Behavior.response(calc1, calc2, trace=True) behavior3 = {year: {'_BE_inc': [-0.2], '_BE_cg': [-0.8]}} behv3 = Behavior() behv3.update_behavior(behavior3) calc2 = Calculator(policy=pol, records=rec, behavior=behv3) assert calc2.behavior.has_response() is True calc2_behv3 = Behavior.response(calc1, calc2) behavior4 = {year: {'_BE_cg': [-0.8]}} behv4 = Behavior() behv4.update_behavior(behavior4) calc2 = Calculator(policy=pol, records=rec, behavior=behv4) assert calc2.behavior.has_response() is True calc2_behv4 = Behavior.response(calc1, calc2) behavior5 = {year: {'_BE_charity': [[-0.5, -0.5, -0.5]]}} behv5 = Behavior() behv5.update_behavior(behavior5) calc2 = Calculator(policy=pol, records=rec, behavior=behv5) assert calc2.behavior.has_response() is True calc2_behv5 = Behavior.response(calc1, calc2) # check that total income tax liability differs across the # six sets of behavioral-response elasticities assert (calc2_behv0.records.iitax.sum() != calc2_behv1.records.iitax.sum() != calc2_behv2.records.iitax.sum() != calc2_behv3.records.iitax.sum() != calc2_behv4.records.iitax.sum() != calc2_behv5.records.iitax.sum())
def calculate(year_n, start_year, use_puf_not_cps, use_full_sample, user_mods, behavior_allowed): """ The calculate function assumes the specified user_mods is a dictionary returned by the Calculator.read_json_param_objects() function. The function returns (calc1, calc2, mask) where calc1 is pre-reform Calculator object calculated for year_n, calc2 is post-reform Calculator object calculated for year_n, and mask is boolean array marking records with reform-induced iitax diffs 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 stime = time.time() 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 = 180 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( # pylint: disable=no-member frac=sampling_frac, random_state=sampling_seed) if use_puf_not_cps: print('puf-read-time= {:.1f}'.format(time.time() - stime)) else: print('cps-read-time= {:.1f}'.format(time.time() - stime)) # create pre-reform Calculator instance if use_puf_not_cps: recs1 = Records(data=copy.deepcopy(sample), gfactors=growfactors_pre) else: recs1 = Records.cps_constructor(data=copy.deepcopy(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 # compute mask array res1 = calc1.dataframe(DIST_VARIABLES) if use_puf_not_cps: # create pre-reform Calculator instance with extra income recs1p = Records(data=copy.deepcopy(sample), gfactors=growfactors_pre) # add one dollar to the income of each filing unit to determine # which filing units undergo a resulting change in tax liability recs1p.e00200 += 1.0 # pylint: disable=no-member recs1p.e00200p += 1.0 # pylint: disable=no-member policy1p = Policy(gfactors=growfactors_pre) # create Calculator with recs1p and calculate for start_year calc1p = Calculator(policy=policy1p, records=recs1p, consumption=consump) while calc1p.current_year < start_year: calc1p.increment_year() calc1p.calc_all() assert calc1p.current_year == start_year # compute mask showing which of the calc1 and calc1p results differ; # mask is true if a filing unit's income tax liability changed after # a dollar was added to the filing unit's wage and salary income res1p = calc1p.dataframe(DIST_VARIABLES) mask = np.logical_not( # pylint: disable=no-member np.isclose(res1.iitax, res1p.iitax, atol=0.001, rtol=0.0)) assert np.any(mask) else: # if use_cps_not_cps is False # indicate that no fuzzing of reform results is required mask = np.zeros(res1.shape[0], dtype=np.int8) # 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=copy.deepcopy(sample), gfactors=growfactors_post) else: recs2 = Records.cps_constructor(data=copy.deepcopy(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() calc2.calc_all() assert calc2.current_year == start_year # 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 and mask return (calc1, calc2, mask)
def dropq_calculate(year_n, start_year, taxrec_df, user_mods, behavior_allowed, mask_computed): """ The dropq_calculate function assumes specified user_mods is a dictionary returned by the Calculator.read_json_parameter_files() function with an extra key:value pair that is specified as 'gdp_elasticity': {'value': <float_value>}. The function returns (calc1, calc2, mask) where calc1 is pre-reform Calculator object calculated for year_n, calc2 is post-reform Calculator object calculated for year_n, and mask is boolean array if compute_mask=True or None otherwise """ # pylint: disable=too-many-arguments,too-many-locals,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 pre-reform Calculator instance recs1 = Records(data=taxrec_df.copy(deep=True), 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 # optionally compute mask if mask_computed: # create pre-reform Calculator instance with extra income recs1p = Records(data=taxrec_df.copy(deep=True), gfactors=growfactors_pre) # add one dollar to total wages and salaries of each filing unit recs1p.e00200 += 1.0 # pylint: disable=no-member recs1p.e00200p += 1.0 # pylint: disable=no-member policy1p = Policy(gfactors=growfactors_pre) # create Calculator with recs1p and calculate for start_year calc1p = Calculator(policy=policy1p, records=recs1p, consumption=consump) while calc1p.current_year < start_year: calc1p.increment_year() calc1p.calc_all() assert calc1p.current_year == start_year # compute mask that shows which of the calc1 and calc1p results differ res1 = results(calc1.records) res1p = results(calc1p.records) mask = (res1.iitax != res1p.iitax) else: mask = None # 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 recs2 = Records(data=taxrec_df.copy(deep=True), 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() calc2.calc_all() assert calc2.current_year == start_year # 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 and mask return (calc1, calc2, mask)