Recipe 5: Redefining Expanded Income#

This is an advanced recipe that should be followed only after mastering the basic recipe. This recipe is almost exactly the same as Directly Comparing Two Reforms, so you might want to read that recipe first.

This recipe introduces a powerful technique for customizing the operation of Tax-Calculator. This calculator-customization technique is used in this recipe to redefine expanded income in a way that allows the redefined income measure to be used seamlessly with all the other (table and graph) methods of the Calculator class. The basic idea behind the calculator-customization technique is to derive a customized Calculator class from the Tax-Calculator Calculator class. This is a standard object-oriented programming technique.

Recipe that illustrates how to customize the taxcalc Calculator class so that it can seamlessly use an alternative definition of expanded income.

The technique for doing this customization is standard in object-oriented programming: a child class is derived from a parent class and then customized. The derived child class inherits all the data and methods of the parent class, but can be customized by adding new data and methods or by overriding inherited methods.

import pandas as pd
import taxcalc as tc


# override the ExpandIncome calcfunction that computes "market income"


@tc.iterate_jit(nopython=True)
def ExpandIncome(e00200, pencon_p, pencon_s, e00300, e00400, e00600,
                 e00700, e00800, e00900, e01100, e01200, e01400, e01500,
                 e02000, e02100, p22250, p23250, cmbtp, ptax_was,
                 expanded_income):
    """
    Calculates expanded_income as "market income" from component income types.
    """
    expanded_income = (
        e00200 +  # wage and salary income net of DC pension contributions
        pencon_p +  # tax-advantaged DC pension contributions for taxpayer
        pencon_s +  # tax-advantaged DC pension contributions for spouse
        e00300 +  # taxable interest income
        e00400 +  # non-taxable interest income
        e00600 +  # dividends
        e00700 +  # state and local income tax refunds
        e00800 +  # alimony received
        e00900 +  # Sch C business net income/loss
        e01100 +  # capital gain distributions not reported on Sch D
        e01200 +  # Form 4797 other net gain/loss
        e01400 +  # taxable IRA distributions
        e01500 +  # total pension & annuity income (including DB-plan benefits)
        e02000 +  # Sch E total rental, ..., partnership, S-corp income/loss
        e02100 +  # Sch F farm net income/loss
        p22250 +  # Sch D: net short-term capital gain/loss
        p23250 +  # Sch D: net long-term capital gain/loss
        cmbtp +  # other AMT taxable income items from Form 6251
        0.5 * ptax_was  # employer share of FICA taxes on wages/salaries
        # excluding:
        # ubi +  # total UBI benefit
        # benefit_value_total  # consumption value of all benefits received;
    )
    return expanded_income

# end overrided calcfunction used by customized Calculator class


class Calculator(tc.Calculator):
    """
    Customized Calculator class that inherits all tc.Calculator data and
    methods, overriding one method to get the desired customization.
    """
    def __init__(self, policy=None, records=None, verbose=False,
                 sync_years=True, consumption=None):
        # use same class constructor arguments as tc.Calculator class
        super().__init__(policy=policy, records=records,
                         verbose=verbose, sync_years=sync_years,
                         consumption=consumption)

    def calc_all(self, zero_out_calc_vars=False):
        """
        Call all tax-calculation functions for the current_year.
        """
        tc.BenefitPrograms(self)
        self._calc_one_year(zero_out_calc_vars)
        tc.BenefitSurtax(self)
        tc.BenefitLimitation(self)
        tc.FairShareTax(self.__policy, self.__records)
        tc.LumpSumTax(self.__policy, self.__records)
        ExpandIncome(self.__policy, self.__records)  # customized (see above)
        tc.AfterTaxIncome(self.__policy, self.__records)

# end of customized Calculator class definition


# top-level logic of program that uses customized Calculator class

# 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:examples@main/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:examples@main/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 customized Calculator objects using bpolicy and rpolicy
recs = tc.Records.cps_constructor()
calc1 = Calculator(policy=bpolicy, records=recs)
calc2 = 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, 'standard_income_bins', '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('')

# 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())
/Users/jason.debacker/Library/Python/3.9/lib/python/site-packages/urllib3/__init__.py:34: NotOpenSSLWarning: urllib3 v2.0 only supports OpenSSL 1.1.1+, currently the 'ssl' module is compiled with 'LibreSSL 2.8.3'. See: https://github.com/urllib3/urllib3/issues/3020
  warnings.warn(
2018_REFORM1_iitax_rev($B)= 1357.959
2018_REFORM2_iitax_rev($B)= 1191.548

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(%)
<$0K          0.129804         0.0         0.0          0.0           0.0                     0.0
=$0K         28.997572    0.003718    0.001407    -0.000261     -0.008994              -10.526744
$0-10K       32.922181    2.554743     1.40047    -0.203435     -6.179258                0.159624
$10-20K      21.708136   12.425967    3.591809    -1.710808     -78.80952                 0.55683
$20-30K      18.701441   14.515061    2.676056    -4.194498   -224.287399                0.984496
$30-40K      16.854735   13.888505    2.418122    -7.553283   -448.140132                1.486787
$40-50K      13.339599   11.341441    1.754578    -8.763624   -656.963111                1.751975
$50-75K      22.042293   19.660424    2.143487    -20.04181   -909.243402                1.832268
$75-100K     13.857519   12.601969    1.151749   -16.995741  -1226.463508                1.787595
$100-200K    21.789028   19.756532    1.942246    -42.49801  -1950.431685                1.850584
$200-500K     7.451103    6.918421    0.529425   -41.047952  -5508.976365                2.663093
$500-1000K    0.967451    0.891342    0.076109   -12.384206  -12800.85902                2.725012
>$1000K       0.445622    0.372131    0.073491   -11.017856 -24724.688454                1.966994
ALL         199.206484  114.930254    17.75895  -166.411483   -835.371819                1.899907