Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EITC phase-in rate, "_EITC_rt" #1485

Closed
MattHJensen opened this issue Jul 18, 2017 · 11 comments
Closed

EITC phase-in rate, "_EITC_rt" #1485

MattHJensen opened this issue Jul 18, 2017 · 11 comments
Labels

Comments

@MattHJensen
Copy link
Contributor

A TaxBrain user is wanting to set an EITC rate (eitc_rt) over 100 percent.

The EITC rate description states that the range of the parameter is between 0 and 100 percent.

To experiment, I ran two reforms, one with the rate set to 100 percent for all filing statuses and one with the rates at 150 percent, and I got identical results for both.

All EITC rates at 100 percent.
http://www.ospc.org/taxbrain/15189/

All EITC rates at 150 percent.
http://www.ospc.org/taxbrain/15190/

  • can we allow rates over 100 percent?
  • was this a silent error, and are there other scenarios where that might be happening?
@martinholmer
Copy link
Collaborator

The current description of the _EITC_rt policy parameter dates back to the merge of pull request #327 on August 12, 2015. Here are the changes made then with respect to _EITC_rt:

      "_EITC_rt":{
          "long_name": "Earned income credit rate",
-         "description": "Apply this rate on Income.",
+         "description": "If one taxpayer's AGI is below AGI phaseout start, he can apply this rate to his earned income to calculate the credit amount eligible. ",
          "irs_ref": "Form 1040, line 66a&b, calculation (table: Max_EIC/Max_EIC_base_income).",
-         "notes": "",
+         "notes": "Range of this parameter is between zero and one. ",
          "start_year": 2013,

There have been no changes in this _EITC_rt entry since then.

I'm going to do several things:
(1) investigate whether or not the notes information is enforced any place in Tax-Calculator;
(2) improve the description wording; and
(3) drop the notes text if Tax-Calculator does not enforce the zero-to-one range.

I will report what I find in my investigations in a pull request, which at a minimum will do item (2).

@MattHJensen

@martinholmer martinholmer changed the title EITC phaseout rate EITC phase-in rate, _EITC_rt Jul 18, 2017
@martinholmer martinholmer changed the title EITC phase-in rate, _EITC_rt EITC phase-in rate, "_EITC_rt" Jul 18, 2017
@martinholmer
Copy link
Collaborator

martinholmer commented Jul 18, 2017

@MattHJensen,

There is no place in Tax-Calculator that constrains the value of _EITC_rt. It is simply passed unchanged to the EITCamount function as its first phasein_rate parameter:

def EITCamount(phasein_rate, earnings, max_amount,
               phaseout_start, eitc_agi, phaseout_rate):
    """
    Return EITC amount given specified parameters
    """
    eitc = min(phasein_rate * earnings, max_amount)
    if earnings > phaseout_start or eitc_agi > phaseout_start:
        eitcx = max(0., (max_amount - phaseout_rate *
                         max(0., max(earnings, eitc_agi) - phaseout_start)))
        eitc = min(eitc, eitcx)
    return eitc

That conclusion, which is based on code inspection, is confirmed by using the Tax-Calculator CLI to simulate the two reforms you analyzed with TaxBrain: an across-the-board 100% phase-in rate and an across-the-board 150% phase-in rate.

EITC_rt     2017 iitax change ($b)
  1.0             -7.1
  1.5             -8.1

The $1.0 billion difference is concentrated in the lowest two expanded income deciles.

The TaxBrain results from the two runs executed by @MattHJensen are -6.9 for both EITC_rt values.

Two possible explanation for the TaxBrain results come to mind:
(1) TaxBrain developers read the incorrect notes for this parameter and implemented TaxBrain logic to constrain the value of the parameter.
(2) TaxBrain results look exactly the same under the two reforms because of the dropq algorithm.

The first explanation doesn't seem very plausible because the constraints have a different field in current_law_policy.json (its key name is "validations") and when those constraints are violated a TaxBrain error is raised (right?).

@martinholmer
Copy link
Collaborator

The descriptions of several EITC policy parameters (including _EITC_rt) have been clarified in the current_law_policy.json file in merged pull request #1486. That pull request also eliminated the incorrect note for the _EITC_rt parameter.

@MattHJensen, Do you want to keep this issue open?

@hdoupe
Copy link
Collaborator

hdoupe commented Jul 20, 2017

Would it make sense to have lower and upper bound parameters in the current_law_policy.json file for policy variables that should be constrained? Then when new values are loaded in, we could check whether they fall in the allowed range.

@MattHJensen @martinholmer

@martinholmer
Copy link
Collaborator

@hdoupe asked in issue #1485

Would it make sense to have lower and upper bound parameters in the current_law_policy.json file for policy variables that should be constrained?

Yes, it would make sense. That is why some of the policy parameters have a "validations" field in the current_law_policy.json file. For example, consider the following entry:

    "_II_credit_prt": {
        "long_name": "Personal refundable credit phaseout rate",
        "description": "The personal refundable credit amount will be reduced at this rate for each dollar of AGI exceeding the _II_credit_ps threshold.",
        "section_1": "Personal Refundable Credit",
        "section_2": "Personal Refundable Credit Phaseout Rate",
        "irs_ref": "",
        "notes": "",
        "row_var": "FLPDYR",
        "row_label": ["2013"],
        "start_year": 2013,
        "cpi_inflated": false,
        "col_var": "",
        "col_label": "",
        "value": [0.0],
        "validations": {"min": 0}
    },

When those "validations" fields were first added there was a discussion about how many of them to have and which applications should enforcement the constraints. That discussion concluded with a minimalist approach: a limited number of "validations" fields and enforcement only in TaxBrain. Maybe your questions will generate another discussion of this matter.

@hdoupe continued:

Then when new values are loaded in, we could check whether they fall in the allowed range.

As I suggested above, Tax-Calculator does not currently enforce these validation constraints.
But my understanding (as limited as it is) is that TaxBrain does use the "validations" information to check inputs made into the boxes on the TaxBrain GUI page. I'm have no idea about whether or not TaxBrain runs these "validation" checks when it is handling a file upload.

@MattHJensen @feenberg @PeterDSteinberg @brittainhard

@feenberg
Copy link
Contributor

feenberg commented Jul 20, 2017 via email

@MattHJensen
Copy link
Contributor Author

@hdoupe, as you are thinking about the correspondence between tax-calculator results pre and post drop q for the sake of webapp-public testing, could you investigate whether it is reasonable to think that dropq could be causing the discrepancy between tax-calculator and taxbrain results in this particular situation? In particular, it would be nice to have some verification that it is just coincidental that TB / TC-post-dropq generates the exact same revenue estimate in every single year for these two different reforms.

@hdoupe
Copy link
Collaborator

hdoupe commented Jul 20, 2017

@martinholmer @feenberg Thank you for your insight. That makes sense.

@MattHJensen said

it would be nice to have some verification that it is just coincidental that TB / TC-post-dropq generates the exact same revenue estimate in every single year for these two different reforms.

Yes, I agree. I'll give this some thought.

@hdoupe
Copy link
Collaborator

hdoupe commented Jul 28, 2017

@MattHJensen said

as you are thinking about the correspondence between tax-calculator results pre and post drop q for the sake of webapp-public testing, could you investigate whether it is reasonable to think that dropq could be causing the discrepancy between tax-calculator and taxbrain results in this particular situation? In particular, it would be nice to have some verification that it is just coincidental that TB / TC-post-dropq generates the exact same revenue estimate in every single year for these two different reforms.

The results from taxbrain for each reform are almost exactly the same. The celery log shows that when _EITC_rt is set to 1.5 by the user on the taxbrain interface, it is rounded down to 1. However, if _EITC_rt is set to 1 by the user on the taxbrain interface, then taxbrain passes it to taxcalc as 1.0.

Celery log for _EITC_rt set to 1:

[2017-07-28 10:16:35,533: WARNING/MainProcess] keywords to dropq
[2017-07-28 10:16:35,534: WARNING/MainProcess] {'start_year': 2017, 'user_mods': {u'growdiff_response': {}, u'consumption': {}, u'growdiff_baseline': {}, u'behavior': {}, u'policy': {2017: {u'_EITC_rt': [[1.0, 1.0, 1.0, 1.0]], u'_NIIT_PT_taxed': [False], u'_ID_BenefitCap_Switch': [[True, True, True, True, 1.0, 1.0, 1.0]], u'_ALD_InvInc_ec_base_RyanBrady': [False], u'_EITC_indiv': [False], u'_ID_BenefitSurtax_Switch': [[True, True, True, True, True, True, True]], u'_II_no_em_nu18': [False], u'_CG_nodiff': [False], u'_CTC_new_refund_limited': [False]}}, u'gdp_elasticity': {}}, 'year_n': 0}

and _EITC_rt set to 1.5:

[2017-07-28 10:32:22,549: WARNING/MainProcess] keywords to dropq
[2017-07-28 10:32:22,550: WARNING/MainProcess] {'start_year': 2017, 'user_mods': {u'growdiff_response': {}, u'consumption': {}, u'growdiff_baseline': {}, u'behavior': {}, u'policy': {2017: {u'_EITC_rt': [[1, 1, 1, 1]], u'_NIIT_PT_taxed': [False], u'_ID_BenefitCap_Switch': [[True, True, True, True, 1.0, 1.0, 1.0]], u'_ALD_InvInc_ec_base_RyanBrady': [False], u'_EITC_indiv': [False], u'_ID_BenefitSurtax_Switch': [[True, True, True, True, True, True, True]], u'_II_no_em_nu18': [False], u'_CG_nodiff': [False], u'_CTC_new_refund_limited': [False]}}, u'gdp_elasticity': {}}, 'year_n': 1}

Since the random seed is determined by the contents of the dictionary user_mods, the seed will be different for each reform. Thus, even though the reform values are effectively the same, the data will be slightly different, and thus the taxbrain results will be slightly different.

Here is code to test this theory:

import pandas as pd
import json
import numpy as np
from taxcalc.dropq import run_nth_year_tax_calc_model

import traceback

def run_dropq(taxrec_df, user_mods, years=None, n_years=10):
    if years is None:
        years = list(range(0, n_years))
    start_year = 2017
    results = []
    for year in years:
        res = run_nth_year_tax_calc_model(year, start_year, taxrec_df, user_mods,
                                          return_json=True)
        results.append(res)

    return results



if __name__ == "__main__":
    taxrec_df = pd.read_csv("puf.csv")

    ref10_0 = {'growdiff_response': {}, 'consumption': {}, 'growdiff_baseline': {},
            'behavior': {}, u'policy': {2017: {'_EITC_rt': [[1.0, 1.0, 1.0, 1.0]],
            '_NIIT_PT_taxed': [False], '_ID_BenefitCap_Switch': [[True, True, True, True, 1.0, 1.0, 1.0]],
            '_ALD_InvInc_ec_base_RyanBrady': [False], '_EITC_indiv': [False],
            '_ID_BenefitSurtax_Switch': [[True, True, True, True, True, True, True]],
            '_II_no_em_nu18': [False], '_CG_nodiff': [False], '_CTC_new_refund_limited': [False]}}, 'gdp_elasticity': {}}

    ref10_1 = {'growdiff_response': {}, 'consumption': {}, 'growdiff_baseline': {},
             'behavior': {}, u'policy': {2017: {'_EITC_rt': [[1.0, 1.0, 1.0, 1.0]],
             '_NIIT_PT_taxed': [False], '_ID_BenefitCap_Switch': [[True, True, True, True, 1.0, 1.0, 1.0]],
             '_ALD_InvInc_ec_base_RyanBrady': [False], '_EITC_indiv': [False],
             '_ID_BenefitSurtax_Switch': [[True, True, True, True, True, True, True]],
             '_II_no_em_nu18': [False], '_CG_nodiff': [False], '_CTC_new_refund_limited': [False]}}, 'gdp_elasticity': {}}

    ref1 = {'growdiff_response': {}, 'consumption': {}, 'growdiff_baseline': {},
             'behavior': {}, u'policy': {2017: {'_EITC_rt': [[1, 1, 1, 1]],
             '_NIIT_PT_taxed': [False], '_ID_BenefitCap_Switch': [[True, True, True, True, 1.0, 1.0, 1.0]],
             '_ALD_InvInc_ec_base_RyanBrady': [False], '_EITC_indiv': [False],
             '_ID_BenefitSurtax_Switch': [[True, True, True, True, True, True, True]],
             '_II_no_em_nu18': [False], '_CG_nodiff': [False], '_CTC_new_refund_limited': [False]}}, 'gdp_elasticity': {}}

    ref15 = {'growdiff_response': {}, 'consumption': {}, 'growdiff_baseline': {},
             'behavior': {}, u'policy': {2017: {'_EITC_rt': [[1.5, 1.5, 1.5, 1.5]],
             '_NIIT_PT_taxed': [False], '_ID_BenefitCap_Switch': [[True, True, True, True, 1.0, 1.0, 1.0]],
             '_ALD_InvInc_ec_base_RyanBrady': [False], '_EITC_indiv': [False],
             '_ID_BenefitSurtax_Switch': [[True, True, True, True, True, True, True]],
             '_II_no_em_nu18': [False], '_CG_nodiff': [False], '_CTC_new_refund_limited': [False]}}, 'gdp_elasticity': {}}


    # baseline
    res10_0 = run_dropq(taxrec_df, ref10_0, n_years=2)
    # prove seed is same for same reform and thus results are same
    res10_1 = run_dropq(taxrec_df, ref10_1, n_years=2)
    # prove using decimal vs integer causes diff in results
    res1 = run_dropq(taxrec_df, ref1, n_years=2)
    # prove 1.5 yields different results from taxbrain 1.5 input
    res15 = run_dropq(taxrec_df, ref15, n_years=2)

    # should be the same
    try:
        np.testing.assert_equal(res10_0, res10_1)
    except AssertionError as e:
        print("res10_0, res10_1")
        traceback.print_exc()

    # should throw AssertionError
    try:
        np.testing.assert_equal(res10_0, res1)
    except AssertionError as e:
        print("res10_0, res1")
        traceback.print_exc()

    # should throw AssertionError
    try:
        np.testing.assert_equal(res10_0, res15)
    except AssertionError as e:
        print("res10_0, res15")
        traceback.print_exc()

    # should throw AssertionError
    try:
        np.testing.assert_equal(res1, res15)
    except AssertionError as e:
        print("res1, res15")
        traceback.print_exc()

@martinholmer @PeterDSteinberg @brittainhard

@martinholmer
Copy link
Collaborator

@hdoupe, Excellent analysis of the question posted in Tax-Calculator issue #1485.

Given your findings we can now see the strange TaxBrain results that prompted the question are caused by a TaxBrain bug. There is no reason to be limiting the value of this parameter to be at most one, so I'm assuming this TaxBrain behavior is accidental. Perhaps you should open a webapp-public issue (with a Bug label) that contains the celery log info you posted here in Tax-Calculator issue #1485. We already know that Tax-Calculator correctly handles values of this parameter in excess of one.

@MattHJensen @feenberg @PeterDSteinberg @brittainhard

@martinholmer
Copy link
Collaborator

Tax-Calculator issue #1485 has been resolved in the sense that the reported problem is caused by a TaxBrain bug. That bug has been diagnosed in TaxBrain issue 596 and the plan to fix that bug has been outlined in TaxBrain issue 619. Given this situation, there is no reason to keep Tax-Calculator issue #1485 open any longer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants