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 run_calc(self): """ Creates baseline, reform, and + $1 Tax-Calculator objects Returns: self.calc1: Calculator object for current law self.calc_reform: Calculator object for reform self.calc_mtr: Calculator object for + $1 """ year = self.data.iloc[0][1] year = year.item() recs = tc.Records(data=self.data, start_year=year) self.calc1 = tc.Calculator(policy=self.pol, records=recs) self.calc1.advance_to_year(year) self.calc1.calc_all() self.calc_reform = tc.Calculator(policy=self.pol2, records=recs) self.calc_reform.advance_to_year(year) self.calc_reform.calc_all() recs_mtr = tc.Records(data=self.data_mtr, start_year=year) self.calc_mtr = tc.Calculator(policy=self.pol2, records=recs_mtr) self.calc_mtr.advance_to_year(year) self.calc_mtr.calc_all() return self.calc1, self.calc_reform, self.calc_mtr
def _make_calculators(self): """ This function creates the baseline and reform calculators used when the `run()` method is called """ # Create two microsimulation calculators gd_base = tc.GrowDiff() gf_base = tc.GrowFactors() # apply user specified growdiff if self.params["growdiff_baseline"]: gd_base.update_growdiff(self.params["growdiff_baseline"]) gd_base.apply_to(gf_base) # Baseline calculator if self.use_cps: records = tc.Records.cps_constructor(data=self.microdata, gfactors=gf_base) else: records = tc.Records(self.microdata, gfactors=gf_base) policy = tc.Policy(gf_base) if self.params["base_policy"]: update_policy(policy, self.params["base_policy"]) base_calc = tc.Calculator(policy=policy, records=records, verbose=self.verbose) # Reform calculator # Initialize a policy object gd_reform = tc.GrowDiff() gf_reform = tc.GrowFactors() if self.params["growdiff_response"]: gd_reform.update_growdiff(self.params["growdiff_response"]) gd_reform.apply_to(gf_reform) if self.use_cps: records = tc.Records.cps_constructor(data=self.microdata, gfactors=gf_reform) else: records = tc.Records(self.microdata, gfactors=gf_reform) policy = tc.Policy(gf_reform) if self.params["base_policy"]: update_policy(policy, self.params["base_policy"]) update_policy(policy, self.params["policy"]) # Initialize Calculator reform_calc = tc.Calculator(policy=policy, records=records, verbose=self.verbose) # delete all unneeded variables del gd_base, gd_reform, records, gf_base, gf_reform, policy return base_calc, reform_calc
def calc_mtr(self, reform_file): """ Calculates income tax, payroll tax, and combined marginal rates """ year = self.invar["FLPDYR"][0] year = int(year.item()) recs_base = tc.Records( data=self.invar, start_year=year, gfactors=None, weights=None, adjust_ratios=None, ) if reform_file is None: pol = tc.Policy() else: pol = self.get_pol(reform_file) calc_base = tc.Calculator(policy=pol, records=recs_base) calc_base.advance_to_year(year) calc_base.calc_all() payrolltax_base = calc_base.array("payrolltax") incometax_base = calc_base.array("iitax") combined_taxes_base = incometax_base + payrolltax_base recs_marg = tc.Records( data=self.invar_marg, start_year=year, gfactors=None, weights=None, adjust_ratios=None, ) calc_marg = tc.Calculator(policy=pol, records=recs_marg) calc_marg.advance_to_year(year) calc_marg.calc_all() payrolltax_marg = calc_marg.array("payrolltax") incometax_marg = calc_marg.array("iitax") combined_taxes_marg = incometax_marg + payrolltax_marg payrolltax_diff = payrolltax_marg - payrolltax_base incometax_diff = incometax_marg - incometax_base combined_diff = combined_taxes_marg - combined_taxes_base mtr_payrolltax = payrolltax_diff / FINITE_DIFF mtr_incometax = incometax_diff / FINITE_DIFF mtr_combined = combined_diff / FINITE_DIFF return (mtr_payrolltax, mtr_incometax, mtr_combined)
def create_table(self, reform_file=None): """ Creates table of liabilities. Default is current law, but user may specify a policy reform which is read and implemented below in get_pol() The reform_file argument can be the 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. Returns: df_res: a Pandas dataframe. Each observation is a separate tax filer """ pol = self.get_pol(reform_file) year = self.invar['FLPDYR'][0] year = year.item() recs = tc.Records(data=self.invar, start_year=year) calc = tc.Calculator(policy=pol, records=recs) calc.advance_to_year(year) calc.calc_all() calcs = calc.dataframe(self.tc_vars) mtr = calc.mtr(wrt_full_compensation=False) mtr_df = pd.DataFrame(data=mtr).transpose() df_res = pd.concat([calcs, mtr_df], axis=1) df_res.columns = self.labels df_res.index = range(self.rows) return df_res
def initiate_itax_calculator(self): """ Creates and calculates an itax.Calculator object for START_YEAR """ calc = itax.Calculator(policy=self.itax_policy, records=itax.Records(data=self.records_data), verbose=False) calc.advance_to_year(START_YEAR) calc.calc_all() return calc
def _make_stacked_objects(self): """ This method makes the base calculator and policy and records objects for stacked reforms. The difference between this and the standard _make_calcuators method is that this method only fully creates the baseline calculator. For the reform, it creates policy and records objects and implements any growth assumptions provided by the user. """ # Create two microsimulation calculators gd_base = tc.GrowDiff() gf_base = tc.GrowFactors() # apply user specified growdiff if self.params["growdiff_baseline"]: gd_base.update_growdiff(self.params["growdiff_baseline"]) gd_base.apply_to(gf_base) # Baseline calculator if self.use_cps: records = tc.Records.cps_constructor(data=self.microdata, gfactors=gf_base) else: records = tc.Records(self.microdata, gfactors=gf_base) policy = tc.Policy(gf_base) if self.params["base_policy"]: update_policy(policy, self.params["base_policy"]) base_calc = tc.Calculator(policy=policy, records=records, verbose=self.verbose) # Reform calculator # Initialize a policy object gd_reform = tc.GrowDiff() gf_reform = tc.GrowFactors() if self.params["growdiff_response"]: gd_reform.update_growdiff(self.params["growdiff_response"]) gd_reform.apply_to(gf_reform) if self.use_cps: records = tc.Records.cps_constructor(data=self.microdata, gfactors=gf_reform) else: records = tc.Records(self.microdata, gfactors=gf_reform) policy = tc.Policy(gf_reform) return base_calc, policy, records
def __init__(self, start_year, end_year=LAST_BUDGET_YEAR, microdata=None, use_cps=False, reform=None, behavior=None, assump=None, verbose=False): """ Constructor for the TaxBrain class Parameters ---------- start_year: First year in the analysis. Must be no earlier than the first year allowed in Tax-Calculator. end_year: Last year in the analysis. Must be no later than the last year allowed in Tax-Calculator. microdata: Either a path to a micro-data file or a Pandas DataFrame containing micro-data. use_cps: A boolean value to indicate whether or not the analysis should be run using the CPS file included in Tax-Calculator. Note: use_cps cannot be True if a file was also specified with the microdata parameter. reform: Individual income tax policy reform. Can be either a string pointing to a JSON reform file, or the contents of a JSON file. behavior: Individual behavior assumptions use by the Behavior-Response package. assump: A string pointing to a JSON file containing user specified economic assumptions. verbose: A boolean value indicated whether or not to write model progress reports. """ if not use_cps and microdata is None: raise ValueError("Must specify microdata or set 'use_cps' to True") assert isinstance(start_year, int) & isinstance(end_year, int), ( "Start and end years must be integers") assert start_year <= end_year, ( f"Specified end year, {end_year}, is before specified start year, " f"{start_year}.") assert TaxBrain.FIRST_BUDGET_YEAR <= start_year, ( f"Specified start_year, {start_year}, comes before first known " f"budget year, {TaxBrain.FIRST_BUDGET_YEAR}.") assert end_year <= TaxBrain.LAST_BUDGET_YEAR, ( f"Specified end_year, {end_year}, comes after last known " f"budget year, {TaxBrain.LAST_BUDGET_YEAR}.") self.use_cps = use_cps self.start_year = start_year self.end_year = end_year self.base_data = {yr: {} for yr in range(start_year, end_year + 1)} self.reform_data = {yr: {} for yr in range(start_year, end_year + 1)} self.verbose = verbose # Process user inputs early to throw any errors quickly self.params = self._process_user_mods(reform, assump) self.params["behavior"] = behavior # Create two microsimulation calculators gd_base = tc.GrowDiff() gf_base = tc.GrowFactors() # apply user specified growdiff if self.params["growdiff_baseline"]: gd_base.update_growdiff(self.params["growdiff_baseline"]) gd_base.apply_to(gf_base) # Baseline calculator if use_cps: records = tc.Records.cps_constructor(data=microdata, gfactors=gf_base) else: records = tc.Records(microdata, gfactors=gf_base) self.base_calc = tc.Calculator(policy=tc.Policy(gf_base), records=records, verbose=self.verbose) # Reform calculator # Initialize a policy object gd_reform = tc.GrowDiff() gf_reform = tc.GrowFactors() if self.params["growdiff_response"]: gd_reform.update_growdiff(self.params["growdiff_response"]) gd_reform.apply_to(gf_reform) if use_cps: records = tc.Records.cps_constructor(data=microdata, gfactors=gf_reform) else: records = tc.Records(microdata, gfactors=gf_reform) policy = tc.Policy(gf_reform) policy.implement_reform(self.params['policy']) # Initialize Calculator self.reform_calc = tc.Calculator(policy=policy, records=records, verbose=self.verbose)
puf = pd.read_csv(PUF_NAME) recs = tc.Records(data=puf, start_year=2011, gfactors=gfactor, weights=WEIGHTS_NAME, adjust_ratios=None) # don't use puf_ratios # recs = tc.Records(data=mypuf, # start_year=2011, # gfactors=gfactor, # weights=WEIGHTS_NAME) # apply built-in puf_ratios.csv # %% advance the file pol = tc.Policy() calc = tc.Calculator(policy=pol, records=recs) CYR = 2018 calc.advance_to_year(CYR) calc.calc_all() # %% create and examine data frame puf_2018 = calc.dataframe(variable_list=[], all_vars=True) puf_2018['pid'] = np.arange(len(puf_2018)) puf_2018.head(10) # %% save advanced file BASE_NAME = 'puf_adjusted'
def report(): """ Generate TaxData history report """ parser = argparse.ArgumentParser() parser.add_argument( "prs", help=( "prs is a list of prs that were used for this report. " "Enter them as a string separated by commas" ), default="", type=str, ) parser.add_argument( "--desc", help=( "File path to a text or markdown file with additonal information " "that will appear at the beginning of the report" ), default="", type=str, ) parser.add_argument( "--basepuf", help=( "File path to the previous puf.csv file. Use when the proposed " "changes affect puf.csv" ), ) args = parser.parse_args() desc = args.desc base_puf_path = args.basepuf if desc: desc = Path(args.desc).open("r").read() plot_paths = [] date = datetime.today().date() template_args = {"date": date, "desc": desc} pull_str = "* [#{}: {}]({})" _prs = args.prs.split(",") session = HTMLSession() prs = [] for pr in _prs: url = f"https://github.com/PSLmodels/taxdata/pull/{pr}" # extract PR title r = session.get(url) elm = r.html.find("span.js-issue-title")[0] title = elm.text prs.append(pull_str.format(pr, title, url)) template_args["prs"] = prs # CBO projection comparisons cbo_projections = [] cur_cbo = pd.read_csv(CBO_URL, index_col=0) new_cbo = pd.read_csv(CBO_PATH, index_col=0) cbo_years = new_cbo.columns.astype(int) last_year = cbo_years.max() first_year = last_year - 9 if new_cbo.equals(cur_cbo): cbo_projections.append("No changes to CBO projections.") else: # we're only going to include the final ten years in our bar chart keep_years = [str(year) for year in range(first_year, last_year + 1)] cur_cbo = cur_cbo.filter(keep_years, axis=1).transpose().reset_index() cur_cbo["Projections"] = "Current" new_cbo = new_cbo.filter(keep_years, axis=1).transpose().reset_index() new_cbo["Projections"] = "New" cbo_data = pd.concat([cur_cbo, new_cbo], axis=0) for col in cbo_data.columns: if col == "index" or col == "Projections": continue chart = cbo_bar_chart(cbo_data, col, CBO_LABELS[col]) img_path = Path(CUR_PATH, f"{col}.png") chart.save(str(img_path)) plot_paths.append(img_path) cbo_projections.append(f"![]({str(img_path)})" + "{.center}") template_args["cbo_projections"] = cbo_projections # changes in data availability cur_meta = pd.read_json(META_URL, orient="index") new_meta = pd.read_json(META_PATH, orient="index") puf_added, puf_removed = compare_vars(cur_meta, new_meta, "puf") cps_added, cps_removed = compare_vars(cur_meta, new_meta, "cps") template_args["puf_added"] = puf_added template_args["puf_removed"] = puf_removed template_args["cps_added"] = cps_added template_args["cps_removed"] = cps_removed # growth rate changes growth_rate_projections = [] cur_grow = pd.read_csv(GROW_FACTORS_URL) new_grow = pd.read_csv(GROW_FACTORS_PATH) if new_grow.equals(cur_grow): growth_rate_projections.append("No changes to growth rate projections") else: new_grow = new_grow[ (new_grow["YEAR"] >= first_year) & (new_grow["YEAR"] <= last_year) ] cur_grow = cur_grow[ (cur_grow["YEAR"] >= first_year) & (cur_grow["YEAR"] <= last_year) ] new_grow["Growth Factors"] = "New" cur_grow["Growth Factors"] = "Current" growth_data = pd.concat([new_grow, cur_grow]) rows = list(growth_data.columns) rows.remove("YEAR"), rows.remove("Growth Factors") n = len(rows) // 3 chart1 = growth_scatter_plot(growth_data, rows[:n]) img_path = Path(CUR_PATH, "growth_factors1.png") chart1.save(str(img_path)) plot_paths.append(img_path) growth_rate_projections.append(f"![]({str(img_path)})" + "{.center}") chart2 = growth_scatter_plot(growth_data, rows[n : 2 * n]) img_path = Path(CUR_PATH, "growth_factors2.png") chart2.save(str(img_path)) plot_paths.append(img_path) growth_rate_projections.append(f"![]({str(img_path)})" + "{.center}") chart3 = growth_scatter_plot(growth_data, rows[2 * n :]) img_path = Path(CUR_PATH, "growth_factors3.png") chart3.save(str(img_path)) plot_paths.append(img_path) growth_rate_projections.append(f"![]({str(img_path)})" + "{.center}") template_args["growth_rate_projections"] = growth_rate_projections # compare tax calculator projections # baseline CPS calculator base_cps = tc.Calculator(records=tc.Records.cps_constructor(), policy=tc.Policy()) base_cps.advance_to_year(first_year) base_cps.calc_all() # updated CPS calculator cps = pd.read_csv(Path(CUR_PATH, "..", "data", "cps.csv.gz"), index_col=None) cps_weights = pd.read_csv( Path(CUR_PATH, "..", "cps_stage2", "cps_weights.csv.gz"), index_col=None ) new_cps = tc.Calculator( records=tc.Records( data=cps, weights=cps_weights, adjust_ratios=None, start_year=2014 ), policy=tc.Policy(), ) new_cps.advance_to_year(first_year) new_cps.calc_all() template_args, plot_paths = compare_calcs( base_cps, new_cps, "cps", template_args, plot_paths ) # PUF comparison if base_puf_path and PUF_AVAILABLE: template_args["puf_msg"] = None # base puf calculator base_puf = tc.Calculator( records=tc.Records(data=base_puf_path), policy=tc.Policy() ) base_puf.advance_to_year(first_year) base_puf.calc_all() # updated puf calculator puf_weights = pd.read_csv( Path(CUR_PATH, "..", "puf_stage2", "puf_weights.csv.gz"), index_col=None ) puf_ratios = pd.read_csv( Path(CUR_PATH, "..", "puf_stage3", "puf_ratios.csv"), index_col=0 ).transpose() new_records = tc.Records( data=str(PUF_PATH), weights=puf_weights, adjust_ratios=puf_ratios ) new_puf = tc.Calculator(records=new_records, policy=tc.Policy()) new_puf.advance_to_year(first_year) new_puf.calc_all() template_args, plot_paths = compare_calcs( base_puf, new_puf, "puf", template_args, plot_paths ) else: msg = "PUF comparisons are not included in this report." template_args["puf_msg"] = msg template_args["puf_agg_plot"] = None template_args["puf_combined_table"] = None template_args["puf_income_table"] = None template_args["puf_payroll_table"] = None # # distribution plots # dist_vars = [ # ("c00100", "AGI Distribution"), # ("combined", "Tax Liability Distribution"), # ] # dist_plots = [] # for var, title in dist_vars: # plot = distplot(calcs, calc_labels, var, title=title) # img_path = Path(CUR_PATH, f"{var}_dist.png") # plot.save(str(img_path)) # plot_paths.append(img_path) # dist_plots.append(f"![]({str(img_path)})" + "{.center}") # template_args["cps_dist_plots"] = dist_plots # # aggregate totals # aggs = defaultdict(list) # var_list = ["payrolltax", "iitax", "combined", "standard", "c04470"] # for year in range(first_year, tc.Policy.LAST_BUDGET_YEAR + 1): # base_aggs = run_calc(base_cps, year, var_list) # new_aggs = run_calc(new_cps, year, var_list) # aggs["Tax Liability"].append(base_aggs["payrolltax"]) # aggs["Tax"].append("Current Payroll") # aggs["Year"].append(year) # aggs["Tax Liability"].append(new_aggs["payrolltax"]) # aggs["Tax"].append("New Payroll") # aggs["Year"].append(year) # aggs["Tax Liability"].append(base_aggs["iitax"]) # aggs["Tax"].append("Current Income") # aggs["Year"].append(year) # aggs["Tax Liability"].append(new_aggs["iitax"]) # aggs["Tax"].append("New Income") # aggs["Year"].append(year) # aggs["Tax Liability"].append(base_aggs["combined"]) # aggs["Tax"].append("Current Combined") # aggs["Year"].append(year) # aggs["Tax Liability"].append(new_aggs["combined"]) # aggs["Tax"].append("New Combined") # aggs["Year"].append(year) # agg_df = pd.DataFrame(aggs) # title = "Aggregate Tax Liability by Year" # agg_chart = ( # alt.Chart(agg_df, title=title) # .mark_line() # .encode( # x=alt.X( # "Year:O", # axis=alt.Axis(labelAngle=0, titleFontSize=20, labelFontSize=15), # ), # y=alt.Y( # "Tax Liability", # title="Tax Liability (Billions)", # axis=alt.Axis(titleFontSize=20, labelFontSize=15), # ), # color=alt.Color( # "Tax", # legend=alt.Legend(symbolSize=150, labelFontSize=15, titleFontSize=20), # ), # ) # .properties(width=800, height=350) # .configure_title(fontSize=24) # ) # img_path = Path(CUR_PATH, "agg_plot.png") # agg_chart.save(str(img_path)) # plot_paths.append(img_path) # template_args["agg_plot"] = f"![]({str(img_path)})" + "{.center}" # # create tax liability tables # template_args["combined_table"] = agg_liability_table(agg_df, "Combined") # template_args["payroll_table"] = agg_liability_table(agg_df, "Payroll") # template_args["income_table"] = agg_liability_table(agg_df, "Income") # write report and delete images used output_path = Path(CUR_PATH, "reports", f"taxdata_report_{date}.pdf") write_page(output_path, TEMPLATE_PATH, **template_args) for path in plot_paths: path.unlink()
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
def create_table(self, reform_file=None): """ Creates table of liabilities. Default is current law, but user may specify a policy reform. The reform_file argument can be the 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. Returns: df_res: a Pandas dataframe. Each observation is a separate tax filer """ REFORMS_URL = ("https://raw.githubusercontent.com/" "PSLmodels/Tax-Calculator/master/taxcalc/reforms/") CURRENT_PATH = os.path.abspath(os.path.dirname(__file__)) # if a reform file is not specified, the default policy is current law if reform_file == None: pol = tc.Policy() else: # check to see if file path to reform_file exists if isinstance(reform_file, str) and os.path.isfile( os.path.join(CURRENT_PATH, reform_file)): reform = tc.Calculator.read_json_param_objects( reform_file, None) # try reform_file as dictionary elif isinstance(reform_file, dict): reform = reform_file # if file path does not exist, check Tax-Calculator reforms file else: try: reform_url = REFORMS_URL + reform_file reform = tc.Calculator.read_json_param_objects( reform_url, None) except: raise 'Reform file does not exist' pol = tc.Policy() pol.implement_reform(reform["policy"]) df_res = [] # create Tax-Calculator records object from each row of csv file and # run calculator for r in range(self.rows): unit = self.invar.iloc[r] unit = pd.DataFrame(unit).transpose() year = unit.iloc[0][1] year = year.item() recs = tc.Records(data=unit, start_year=year) calc = tc.Calculator(policy=pol, records=recs) calc.calc_all() calcs = calc.dataframe(self.tc_vars) # calculate marginal tax rate for each unit mtr = calc.mtr(wrt_full_compensation=False) # income tax MTR, payroll tax MTR mtr_df = pd.DataFrame(data=[mtr[1], mtr[0]]).transpose() table = pd.concat([calcs, mtr_df], axis=1) df_res.append(table) df_res = pd.concat(df_res) df_res.columns = self.labels df_res.index = range(self.rows) return df_res