Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions .github/workflows/build_and_test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Build Package and Test Source Code [Python 3.7, 3.8, 3.9]

on: [push, pull_request]

jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.7, 3.8, 3.9]

steps:
- name: Checkout
uses: actions/checkout@v2
with:
persist-credentials: false

- name: Setup Miniconda using Python ${{ matrix.python-version }}
uses: conda-incubator/setup-miniconda@v2
with:
activate-environment: behresp-dev
environment-file: environment.yml
python-version: ${{ matrix.python-version }}
auto-activate-base: false

- name: Build
shell: bash -l {0}
run: |
pip install -e .
pip install pytest-cov
pip install pytest-pycodestyle
- name: Test
shell: bash -l {0}
working-directory: ./
run: |
pytest -m 'not requires_pufcsv and not pre_release and not local' --pycodestyle --cov=./ --cov-report=xml
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v1
with:
file: ./coverage.xml
flags: unittests
name: codecov-umbrella
fail_ci_if_error: true
verbose: true
4 changes: 2 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
dist: xenial
language: python
python:
- "3.6"
- "3.7"
- "3.8"
- "3.9"

install:
# Install conda
Expand All @@ -15,7 +15,7 @@ install:
- conda create -n behresp-dev python=$TRAVIS_PYTHON_VERSION;
- source activate behresp-dev
- conda env update -f environment.yml
- pip install pytest-pycodestyle
- pip install pytest-pycodestyle
- pip install pyyaml
- pip install coverage
- pip install codecov
Expand Down
40 changes: 26 additions & 14 deletions behresp/behavior.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,23 @@
import taxcalc as tc


def response(calc_1, calc_2, elasticities, dump=False):
def response(calc_1, calc_2, elasticities, dump=False, inplace=False):
"""
Implements TaxBrain "Partial Equilibrium Simulation" dynamic analysis
returning results as a tuple of Pandas DataFrame objects (df1, df2) where:
df1 is extracted from a baseline-policy calc_1 copy, and
df2 is extracted from a reform-policy calc_2 copy that incorporates the
df1 is extracted from a baseline-policy calc_1, and
df2 is extracted from a reform-policy calc_2 that incorporates the
behavioral responses given by the nature of the baseline-to-reform
change in policy and elasticities in the specified behavior dictionary.

Note: this function internally modifies a copy of calc_2 records to account
for behavioral responses that arise from the policy reform that involves
moving from calc1 policy to calc2 policy. Neither calc_1 nor calc_2 need
to have had calc_all() executed before calling the response function.
And neither calc_1 nor calc_2 are affected by this response function.
Note: By default, this function internally modifies a copy of calc_2
records to account for behavioral responses that arise from the
policy reform that involves moving from calc1 policy to calc2
policy. Neither calc_1 nor calc_2 need to have had calc_all()
executed before calling the response function. By default, neither
calc_1 nor calc_2 are affected by this response function. To
perform in-place calculations that affect calc_1 and calc_2, set
inplace equal to True.

The elasticities argument is a dictionary containing the assumed response
elasticities. Omitting an elasticity key:value pair in the dictionary
Expand Down Expand Up @@ -93,8 +96,12 @@ def response(calc_1, calc_2, elasticities, dump=False):
# pylint: disable=too-many-locals,too-many-statements,too-many-branches

# Check function argument types and elasticity values
calc1 = copy.deepcopy(calc_1)
calc2 = copy.deepcopy(calc_2)
if inplace:
calc1 = calc_1
calc2 = calc_2
else:
calc1 = copy.deepcopy(calc_1)
calc2 = copy.deepcopy(calc_2)
assert isinstance(calc1, tc.Calculator)
assert isinstance(calc2, tc.Calculator)
assert isinstance(elasticities, dict)
Expand Down Expand Up @@ -220,10 +227,14 @@ def _mtr12(calc__1, calc__2, mtr_of='e00200p', tax_type='combined'):
df1['mtr_combined'] = wage_mtr1 * 100
else:
df1 = calc1.dataframe(tc.DIST_VARIABLES)
del calc1
if not inplace:
del calc1
# Add behavioral-response changes to income sources
calc2_behv = copy.deepcopy(calc2)
del calc2
if inplace:
calc2_behv = calc2
else:
calc2_behv = copy.deepcopy(calc2)
del calc2
if not zero_sub_and_inc:
calc2_behv = _update_ordinary_income(si_chg, calc2_behv)
calc2_behv = _update_cap_gain_income(ltcg_chg, calc2_behv)
Expand All @@ -237,7 +248,8 @@ def _mtr12(calc__1, calc__2, mtr_of='e00200p', tax_type='combined'):
df2['mtr_combined'] = wage_mtr2 * 100
else:
df2 = calc2_behv.dataframe(tc.DIST_VARIABLES)
del calc2_behv
if not inplace:
del calc2_behv
# Return the two dataframes
return (df1, df2)

Expand Down
33 changes: 28 additions & 5 deletions behresp/tests/test_behavior.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
from behresp import response, quantity_response, labor_response


def test_default_response_function(cps_subsample):
@pytest.mark.parametrize("inplace", [False, True])
def test_default_response_function(cps_subsample, inplace):
"""
Test that default behavior parameters produce static results.
"""
Expand All @@ -37,10 +38,32 @@ def test_default_response_function(cps_subsample):
calc2s.calc_all()
df2s = calc2s.dataframe(['iitax', 's006'])
itax2s = round((df2s['iitax'] * df2s['s006']).sum() * 1e-9, 3)

# Keep track of some of the variables that will be modifed
# in response if inplace is True.
df_before_1 = calc1.dataframe(tc.DIST_VARIABLES)
df_before_2 = calc2d.dataframe(tc.DIST_VARIABLES)

# ... calculate aggregate inctax using zero response elasticities
_, df2d = response(calc1, calc2d, elasticities={}, dump=True)
_, df2d = response(calc1, calc2d, elasticities={}, dump=True,
inplace=inplace)
itax2d = round((df2d['iitax'] * df2d['s006']).sum() * 1e-9, 3)
assert np.allclose(itax2d, itax2s)

# Grab the same variables after.
df_after_1 = calc1.dataframe(tc.DIST_VARIABLES)
df_after_2 = calc2d.dataframe(tc.DIST_VARIABLES)

# If inplace is True, the variables should have been modified.
# If inplace is False, response only modified a copy of calc_1
# and calc_2.
if inplace:
assert not df_before_1.equals(df_after_1)
assert not df_before_2.equals(df_after_2)
else:
assert df_before_1.equals(df_after_1)
assert df_before_2.equals(df_after_2)

# ... clean up
del calc1
del calc2s
Expand Down Expand Up @@ -76,9 +99,9 @@ def test_nondefault_response_function(be_inc, cps_subsample):
del df1
del df2
if be_inc == 0.0:
assert np.allclose([itax1, itax2], [1355.556, 1304.984])
assert np.allclose([itax1, itax2], [1354.7, 1304.166])
elif be_inc == -0.1:
assert np.allclose([itax1, itax2], [1355.556, 1303.898])
assert np.allclose([itax1, itax2], [1354.7, 1303.08])


def test_alternative_behavior_parameters(cps_subsample):
Expand Down Expand Up @@ -107,7 +130,7 @@ def test_alternative_behavior_parameters(cps_subsample):
itax2 = round((df2['iitax'] * df2['s006']).sum() * 1e-9, 3)
del df1
del df2
assert np.allclose([itax1, itax2], [1355.556, 1302.09])
assert np.allclose([itax1, itax2], [1354.7, 1301.281])


def test_quantity_response():
Expand Down
4 changes: 2 additions & 2 deletions conda.recipe/meta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ package:
requirements:
build:
- python
- "taxcalc>=3.0.0"
- "taxcalc>=3.1.0"
- pytest

run:
- python
- "taxcalc>=3.0.0"
- "taxcalc>=3.1.0"
- pytest

test:
Expand Down
3 changes: 1 addition & 2 deletions environment.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
name: behresp-dev
channels:
- PSLmodels
- conda-forge
dependencies:
- python
- "taxcalc>=3.0.0"
- "taxcalc>=3.1.0"
- pytest