Basic Recipe: Static Analysis of a Simple Reform

Basic Recipe: Static Analysis of a Simple Reform#

This is the recipe you should follow first. Mastering this recipe is a prerequisite for all the other recipes in this cookbook.

Imports#

import taxcalc as tc
import pandas as pd
from bokeh.io import show, output_notebook

Setup#

Use publicly-available CPS input file.

NOTE: if you have access to the restricted-use IRS-SOI PUF-based input file and you have that file (named ‘puf.csv’) located in the directory where this script is located, then you can substitute the following statement for the prior statement:

recs = tc.Records()

recs = tc.Records.cps_constructor()

Specify Calculator object for static analysis of current-law policy.

pol = tc.Policy()
calc1 = tc.Calculator(policy=pol, records=recs)

NOTE: calc1 now contains a PRIVATE COPY of pol and a PRIVATE COPY of recs, so we can continue to use pol and recs in this script without any concern about side effects from Calculator method calls on calc1.

CYR = 2020

Calculate aggregate current-law income tax liabilities for CYR.

calc1.advance_to_year(CYR)
calc1.calc_all()
itax_rev1 = calc1.weighted_total('iitax')

Read JSON reform file and use (the default) static analysis assumptions.

reform_filename = 'github://PSLmodels:Tax-Calculator@master/docs/recipes/_static/reformA.json'
params = tc.Calculator.read_json_param_objects(reform_filename, None)

Specify Calculator object for static analysis of reform policy.

pol.implement_reform(params['policy'])
calc2 = tc.Calculator(policy=pol, records=recs)
---------------------------------------------------------------------------
ValidationError                           Traceback (most recent call last)
Cell In[8], line 1
----> 1 pol.implement_reform(params['policy'])
      2 calc2 = tc.Calculator(policy=pol, records=recs)

File ~/work/Tax-Calculator/Tax-Calculator/taxcalc/policy.py:170, in Policy.implement_reform(self, reform, print_warnings, raise_errors)
    163     if param in deprecated_parameters:
    164         print(  # pragma: no cover
    165             f'DEPRECATION WARNING: the {param} policy parameter\n'
    166             'is scheduled to be removed in Tax-Calculator 6.0.0;\n'
    167             'if you think this removal should not happen, open an\n'
    168             'issue on GitHub to make your case for non-removal.'
    169         )
--> 170 return self._update(reform, print_warnings, raise_errors)

File ~/work/Tax-Calculator/Tax-Calculator/taxcalc/parameters.py:615, in Parameters._update(self, revision, print_warnings, raise_errors)
    613     else:
    614         msg = f'Parameter {param} does not exist'
--> 615     raise paramtools.ValidationError(
    616         {'errors': {'schema': msg}},
    617         None
    618     )
    619 if param.endswith('-indexed'):
    620     for year, yearval in val.items():

ValidationError: {
    "errors": {
        "schema": "PT_rt5 was removed in Tax-Calculator 5.0.0"
    }
}

Calculate#

Calculate reform income tax liabilities for CYR.

calc2.advance_to_year(CYR)
calc2.calc_all()
itax_rev2 = calc2.weighted_total('iitax')

Results#

Print total revenue estimates for 2018.

Estimates in billons of dollars rounded to nearest hundredth of a billion.

print('{}_CLP_itax_rev($B)= {:.3f}'.format(CYR, itax_rev1 * 1e-9))
print('{}_REF_itax_rev($B)= {:.3f}'.format(CYR, itax_rev2 * 1e-9))
2020_CLP_itax_rev($B)= 764.170
2020_REF_itax_rev($B)= 766.855

Generate several other standard results tables.

# Aggregate diagnostic tables for CYR.
clp_diagnostic_table = calc1.diagnostic_table(1)
ref_diagnostic_table = calc2.diagnostic_table(1)

# Income-tax distribution for CYR with CLP and REF results side-by-side.
dist_table1, dist_table2 = calc1.distribution_tables(calc2, 'weighted_deciles')
assert isinstance(dist_table1, pd.DataFrame)
assert isinstance(dist_table2, pd.DataFrame)
dist_extract = pd.DataFrame()
dist_extract['funits(#m)'] = dist_table1['count']
dist_extract['itax1($b)'] = dist_table1['iitax']
dist_extract['itax2($b)'] = dist_table2['iitax']
dist_extract['aftertax_inc1($b)'] = dist_table1['aftertax_income']
dist_extract['aftertax_inc2($b)'] = dist_table2['aftertax_income']

# Income-tax difference table by expanded-income decile for CYR.
diff_table = calc1.difference_table(calc2, 'weighted_deciles', 'iitax')
assert isinstance(diff_table, pd.DataFrame)
diff_extract = pd.DataFrame()
dif_colnames = ['count', 'tot_change', 'mean', 'pc_aftertaxinc']
ext_colnames = ['funits(#m)', 'agg_diff($b)', 'mean_diff($)', 'aftertaxinc_diff(%)']
for dname, ename in zip(dif_colnames, ext_colnames):
    diff_extract[ename] = diff_table[dname]

Plotting#

Generate a decile graph and display it using Bokeh (will render in Jupyter, not in webpage).

fig = calc1.pch_graph(calc2)
output_notebook()
show(fig)
Loading BokehJS ...