Пример #1
0
    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
Пример #2
0
    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
Пример #3
0
    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 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)
Пример #5
0
    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
Пример #6
0
    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
Пример #7
0
 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
Пример #8
0
    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)
Пример #9
0
# AGI groups to target separately
IRS_AGI_STUBS = [-9e99, 1.0, 5e3, 10e3, 15e3, 20e3, 25e3, 30e3, 40e3, 50e3,
                 75e3, 100e3, 200e3, 500e3, 1e6, 1.5e6, 2e6, 5e6, 10e6, 9e99]
HT2_AGI_STUBS = [-9e99, 1.0, 10e3, 25e3, 50e3, 75e3, 100e3,
                 200e3, 500e3, 1e6, 9e99]


# %% create objects
gfactor = tc.GrowFactors(GF_NAME)
dir(gfactor)

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()
Пример #10
0
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()
Пример #11
0
def response(calc_1, calc_2, elasticities, dump=False):
    """
    Implements TaxBrain "Partial Equilibrium Simulation" dynamic analysis
    returning results as a tuple of Pandas DataFrame objects (df1, df2) where:
    df1 is extracted from a baseline-policy calc_1 copy, and
    df2 is extracted from a reform-policy calc_2 copy that incorporates the
        behavioral responses given by the nature of the baseline-to-reform
        change in policy and elasticities in the specified behavior dictionary.

    Note: this function internally modifies a copy of calc_2 records to account
      for behavioral responses that arise from the policy reform that involves
      moving from calc1 policy to calc2 policy.  Neither calc_1 nor calc_2 need
      to have had calc_all() executed before calling the response function.
      And neither calc_1 nor calc_2 are affected by this response function.

    The elasticities argument is a dictionary containing the assumed response
    elasticities.  Omitting an elasticity key:value pair in the dictionary
    implies the omitted elasticity is assumed to be zero.  Here is the full
    dictionary content and each elasticity's internal name:

     be_sub = elasticities['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 = elasticities['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 = elasticities['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.
       Read response function documentation (see below) for discussion of
       appropriate values.

    The optional dump argument controls the number of variables included
    in the two returned DataFrame objects.  When dump=False (its default
    value), the variables in the two returned DataFrame objects include
    just the variables in the Tax-Calculator DIST_VARIABLES list, which
    is sufficient for constructing the standard Tax-Calculator tables.
    When dump=True, the variables in the two returned DataFrame objects
    include all the Tax-Calculator input and calculated output variables,
    which is the same output as produced by the Tax-Calculator tc --dump
    option except for one difference: the tc --dump option provides two
    calculated variables, mtr_inctax and mtr_paytax, that are replaced
    in the dump output of this response function by mtr_combined, which
    is the sum of mtr_inctax and mtr_paytax.

    Note: the use here of a dollar-change income elasticity (rather than
      a proportional-change elasticity) is consistent with Feldstein and
      Feenberg, "The Taxation of Two Earner Families", NBER Working Paper
      No. 5155 (June 1995).  A proportional-change elasticity was used by
      Gruber and Saez, "The elasticity of taxable income: evidence and
      implications", Journal of Public Economics 84:1-32 (2002) [see
      equation 2 on page 10].

    Note: the nature of the capital-gains elasticity used here is similar
      to that used in Joint Committee on Taxation, "New Evidence on the
      Tax Elasticity of Capital Gains: A Joint Working Paper of the Staff
      of the Joint Committee on Taxation and the Congressional Budget
      Office", (JCX-56-12), June 2012.  In particular, the elasticity
      use here is equivalent to the term inside the square brackets on
      the right-hand side of equation (4) on page 11 --- not the epsilon
      variable on the left-hand side of equation (4), which is equal to
      the elasticity used here times the weighted average marginal tax
      rate on long-term capital gains.  So, the JCT-CBO estimate of
      -0.792 for the epsilon elasticity (see JCT-CBO, Table 5) translates
      into a much larger absolute value for the be_cg semi-elasticity
      used by Tax-Calculator.
      To calculate the elasticity from a semi-elasticity, we multiply by
      MTRs from TC and weight by shares of taxable gains. To avoid those
      with zero MTRs, we restrict this to the top 40% of tax units by AGI.
      Using this function, a semi-elasticity of -3.45 corresponds to a tax
      rate elasticity of -0.792.

    """
    # pylint: disable=too-many-locals,too-many-statements,too-many-branches

    # Check function argument types and elasticity values
    calc1 = copy.deepcopy(calc_1)
    calc2 = copy.deepcopy(calc_2)
    assert isinstance(calc1, tc.Calculator)
    assert isinstance(calc2, tc.Calculator)
    assert isinstance(elasticities, dict)
    be_sub = elasticities['sub'] if 'sub' in elasticities else 0.0
    be_inc = elasticities['inc'] if 'inc' in elasticities else 0.0
    be_cg = elasticities['cg'] if 'cg' in elasticities else 0.0
    assert be_sub >= 0.0
    assert be_inc <= 0.0
    assert be_cg <= 0.0

    # Begin nested functions used only in this response function
    def _update_ordinary_income(taxinc_change, calc):
        """
        Implement total taxable income change induced by behavioral response.
        """
        # compute AGI minus itemized deductions, agi_m_ided
        agi = calc.array('c00100')
        ided = np.where(
            calc.array('c04470') < calc.array('standard'), 0.,
            calc.array('c04470'))
        agi_m_ided = agi - ided
        # assume behv response only for filing units with positive agi_m_ided
        pos = np.array(agi_m_ided > 0., dtype=bool)
        delta_income = np.where(pos, taxinc_change, 0.)
        # allocate delta_income into three parts
        # pylint: disable=unsupported-assignment-operation
        winc = calc.array('e00200')
        delta_winc = np.zeros_like(agi)
        delta_winc[pos] = delta_income[pos] * winc[pos] / agi_m_ided[pos]
        oinc = agi - winc
        delta_oinc = np.zeros_like(agi)
        delta_oinc[pos] = delta_income[pos] * oinc[pos] / agi_m_ided[pos]
        delta_ided = np.zeros_like(agi)
        delta_ided[pos] = delta_income[pos] * ided[pos] / agi_m_ided[pos]
        # confirm that the three parts are consistent with delta_income
        assert np.allclose(delta_income, delta_winc + delta_oinc - delta_ided)
        # add the three parts to different records variables embedded in calc
        calc.incarray('e00200', delta_winc)
        calc.incarray('e00200p', delta_winc)
        calc.incarray('e00300', delta_oinc)
        calc.incarray('e19200', delta_ided)
        return calc

    def _update_cap_gain_income(cap_gain_change, calc):
        """
        Implement capital gain change induced by behavioral responses.
        """
        calc.incarray('p23250', cap_gain_change)
        return calc

    def _mtr12(calc__1, calc__2, mtr_of='e00200p', tax_type='combined'):
        """
        Computes marginal tax rates for Calculator objects calc__1 and calc__2
        for specified mtr_of income type and specified tax_type.
        """
        assert tax_type in ('combined', 'iitax')
        _, iitax1, combined1 = calc__1.mtr(mtr_of, wrt_full_compensation=True)
        _, iitax2, combined2 = calc__2.mtr(mtr_of, wrt_full_compensation=True)
        if tax_type == 'combined':
            return (combined1, combined2)
        return (iitax1, iitax2)

    # End nested functions used only in this response function

    # Begin main logic of response function
    calc1.calc_all()
    calc2.calc_all()
    assert calc1.array_len == calc2.array_len
    assert calc1.current_year == calc2.current_year
    mtr_cap = 0.99
    if dump:
        recs_vinfo = tc.Records(data=None)  # contains records VARINFO only
        dvars = list(recs_vinfo.USABLE_READ_VARS | recs_vinfo.CALCULATED_VARS)
    # Calculate sum of substitution and income effects
    if be_sub == 0.0 and be_inc == 0.0:
        zero_sub_and_inc = True
        if dump:
            wage_mtr1 = np.zeros(calc1.array_len)
            wage_mtr2 = np.zeros(calc2.array_len)
    else:
        zero_sub_and_inc = False
        # calculate marginal combined tax rates on taxpayer wages+salary
        # (e00200p is taxpayer's wages+salary)
        wage_mtr1, wage_mtr2 = _mtr12(calc1,
                                      calc2,
                                      mtr_of='e00200p',
                                      tax_type='combined')
        # calculate magnitude of substitution effect
        if be_sub == 0.0:
            sub = np.zeros(calc1.array_len)
        else:
            # proportional change in marginal net-of-tax rates on earnings
            mtr1 = np.where(wage_mtr1 > mtr_cap, mtr_cap, wage_mtr1)
            mtr2 = np.where(wage_mtr2 > mtr_cap, mtr_cap, wage_mtr2)
            pch = ((1. - mtr2) / (1. - mtr1)) - 1.
            # Note: c04800 is filing unit's taxable income
            sub = be_sub * pch * calc1.array('c04800')
        # calculate magnitude of income effect
        if be_inc == 0.0:
            inc = np.zeros(calc1.array_len)
        else:
            # dollar change in after-tax income
            # Note: combined is f.unit's income+payroll tax liability
            dch = calc1.array('combined') - calc2.array('combined')
            inc = be_inc * dch
        # calculate sum of substitution and income effects
        si_chg = sub + inc
    # Calculate long-term capital-gains effect
    if be_cg == 0.0:
        ltcg_chg = np.zeros(calc1.array_len)
    else:
        # calculate marginal tax rates on long-term capital gains
        #  p23250 is filing units' long-term capital gains
        ltcg_mtr1, ltcg_mtr2 = _mtr12(calc1,
                                      calc2,
                                      mtr_of='p23250',
                                      tax_type='iitax')
        rch = ltcg_mtr2 - ltcg_mtr1
        exp_term = np.exp(be_cg * rch)
        new_ltcg = calc1.array('p23250') * exp_term
        ltcg_chg = new_ltcg - calc1.array('p23250')
    # Extract dataframe from calc1
    if dump:
        df1 = calc1.dataframe(dvars)
        df1.drop('mtr_inctax', axis='columns', inplace=True)
        df1.drop('mtr_paytax', axis='columns', inplace=True)
        df1['mtr_combined'] = wage_mtr1 * 100
    else:
        df1 = calc1.dataframe(tc.DIST_VARIABLES)
    del calc1
    # Add behavioral-response changes to income sources
    calc2_behv = copy.deepcopy(calc2)
    del calc2
    if not zero_sub_and_inc:
        calc2_behv = _update_ordinary_income(si_chg, calc2_behv)
    calc2_behv = _update_cap_gain_income(ltcg_chg, calc2_behv)
    # Recalculate post-reform taxes incorporating behavioral responses
    calc2_behv.calc_all()
    # Extract dataframe from calc2_behv
    if dump:
        df2 = calc2_behv.dataframe(dvars)
        df2.drop('mtr_inctax', axis='columns', inplace=True)
        df2.drop('mtr_paytax', axis='columns', inplace=True)
        df2['mtr_combined'] = wage_mtr2 * 100
    else:
        df2 = calc2_behv.dataframe(tc.DIST_VARIABLES)
    del calc2_behv
    # Return the two dataframes
    return (df1, df2)
Пример #12
0
    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
Пример #13
0
    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
Пример #14
0
import numpy as np
import pandas as pd
import taxcalc as tc
from .impute_pencon import impute_pension_contributions
from .constants import UNUSED_READ_VARS
from pathlib import Path

CUR_PATH = Path(__file__).resolve().parent
USABLE_VARS = tc.Records(data=None).USABLE_READ_VARS
USABLE_VARS.add("filer")


def finalprep(data):
    """
    Contains all the logic of the puf_data/finalprep.py script.
    """
    # - Check the PUF year
    max_flpdyr = max(data["flpdyr"])
    if max_flpdyr == 2008:
        data = transform_2008_varnames_to_2009_varnames(data)
    else:  # if PUF year is 2009+
        data = age_consistency(data)

    # - Make recid variable be a unique integer key:
    data = create_new_recid(data)

    # - Make several variable names be uppercase as in SOI PUF:
    data = capitalize_varnames(data)

    # - Impute cmbtp variable to estimate income on Form 6251 but not in AGI:
    cmbtp_standard = data["e62100"] - data["e00100"] + data["e00700"]