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.288212e+12 3.059889e+12 3.235148e+12 3.437784e+12 3.583177e+12 3.773408e+12 3.935339e+12 4.093183e+12 4.288115e+12 4.504975e+12
Reform 2.345440e+12 3.136974e+12 3.330267e+12 3.552937e+12 3.723043e+12 3.934557e+12 4.119399e+12 4.301007e+12 4.503058e+12 4.731386e+12
Difference 5.722739e+10 7.708472e+10 9.511880e+10 1.151522e+11 1.398666e+11 1.611492e+11 1.840604e+11 2.078243e+11 2.149425e+11 2.264111e+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.236588 0.000841 0.001469 0.0 0.0 0.0 0.006412
0-10z 8.391348 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.213921 0.000000 0.000000 5.510582 45.117228 3.707203 0.045279 0.079122 0.0 0.0 0.0 -0.030305
10-20 20.706676 0.000000 0.000000 15.019126 72.532770 17.226980 0.356713 0.623326 0.0 0.0 0.0 -0.049684
20-30 20.708035 0.017725 0.085596 13.199854 63.742668 26.966834 0.558430 0.975809 0.0 0.0 0.0 -0.047380
30-40 20.707242 0.260630 1.258640 11.858963 57.269639 29.406191 0.608921 1.064038 0.0 0.0 0.0 -0.036929
40-50 20.706111 1.050553 5.073636 12.921486 62.404214 27.623797 0.571981 0.999489 0.0 0.0 0.0 -0.013555
50-60 20.709566 2.990642 14.440875 12.844296 62.021077 -38.064106 -0.788291 -1.377472 0.0 0.0 0.0 0.113286
60-70 20.707672 3.782751 18.267387 12.709595 61.376265 -155.359402 -3.217131 -5.621664 0.0 0.0 0.0 0.261655
70-80 20.707340 4.037743 19.499093 13.370323 64.568038 -199.944135 -4.140311 -7.234842 0.0 0.0 0.0 0.272457
80-90 20.707567 4.393761 21.218142 14.284286 68.980998 -426.775011 -8.837472 -15.442731 0.0 0.0 0.0 0.415269
90-100 20.707849 1.410542 6.811631 17.365216 83.858137 3480.247151 72.068431 125.933455 0.0 0.0 0.0 -1.289472
ALL 207.075375 17.944347 8.665611 129.118893 62.353572 276.360195 57.227391 100.000000 0.0 0.0 0.0 -0.321254
90-95 10.351302 1.275021 12.317495 8.254645 79.744991 -294.885259 -3.052446 -5.333891 0.0 0.0 0.0 0.254021
95-99 8.285442 0.135521 1.635654 7.285839 87.935429 325.629408 2.697984 4.714497 0.0 0.0 0.0 -0.073161
Top 1% 2.071104 0.000000 0.000000 1.824732 88.104300 34968.247292 72.422894 126.552849 0.0 0.0 0.0 -4.300264

TaxBrain comes with two (and counting) built in plots as well

differences_plot(tb_static, 'combined', figsize=(10, 8));
../../_images/320298b17a31e8192f3424438dc4e98a2949ec9a1566c2b1f215bdf4390e2900.png
distribution_plot(tb_static, 2021, figsize=(10, 8));
../../_images/e53ca6d1cbe9f4b38e24b16e7b3b2dbb1b677c9c485f90bbc17f3a1c985f686a.png

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)
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.13/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.13/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.13/site-packages/taxcalc/calculator.py:636, in Calculator.mtr(self, variable_str, negative_finite_diff, zero_out_calculated_vars, calc_all_already_called, wrt_full_compensation)
    634     finite_diff *= -1.0
    635 # remember records object in order to restore it after mtr computations
--> 636 self.store_records()
    637 # extract variable array(s) from embedded records object
    638 variable = self.array(variable_str)

File /usr/share/miniconda/envs/taxbrain-dev/lib/python3.13/site-packages/taxcalc/calculator.py:254, in Calculator.store_records(self)
    248 """
    249 Make internal copy of embedded Records object that can then be
    250 restored after interim calculations that make temporary changes
    251 to the embedded Records object.
    252 """
    253 assert self.__stored_records is None
--> 254 self.__stored_records = copy.deepcopy(self.__records)

File /usr/share/miniconda/envs/taxbrain-dev/lib/python3.13/copy.py:163, in deepcopy(x, memo, _nil)
    161                 y = x
    162             else:
--> 163                 y = _reconstruct(x, memo, *rv)
    165 # If is its own copy, don't memoize.
    166 if y is not x:

File /usr/share/miniconda/envs/taxbrain-dev/lib/python3.13/copy.py:260, in _reconstruct(x, memo, func, args, state, listiter, dictiter, deepcopy)
    258 if state is not None:
    259     if deep:
--> 260         state = deepcopy(state, memo)
    261     if hasattr(y, '__setstate__'):
    262         y.__setstate__(state)

File /usr/share/miniconda/envs/taxbrain-dev/lib/python3.13/copy.py:137, in deepcopy(x, memo, _nil)
    135 copier = _deepcopy_dispatch.get(cls)
    136 if copier is not None:
--> 137     y = copier(x, memo)
    138 else:
    139     if issubclass(cls, type):

File /usr/share/miniconda/envs/taxbrain-dev/lib/python3.13/copy.py:222, in _deepcopy_dict(x, memo, deepcopy)
    220 memo[id(x)] = y
    221 for key, value in x.items():
--> 222     y[deepcopy(key, memo)] = deepcopy(value, memo)
    223 return y

File /usr/share/miniconda/envs/taxbrain-dev/lib/python3.13/copy.py:144, in deepcopy(x, memo, _nil)
    142 copier = getattr(x, "__deepcopy__", None)
    143 if copier is not None:
--> 144     y = copier(memo)
    145 else:
    146     reductor = dispatch_table.get(cls)

KeyboardInterrupt: 

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.288212e+12 3.059889e+12 3.235148e+12 3.437784e+12 3.583177e+12 3.773408e+12 3.935339e+12 4.093183e+12 4.288115e+12 4.504975e+12
Reform 2.325453e+12 3.110598e+12 3.300424e+12 3.519527e+12 3.685534e+12 3.893183e+12 4.078195e+12 4.252419e+12 4.456083e+12 4.678727e+12
Difference 3.724111e+10 5.070951e+10 6.527599e+10 8.174276e+10 1.023573e+11 1.197758e+11 1.428562e+11 1.592361e+11 1.679683e+11 1.737520e+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.551475 0.018507 -5.739144 0.000000 0.000000 0.0 0.000000 0.000000 -7.668671 ... 0.000000 0.354646 -0.354646 0.065142 -0.289504 0.0 0.799655 0.799655 -6.844299 -6.554795
0-10z 8.391348 -0.092268 8.391348 112.514600 0.000000 0.000000 0.0 0.000000 0.000000 -0.092268 ... 0.000000 17.779859 -17.779859 0.000000 -17.779859 0.0 0.000000 0.000000 0.000000 17.779859
0-10p 12.213828 28.363546 12.209145 167.074740 0.004683 0.027016 0.0 0.100207 0.003218 28.336481 ... 0.033097 28.418745 -28.382430 3.474003 -24.908426 0.0 19.599450 19.599450 50.481331 75.389758
10-20 20.707315 207.266449 20.219094 279.867767 0.483364 7.819285 0.0 26.200225 2.421113 200.099117 ... 0.331173 68.781584 -66.029655 27.329143 -38.700513 0.0 107.784243 107.784243 323.910547 362.611060
20-30 20.707625 316.289597 19.649759 286.983701 1.052469 18.726860 0.0 116.366956 11.594040 299.114044 ... 1.432791 67.460373 -54.426360 42.119995 -12.306365 0.0 259.426376 259.426376 591.717032 604.023396
30-40 20.707435 393.351699 19.512297 297.558986 1.186973 22.195982 0.0 185.206436 19.348877 371.709095 ... 4.166711 67.369167 -43.840477 49.574998 5.734521 0.0 369.191424 369.191424 788.545275 782.810753
40-50 20.707961 552.209879 18.994523 306.898949 1.711113 34.104147 0.0 288.666156 30.671201 520.642787 ... 4.748446 78.301379 -42.872126 68.367430 25.495304 0.0 404.257894 404.257894 996.075146 970.579843
50-60 20.707095 737.129680 18.309919 326.130739 2.394784 49.895909 0.0 426.792560 47.171340 692.907192 ... 5.637376 93.235926 -40.420905 90.294542 49.873637 0.0 467.635499 467.635499 1260.177637 1210.304000
60-70 20.707381 961.742223 17.764738 357.306361 2.940364 63.599918 0.0 607.828736 72.546325 907.691224 ... 6.013450 112.612372 -34.043261 116.488514 82.445253 0.0 579.152777 579.152777 1606.065255 1523.620002
70-80 20.708144 1372.350292 16.677503 372.939658 4.025062 99.449881 0.0 947.362325 119.797534 1293.484222 ... 5.554451 121.655746 3.718197 167.669051 171.387249 0.0 633.395089 633.395089 2091.024672 1919.637424
80-90 20.707006 2103.906673 14.470064 347.670867 6.234550 182.465615 0.0 1588.577405 220.290877 1960.285059 ... 9.707599 124.991789 105.021534 248.059508 353.081042 0.0 639.509689 639.509689 2880.096841 2527.015798
90-100 20.708187 6292.547105 9.379556 232.889415 11.328631 394.269174 0.0 5648.705809 1207.909607 5982.339577 ... 46.456075 47.149697 1208.574348 522.846625 1731.420972 0.0 512.221094 512.221094 7028.992513 5297.571541
ALL 207.075375 12957.513399 175.596455 3082.096640 31.361992 872.553786 0.0 9835.806814 1731.754134 12248.847858 ... 84.081169 828.111282 989.164360 1336.288951 2325.453311 0.0 3992.973190 3992.973190 17610.241951 15284.788640
90-95 10.353161 1674.594395 5.627299 139.772487 4.725862 149.995546 0.0 1381.007281 221.544160 1559.830372 ... 6.023246 33.605474 193.962254 183.586874 377.549128 0.0 284.698888 284.698888 2045.312779 1667.763651
95-99 8.283613 2352.125355 3.275722 81.678533 5.007891 171.274729 0.0 2086.343615 399.406887 2214.364099 ... 13.727862 13.504787 399.629963 200.112594 599.742557 0.0 191.342406 191.342406 2635.685291 2035.942735
Top 1% 2.071413 2265.827356 0.476535 11.438396 1.594878 72.998899 0.0 2181.354913 586.958561 2208.145107 ... 26.704967 0.039436 614.982131 139.147157 754.129288 0.0 36.179800 36.179800 2347.994443 1593.865155

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.288212e+12 3.059889e+12 3.235148e+12 3.437784e+12 3.583177e+12 3.773408e+12 3.935339e+12 4.093183e+12 4.288115e+12 4.504975e+12
Reform 2.327445e+12 3.112131e+12 3.302017e+12 3.521124e+12 3.687134e+12 3.894808e+12 4.078463e+12 4.254183e+12 4.455432e+12 4.680146e+12
Difference 3.923231e+10 5.224220e+10 6.686913e+10 8.333985e+10 1.039572e+11 1.214001e+11 1.431243e+11 1.609999e+11 1.673168e+11 1.751709e+11