def test_correct_Growfactors_usage(): gf = Growfactors() pir = gf.price_inflation_rates(2013, 2020) assert len(pir) == 8 wgr = gf.wage_growth_rates(2013, 2021) assert len(wgr) == 9 val = gf.factor_value('AWAGE', 2013) assert val > 1.0
def test_growfactors_csv_values(): """ Test numerical contents of growfactors.csv file. """ gfo = Growfactors() min_data_year = min(Records.PUFCSV_YEAR, Records.CPSCSV_YEAR) if min_data_year < Policy.JSON_START_YEAR: for gfname in Growfactors.VALID_NAMES: val = gfo.factor_value(gfname, min_data_year) assert val == 1
def test_proper_usage(): """ Test proper usage of Growfactors object. """ gfo = Growfactors() pir = gfo.price_inflation_rates(2013, 2020) assert len(pir) == 8 wgr = gfo.wage_growth_rates(2013, 2021) assert len(wgr) == 9 val = gfo.factor_value('AWAGE', 2013) assert val > 1.0
def test_correct_Records_instantiation(cps_subsample): rec1 = Records.cps_constructor(data=cps_subsample) assert rec1 assert np.all(rec1.MARS != 0) assert rec1.current_year == rec1.data_year sum_e00200_in_cps_year = rec1.e00200.sum() rec1.set_current_year(rec1.data_year + 1) sum_e00200_in_cps_year_plus_one = rec1.e00200.sum() assert sum_e00200_in_cps_year_plus_one == sum_e00200_in_cps_year wghts_path = os.path.join(Records.CUR_PATH, Records.CPS_WEIGHTS_FILENAME) wghts_df = pd.read_csv(wghts_path) ratio_path = os.path.join(Records.CUR_PATH, Records.PUF_RATIOS_FILENAME) ratio_df = pd.read_csv(ratio_path) ratio_df = ratio_df.transpose() benefit_path = os.path.join(Records.CUR_PATH, Records.CPS_BENEFITS_FILENAME) benefit_df = pd.read_csv(benefit_path) rec2 = Records(data=cps_subsample, exact_calculations=False, gfactors=Growfactors(), weights=wghts_df, adjust_ratios=ratio_df, benefits=benefit_df, start_year=Records.CPSCSV_YEAR) assert rec2 assert np.all(rec2.MARS != 0) assert rec2.current_year == rec2.data_year
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_chained_cpi_reform(offset): """ Test that _cpi_offset policy parameter works as expected. """ # specify reform without using cpi_offset parameter pem = 10000 bare_reform = {2022: {'_II_em': [pem]}} with_reform = {2022: {'_II_em': [pem]}, 2020: {'_cpi_offset': [offset]}} syr = Policy.JSON_START_YEAR pol_bare = Policy(start_year=syr) pol_bare.implement_reform(bare_reform) pol_with = Policy(start_year=syr) pol_with.implement_reform(with_reform) # check relative _II_em values in the two reforms for several years pem_bare = pol_bare._II_em pem_with = pol_with._II_em if offset == 0: assert pem_with[2019 - syr] == pem_bare[2019 - syr] assert pem_with[2020 - syr] == pem_bare[2020 - syr] assert pem_with[2021 - syr] == pem_bare[2021 - syr] assert pem_with[2022 - syr] == pem assert pem_bare[2022 - syr] == pem assert pem_with[2023 - syr] == pem_bare[2023 - syr] elif offset < 0: assert pem_with[2019 - syr] == pem_bare[2019 - syr] assert pem_with[2020 - syr] == pem_bare[2020 - syr] assert pem_with[2021 - syr] < pem_bare[2021 - syr] assert pem_with[2022 - syr] == pem assert pem_bare[2022 - syr] == pem assert pem_with[2023 - syr] < pem_bare[2023 - syr] # check exact _II_em values for 2023, which are # equal to 2022 values indexed by 2022 inflation rates unchained_cpi = pol_bare.inflation_rates()[2022 - syr] assert pem_bare[2023 - syr] == round(pem * (1 + unchained_cpi), 2) chained_cpi = unchained_cpi + offset assert pem_with[2023 - syr] == round(pem * (1 + chained_cpi), 2) # check that _STD value for 2023 with chained CPI indexing is # equal to _STD value for 2023 when specifying chained CPI indexing # as a difference in assumed inflation rates if offset != 0: # ... compute _STD value using difference-in-growth-factors approach growfactors = Growfactors() growdiff = Growdiff() growdiff.update_growdiff({2020: {'_ACPIU': [offset]}}) growdiff.apply_to(growfactors) pol = Policy(gfactors=growfactors, start_year=syr) pol.implement_reform(bare_reform) # ... compare the _STD values derived from the two approaches assert_allclose(pol_with._STD[2023 - syr], pol._STD[2023 - syr], atol=0.01, rtol=0.0)
def test_update_after_use(): """ Test of improper update after Growfactors object has been used. """ gfo = Growfactors() gfo.price_inflation_rates(gfo.first_year, gfo.last_year) with pytest.raises(ValueError): gfo.update('AWAGE', 2013, 0.01)
def test_correct_Records_instantiation_sample(puf_1991, weights_1991): sample = puf_1991.sample(frac=0.10) # instantiate Records object with no extrapolation rec1 = Records(data=sample, gfactors=None, weights=weights_1991) assert rec1 assert np.all(rec1.MARS != 0) assert rec1.current_year == Records.PUF_YEAR sum_e00200_in_puf_year = rec1.e00200.sum() rec1.set_current_year(Records.PUF_YEAR + 1) sum_e00200_in_puf_year_plus_one = rec1.e00200.sum() assert sum_e00200_in_puf_year_plus_one == sum_e00200_in_puf_year # instantiate Records object with default extrapolation rec2 = Records(data=sample, gfactors=Growfactors(), weights=None) assert rec2 assert np.all(rec2.MARS != 0) assert rec2.current_year == Records.PUF_YEAR
def test_correct_Records_instantiation(puf_1991, puf_1991_path, weights_1991): rec1 = Records(data=puf_1991_path, gfactors=None, weights=weights_1991) assert rec1 assert np.all(rec1.MARS != 0) assert rec1.current_year == Records.PUF_YEAR sum_e00200_in_puf_year = rec1.e00200.sum() rec1.set_current_year(Records.PUF_YEAR + 1) sum_e00200_in_puf_year_plus_one = rec1.e00200.sum() assert sum_e00200_in_puf_year_plus_one == sum_e00200_in_puf_year rec2 = Records(data=puf_1991, gfactors=Growfactors(), weights=None) assert rec2 assert np.all(rec2.MARS != 0) assert rec2.current_year == Records.PUF_YEAR adj_df = pd.read_csv(Records.ADJUST_RATIOS_PATH) adj_df = adj_df.transpose() rec3 = Records(data=puf_1991, weights=None, adjust_ratios=adj_df) assert rec3 assert np.all(rec3.MARS != 0) assert rec3.current_year == Records.PUF_YEAR
def test_update_and_apply_growdiff(): syr = 2013 nyrs = 5 lyr = syr + nyrs - 1 gdiff = Growdiff(start_year=syr, num_years=nyrs) # update Growdiff instance diffs = {2014: {'_AWAGE': [0.01]}, 2016: {'_AWAGE': [0.02]}} gdiff.update_growdiff(diffs) expected_wage_diffs = [0.00, 0.01, 0.01, 0.02, 0.02] assert_allclose(gdiff._AWAGE, expected_wage_diffs, atol=0.0, rtol=0.0) # apply growdiff to Growfactors instance gf = Growfactors() pir_pre = gf.price_inflation_rates(syr, lyr) wgr_pre = gf.wage_growth_rates(syr, lyr) gfactors = Growfactors() gdiff.apply_to(gfactors) pir_pst = gfactors.price_inflation_rates(syr, lyr) wgr_pst = gfactors.wage_growth_rates(syr, lyr) expected_wgr_pst = [ wgr_pre[i] + expected_wage_diffs[i] for i in range(0, nyrs) ] assert_allclose(pir_pre, pir_pst, atol=0.0, rtol=0.0) assert_allclose(wgr_pst, expected_wgr_pst, atol=1.0e-9, rtol=0.0)
def test_improper_usage(bad_gf_file): """ Tests of improper usage of Growfactors object. """ with pytest.raises(ValueError): gfo = Growfactors(dict()) with pytest.raises(ValueError): gfo = Growfactors(bad_gf_file.name) gfo = Growfactors() fyr = gfo.first_year lyr = gfo.last_year with pytest.raises(ValueError): gfo.price_inflation_rates(fyr - 1, lyr) with pytest.raises(ValueError): gfo.price_inflation_rates(fyr, lyr + 1) with pytest.raises(ValueError): gfo.price_inflation_rates(lyr, fyr) with pytest.raises(ValueError): gfo.wage_growth_rates(fyr - 1, lyr) with pytest.raises(ValueError): gfo.wage_growth_rates(fyr, lyr + 1) with pytest.raises(ValueError): gfo.wage_growth_rates(lyr, fyr) with pytest.raises(ValueError): gfo.factor_value('BADNAME', fyr) with pytest.raises(ValueError): gfo.factor_value('AWAGE', fyr - 1) with pytest.raises(ValueError): gfo.factor_value('AWAGE', lyr + 1)
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)
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 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 test_update_after_use(): gf = Growfactors() pir = gf.price_inflation_rates(gf.first_year, gf.last_year) with pytest.raises(ValueError): gf.update('AWAGE', 2013, 0.01)
def test_incorrect_Growfactors_usage(bad_gf_file): with pytest.raises(ValueError): gf = Growfactors(dict()) with pytest.raises(ValueError): gf = Growfactors(bad_gf_file.name) gf = Growfactors() with pytest.raises(ValueError): pir = gf.price_inflation_rates(2000, 2099) with pytest.raises(ValueError): pir = gf.price_inflation_rates(2009, 2099) with pytest.raises(ValueError): pir = gf.price_inflation_rates(2021, 2013) with pytest.raises(ValueError): wgr = gf.wage_growth_rates(2000, 2099) with pytest.raises(ValueError): wgr = gf.wage_growth_rates(2009, 2099) with pytest.raises(ValueError): wgr = gf.wage_growth_rates(2021, 2013) with pytest.raises(ValueError): val = gf.factor_value('BADNAME', 2020) with pytest.raises(ValueError): val = gf.factor_value('AWAGE', 2000) with pytest.raises(ValueError): val = gf.factor_value('AWAGE', 2099)
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) 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 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=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_agg(tests_path): """ 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 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 cps.csv input records rec = Records.cps_constructor() # create a Calculator object using baseline policy and cps 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 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 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_results.splitlines(True), expected_results.splitlines(True), small) 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 cps_filepath = os.path.join(tests_path, '..', 'cps.csv.gz') fullsample = pd.read_csv(cps_filepath) rn_seed = 180 # to ensure sub-sample is always the same subfrac = 0.03 # sub-sample fraction subsample = fullsample.sample(frac=subfrac, random_state=rn_seed) rec_subsample = Records(data=subsample, gfactors=Growfactors(), weights=Records.CPS_WEIGHTS_FILENAME, adjust_ratios=Records.CPS_RATIOS_FILENAME, start_year=Records.CPSCSV_YEAR) 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 # TODO: skip first year because of BUG in cps_weights.csv file taxes_subsample = taxes_subsample[1:] # TODO: eliminate code taxes_fullsample = taxes_fullsample[1:] # TODO: eliminate code if not np.allclose( taxes_subsample, taxes_fullsample, atol=0.0, rtol=reltol): msg = 'CPSCSV 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 run_nth_year_gdp_elast_model(year_n, start_year, taxrec_df, user_mods, return_json=True): """ The run_nth_year_gdp_elast_model function assumes 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-arguments,too-many-locals,too-many-statements check_user_mods(user_mods) # Only makes sense to run for budget years 1 through n-1 (not for year 0) assert year_n > 0 # Specify value of gdp_elasticity gdp_elasticity = user_mods['gdp_elasticity']['value'] # Specify Consumption instance consump = Consumption() consump_assumps = user_mods['consumption'] consump.update_consumption(consump_assumps) # 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 records1 = Records(data=taxrec_df.copy(deep=True), gfactors=growfactors_pre) policy1 = Policy(gfactors=growfactors_pre) calc1 = Calculator(policy=policy1, records=records1, consumption=consump) while calc1.current_year < start_year: calc1.increment_year() assert calc1.current_year == start_year # Create post-reform Calculator instance records2 = 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=records2, consumption=consump) while calc2.current_year < start_year: calc2.increment_year() assert calc2.current_year == start_year # Seed random number generator with a seed value based on user_mods seed = random_seed(user_mods) np.random.seed(seed) # pylint: disable=no-member for _ in range(0, year_n - 1): calc1.increment_year() calc2.increment_year() calc1.calc_all() calc2.calc_all() # Assert that the current year is one behind the year we are calculating assert (calc1.current_year + 1) == (start_year + year_n) assert (calc2.current_year + 1) == (start_year + year_n) # Compute gdp effect gdp_effect = proportional_change_gdp(calc1, calc2, gdp_elasticity) # Return gdp_effect results if return_json: gdp_df = pd.DataFrame(data=[gdp_effect], columns=['col0']) gdp_elast_names_i = [ x + '_' + str(year_n) for x in GDP_ELAST_ROW_NAMES ] gdp_elast_total = create_json_table(gdp_df, row_names=gdp_elast_names_i, num_decimals=5) gdp_elast_total = dict((k, v[0]) for k, v in gdp_elast_total.items()) return gdp_elast_total else: return gdp_effect