diff --git a/conda.recipe/meta.yaml b/conda.recipe/meta.yaml index 2fa3210cb..1f60694fc 100755 --- a/conda.recipe/meta.yaml +++ b/conda.recipe/meta.yaml @@ -11,9 +11,8 @@ requirements: - pytest - setuptools - python - - mock - - numpy ==1.11.2 - - pandas <=0.18.0 + - numpy >=1.11.2 + - pandas >=0.18.0 - numba - toolz - six @@ -21,9 +20,8 @@ requirements: - pytest - setuptools - python - - mock - - numpy ==1.11.2 - - pandas <=0.18.0 + - numpy >=1.11.2 + - pandas >=0.18.0 - numba - toolz - six diff --git a/environment.yml b/environment.yml index b358c3088..afca84c78 100644 --- a/environment.yml +++ b/environment.yml @@ -2,16 +2,15 @@ name: taxcalc-dev dependencies: - pytest - setuptools -- numpy =1.11.2 -- pandas =0.18.0 +- numpy >=1.11.2 +- pandas >=0.18.0 - numba -- bokeh =0.12.3 - toolz - six -- ipython -- ipython-notebook +- bokeh >=0.12.3 +- mock - pep8 - pylint -- mock +- coverage - pip: - pytest-pep8 diff --git a/taxcalc/behavior.py b/taxcalc/behavior.py index d7805c996..0a582f0a4 100644 --- a/taxcalc/behavior.py +++ b/taxcalc/behavior.py @@ -183,9 +183,8 @@ def response(calc_x, calc_y): new_ltcg = calc_x.records.p23250 * exp_term ltcg_chg = new_ltcg - calc_x.records.p23250 # calculate charitable giving effect - no_charity_response = \ - calc_y.behavior.BE_charity.tolist() == \ - [0.0, 0.0, 0.0] + no_charity_response = (calc_y.behavior.BE_charity.tolist() == + [0.0, 0.0, 0.0]) if no_charity_response: c_charity_chg = np.zeros(calc_x.records.dim) nc_charity_chg = np.zeros(calc_x.records.dim) @@ -299,23 +298,22 @@ def _update_ordinary_income(taxinc_change, calc): calc.records.c04470) agi_m_ided = agi - ided # assume behv response only for filing units with positive agi_m_ided - delta_income = np.where(agi_m_ided > 0., taxinc_change, 0.) + pos = np.array(agi_m_ided > 0., dtype=bool) + delta_income = np.where(pos, taxinc_change, 0.) # allocate delta_income into three parts - delta_wage = np.where(agi_m_ided > 0., - delta_income * calc.records.e00200 / agi_m_ided, - 0.) - other_income = agi - calc.records.e00200 - delta_oinc = np.where(agi_m_ided > 0., - delta_income * other_income / agi_m_ided, - 0.) - delta_ided = np.where(agi_m_ided > 0., - delta_income * ided / agi_m_ided, - 0.) + winc = calc.records.e00200 + delta_winc = np.zeros_like(agi) + delta_winc[pos] = delta_income[pos] * winc[pos] / agi_m_ided[pos] + oinc = agi - winc + delta_oinc = np.zeros_like(agi) + delta_oinc[pos] = delta_income[pos] * oinc[pos] / agi_m_ided[pos] + delta_ided = np.zeros_like(agi) + delta_ided[pos] = delta_income[pos] * ided[pos] / agi_m_ided[pos] # confirm that the three parts are consistent with delta_income - assert np.allclose(delta_income, delta_wage + delta_oinc - delta_ided) + assert np.allclose(delta_income, delta_winc + delta_oinc - delta_ided) # add the three parts to different calc.records variables - calc.records.e00200 = calc.records.e00200 + delta_wage - calc.records.e00200p = calc.records.e00200p + delta_wage + calc.records.e00200 = calc.records.e00200 + delta_winc + calc.records.e00200p = calc.records.e00200p + delta_winc calc.records.e00300 = calc.records.e00300 + delta_oinc calc.records.e19200 = calc.records.e19200 + delta_ided return calc diff --git a/taxcalc/cli/install_local_taxcalc_package b/taxcalc/cli/install_local_taxcalc_package index 64943dec0..fa097a71f 100755 --- a/taxcalc/cli/install_local_taxcalc_package +++ b/taxcalc/cli/install_local_taxcalc_package @@ -1,8 +1,9 @@ #!/bin/bash # USAGE: ./install_local_taxcalc_package # ACTION: (1) uninstalls any installed taxcalc package (conda uninstall) -# (2) builds local taxcalc=0.0.0 package (conda build) -# (3) installs local taxcalc=0.0.0 package (conda install) +# (2) executes "conda install conda-build" (if necessary) +# (3) builds local taxcalc=0.0.0 package (conda build) +# (4) installs local taxcalc=0.0.0 package (conda install) # NOTE: for those with experience working with compiled languages, # building a local conda package is analogous to compiling an executable @@ -14,6 +15,14 @@ if [ $? -eq 1 ]; then ./uninstall_taxcalc_package fi +# install conda-build package if not present +conda list build | awk '$1~/conda-build/{rc=1}END{exit(rc)}' +if [ $? -eq 0 ]; then + echo "==> Installing conda-build package" + conda install conda-build --yes 2>&1 > /dev/null + echo "==> Continue building taxcalc package" +fi + # build taxcalc conda package cd ../../conda.recipe/ conda build --python 2.7 . 2>&1 | awk '$1~/BUILD/||$1~/TEST/' diff --git a/taxcalc/tests/conftest.py b/taxcalc/tests/conftest.py index 48be6d69f..6e81d4ccf 100644 --- a/taxcalc/tests/conftest.py +++ b/taxcalc/tests/conftest.py @@ -1,11 +1,14 @@ import os - +import numpy import pandas import pytest - from taxcalc import Records +# convert all numpy warnings into errors so they can be detected in tests +numpy.seterr(all='raise') + + @pytest.fixture(scope='session') def tests_path(): return os.path.abspath(os.path.dirname(__file__)) diff --git a/taxcalc/tests/test_behavior.py b/taxcalc/tests/test_behavior.py index 20dc95585..700bdbf01 100644 --- a/taxcalc/tests/test_behavior.py +++ b/taxcalc/tests/test_behavior.py @@ -11,6 +11,8 @@ def test_incorrect_Behavior_instantiation(): behv = Behavior(behavior_dict=bad_behv_dict) with pytest.raises(ValueError): behv = Behavior(num_years=0) + with pytest.raises(FloatingPointError): + np.divide(1., 0.) def test_correct_but_not_recommended_Behavior_instantiation(): diff --git a/taxcalc/utils.py b/taxcalc/utils.py index f9cac04f2..663681065 100644 --- a/taxcalc/utils.py +++ b/taxcalc/utils.py @@ -925,7 +925,7 @@ def atr_graph_data(calc1, calc2, min_avginc : float specifies the minimum average expanded income for a percentile to - be included in the graph data + be included in the graph data; value must be positive Returns ------- @@ -962,6 +962,8 @@ def atr_graph_data(calc1, calc2, msg = ('atr_measure="{}" is neither ' '"itax" nor "ptax" nor "combined"') raise ValueError(msg.format(atr_measure)) + # . . check min_avginc value + assert min_avginc > 0. # calculate taxes and expanded income calc1.calc_all() calc2.calc_all() @@ -994,19 +996,18 @@ def atr_graph_data(calc1, calc2, avginc_series = gdfx.apply(weighted_mean, 'expanded_income') avgtax1_series = gdfx.apply(weighted_mean, 'tax1') avgtax2_series = gdfx.apply(weighted_mean, 'tax2') - # compute average tax rates by income percentile - # pylint: disable=no-member - # (above pylint comment eliminates bogus np.divide warnings) - atr1_series = np.divide(avgtax1_series, avginc_series) - atr2_series = np.divide(avgtax2_series, avginc_series) + # compute average tax rates for each included income percentile + included = np.array(avginc_series >= min_avginc, dtype=bool) + atr1_series = np.zeros_like(avginc_series) + atr1_series[included] = avgtax1_series[included] / avginc_series[included] + atr2_series = np.zeros_like(avginc_series) + atr2_series[included] = avgtax2_series[included] / avginc_series[included] # construct DataFrame containing the two atr?_series lines = pd.DataFrame() - lines['avginc'] = avginc_series lines['base'] = atr1_series lines['reform'] = atr2_series - # drop percentiles with average income below the specified minimum - lines = lines[lines['avginc'] >= min_avginc] - lines.drop('avginc', axis=1, inplace=True) + # include only percentiles with average income no less than min_avginc + lines = lines[included] # construct dictionary containing plot lines and auto-generated labels data = dict() data['lines'] = lines