Recipe 1: Directly Comparing Two Reforms

This is an advanced recipe that should be followed only after mastering the basic recipe. 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).

import pandas as pd
import taxcalc as tc

# read an "old" reform file
# ("old" means the reform file is defined relative to pre-TCJA policy)
# specify reform dictionary for pre-TCJA policy
reform1 = tc.Policy.read_json_reform('github://PSLmodels:[email protected]/psl_examples/taxcalc/2017_law.json')

# specify reform dictionary for TCJA as passed by Congress in late 2017
reform2 = tc.Policy.read_json_reform('github://PSLmodels:[email protected]/psl_examples/taxcalc/TCJA.json')

# specify Policy object for pre-TCJA policy
bpolicy = tc.Policy()
bpolicy.implement_reform(reform1, print_warnings=False, raise_errors=False)
assert not bpolicy.parameter_errors

# specify Policy object for TCJA reform relative to pre-TCJA policy
rpolicy = tc.Policy()
rpolicy.implement_reform(reform1, print_warnings=False, raise_errors=False)
assert not rpolicy.parameter_errors
rpolicy.implement_reform(reform2, print_warnings=False, raise_errors=False)
assert not rpolicy.parameter_errors

# specify Calculator objects using bpolicy and rpolicy
recs = tc.Records.cps_constructor()
calc1 = tc.Calculator(policy=bpolicy, records=recs)
calc2 = tc.Calculator(policy=rpolicy, records=recs)

CYR = 2018

# calculate for specified CYR
calc1.advance_to_year(CYR)
calc1.calc_all()
calc2.advance_to_year(CYR)
calc2.calc_all()

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

# construct reform-vs-baseline difference table with results for income deciles
diff_table = calc1.difference_table(calc2, 'weighted_deciles', '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($)', 'aftertax_income_diff(%)']
for dname, ename in zip(dif_colnames, ext_colnames):
    diff_extract[ename] = diff_table[dname]

# print total revenue estimates for cyr
# (estimates in billons of dollars)
print('{}_REFORM1_iitax_rev($B)= {:.3f}'.format(CYR, iitax_rev1 * 1e-9))
print('{}_REFORM2_iitax_rev($B)= {:.3f}'.format(CYR, iitax_rev2 * 1e-9))
print('')
---------------------------------------------------------------------------
KeyboardInterrupt                         Traceback (most recent call last)
<ipython-input-2-e2bafec41239> in <module>
     31 # calculate for specified CYR
     32 calc1.advance_to_year(CYR)
---> 33 calc1.calc_all()
     34 calc2.advance_to_year(CYR)
     35 calc2.calc_all()

~/work/Tax-Calculator/Tax-Calculator/taxcalc/calculator.py in calc_all(self, zero_out_calc_vars)
    170         UBI(self.__policy, self.__records)
    171         BenefitPrograms(self)
--> 172         self._calc_one_year(zero_out_calc_vars)
    173         BenefitSurtax(self)
    174         BenefitLimitation(self)

~/work/Tax-Calculator/Tax-Calculator/taxcalc/calculator.py in _calc_one_year(self, zero_out_calc_vars)
   1383         AGI(self.__policy, self.__records)
   1384         ItemDedCap(self.__policy, self.__records)
-> 1385         ItemDed(self.__policy, self.__records)
   1386         AdditionalMedicareTax(self.__policy, self.__records)
   1387         StdDed(self.__policy, self.__records)

~/work/Tax-Calculator/Tax-Calculator/taxcalc/decorators.py in wrapper(*args, **kwargs)
    323                  {"applied_f": applied_jitted_f}, fakeglobals)
    324             high_level_fn = fakeglobals['hl_func']
--> 325             ans = high_level_fn(*args, **kwargs)
    326             return ans
    327 

<string> in hl_func(pm, pf)

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/dispatcher.py in _compile_for_args(self, *args, **kws)
    370         return_val = None
    371         try:
--> 372             return_val = self.compile(tuple(argtypes))
    373         except errors.ForceLiteralArg as e:
    374             # Received request for compiler re-entry with the list of arguments

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/dispatcher.py in compile(self, sig)
    907                 with ev.trigger_event("numba:compile", data=ev_details):
    908                     try:
--> 909                         cres = self._compiler.compile(args, return_type)
    910                     except errors.ForceLiteralArg as e:
    911                         def folded(args, kws):

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/dispatcher.py in compile(self, args, return_type)
     77 
     78     def compile(self, args, return_type):
---> 79         status, retval = self._compile_cached(args, return_type)
     80         if status:
     81             return retval

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/dispatcher.py in _compile_cached(self, args, return_type)
     91 
     92         try:
---> 93             retval = self._compile_core(args, return_type)
     94         except errors.TypingError as e:
     95             self._failed_cache[key] = e

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/dispatcher.py in _compile_core(self, args, return_type)
    104 
    105         impl = self._get_implementation(args, {})
--> 106         cres = compiler.compile_extra(self.targetdescr.typing_context,
    107                                       self.targetdescr.target_context,
    108                                       impl,

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/compiler.py in compile_extra(typingctx, targetctx, func, args, return_type, flags, locals, library, pipeline_class)
    604     pipeline = pipeline_class(typingctx, targetctx, library,
    605                               args, return_type, flags, locals)
--> 606     return pipeline.compile_extra(func)
    607 
    608 

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/compiler.py in compile_extra(self, func)
    351         self.state.lifted = ()
    352         self.state.lifted_from = None
--> 353         return self._compile_bytecode()
    354 
    355     def compile_ir(self, func_ir, lifted=(), lifted_from=None):

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/compiler.py in _compile_bytecode(self)
    413         """
    414         assert self.state.func_ir is None
--> 415         return self._compile_core()
    416 
    417     def _compile_ir(self):

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/compiler.py in _compile_core(self)
    384             res = None
    385             try:
--> 386                 pm.run(self.state)
    387                 if self.state.cr is not None:
    388                     break

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/compiler_machinery.py in run(self, state)
    328                 pass_inst = _pass_registry.get(pss).pass_inst
    329                 if isinstance(pass_inst, CompilerPass):
--> 330                     self._runPass(idx, pass_inst, state)
    331                 else:
    332                     raise BaseException("Legacy pass in use")

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/compiler_lock.py in _acquire_compile_lock(*args, **kwargs)
     33         def _acquire_compile_lock(*args, **kwargs):
     34             with self:
---> 35                 return func(*args, **kwargs)
     36         return _acquire_compile_lock
     37 

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/compiler_machinery.py in _runPass(self, index, pss, internal_state)
    287             mutated |= check(pss.run_initialization, internal_state)
    288         with SimpleTimer() as pass_time:
--> 289             mutated |= check(pss.run_pass, internal_state)
    290         with SimpleTimer() as finalize_time:
    291             mutated |= check(pss.run_finalizer, internal_state)

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/compiler_machinery.py in check(func, compiler_state)
    260 
    261         def check(func, compiler_state):
--> 262             mangled = func(compiler_state)
    263             if mangled not in (True, False):
    264                 msg = ("CompilerPass implementations should return True/False. "

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/typed_passes.py in run_pass(self, state)
    102                               % (state.func_id.func_name,)):
    103             # Type inference
--> 104             typemap, return_type, calltypes, errs = type_inference_stage(
    105                 state.typingctx,
    106                 state.func_ir,

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/typed_passes.py in type_inference_stage(typingctx, interp, args, return_type, locals, raise_errors)
     80         infer.build_constraint()
     81         # return errors in case of partial typing
---> 82         errs = infer.propagate(raise_errors=raise_errors)
     83         typemap, restype, calltypes = infer.unify(raise_errors=raise_errors)
     84 

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/typeinfer.py in propagate(self, raise_errors)
   1061             # Errors can appear when the type set is incomplete; only
   1062             # raise them when there is no progress anymore.
-> 1063             errors = self.constraints.propagate(self)
   1064             newtoken = self.get_state_token()
   1065             self.debug.propagate_finished()

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/typeinfer.py in propagate(self, typeinfer)
    152                                                    lineno=loc.line):
    153                 try:
--> 154                     constraint(typeinfer)
    155                 except ForceLiteralArg as e:
    156                     errors.append(e)

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/typeinfer.py in __call__(self, typeinfer)
    564                 fnty = typevars[self.func].getone()
    565             with new_error_context("resolving callee type: {0}", fnty):
--> 566                 self.resolve(typeinfer, typevars, fnty)
    567 
    568     def resolve(self, typeinfer, typevars, fnty):

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/typeinfer.py in resolve(self, typeinfer, typevars, fnty)
    584         # Resolve call type
    585         try:
--> 586             sig = typeinfer.resolve_call(fnty, pos_args, kw_args)
    587         except ForceLiteralArg as e:
    588             # Adjust for bound methods

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/typeinfer.py in resolve_call(self, fnty, pos_args, kw_args)
   1536         else:
   1537             # Normal non-recursive call
-> 1538             return self.context.resolve_function_type(fnty, pos_args, kw_args)
   1539 
   1540     def typeof_global(self, inst, target, gvar):

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/typing/context.py in resolve_function_type(self, func, args, kws)
    192         # Prefer user definition first
    193         try:
--> 194             res = self._resolve_user_function_type(func, args, kws)
    195         except errors.TypingError as e:
    196             # Capture any typing error

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/typing/context.py in _resolve_user_function_type(self, func, args, kws, literals)
    244         if isinstance(func, types.Callable):
    245             # XXX fold this into the __call__ attribute logic?
--> 246             return func.get_call_type(self, args, kws)
    247 
    248     def _get_attribute_templates(self, typ):

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/types/functions.py in get_call_type(self, context, args, kws)
    526         """
    527         template, pysig, args, kws = \
--> 528             self.dispatcher.get_call_template(args, kws)
    529         sig = template(context).apply(args, kws)
    530         if sig:

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/dispatcher.py in get_call_template(self, args, kws)
    313         # Ensure an overload is available
    314         if self._can_compile:
--> 315             self.compile(tuple(args))
    316 
    317         # Create function type for typing

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/dispatcher.py in compile(self, sig)
    907                 with ev.trigger_event("numba:compile", data=ev_details):
    908                     try:
--> 909                         cres = self._compiler.compile(args, return_type)
    910                     except errors.ForceLiteralArg as e:
    911                         def folded(args, kws):

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/dispatcher.py in compile(self, args, return_type)
     77 
     78     def compile(self, args, return_type):
---> 79         status, retval = self._compile_cached(args, return_type)
     80         if status:
     81             return retval

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/dispatcher.py in _compile_cached(self, args, return_type)
     91 
     92         try:
---> 93             retval = self._compile_core(args, return_type)
     94         except errors.TypingError as e:
     95             self._failed_cache[key] = e

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/dispatcher.py in _compile_core(self, args, return_type)
    104 
    105         impl = self._get_implementation(args, {})
--> 106         cres = compiler.compile_extra(self.targetdescr.typing_context,
    107                                       self.targetdescr.target_context,
    108                                       impl,

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/compiler.py in compile_extra(typingctx, targetctx, func, args, return_type, flags, locals, library, pipeline_class)
    604     pipeline = pipeline_class(typingctx, targetctx, library,
    605                               args, return_type, flags, locals)
--> 606     return pipeline.compile_extra(func)
    607 
    608 

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/compiler.py in compile_extra(self, func)
    351         self.state.lifted = ()
    352         self.state.lifted_from = None
--> 353         return self._compile_bytecode()
    354 
    355     def compile_ir(self, func_ir, lifted=(), lifted_from=None):

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/compiler.py in _compile_bytecode(self)
    413         """
    414         assert self.state.func_ir is None
--> 415         return self._compile_core()
    416 
    417     def _compile_ir(self):

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/compiler.py in _compile_core(self)
    384             res = None
    385             try:
--> 386                 pm.run(self.state)
    387                 if self.state.cr is not None:
    388                     break

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/compiler_machinery.py in run(self, state)
    328                 pass_inst = _pass_registry.get(pss).pass_inst
    329                 if isinstance(pass_inst, CompilerPass):
--> 330                     self._runPass(idx, pass_inst, state)
    331                 else:
    332                     raise BaseException("Legacy pass in use")

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/compiler_lock.py in _acquire_compile_lock(*args, **kwargs)
     33         def _acquire_compile_lock(*args, **kwargs):
     34             with self:
---> 35                 return func(*args, **kwargs)
     36         return _acquire_compile_lock
     37 

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/compiler_machinery.py in _runPass(self, index, pss, internal_state)
    287             mutated |= check(pss.run_initialization, internal_state)
    288         with SimpleTimer() as pass_time:
--> 289             mutated |= check(pss.run_pass, internal_state)
    290         with SimpleTimer() as finalize_time:
    291             mutated |= check(pss.run_finalizer, internal_state)

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/compiler_machinery.py in check(func, compiler_state)
    260 
    261         def check(func, compiler_state):
--> 262             mangled = func(compiler_state)
    263             if mangled not in (True, False):
    264                 msg = ("CompilerPass implementations should return True/False. "

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/typed_passes.py in run_pass(self, state)
    461 
    462         # TODO: Pull this out into the pipeline
--> 463         NativeLowering().run_pass(state)
    464         lowered = state['cr']
    465         signature = typing.signature(state.return_type, *state.args)

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/typed_passes.py in run_pass(self, state)
    382                 lower = lowering.Lower(targetctx, library, fndesc, interp,
    383                                        metadata=metadata)
--> 384                 lower.lower()
    385                 if not flags.no_cpython_wrapper:
    386                     lower.create_cpython_wrapper(flags.release_gil)

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/lowering.py in lower(self)
    134         if self.generator_info is None:
    135             self.genlower = None
--> 136             self.lower_normal_function(self.fndesc)
    137         else:
    138             self.genlower = self.GeneratorLower(self)

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/lowering.py in lower_normal_function(self, fndesc)
    188         # Init argument values
    189         self.extract_function_arguments()
--> 190         entry_block_tail = self.lower_function_body()
    191 
    192         # Close tail of entry block

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/lowering.py in lower_function_body(self)
    214             bb = self.blkmap[offset]
    215             self.builder.position_at_end(bb)
--> 216             self.lower_block(block)
    217         self.post_lower()
    218         return entry_block_tail

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/lowering.py in lower_block(self, block)
    228             with new_error_context('lowering "{inst}" at {loc}', inst=inst,
    229                                    loc=self.loc, errcls_=defaulterrcls):
--> 230                 self.lower_inst(inst)
    231         self.post_block(block)
    232 

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/lowering.py in lower_inst(self, inst)
    325         if isinstance(inst, ir.Assign):
    326             ty = self.typeof(inst.target.name)
--> 327             val = self.lower_assign(ty, inst)
    328             self.storevar(val, inst.target.name)
    329 

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/lowering.py in lower_assign(self, ty, inst)
    500 
    501         elif isinstance(value, ir.Expr):
--> 502             return self.lower_expr(ty, value)
    503 
    504         elif isinstance(value, ir.Var):

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/lowering.py in lower_expr(self, resty, expr)
   1156         elif expr.op == "getitem":
   1157             signature = self.fndesc.calltypes[expr]
-> 1158             return self.lower_getitem(resty, expr, expr.value, expr.index,
   1159                                       signature)
   1160 

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/lowering.py in lower_getitem(self, resty, expr, value, index, signature)
    636         op = operator.getitem
    637         fnop = self.context.typing_context.resolve_value_type(op)
--> 638         callsig = fnop.get_call_type(
    639             self.context.typing_context, signature.args, {},
    640         )

/usr/share/miniconda/envs/taxcalc-dev/lib/python3.8/site-packages/numba/core/types/functions.py in get_call_type(self, context, args, kws)
    297                     else:
    298                         nolitargs = tuple([_unlit_non_poison(a) for a in args])
--> 299                         nolitkws = {k: _unlit_non_poison(v)
    300                                     for k, v in kws.items()}
    301                         sig = temp.apply(nolitargs, nolitkws)

KeyboardInterrupt: 

Print reform2-vs-reform1 difference table

title = 'Extract of {} income-tax difference table by expanded-income decile'
print(title.format(CYR))
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.to_string())
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($)  aftertax_income_diff(%)
0-10n     0.106585     0.000000     0.000000      0.000000      0.000000                 0.000000
0-10z     8.200964     0.000000     0.000000      0.000000      0.000000                      NaN
0-10p    11.685544     0.145729     0.017773     -0.010031     -0.858403                 0.026886
10-20    19.993379     6.131286     2.879208     -0.737282    -36.876308                 0.307117
20-30    19.992590     9.819025     2.904122     -1.767789    -88.422187                 0.388099
30-40    19.995314     8.928170     2.495154     -3.420389   -171.059528                 0.551945
40-50    19.993436    10.540007     2.490796     -5.754637   -287.826313                 0.746778
50-60    19.994152    12.663991     2.070555     -8.569910   -428.620835                 0.894647
60-70    19.992890    13.910415     1.784819    -11.604895   -580.451123                 0.964668
70-80    19.994268    15.376689     1.552446    -16.537168   -827.095476                 1.080738
80-90    19.993895    17.040071     1.620336    -26.070610  -1303.928552                 1.277742
90-100   19.993808    18.220631     1.530360    -97.520960  -4877.557973                 2.180188
ALL     199.936825   112.776014    19.345569   -171.993672   -860.240085                 1.396031
90-95     9.996300     9.017015     0.757695    -23.229363  -2323.796032                 1.660532
95-99     7.997983     7.390026     0.586730    -41.609762  -5202.531695                 2.400130
Top 1%    1.999525     1.813590     0.185935    -32.681835 -16344.800972                 2.438041