def test_nondefault_response_function(be_inc, cps_subsample): """ Test that non-default behavior parameters produce expected results. """ # ... specify Records object and policy reform rec = tc.Records.cps_constructor(data=cps_subsample) refyear = 2020 reform = {'II_em': {refyear: 1500}} # ... specify non-default1 response elasticities elasticities_dict = {'sub': 0.25, 'inc': be_inc, 'cg': -0.79} # ... calculate behavioral response to reform pol = tc.Policy() calc1 = tc.Calculator(records=rec, policy=pol) pol.implement_reform(reform) calc2 = tc.Calculator(records=rec, policy=pol) del pol calc1.advance_to_year(refyear) calc2.advance_to_year(refyear) df1, df2 = response(calc1, calc2, elasticities_dict) del calc1 del calc2 itax1 = round((df1['iitax'] * df1['s006']).sum() * 1e-9, 3) itax2 = round((df2['iitax'] * df2['s006']).sum() * 1e-9, 3) del df1 del df2 if be_inc == 0.0: assert np.allclose([itax1, itax2], [1355.556, 1304.984]) elif be_inc == -0.1: assert np.allclose([itax1, itax2], [1355.556, 1303.898])
def test_alternative_behavior_parameters(cps_subsample): """ Test alternative behavior parameters to improve code coverage. Also, test response function's dump argument. """ # ... specify Records object and policy reform rec = tc.Records.cps_constructor(data=cps_subsample) refyear = 2020 reform = {'II_em': {refyear: 1500}} # ... specify non-default response elasticities elasticities_dict = {'inc': -0.1} # ... calculate behavioral response to reform pol = tc.Policy() calc1 = tc.Calculator(records=rec, policy=pol) pol.implement_reform(reform) calc2 = tc.Calculator(records=rec, policy=pol) del pol calc1.advance_to_year(refyear) calc2.advance_to_year(refyear) df1, df2 = response(calc1, calc2, elasticities_dict) del calc1 del calc2 itax1 = round((df1['iitax'] * df1['s006']).sum() * 1e-9, 3) itax2 = round((df2['iitax'] * df2['s006']).sum() * 1e-9, 3) del df1 del df2 assert np.allclose([itax1, itax2], [1355.556, 1302.09])
def test_sub_effect_independence(stcg): """ Ensure that LTCG amount does not affect magnitude of substitution effect. """ # pylint: disable=too-many-locals # specify reform that raises top-bracket marginal tax rate refyear = 2020 reform = {'II_rt7': {refyear: 0.70}} # specify a substitution effect behavioral response elasticity elasticities_dict = {'sub': 0.25} # specify several high-earning filing units num_recs = 9 input_csv = (u'RECID,MARS,e00200,e00200p,p22250,p23250\n' u'1,2,1000000,1000000,stcg, 0\n' u'2,2,1000000,1000000,stcg, 4800\n' u'3,2,1000000,1000000,stcg, 3600\n' u'4,2,1000000,1000000,stcg, 2400\n' u'5,2,1000000,1000000,stcg, 1200\n' u'6,2,1000000,1000000,stcg, 0\n' u'7,2,1000000,1000000,stcg,-1200\n' u'8,2,1000000,1000000,stcg,-2400\n' u'9,2,1000000,1000000,stcg,-3600\n') inputcsv = input_csv.replace('stcg', str(stcg)) input_dataframe = pd.read_csv(StringIO(inputcsv)) assert len(input_dataframe.index) == num_recs recs = tc.Records(data=input_dataframe, start_year=refyear, gfactors=None, weights=None) pol = tc.Policy() calc1 = tc.Calculator(records=recs, policy=pol) assert calc1.current_year == refyear pol.implement_reform(reform) calc2 = tc.Calculator(records=recs, policy=pol) assert calc2.current_year == refyear del pol df1, df2 = response(calc1, calc2, elasticities_dict) del calc1 del calc2 # compute change in taxable income for each of the filing units chg_funit = dict() for rid in range(1, num_recs + 1): idx = rid - 1 chg_funit[rid] = df2['c04800'][idx] - df1['c04800'][idx] del df1 del df2 # confirm reform reduces taxable income when assuming substitution effect emsg = '' for rid in range(1, num_recs + 1): if not chg_funit[rid] < 0: txt = '\nFAIL: stcg={} : chg[{}]={:.2f} is not negative' emsg += txt.format(stcg, rid, chg_funit[rid]) # confirm change in taxable income is same for all filing units for rid in range(2, num_recs + 1): if not np.allclose(chg_funit[rid], chg_funit[1]): txt = '\nFAIL: stcg={} : chg[{}]={:.2f} != chg[1]={:.2f}' emsg += txt.format(stcg, rid, chg_funit[rid], chg_funit[1]) del chg_funit if emsg: raise ValueError(emsg)
def test_default_response_function(cps_subsample): """ Test that default behavior parameters produce static results. """ # ... specify Records object and policy reform rec = tc.Records.cps_constructor(data=cps_subsample) refyear = 2020 assert refyear >= 2018 reform = {'II_em': {refyear: 1500}} # ... construct pre-reform calculator pol = tc.Policy() calc1 = tc.Calculator(records=rec, policy=pol) calc1.advance_to_year(refyear) # ... construct two post-reform calculators pol.implement_reform(reform) calc2s = tc.Calculator(records=rec, policy=pol) # for static assumptions calc2s.advance_to_year(refyear) calc2d = tc.Calculator(records=rec, policy=pol) # for default behavior calc2d.advance_to_year(refyear) del pol # ... calculate aggregate inctax using static assumptions calc2s.calc_all() df2s = calc2s.dataframe(['iitax', 's006']) itax2s = round((df2s['iitax'] * df2s['s006']).sum() * 1e-9, 3) # ... calculate aggregate inctax using zero response elasticities _, df2d = response(calc1, calc2d, elasticities={}, dump=True) itax2d = round((df2d['iitax'] * df2d['s006']).sum() * 1e-9, 3) assert np.allclose(itax2d, itax2s) # ... clean up del calc1 del calc2s del calc2d del df2s del df2d
def _dynamic_run(self, varlist, base_calc, reform_calc): """ Run a dynamic response """ if "s006" not in varlist: # ensure weight is always included varlist.append("s006") for year in range(self.start_year, self.end_year + 1): base_calc.advance_to_year(year) reform_calc.advance_to_year(year) base, reform = behresp.response(base_calc, reform_calc, self.params["behavior"], dump=True) self.base_data[year] = base[varlist] self.reform_data[year] = reform[varlist]
def _run_dynamic_calc(self, calc1, calc2, behavior, year, varlist): """ Function used to parallelize the dynamic run function Parameters ---------- calc1: Calculator object representing the baseline policy calc2: Calculator object representing the reform policy year: year for the calculations """ calc1_copy = copy.deepcopy(calc1) calc2_copy = copy.deepcopy(calc2) calc1_copy.advance_to_year(year) calc2_copy.advance_to_year(year) # use response function to capture dynamic effects base, reform = response(calc1_copy, calc2_copy, behavior, dump=True) self.base_data[year] = base[varlist] self.reform_data[year] = reform[varlist] del calc1_copy, calc2_copy, base, reform
def _behresp_advance(self, base_calc, reform_calc, varlist, year): ''' This function advances the year used in the Behavioral Responses model and saves the results to a dictionary. Args: calc1 (Tax-Calculator Calculator object): TC calculator year (int): year to begin advancing from Returns: tax_dict (dict): a dictionary of microdata with marginal tax rates and other information computed in TC ''' base_calc.advance_to_year(year) reform_calc.advance_to_year(year) base, reform = behresp.response(base_calc, reform_calc, self.params["behavior"], dump=True) base_df = base[varlist] reform_df = reform[varlist] return [base_df, reform_df]
pol.implement_reform(params['policy']) calc2 = Calculator(policy=pol, records=recs) # calculate reform income tax liabilities for cyr under static assumptions calc2.advance_to_year(cyr) calc2.calc_all() itax_rev2sa = calc2.weighted_total('iitax') # specify behavioral-response assumptions behresp_json = '{"BE_sub": {"2018": 0.25}}' behresp_dict = Calculator.read_json_assumptions(behresp_json) # specify Calculator object for analysis of reform with behavioral response calc2 = Calculator(policy=pol, records=recs) calc2.advance_to_year(cyr) _, df2br = behresp.response(calc1, calc2, behresp_dict) # calculate reform income tax liabilities for cyr with behavioral response itax_rev2br = (df2br['iitax'] * df2br['s006']).sum() # print total income tax revenue estimates for cyr # (estimates in billons of dollars rounded to nearest hundredth of a billion) print('{}_CURRENT_LAW_P__itax_rev($B)= {:.3f}'.format(cyr, itax_rev1 * 1e-9)) print('{}_REFORM_STATIC__itax_rev($B)= {:.3f}'.format(cyr, itax_rev2sa * 1e-9)) print('{}_REFORM_DYNAMIC_itax_rev($B)= {:.3f}'.format(cyr, itax_rev2br * 1e-9)) # create multi-year diagnostic tables for # (1) baseline, # (2) reform excluding behavioral responses, and # (3) reform including behavioral responses num_years = 3 # number of diagnostic table years beginning with cyr
# specify Calculator object for static analysis of reform policy pol.implement_reform(params['policy']) calc2 = Calculator(policy=pol, records=recs) # calculate reform income tax liabilities for cyr under static assumptions calc2.advance_to_year(cyr) calc2.calc_all() itax_rev2sa = calc2.weighted_total('iitax') # specify assumed non-zero response-function substitution elasticity response_elasticities = {'sub': 0.25} # specify Calculator object for analysis of reform with behavioral responses calc2 = Calculator(policy=pol, records=recs) calc2.advance_to_year(cyr) _, df2br = behresp.response(calc1, calc2, response_elasticities) # calculate reform income tax liabilities for cyr with behavioral response itax_rev2br = (df2br['iitax'] * df2br['s006']).sum() # print total income tax revenue estimates for cyr # (estimates in billons of dollars) print('{}_CURRENT_LAW_P__itax_rev($B)= {:.3f}'.format(cyr, itax_rev1 * 1e-9)) print('{}_REFORM_STATIC__itax_rev($B)= {:.3f}'.format(cyr, itax_rev2sa * 1e-9)) print('{}_REFORM_DYNAMIC_itax_rev($B)= {:.3f}'.format(cyr, itax_rev2br * 1e-9)) # create multi-year diagnostic tables for # (1) baseline, # (2) reform excluding behavioral responses, and # (3) reform including behavioral responses num_years = 3 # number of diagnostic table years beginning with cyr
# specify Calculator object for static analysis of reform policy pol.implement_reform(Policy.read_json_reform('reformA.json')) calc2 = Calculator(policy=pol, records=recs) # calculate reform income tax liabilities for cyr under static assumptions calc2.advance_to_year(cyr) calc2.calc_all() itax_rev2sa = calc2.weighted_total('iitax') # specify assumed non-zero response-function substitution elasticity response_elasticities = {'sub': 0.25} # specify Calculator object for analysis of reform with behavioral responses calc2 = Calculator(policy=pol, records=recs) calc2.advance_to_year(cyr) _, df2br = behresp.response(calc1, calc2, response_elasticities) # calculate reform income tax liabilities for cyr with behavioral response itax_rev2br = (df2br['iitax'] * df2br['s006']).sum() # print total income tax revenue estimates for cyr # (estimates in billons of dollars) print('{}_CURRENT_LAW_P__itax_rev($B)= {:.3f}'.format(cyr, itax_rev1 * 1e-9)) print('{}_REFORM_STATIC__itax_rev($B)= {:.3f}'.format(cyr, itax_rev2sa * 1e-9)) print('{}_REFORM_DYNAMIC_itax_rev($B)= {:.3f}'.format(cyr, itax_rev2br * 1e-9)) # create multi-year diagnostic tables for # (1) baseline, # (2) reform excluding behavioral responses, and # (3) reform including behavioral responses num_years = 3 # number of diagnostic table years beginning with cyr
def create_table( self, reform_file=None, tc_vars=None, tc_labels=None, include_mtr=True, be_sub=0, be_inc=0, be_cg=0, ): """ Creates table of liabilities. Default is current law with no behavioral response (i.e. static analysis). User may specify a policy reform which is read and implemented below in get_pol() and/or or may specify elasticities for partial- equilibrium behavioral responses. reform_file: name of a reform file in the Tax-Calculator reforms folder, a file path to a custom JSON reform file, or a dictionary with a policy reform. tc_vars: list of Tax-Calculator output variables. tc_labels: list of labels for output table include_mtr: include MTR calculations in output table be_sub: Substitution elasticity of taxable income. Defined as proportional change in taxable income divided by proportional change in marginal net-of-tax rate (1-MTR) on taxpayer earnings caused by the reform. Must be zero or positive. be_inc: Income elasticity of taxable income. Defined as dollar change in taxable income divided by dollar change in after-tax income caused by the reform. Must be zero or negative. be_cg: Semi-elasticity of long-term capital gains. Defined as change in logarithm of long-term capital gains divided by change in marginal tax rate (MTR) on long-term capital gains caused by the reform. Must be zero or negative. Returns: df_res: a Pandas dataframe. Each observation is a separate tax filer """ year = self.invar["FLPDYR"][0] year = int(year.item()) recs = tc.Records( data=self.invar, start_year=year, gfactors=None, weights=None, adjust_ratios=None, ) # if tc_vars and tc_labels are not specified, defaults are used if tc_vars is None: tc_vars = self.TC_VARS if tc_labels is None: tc_labels = self.TC_LABELS assert len(tc_vars) > 0 assert len(tc_vars) == len(tc_labels) # if no reform file is passed, table will show current law values if reform_file is None: pol = tc.Policy() assert be_sub == be_inc == be_cg == 0 calc = tc.Calculator(policy=pol, records=recs) calc.advance_to_year(year) calc.calc_all() calcs = calc.dataframe(tc_vars) # if a reform file is passed, table will show reform values else: pol = self.get_pol(reform_file) calc = tc.Calculator(policy=pol, records=recs) pol_base = tc.Policy() calc_base = tc.Calculator(policy=pol_base, records=recs) response_elasticities = {"sub": be_sub, "inc": be_inc, "cg": be_cg} _, df2br = br.response(calc_base, calc, response_elasticities, dump=True) calcs = df2br[tc_vars] # if include_mtr is True, the tables includes three columns with MTRs if include_mtr: mtr = self.calc_mtr(reform_file) mtr_df = pd.DataFrame(data=mtr).transpose() df_res = pd.concat([calcs, mtr_df], axis=1) col_labels = tc_labels + self.MTR_LABELS df_res.columns = col_labels df_res.index = range(self.rows) else: df_res = calcs df_res.columns = tc_labels df_res.index = range(self.rows) return df_res