This notebook contains an example for how to use the taxbrain python package
# # Install conda, taxbrain, and taxcalc if in Google Colab.
import sys
if 'google.colab' in sys.modules and 'taxbrain' not in sys.modules:
# Install taxbrain and dependencies
!pip install taxbrain &> /dev/null
!pip install taxcalc &> /dev/null
!pip install pypandoc &> /dev/null
!pip install -U pandas &> /dev/null # make sure pandas is up to date
from taxbrain import TaxBrain, differences_plot, distribution_plot
reform_url = "https://raw.githubusercontent.com/PSLmodels/Tax-Calculator/master/taxcalc/reforms/Larson2019.json"
start_year = 2021
end_year = 2030
Static Reform#
After importing the TaxBrain class from the taxbrain package, we initiate an instance of the class by specifying the start and end year of the anlaysis, which microdata to use, and a policy reform. Additional arguments can be used to specify econoimc assumptions and individual behavioral elasticites.
Once the class has been initiated, the run() method will handle executing each model
tb_static = TaxBrain(start_year, end_year, microdata="CPS", reform=reform_url)
tb_static.run()
Once the calculators have been run, you can produce a number of tables, including a weighted total of a given variable each year under both current law and the user reform.
print("Combined Tax Liability Over the Budget Window")
tb_static.weighted_totals("combined")
Combined Tax Liability Over the Budget Window
| 2021 | 2022 | 2023 | 2024 | 2025 | 2026 | 2027 | 2028 | 2029 | 2030 | |
|---|---|---|---|---|---|---|---|---|---|---|
| Base | 2.269723e+12 | 3.065057e+12 | 3.187451e+12 | 3.430286e+12 | 3.558234e+12 | 3.749566e+12 | 3.923694e+12 | 4.101939e+12 | 4.316249e+12 | 4.541999e+12 |
| Reform | 2.326814e+12 | 3.143256e+12 | 3.282028e+12 | 3.545953e+12 | 3.699010e+12 | 3.912748e+12 | 4.109452e+12 | 4.311587e+12 | 4.533253e+12 | 4.770344e+12 |
| Difference | 5.709077e+10 | 7.819926e+10 | 9.457664e+10 | 1.156674e+11 | 1.407758e+11 | 1.631815e+11 | 1.857588e+11 | 2.096474e+11 | 2.170046e+11 | 2.283448e+11 |
If you are interested in a detailed look on the reform’s effect, you can produce a differences table for a given year.
print("Differences Table")
tb_static.differences_table(start_year, "weighted_deciles", "combined")
Differences Table
| count | tax_cut | perc_cut | tax_inc | perc_inc | mean | tot_change | share_of_change | ubi | benefit_cost_total | benefit_value_total | pc_aftertaxinc | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0-10n | 0.102049 | 0.000000 | 0.000000 | 0.035165 | 34.458500 | 8.218450 | 0.000839 | 0.001469 | 0.0 | 0.0 | 0.0 | 0.006305 |
| 0-10z | 8.390748 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.0 | 0.0 | 0.0 | 0.000000 |
| 0-10p | 12.213889 | 0.000000 | 0.000000 | 5.510201 | 45.114223 | 3.693099 | 0.045107 | 0.079009 | 0.0 | 0.0 | 0.0 | -0.030271 |
| 10-20 | 20.708304 | 0.000000 | 0.000000 | 15.031974 | 72.589110 | 17.199769 | 0.356178 | 0.623880 | 0.0 | 0.0 | 0.0 | -0.049703 |
| 20-30 | 20.707505 | 0.019010 | 0.091801 | 13.230015 | 63.889952 | 27.007242 | 0.559253 | 0.979585 | 0.0 | 0.0 | 0.0 | -0.047437 |
| 30-40 | 20.706950 | 0.259493 | 1.253170 | 11.850040 | 57.227357 | 29.341194 | 0.607567 | 1.064212 | 0.0 | 0.0 | 0.0 | -0.036801 |
| 40-50 | 20.706181 | 1.020739 | 4.929636 | 12.904516 | 62.322051 | 27.887624 | 0.577446 | 1.011453 | 0.0 | 0.0 | 0.0 | -0.014320 |
| 50-60 | 20.708634 | 2.983512 | 14.407092 | 12.853635 | 62.068967 | -36.541184 | -0.756718 | -1.325465 | 0.0 | 0.0 | 0.0 | 0.110734 |
| 60-70 | 20.707540 | 3.810788 | 18.402900 | 12.691879 | 61.291099 | -155.091891 | -3.211572 | -5.625378 | 0.0 | 0.0 | 0.0 | 0.261506 |
| 70-80 | 20.705701 | 4.042144 | 19.521890 | 13.340665 | 64.429912 | -198.758187 | -4.115428 | -7.208569 | 0.0 | 0.0 | 0.0 | 0.271550 |
| 80-90 | 20.709850 | 4.418265 | 21.334121 | 14.297969 | 69.039464 | -427.711584 | -8.857843 | -15.515367 | 0.0 | 0.0 | 0.0 | 0.416827 |
| 90-100 | 20.708024 | 1.431391 | 6.912254 | 17.352383 | 83.795459 | 3471.405278 | 71.885943 | 125.915170 | 0.0 | 0.0 | 0.0 | -1.292652 |
| ALL | 207.075375 | 17.985342 | 8.685408 | 129.098443 | 62.343696 | 275.700439 | 57.090772 | 100.000000 | 0.0 | 0.0 | 0.0 | -0.321387 |
| 90-95 | 10.354203 | 1.292340 | 12.481311 | 8.241434 | 79.595059 | -298.772462 | -3.093551 | -5.418653 | 0.0 | 0.0 | 0.0 | 0.256932 |
| 95-99 | 8.282277 | 0.139051 | 1.678898 | 7.283324 | 87.938673 | 318.774347 | 2.640177 | 4.624526 | 0.0 | 0.0 | 0.0 | -0.070544 |
| Top 1% | 2.071544 | 0.000000 | 0.000000 | 1.827625 | 88.225265 | 34920.477840 | 72.339316 | 126.709297 | 0.0 | 0.0 | 0.0 | -4.338101 |
TaxBrain comes with two (and counting) built in plots as well
differences_plot(tb_static, 'combined', figsize=(10, 8));
distribution_plot(tb_static, 2021, figsize=(10, 8));
You can run a partial-equlibrium dynamic simulation by initiating the TaxBrian instance exactly as you would for the static reform, but with your behavioral assumptions specified
tb_dynamic = TaxBrain(start_year, end_year, microdata="CPS", reform=reform_url,
behavior={"sub": 0.25})
tb_dynamic.run()
---------------------------------------------------------------------------
KeyboardInterrupt Traceback (most recent call last)
File /usr/share/miniconda/envs/taxbrain-dev/lib/python3.12/site-packages/numba/core/serialize.py:30, in _numba_unpickle(address, bytedata, hashed)
27 _unpickled_memo = {}
---> 30 def _numba_unpickle(address, bytedata, hashed):
31 """Used by `numba_unpickle` from _helperlib.c
32
33 Parameters
(...) 42 unpickled object
43 """
KeyboardInterrupt:
The above exception was the direct cause of the following exception:
SystemError Traceback (most recent call last)
SystemError: <function _numba_unpickle at 0x7f9eb32b0e00> returned a result with an exception set
The above exception was the direct cause of the following exception:
SystemError Traceback (most recent call last)
SystemError: <function _numba_unpickle at 0x7f9eb32b0e00> returned a result with an exception set
The above exception was the direct cause of the following exception:
SystemError Traceback (most recent call last)
SystemError: <function _numba_unpickle at 0x7f9eb32b0e00> returned a result with an exception set
The above exception was the direct cause of the following exception:
SystemError Traceback (most recent call last)
SystemError: <function _numba_unpickle at 0x7f9eb32b0e00> returned a result with an exception set
The above exception was the direct cause of the following exception:
SystemError Traceback (most recent call last)
SystemError: <function _numba_unpickle at 0x7f9eb32b0e00> returned a result with an exception set
The above exception was the direct cause of the following exception:
SystemError Traceback (most recent call last)
SystemError: <function _numba_unpickle at 0x7f9eb32b0e00> returned a result with an exception set
The above exception was the direct cause of the following exception:
SystemError Traceback (most recent call last)
Cell In[9], line 3
1 tb_dynamic = TaxBrain(start_year, end_year, microdata="CPS", reform=reform_url,
2 behavior={"sub": 0.25})
----> 3 tb_dynamic.run()
File ~/work/Tax-Brain/Tax-Brain/taxbrain/taxbrain.py:178, in TaxBrain.run(self, varlist, client, num_workers)
176 if self.verbose:
177 print("Running dynamic simulations")
--> 178 self._dynamic_run(
179 varlist, base_calc, reform_calc, client, num_workers
180 )
181 else:
182 if self.verbose:
File ~/work/Tax-Brain/Tax-Brain/taxbrain/taxbrain.py:484, in TaxBrain._dynamic_run(self, varlist, base_calc, reform_calc, client, num_workers)
479 lazy_values = []
480 for yr in range(self.start_year, self.end_year + 1):
481 lazy_values.extend(
482 [
483 delayed(
--> 484 self._behresp_advance(
485 base_calc, reform_calc, varlist, yr
486 )
487 )
488 ]
489 )
490 if client:
491 futures = client.compute(lazy_values, num_workers=num_workers)
File ~/work/Tax-Brain/Tax-Brain/taxbrain/taxbrain.py:424, in TaxBrain._behresp_advance(self, base_calc, reform_calc, varlist, year)
416 if self.corp_revenue is not None:
417 reform_calc = dist_corp(
418 reform_calc,
419 self.corp_revenue,
(...) 422 self.ci_params,
423 )
--> 424 base, reform = behresp.response(
425 base_calc, reform_calc, self.params["behavior"], dump=True
426 )
427 base_df = base[varlist]
428 reform_df = reform[varlist]
File /usr/share/miniconda/envs/taxbrain-dev/lib/python3.12/site-packages/behresp/behavior.py:179, in response(calc_1, calc_2, elasticities, dump)
176 zero_sub_and_inc = False
177 # calculate marginal combined tax rates on taxpayer wages+salary
178 # (e00200p is taxpayer's wages+salary)
--> 179 wage_mtr1, wage_mtr2 = _mtr12(calc1, calc2,
180 mtr_of='e00200p',
181 tax_type='combined')
182 # calculate magnitude of substitution effect
183 if be_sub == 0.0:
File /usr/share/miniconda/envs/taxbrain-dev/lib/python3.12/site-packages/behresp/behavior.py:153, in response.<locals>._mtr12(calc__1, calc__2, mtr_of, tax_type)
148 """
149 Computes marginal tax rates for Calculator objects calc__1 and calc__2
150 for specified mtr_of income type and specified tax_type.
151 """
152 assert tax_type in ('combined', 'iitax')
--> 153 _, iitax1, combined1 = calc__1.mtr(mtr_of, wrt_full_compensation=True)
154 _, iitax2, combined2 = calc__2.mtr(mtr_of, wrt_full_compensation=True)
155 if tax_type == 'combined':
File /usr/share/miniconda/envs/taxbrain-dev/lib/python3.12/site-packages/taxcalc/calculator.py:676, in Calculator.mtr(self, variable_str, negative_finite_diff, zero_out_calculated_vars, calc_all_already_called, wrt_full_compensation)
674 self.restore_records()
675 if not calc_all_already_called or zero_out_calculated_vars:
--> 676 self.calc_all(zero_out_calc_vars=zero_out_calculated_vars)
677 payrolltax_base = self.array('payrolltax')
678 incometax_base = self.array('iitax')
File /usr/share/miniconda/envs/taxbrain-dev/lib/python3.12/site-packages/taxcalc/calculator.py:170, in Calculator.calc_all(self, zero_out_calc_vars)
168 UBI(self.__policy, self.__records)
169 BenefitPrograms(self)
--> 170 self._calc_one_year(zero_out_calc_vars)
171 FairShareTax(self.__policy, self.__records)
172 LumpSumTax(self.__policy, self.__records)
File /usr/share/miniconda/envs/taxbrain-dev/lib/python3.12/site-packages/taxcalc/calculator.py:1441, in Calculator._calc_one_year(self, zero_out_calc_vars)
1439 del item_cvar
1440 # Calculate taxes with optimal itemized deduction
-> 1441 self._taxinc_to_amt()
1442 F2441(self.__policy, self.__records)
1443 EITC(self.__policy, self.__records)
File /usr/share/miniconda/envs/taxbrain-dev/lib/python3.12/site-packages/taxcalc/calculator.py:1372, in Calculator._taxinc_to_amt(self)
1370 TaxInc(self.__policy, self.__records)
1371 SchXYZTax(self.__policy, self.__records)
-> 1372 GainsTax(self.__policy, self.__records)
1373 AGIsurtax(self.__policy, self.__records)
1374 NetInvIncTax(self.__policy, self.__records)
File /usr/share/miniconda/envs/taxbrain-dev/lib/python3.12/site-packages/taxcalc/decorators.py:326, in iterate_jit.<locals>.make_wrapper.<locals>.wrapper(*args, **kwargs)
323 eval(func_code, # pylint: disable=eval-used
324 {"applied_f": applied_jitted_f}, fakeglobals)
325 high_level_fn = fakeglobals["hl_func"]
--> 326 ans = high_level_fn(*args, **kwargs)
327 return ans
File <string>:12, in hl_func(pm, pf)
SystemError: CPUDispatcher(<function ap_func at 0x7f9eb20fc040>) returned a result with an exception set
Once that finishes running, we can produce the same weighted total table as we did with the static run.
print("Partial Equilibrium - Combined Tax Liability")
tb_dynamic.weighted_totals("combined")
Partial Equilibrium - Combined Tax Liability
| 2021 | 2022 | 2023 | 2024 | 2025 | 2026 | 2027 | 2028 | 2029 | 2030 | |
|---|---|---|---|---|---|---|---|---|---|---|
| Base | 2.269727e+12 | 3.067767e+12 | 3.246623e+12 | 3.395728e+12 | 3.575938e+12 | 3.979716e+12 | 4.161639e+12 | 4.344151e+12 | 4.543397e+12 | 4.749701e+12 |
| Reform | 2.307804e+12 | 3.119395e+12 | 3.312243e+12 | 3.476774e+12 | 3.673491e+12 | 4.082934e+12 | 4.282357e+12 | 4.483171e+12 | 4.690507e+12 | 4.904587e+12 |
| Difference | 3.807725e+10 | 5.162814e+10 | 6.561941e+10 | 8.104634e+10 | 9.755272e+10 | 1.032176e+11 | 1.207186e+11 | 1.390202e+11 | 1.471100e+11 | 1.548863e+11 |
Or we can produce a distribution table to see details on the effects of the reform.
print("Distribution Table")
tb_dynamic.distribution_table(start_year, "weighted_deciles", "expanded_income", "reform")
Distribution Table
| count | c00100 | count_StandardDed | standard | count_ItemDed | c04470 | c04600 | c04800 | taxbc | c62100 | ... | othertaxes | refund | iitax | payrolltax | combined | ubi | benefit_cost_total | benefit_value_total | expanded_income | aftertax_income | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0-10n | 0.102049 | -7.647952 | 0.018507 | -5.835622 | 0.000000 | 0.000000 | 0.0 | 0.000000 | 0.000000 | -7.764826 | ... | 0.000000 | 0.354646 | -0.354646 | 0.064998 | -0.289648 | 0.0 | 0.799655 | 0.799655 | -6.940854 | -6.651206 |
| 0-10z | 8.391348 | -0.092134 | 8.391348 | 112.514735 | 0.000000 | 0.000000 | 0.0 | 0.000000 | 0.000000 | -0.092134 | ... | 0.000000 | 17.779859 | -17.779859 | 0.000000 | -17.779859 | 0.0 | 0.000000 | 0.000000 | 0.000000 | 17.779859 |
| 0-10p | 12.213289 | 28.286909 | 12.207105 | 167.047672 | 0.006184 | 0.035899 | 0.0 | 0.109175 | 0.003643 | 28.250938 | ... | 0.000000 | 28.404318 | -28.400675 | 3.495389 | -24.905286 | 0.0 | 19.592770 | 19.592770 | 50.389807 | 75.295093 |
| 10-20 | 20.707972 | 207.041657 | 20.230779 | 280.157298 | 0.472337 | 7.619006 | 0.0 | 25.986559 | 2.401345 | 200.049307 | ... | 0.000000 | 68.924569 | -66.523628 | 27.626076 | -38.897552 | 0.0 | 107.290602 | 107.290602 | 323.220845 | 362.118396 |
| 20-30 | 20.707385 | 316.378323 | 19.648591 | 287.017567 | 1.053396 | 18.740597 | 0.0 | 116.225685 | 11.584224 | 299.238036 | ... | 0.000000 | 67.787144 | -56.195886 | 43.577393 | -12.618493 | 0.0 | 258.441980 | 258.441980 | 590.805721 | 603.424214 |
| 30-40 | 20.707088 | 392.642544 | 19.510973 | 297.416383 | 1.187950 | 22.114507 | 0.0 | 184.988094 | 19.315954 | 371.147034 | ... | 0.000000 | 67.101813 | -47.772787 | 53.603619 | 5.830832 | 0.0 | 369.313918 | 369.313918 | 787.711190 | 781.880357 |
| 40-50 | 20.708472 | 550.480439 | 19.029242 | 307.522806 | 1.676905 | 33.455448 | 0.0 | 286.992712 | 30.516102 | 519.344714 | ... | 0.000000 | 78.292375 | -47.767104 | 72.879144 | 25.112041 | 0.0 | 404.535394 | 404.535394 | 994.589197 | 969.477157 |
| 50-60 | 20.707393 | 735.630028 | 18.331876 | 326.411031 | 2.373124 | 49.426182 | 0.0 | 425.521954 | 47.073700 | 691.665427 | ... | 0.000000 | 93.114874 | -46.034925 | 95.833407 | 49.798482 | 0.0 | 466.945896 | 466.945896 | 1258.080439 | 1208.281958 |
| 60-70 | 20.707515 | 959.511949 | 17.811997 | 357.787922 | 2.893239 | 62.644720 | 0.0 | 606.027111 | 72.245168 | 905.890709 | ... | 0.000000 | 112.380798 | -40.126138 | 122.146512 | 82.020374 | 0.0 | 578.376040 | 578.376040 | 1603.049167 | 1521.028793 |
| 70-80 | 20.707197 | 1365.708941 | 16.719155 | 373.852288 | 3.982463 | 98.050853 | 0.0 | 941.858952 | 119.044547 | 1288.034856 | ... | 0.000000 | 121.724695 | -2.658703 | 172.536431 | 169.877728 | 0.0 | 634.000703 | 634.000703 | 2085.339711 | 1915.461983 |
| 80-90 | 20.707626 | 2093.482677 | 14.553142 | 349.813909 | 6.152092 | 179.989249 | 0.0 | 1578.829459 | 218.697073 | 1951.769562 | ... | 0.004426 | 125.344051 | 93.372100 | 257.189635 | 350.561734 | 0.0 | 640.771616 | 640.771616 | 2872.252958 | 2521.691223 |
| 90-100 | 20.708041 | 6246.236344 | 9.453390 | 234.654561 | 11.254651 | 386.830268 | 0.0 | 5608.201771 | 1197.758029 | 5942.795098 | ... | 13.805670 | 47.686797 | 1165.232430 | 553.861208 | 1719.093638 | 0.0 | 512.904614 | 512.904614 | 6987.435086 | 5268.341448 |
| ALL | 207.075375 | 12887.659726 | 175.906106 | 3088.360551 | 31.052341 | 858.906729 | 0.0 | 9774.741472 | 1718.639786 | 12190.328720 | ... | 13.810095 | 828.895941 | 904.990179 | 1402.813813 | 2307.803992 | 0.0 | 3992.973188 | 3992.973188 | 17545.933268 | 15238.129276 |
| 90-95 | 10.353704 | 1667.324398 | 5.676570 | 140.884353 | 4.677134 | 148.216845 | 0.0 | 1374.670598 | 220.454555 | 1553.995778 | ... | 0.030698 | 34.014434 | 186.471142 | 189.172816 | 375.643958 | 0.0 | 284.797859 | 284.797859 | 2039.255590 | 1663.611631 |
| 95-99 | 8.283571 | 2341.021395 | 3.296412 | 82.219564 | 4.987159 | 170.622619 | 0.0 | 2075.284086 | 397.064195 | 2203.591113 | ... | 1.300444 | 13.626819 | 384.737819 | 212.266995 | 597.004815 | 0.0 | 192.019886 | 192.019886 | 2627.393087 | 2030.388273 |
| Top 1% | 2.070766 | 2237.890550 | 0.480409 | 11.550644 | 1.590357 | 67.990803 | 0.0 | 2158.247087 | 580.239279 | 2185.208206 | ... | 12.474527 | 0.045543 | 594.023468 | 152.421397 | 746.444865 | 0.0 | 36.086868 | 36.086868 | 2320.786409 | 1574.341544 |
16 rows × 24 columns
Dynamic Reform with Corporate Income Tax Incidence#
Now we simulate a dynamic revenue estimate while accounting for the incidence of a corporate income tax change.
# Corporate revenue estimate
corp_rev = [5_000_000_000] * (end_year - start_year + 1)
incidence_assumptions = {
"Incidence": { # long-run incidence of corporate tax
"Labor share": 0.5,
"Shareholder share": 0.4,
"All capital share": 0.1,
},
"Long run years": 10, # number of years to reach long-run incidence
}
tb_dynamic = TaxBrain(start_year, end_year, microdata="CPS", reform=reform_url,
behavior={"sub": 0.25},
corp_revenue=corp_rev,
corp_incidence_assumptions=incidence_assumptions)
tb_dynamic.run()
print("Partial Equilibrium - Combined Tax Liability")
tb_dynamic.weighted_totals("combined")
Partial Equilibrium - Combined Tax Liability
| 2021 | 2022 | 2023 | 2024 | 2025 | 2026 | 2027 | 2028 | 2029 | 2030 | |
|---|---|---|---|---|---|---|---|---|---|---|
| Base | 2.269727e+12 | 3.067767e+12 | 3.246623e+12 | 3.395728e+12 | 3.575938e+12 | 3.979716e+12 | 4.161639e+12 | 4.344151e+12 | 4.543397e+12 | 4.749701e+12 |
| Reform | 2.309448e+12 | 3.121009e+12 | 3.313788e+12 | 3.478349e+12 | 3.675071e+12 | 4.084663e+12 | 4.284063e+12 | 4.484873e+12 | 4.692225e+12 | 4.906309e+12 |
| Difference | 3.972134e+10 | 5.324191e+10 | 6.716455e+10 | 8.262134e+10 | 9.913307e+10 | 1.049468e+11 | 1.224239e+11 | 1.407223e+11 | 1.488282e+11 | 1.566081e+11 |