Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Cookbook recipe that shows how two reforms can be directly compared #1784

Merged
merged 5 commits into from
Dec 20, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 16 additions & 8 deletions docs/cookbook.html
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ <h2>Cookbook Contents</h2>

<p><b>Advanced Recipes</b></p>

<p><a href="#recipe01">...coming soon...</a></p>
<p><a href="#recipe01">Directly Comparing Two Reforms</a></p>

<p>...more coming soon...</p>


<h2 id="setup">Preliminaries: Kitchen Setup</h2>
Expand Down Expand Up @@ -202,24 +204,30 @@ <h2 id="recipe00">Basic Recipe: Static Analysis of a Simple Reform</h2>
<p><a href="#toc">Back to Cookbook Contents</a></p>


<h2 id="recipe01">Advanced Recipe: ...</h2>
<h2 id="recipe01">Advanced Recipe: Directly Comparing Two Reforms</h2>

<p>This is an advanced recipe that should be followed only after
mastering the <a href="#recipe00">basic recipe</a>.</p>
mastering the <a href="#recipe00">basic recipe</a>. This recipe
shows how to compare two reforms (instead of comparing a reform
to current-law policy) and also shows how to use the reform files
available on the Tax-Calculator website (instead of reform files
on your computer's disk).</p>

<p><b>Ingredients</b></p>

<p>...</p>
<p>No ingredients required because we read reform files from the
Tax-Calculator website.</p>

<p><b>Instructions</b></p>

<p>Step-by-step instructions included in the <kbd>recipe01.py</kbd> file.</p>
<p><a href="recipe01.py.html" target="_blank">Step-by-step
instructions</a> in the <kbd>recipe01.py</kbd> file.</p>

<p><b>Results</b></p>

<p>Expected text results from executing
<kbd>python recipe01.py > recipe01.out</kbd> at the command prompt as
shown <a href="#execution">above</a>.</p>
<p><a href="recipe01.res.html" target="_blank">Expected text
results</a> from executing <kbd>python recipe01.py > recipe01.out</kbd> at
the command prompt as shown <a href="#execution">above</a>.</p>

<p><a href="#toc">Back to Cookbook Contents</a></p>

Expand Down
1 change: 1 addition & 0 deletions docs/cookbook/make_cookbook.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ def write_html_file(filename):
graph = '{}.graph.html'.format(recipe_root)
if os.path.exists(graph):
shutil.copy(graph, '..')
os.remove(graph)

# make list of ingredient/*json filenames
INGREDIENTS = glob.glob('ingredients/*json')
Expand Down
64 changes: 64 additions & 0 deletions docs/cookbook/recipe01.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
from __future__ import print_function # necessary only if using Python 2.7
from taxcalc import *
import urllib as url_lib # on Python 3.6 use "import urllib.request as url_lib"

# read two reform files from Tax-Calculator website
BASE_URL = ('https://raw.githubusercontent.com/'
'open-source-economics/Tax-Calculator/master/taxcalc/reforms/')
reform1_name = 'TCJA_House_Amended.json' # TCJA as orginally passed by House
reform2_name = 'TCJA_Reconciliation.json' # TCJA conference committee bill
reform1_text = url_lib.urlopen(BASE_URL + reform1_name).read()
reform2_text = url_lib.urlopen(BASE_URL + reform2_name).read()

# specify Policy object for static analysis of reform1
reform1 = Calculator.read_json_param_objects(reform1_text, None)
policy1 = Policy()
policy1.implement_reform(reform1['policy'])
if policy1.reform_errors:
print(policy1.reform_errors)
exit(1)

# specify Policy object for static analysis of reform2
reform2 = Calculator.read_json_param_objects(reform2_text, None)
policy2 = Policy()
policy2.implement_reform(reform2['policy'])
if policy2.reform_errors:
print(policy2.reform_errors)
exit(1)

# specify Calculator objects using policy1 and policy2 and calculate for 2018
calc1 = Calculator(policy=policy1, records=Records.cps_constructor())
calc1.advance_to_year(2018)
calc1.calc_all()
calc2 = Calculator(policy=policy2, records=Records.cps_constructor())
calc2.advance_to_year(2018)
calc2.calc_all()

# compare aggregate individual income tax revenue in 2018
iitax_rev1 = calc1.weighted_total('iitax')
iitax_rev2 = calc2.weighted_total('iitax')

# construct reform2-vs-reform1 difference table with results for income deciles
diff_table = calc1.difference_table(calc2, tax_to_diff='iitax')
assert isinstance(diff_table, pd.DataFrame)
diff_extract = pd.DataFrame()
dif_colnames = ['count', 'tax_cut', 'tax_inc',
'tot_change', 'mean', 'pc_aftertaxinc']
ext_colnames = ['funits(#m)', 'taxfall(#m)', 'taxrise(#m)',
'agg_diff($b)', 'mean_diff($)', 'ataxinc_diff(%)']
scaling_factors = [1e-6, 1e-6, 1e-6, 1e-9, 1e0, 1e0, 1e0]
for dname, ename, sfactor in zip(dif_colnames, ext_colnames, scaling_factors):
diff_extract[ename] = diff_table[dname] * sfactor

# print total revenue estimates for 2018
# (estimates in billons of dollars rounded to nearest tenth of a billion)
print('2018_REFORM1_iitax_rev($B)= {:.1f}'.format(iitax_rev1 * 1e-9))
print('2018_REFORM2_iitax_rev($B)= {:.1f}'.format(iitax_rev2 * 1e-9))
print('')

print('Extract of 2018 income-tax difference table by expanded-income decile')
print('(taxfall is count of funits with cut in income tax in reform 2 vs 1)')
print('(taxrise is count of funits with rise in income tax in reform 2 vs 1)')
print(diff_extract)
print('Note: deciles are numbered 0-9 with top decile divided into bottom 5%,')
print(' next 4%, and top 1%, in the lines numbered 11-13, respectively')
43 changes: 43 additions & 0 deletions docs/cookbook/recipe01.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
You loaded data for 2014.
Tax-Calculator startup automatically extrapolated your data to 2014.
You loaded data for 2014.
Tax-Calculator startup automatically extrapolated your data to 2014.
2018_REFORM1_iitax_rev($B)= 1219.4
2018_REFORM2_iitax_rev($B)= 1138.4

Extract of 2018 income-tax difference table by expanded-income decile
(taxfall is count of funits with cut in income tax in reform 2 vs 1)
(taxrise is count of funits with rise in income tax in reform 2 vs 1)
funits(#m) taxfall(#m) taxrise(#m) agg_diff($b) mean_diff($) \
0 16.99 1.11 0.08 -0.08 -4.88
1 16.99 2.98 0.47 -0.28 -16.65
2 16.99 3.23 4.03 0.12 6.86
3 16.99 5.17 4.99 -1.07 -63.11
4 16.99 6.56 6.34 -1.98 -116.32
5 16.99 7.59 6.98 -2.77 -163.03
6 16.99 7.54 8.64 -1.75 -103.23
7 16.99 10.37 6.53 -5.45 -320.51
8 16.99 9.81 7.17 -7.28 -428.31
9 16.99 15.33 1.66 -60.45 -3,558.27
10 169.89 69.69 46.88 -81.00 -476.75
11 8.49 7.36 1.14 -11.99 -1,411.59
12 6.79 6.42 0.38 -27.49 -4,046.07
13 1.70 1.55 0.15 -20.97 -12,332.42

ataxinc_diff(%)
0 -1.57
1 0.17
2 -0.04
3 0.26
4 0.38
5 0.41
6 0.20
7 0.47
8 0.44
9 1.67
10 0.87
11 1.04
12 1.98
13 1.96
Note: deciles are numbered 0-9 with top decile divided into bottom 5%,
next 4%, and top 1%, in the lines numbered 11-13, respectively
10 changes: 5 additions & 5 deletions docs/recipe00.graph.html

Large diffs are not rendered by default.

66 changes: 66 additions & 0 deletions docs/recipe01.py.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<title>recipe01.py</title><pre>
from __future__ import print_function # necessary only if using Python 2.7
from taxcalc import *
import urllib as url_lib # on Python 3.6 use "import urllib.request as url_lib"

# read two reform files from Tax-Calculator website
BASE_URL = ('https://raw.githubusercontent.com/'
'open-source-economics/Tax-Calculator/master/taxcalc/reforms/')
reform1_name = 'TCJA_House_Amended.json' # TCJA as orginally passed by House
reform2_name = 'TCJA_Reconciliation.json' # TCJA conference committee bill
reform1_text = url_lib.urlopen(BASE_URL + reform1_name).read()
reform2_text = url_lib.urlopen(BASE_URL + reform2_name).read()

# specify Policy object for static analysis of reform1
reform1 = Calculator.read_json_param_objects(reform1_text, None)
policy1 = Policy()
policy1.implement_reform(reform1['policy'])
if policy1.reform_errors:
print(policy1.reform_errors)
exit(1)

# specify Policy object for static analysis of reform2
reform2 = Calculator.read_json_param_objects(reform2_text, None)
policy2 = Policy()
policy2.implement_reform(reform2['policy'])
if policy2.reform_errors:
print(policy2.reform_errors)
exit(1)

# specify Calculator objects using policy1 and policy2 and calculate for 2018
calc1 = Calculator(policy=policy1, records=Records.cps_constructor())
calc1.advance_to_year(2018)
calc1.calc_all()
calc2 = Calculator(policy=policy2, records=Records.cps_constructor())
calc2.advance_to_year(2018)
calc2.calc_all()

# compare aggregate individual income tax revenue in 2018
iitax_rev1 = calc1.weighted_total('iitax')
iitax_rev2 = calc2.weighted_total('iitax')

# construct reform2-vs-reform1 difference table with results for income deciles
diff_table = calc1.difference_table(calc2, tax_to_diff='iitax')
assert isinstance(diff_table, pd.DataFrame)
diff_extract = pd.DataFrame()
dif_colnames = ['count', 'tax_cut', 'tax_inc',
'tot_change', 'mean', 'pc_aftertaxinc']
ext_colnames = ['funits(#m)', 'taxfall(#m)', 'taxrise(#m)',
'agg_diff($b)', 'mean_diff($)', 'ataxinc_diff(%)']
scaling_factors = [1e-6, 1e-6, 1e-6, 1e-9, 1e0, 1e0, 1e0]
for dname, ename, sfactor in zip(dif_colnames, ext_colnames, scaling_factors):
diff_extract[ename] = diff_table[dname] * sfactor

# print total revenue estimates for 2018
# (estimates in billons of dollars rounded to nearest tenth of a billion)
print('2018_REFORM1_iitax_rev($B)= {:.1f}'.format(iitax_rev1 * 1e-9))
print('2018_REFORM2_iitax_rev($B)= {:.1f}'.format(iitax_rev2 * 1e-9))
print('')

print('Extract of 2018 income-tax difference table by expanded-income decile')
print('(taxfall is count of funits with cut in income tax in reform 2 vs 1)')
print('(taxrise is count of funits with rise in income tax in reform 2 vs 1)')
print(diff_extract)
print('Note: deciles are numbered 0-9 with top decile divided into bottom 5%,')
print(' next 4%, and top 1%, in the lines numbered 11-13, respectively')
</pre>
45 changes: 45 additions & 0 deletions docs/recipe01.res.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<title>recipe01.res</title><pre>
You loaded data for 2014.
Tax-Calculator startup automatically extrapolated your data to 2014.
You loaded data for 2014.
Tax-Calculator startup automatically extrapolated your data to 2014.
2018_REFORM1_iitax_rev($B)= 1219.4
2018_REFORM2_iitax_rev($B)= 1138.4

Extract of 2018 income-tax difference table by expanded-income decile
(taxfall is count of funits with cut in income tax in reform 2 vs 1)
(taxrise is count of funits with rise in income tax in reform 2 vs 1)
funits(#m) taxfall(#m) taxrise(#m) agg_diff($b) mean_diff($) \
0 16.99 1.11 0.08 -0.08 -4.88
1 16.99 2.98 0.47 -0.28 -16.65
2 16.99 3.23 4.03 0.12 6.86
3 16.99 5.17 4.99 -1.07 -63.11
4 16.99 6.56 6.34 -1.98 -116.32
5 16.99 7.59 6.98 -2.77 -163.03
6 16.99 7.54 8.64 -1.75 -103.23
7 16.99 10.37 6.53 -5.45 -320.51
8 16.99 9.81 7.17 -7.28 -428.31
9 16.99 15.33 1.66 -60.45 -3,558.27
10 169.89 69.69 46.88 -81.00 -476.75
11 8.49 7.36 1.14 -11.99 -1,411.59
12 6.79 6.42 0.38 -27.49 -4,046.07
13 1.70 1.55 0.15 -20.97 -12,332.42

ataxinc_diff(%)
0 -1.57
1 0.17
2 -0.04
3 0.26
4 0.38
5 0.41
6 0.20
7 0.47
8 0.44
9 1.67
10 0.87
11 1.04
12 1.98
13 1.96
Note: deciles are numbered 0-9 with top decile divided into bottom 5%,
next 4%, and top 1%, in the lines numbered 11-13, respectively
</pre>
24 changes: 17 additions & 7 deletions taxcalc/tests/test_pufcsv.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@
# pylint --disable=locally-disabled test_pufcsv.py

import os
import sys
import json
import difflib
import pytest
import numpy as np
import pandas as pd
# pylint: disable=import-error
from taxcalc import Policy, Records, Calculator
from taxcalc import nonsmall_diff_line_list


@pytest.mark.requires_pufcsv
Expand All @@ -45,19 +47,27 @@ def test_agg(tests_path, puf_fullsample):
taxes_fullsample = adt.loc["Combined Liability ($b)"]
# convert adt results to a string with a trailing EOL character
adtstr = adt.to_string() + '\n'
# generate differences between actual and expected results
# create actual and expected lists of diagnostic table lines
actual = adtstr.splitlines(True)
aggres_path = os.path.join(tests_path, 'pufcsv_agg_expect.txt')
with open(aggres_path, 'r') as expected_file:
txt = expected_file.read()
expected_results = txt.rstrip('\n\t ') + '\n' # cleanup end of file txt
expected = expected_results.splitlines(True)
diff = difflib.unified_diff(expected, actual,
fromfile='expected', tofile='actual', n=0)
# convert diff generator into a list of lines:
expect = expected_results.splitlines(True)
# ensure actual and expect lines have differences less than "small" value
epsilon = 1e-6
if sys.version_info.major == 2:
small = epsilon # tighter test for Python 2.7
else:
small = 0.1 + epsilon # looser test for Python 3.6
diff_lines = list()
for line in diff:
diff_lines.append(line)
assert len(actual) == len(expect)
for actline, expline in zip(actual, expect):
if actline == expline:
continue
diffs = nonsmall_diff_line_list(actline, expline, small)
if diffs:
diff_lines.extend(diffs)
# test failure if there are any diff_lines
if diff_lines:
new_filename = '{}{}'.format(aggres_path[:-10], 'actual.txt')
Expand Down