diff --git a/ccc/get_taxcalc_rates.py b/ccc/get_taxcalc_rates.py index df22448d..f464cd48 100644 --- a/ccc/get_taxcalc_rates.py +++ b/ccc/get_taxcalc_rates.py @@ -7,6 +7,7 @@ def get_calculator( baseline, calculator_start_year, + baseline_policy=None, reform=None, data="cps", gfactors=None, @@ -19,6 +20,7 @@ def get_calculator( Args: baseline (bool): `True` if baseline tax policy calculator_start_year (integer): first year of budget window + baseline_policy (dictionary): IIT baseline parameters reform (dictionary): IIT reform parameters data (string or Pandas DataFrame): path to file or DataFrame for Tax-Calculator Records object (optional) @@ -52,10 +54,14 @@ def get_calculator( records1 = Records() # pragma: no cover if baseline: - # Should not be a reform if baseline is True - assert not reform + if ( + baseline_policy + ): # if something other than current law policy baseline + update_policy(policy1, baseline_policy) if not baseline: + if baseline_policy: # update baseline policy to layer reform on top + update_policy(policy1, baseline_policy) update_policy(policy1, reform) # the default set up increments year to 2013 @@ -73,7 +79,11 @@ def get_calculator( def get_rates( - baseline=False, start_year=DEFAULT_START_YEAR, reform={}, data="cps" + baseline=False, + start_year=DEFAULT_START_YEAR, + baseline_policy=None, + reform={}, + data="cps", ): """ This function computes weighted average marginal tax rates using @@ -92,6 +102,7 @@ def get_rates( calc1 = get_calculator( baseline=baseline, calculator_start_year=start_year, + baseline_policy=baseline_policy, reform=reform, data=data, ) diff --git a/ccc/parameters.py b/ccc/parameters.py index e8495460..2851c478 100644 --- a/ccc/parameters.py +++ b/ccc/parameters.py @@ -26,6 +26,7 @@ def __init__( baseline=False, year=DEFAULT_START_YEAR, call_tc=False, + baseline_policy=None, iit_reform={}, data="cps", ): @@ -34,6 +35,7 @@ def __init__( self.test = test self.baseline = baseline self.year = year + self.baseline_policy = baseline_policy self.iit_reform = iit_reform self.data = data # initialize parameter values from JSON @@ -55,7 +57,11 @@ def ccc_initialize(self, call_tc=False): if call_tc: # Find individual income tax rates from Tax-Calculator indiv_rates = get_rates( - self.baseline, self.year, self.iit_reform, self.data + self.baseline, + self.year, + self.baseline_policy, + self.iit_reform, + self.data, ) self.tau_pt = indiv_rates["tau_pt"] self.tau_div = indiv_rates["tau_div"] diff --git a/ccc/tcja_extension.json b/ccc/tcja_extension.json new file mode 100644 index 00000000..32cf24dc --- /dev/null +++ b/ccc/tcja_extension.json @@ -0,0 +1,276 @@ +{ + "BonusDeprec_3yr": [ + { + "year": 2026, + "value": 0.4 + } + ], + "BonusDeprec_5yr": [ + { + "year": 2026, + "value": 0.4 + } + ], + "BonusDeprec_7yr": [ + { + "year": 2026, + "value": 0.4 + } + ], + "BonusDeprec_10yr": [ + { + "year": 2026, + "value": 0.4 + } + ], + "BonusDeprec_15yr": [ + { + "year": 2026, + "value": 0.4 + } + ], + "BonusDeprec_20yr": [ + { + "year": 2026, + "value": 0.4 + } + ], + "tau_pt": [ + { + "year": 2026, + "value": 0.20167244909882368 + }, + { + "year": 2027, + "value": 0.20348817617368892 + }, + { + "year": 2028, + "value": 0.20439272346443807 + }, + { + "year": 2029, + "value": 0.20308698621629706 + }, + { + "year": 2030, + "value": 0.2035939509415052 + }, + { + "year": 2031, + "value": 0.20240046451418342 + }, + { + "year": 2032, + "value": 0.2012693740494531 + }, + { + "year": 2033, + "value": 0.2006450797882713 + } + ], + "tau_div": [ + { + "year": 2026, + "value": 0.17318090272505438 + }, + { + "year": 2027, + "value": 0.17432284289770666 + }, + { + "year": 2028, + "value": 0.17282374074928825 + }, + { + "year": 2029, + "value": 0.17340175754076134 + }, + { + "year": 2030, + "value": 0.17422447893073145 + }, + { + "year": 2031, + "value": 0.17454968182589295 + }, + { + "year": 2032, + "value": 0.1740630397322921 + }, + { + "year": 2033, + "value": 0.17390183556629837 + } + ], + "tau_int": [ + { + "year": 2026, + "value": 0.3128559611626518 + }, + { + "year": 2027, + "value": 0.3139006325469027 + }, + { + "year": 2028, + "value": 0.31232532045301753 + }, + { + "year": 2029, + "value": 0.31278491973780054 + }, + { + "year": 2030, + "value": 0.3133435092632634 + }, + { + "year": 2031, + "value": 0.31314414677255953 + }, + { + "year": 2032, + "value": 0.31327752215037974 + }, + { + "year": 2033, + "value": 0.3129790702889989 + } + ], + "tau_scg": [ + { + "year": 2026, + "value": 0.28336133996697926 + }, + { + "year": 2027, + "value": 0.2833684122562315 + }, + { + "year": 2028, + "value": 0.2828407445954024 + }, + { + "year": 2029, + "value": 0.2840791423502338 + }, + { + "year": 2030, + "value": 0.2845544888165263 + }, + { + "year": 2031, + "value": 0.28481607001900205 + }, + { + "year": 2032, + "value": 0.2848967373063876 + }, + { + "year": 2033, + "value": 0.285277625628927 + } + ], + "tau_lcg": [ + { + "year": 2026, + "value": 0.18280630598531608 + }, + { + "year": 2027, + "value": 0.1830144476663731 + }, + { + "year": 2028, + "value": 0.1829679688634312 + }, + { + "year": 2029, + "value": 0.18388090065696877 + }, + { + "year": 2030, + "value": 0.18437947635832222 + }, + { + "year": 2031, + "value": 0.1847541804054145 + }, + { + "year": 2032, + "value": 0.1845754751225953 + }, + { + "year": 2033, + "value": 0.18508416618645537 + } + ], + "tau_td": [ + { + "year": 2026, + "value": 0.2053038317366772 + }, + { + "year": 2027, + "value": 0.20649173093016085 + }, + { + "year": 2028, + "value": 0.20768641729537382 + }, + { + "year": 2029, + "value": 0.20816493005521833 + }, + { + "year": 2030, + "value": 0.2087589110312411 + }, + { + "year": 2031, + "value": 0.20936047682363962 + }, + { + "year": 2032, + "value": 0.20872441217334245 + }, + { + "year": 2033, + "value": 0.20792201918205175 + } + ], + "tau_h": [ + { + "year": 2026, + "value": 0.03385918037116764 + }, + { + "year": 2027, + "value": 0.03376110325345599 + }, + { + "year": 2028, + "value": 0.03370990009622134 + }, + { + "year": 2029, + "value": 0.033444213400923 + }, + { + "year": 2030, + "value": 0.033168637894013954 + }, + { + "year": 2031, + "value": 0.032904420741914325 + }, + { + "year": 2032, + "value": 0.032489842789964196 + }, + { + "year": 2033, + "value": 0.03214152480222879 + } + ] +} \ No newline at end of file diff --git a/ccc/tests/test_get_taxcalc_rates.py b/ccc/tests/test_get_taxcalc_rates.py index c928328b..4645e64d 100644 --- a/ccc/tests/test_get_taxcalc_rates.py +++ b/ccc/tests/test_get_taxcalc_rates.py @@ -15,13 +15,21 @@ def test_get_calculator_cps(): @pytest.mark.needs_puf @pytest.mark.parametrize( - "data", ["puf.csv", None], ids=["data=PUF", "data=None"] + "baseline,data", + [(True, "puf.csv"), (True, None), (False, None)], + ids=["baseline,data=PUF", "baseline,data=None", "reform,data=None"], ) -def test_get_calculator(data): +def test_get_calculator(baseline, data): """ Test the get_calculator() function """ - calc1 = tc.get_calculator(True, 2019, data=data) + calc1 = tc.get_calculator( + baseline, + 2019, + baseline_policy={"FICA_ss_trt": {2018: 0.15}}, + reform={"FICA_ss_trt": {2018: 0.125}}, + data=data, + ) assert calc1.current_year == 2019 @@ -39,7 +47,11 @@ def test_get_rates(): """ p = Specification(year=2020) # has default tax rates, with should equal TC test_dict = tc.get_rates( - baseline=False, start_year=2020, reform={}, data="cps" + baseline=False, + start_year=2020, + baseline_policy={}, + reform={}, + data="cps", ) for k, v in test_dict.items(): print("Tax rate = ", k) @@ -53,7 +65,7 @@ def test_get_rates(): ) def test_is_paramtools_format(reform, expected): """ - Test get_taxcalc_rates.is_parametools_format function. + Test get_taxcalc_rates.is_paramtools_format function. """ returned_value = tc.is_paramtools_format(reform) diff --git a/docs/book/content/examples/TCJA_extension.ipynb b/docs/book/content/examples/TCJA_extension.ipynb new file mode 100644 index 00000000..77dea940 --- /dev/null +++ b/docs/book/content/examples/TCJA_extension.ipynb @@ -0,0 +1,341 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Using the Cost-of-Capital-Calculator (CCC) with an alternative baseline\n", + "\n", + "This notebook provides an illustration of using the Cost-of-Capital-Calculator (CCC) with an alternative baseline. In particular, it will simulate and compare results using a current law baseline and a current policy baseline (i.e., TCJA permanence).\n", + "\n", + "To run this notebook on your machine, you will need to follow the instructions to install CCC as described in the CCC README [here](https://github.com/PSLmodels/Cost-of-Capital-Calculator). In particular, you need to:\n", + "\n", + "* Install the [Anaconda distribution](https://www.anaconda.com/distribution/) of Python\n", + "* Install the CCC package by typing `conda install -c conda-forge ccc` (or `pip install cost-of-captial-calculator`) in the command prompt.\n", + "\n", + "Once you follow the above, you will be ready to work with this Jupyter Notebook.\n", + "\n", + "## First things first, import necessary packages" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "# To install ccc package (if not already):\n", + "import sys\n", + "if 'ccc' not in sys.modules:\n", + " !pip install cost-of-capital-calculator" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + " \n", + "
\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/javascript": "(function(root) {\n function now() {\n return new Date();\n }\n\n const force = true;\n\n if (typeof root._bokeh_onload_callbacks === \"undefined\" || force === true) {\n root._bokeh_onload_callbacks = [];\n root._bokeh_is_loading = undefined;\n }\n\nconst JS_MIME_TYPE = 'application/javascript';\n const HTML_MIME_TYPE = 'text/html';\n const EXEC_MIME_TYPE = 'application/vnd.bokehjs_exec.v0+json';\n const CLASS_NAME = 'output_bokeh rendered_html';\n\n /**\n * Render data to the DOM node\n */\n function render(props, node) {\n const script = document.createElement(\"script\");\n node.appendChild(script);\n }\n\n /**\n * Handle when an output is cleared or removed\n */\n function handleClearOutput(event, handle) {\n function drop(id) {\n const view = Bokeh.index.get_by_id(id)\n if (view != null) {\n view.model.document.clear()\n Bokeh.index.delete(view)\n }\n }\n\n const cell = handle.cell;\n\n const id = cell.output_area._bokeh_element_id;\n const server_id = cell.output_area._bokeh_server_id;\n\n // Clean up Bokeh references\n if (id != null) {\n drop(id)\n }\n\n if (server_id !== undefined) {\n // Clean up Bokeh references\n const cmd_clean = \"from bokeh.io.state import curstate; print(curstate().uuid_to_server['\" + server_id + \"'].get_sessions()[0].document.roots[0]._id)\";\n cell.notebook.kernel.execute(cmd_clean, {\n iopub: {\n output: function(msg) {\n const id = msg.content.text.trim()\n drop(id)\n }\n }\n });\n // Destroy server and session\n const cmd_destroy = \"import bokeh.io.notebook as ion; ion.destroy_server('\" + server_id + \"')\";\n cell.notebook.kernel.execute(cmd_destroy);\n }\n }\n\n /**\n * Handle when a new output is added\n */\n function handleAddOutput(event, handle) {\n const output_area = handle.output_area;\n const output = handle.output;\n\n // limit handleAddOutput to display_data with EXEC_MIME_TYPE content only\n if ((output.output_type != \"display_data\") || (!Object.prototype.hasOwnProperty.call(output.data, EXEC_MIME_TYPE))) {\n return\n }\n\n const toinsert = output_area.element.find(\".\" + CLASS_NAME.split(' ')[0]);\n\n if (output.metadata[EXEC_MIME_TYPE][\"id\"] !== undefined) {\n toinsert[toinsert.length - 1].firstChild.textContent = output.data[JS_MIME_TYPE];\n // store reference to embed id on output_area\n output_area._bokeh_element_id = output.metadata[EXEC_MIME_TYPE][\"id\"];\n }\n if (output.metadata[EXEC_MIME_TYPE][\"server_id\"] !== undefined) {\n const bk_div = document.createElement(\"div\");\n bk_div.innerHTML = output.data[HTML_MIME_TYPE];\n const script_attrs = bk_div.children[0].attributes;\n for (let i = 0; i < script_attrs.length; i++) {\n toinsert[toinsert.length - 1].firstChild.setAttribute(script_attrs[i].name, script_attrs[i].value);\n toinsert[toinsert.length - 1].firstChild.textContent = bk_div.children[0].textContent\n }\n // store reference to server id on output_area\n output_area._bokeh_server_id = output.metadata[EXEC_MIME_TYPE][\"server_id\"];\n }\n }\n\n function register_renderer(events, OutputArea) {\n\n function append_mime(data, metadata, element) {\n // create a DOM node to render to\n const toinsert = this.create_output_subarea(\n metadata,\n CLASS_NAME,\n EXEC_MIME_TYPE\n );\n this.keyboard_manager.register_events(toinsert);\n // Render to node\n const props = {data: data, metadata: metadata[EXEC_MIME_TYPE]};\n render(props, toinsert[toinsert.length - 1]);\n element.append(toinsert);\n return toinsert\n }\n\n /* Handle when an output is cleared or removed */\n events.on('clear_output.CodeCell', handleClearOutput);\n events.on('delete.Cell', handleClearOutput);\n\n /* Handle when a new output is added */\n events.on('output_added.OutputArea', handleAddOutput);\n\n /**\n * Register the mime type and append_mime function with output_area\n */\n OutputArea.prototype.register_mime_type(EXEC_MIME_TYPE, append_mime, {\n /* Is output safe? */\n safe: true,\n /* Index of renderer in `output_area.display_order` */\n index: 0\n });\n }\n\n // register the mime type if in Jupyter Notebook environment and previously unregistered\n if (root.Jupyter !== undefined) {\n const events = require('base/js/events');\n const OutputArea = require('notebook/js/outputarea').OutputArea;\n\n if (OutputArea.prototype.mime_types().indexOf(EXEC_MIME_TYPE) == -1) {\n register_renderer(events, OutputArea);\n }\n }\n if (typeof (root._bokeh_timeout) === \"undefined\" || force === true) {\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_failed_load = false;\n }\n\n const NB_LOAD_WARNING = {'data': {'text/html':\n \"\\n\"+\n \"BokehJS does not appear to have successfully loaded. If loading BokehJS from CDN, this \\n\"+\n \"may be due to a slow or bad network connection. Possible fixes:\\n\"+\n \"
\\n\"+\n \"\\n\"+\n \"from bokeh.resources import INLINE\\n\"+\n \"output_notebook(resources=INLINE)\\n\"+\n \"
\\n\"+\n \"\n", + " | \n", + " | Marginal Effective Total Tax Rate Under Baseline Policy | \n", + "Marginal Effective Total Tax Rate Under Reform Policy | \n", + "Change from Baseline (pp) | \n", + "
---|---|---|---|---|
0 | \n", + "Overall | \n", + "22.061070 | \n", + "20.280837 | \n", + "-1.780234 | \n", + "
1 | \n", + "Corporations | \n", + "22.053440 | \n", + "21.173608 | \n", + "-0.879832 | \n", + "
2 | \n", + "Equity Financed | \n", + "24.290534 | \n", + "23.435529 | \n", + "-0.855005 | \n", + "
3 | \n", + "Debt Financed | \n", + "14.099487 | \n", + "13.100244 | \n", + "-0.999243 | \n", + "
4 | \n", + "Pass-Through Entities | \n", + "23.679694 | \n", + "20.858889 | \n", + "-2.820805 | \n", + "
5 | \n", + "Equity Financed | \n", + "22.924874 | \n", + "18.482954 | \n", + "-4.441920 | \n", + "
6 | \n", + "Debt Financed | \n", + "26.911062 | \n", + "29.792381 | \n", + "2.881319 | \n", + "