Exemple #1
0
    def __init__(self,
                 gfactors=None,
                 parameter_dict=None,
                 start_year=JSON_START_YEAR,
                 num_years=DEFAULT_NUM_YEARS):
        super(Policy, self).__init__()

        if gfactors is None:
            self._gfactors = Growfactors()
        elif isinstance(gfactors, Growfactors):
            self._gfactors = gfactors
        else:
            raise ValueError('gfactors is not None or a Growfactors instance')

        if parameter_dict is None:  # read default parameters
            self._vals = self._params_dict_from_json_file()
        elif isinstance(parameter_dict, dict):
            self._vals = parameter_dict
        else:
            raise ValueError('parameter_dict is not None or a dictionary')

        if num_years < 1:
            raise ValueError('num_years cannot be less than one')

        syr = start_year
        lyr = start_year + num_years - 1
        self._inflation_rates = self._gfactors.price_inflation_rates(syr, lyr)
        self._apply_clp_cpi_offset(self._vals['_cpi_offset'], num_years)
        self._wage_growth_rates = self._gfactors.wage_growth_rates(syr, lyr)

        self.initialize(start_year, num_years)

        self.reform_warnings = ''
        self.reform_errors = ''
        self._ignore_errors = False
Exemple #2
0
    def __init__(self,
                 gfactors=Growfactors(),
                 parameter_dict=None,
                 start_year=JSON_START_YEAR,
                 num_years=DEFAULT_NUM_YEARS):
        """
        Policy class constructor.
        """
        # pylint: disable=too-many-arguments
        # pylint: disable=too-many-branches
        super(Policy, self).__init__()

        if not isinstance(gfactors, Growfactors):
            raise ValueError('gfactors is not a Growfactors instance')
        self._gfactors = gfactors

        if parameter_dict is None:  # read default parameters
            self._vals = self._params_dict_from_json_file()
        elif isinstance(parameter_dict, dict):
            self._vals = parameter_dict
        else:
            raise ValueError('parameter_dict is not None or a dictionary')

        if num_years < 1:
            raise ValueError('num_years cannot be less than one')

        syr = start_year
        lyr = start_year + num_years - 1
        self._inflation_rates = gfactors.price_inflation_rates(syr, lyr)
        self._wage_growth_rates = gfactors.wage_growth_rates(syr, lyr)

        self.initialize(start_year, num_years)
Exemple #3
0
 def with_suffix(gdict, growdiff_baseline_dict, growdiff_response_dict):
     """
     Return param_base:year dictionary having only suffix parameters.
     """
     if bool(growdiff_baseline_dict) or bool(growdiff_response_dict):
         gdiff_baseline = Growdiff()
         gdiff_baseline.update_growdiff(growdiff_baseline_dict)
         gdiff_response = Growdiff()
         gdiff_response.update_growdiff(growdiff_response_dict)
         growfactors = Growfactors()
         gdiff_baseline.apply_to(growfactors)
         gdiff_response.apply_to(growfactors)
     else:
         growfactors = None
     pol = Policy(gfactors=growfactors)
     pol.ignore_reform_errors()
     odict = dict()
     for param in gdict.keys():
         odict[param] = dict()
         for year in sorted(gdict[param].keys()):
             odict[param][year] = dict()
             for suffix in gdict[param][year].keys():
                 plist = getattr(pol, param).tolist()
                 dvals = plist[int(year) - Policy.JSON_START_YEAR]
                 odict[param][year] = [dvals]
                 idx = Policy.JSON_REFORM_SUFFIXES[suffix]
                 odict[param][year][0][idx] = gdict[param][year][suffix]
                 udict = {int(year): {param: odict[param][year]}}
                 pol.implement_reform(udict)
     return odict
Exemple #4
0
 def cps_constructor(data=None,
                     no_benefits=False,
                     exact_calculations=False,
                     gfactors=Growfactors()):
     """
     Static method returns a Records object instantiated with CPS
     input data.  This works in a analogous way to Records(), which
     returns a Records object instantiated with PUF input data.
     This is a convenience method that eliminates the need to
     specify all the details of the CPS input data just as the
     default values of the arguments of the Records class constructor
     eliminate the need to specify all the details of the PUF input
     data.
     """
     if data is None:
         data = os.path.join(Records.CUR_PATH, 'cps.csv.gz')
     if no_benefits:
         benefits_filename = None
     else:
         benefits_filename = Records.CPS_BENEFITS_FILENAME
     return Records(data=data,
                    exact_calculations=exact_calculations,
                    gfactors=gfactors,
                    weights=Records.CPS_WEIGHTS_FILENAME,
                    adjust_ratios=Records.CPS_RATIOS_FILENAME,
                    benefits=benefits_filename,
                    start_year=Records.CPSCSV_YEAR)
Exemple #5
0
class Policy(ParametersBase):
    """
    Policy is a subclass of the abstract ParametersBase class, and
    therefore, inherits its methods (none of which are shown here).

    Constructor for the federal tax policy class.

    Parameters
    ----------
    gfactors: Growfactors class instance
        containing price inflation rates and wage growth rates

    parameter_dict: dictionary of PARAM:DESCRIPTION pairs
        dictionary of policy parameters; if None, default policy
        parameters are read from the current_law_policy.json file.

    start_year: integer
        first calendar year for historical policy parameters.

    num_years: integer
        number of calendar years for which to specify policy parameter
        values beginning with start_year.

    Raises
    ------
    ValueError:
        if gfactors is not a Growfactors class instance.
        if parameter_dict is neither None nor a dictionary.
        if num_years is less than one.

    Returns
    -------
    class instance: Policy
    """

    DEFAULTS_FILENAME = 'current_law_policy.json'
    JSON_START_YEAR = 2013  # remains the same unless earlier data added
    LAST_KNOWN_YEAR = 2017  # last year for which indexed param vals are known
    LAST_BUDGET_YEAR = 2027  # increases by one every calendar year
    DEFAULT_NUM_YEARS = LAST_BUDGET_YEAR - JSON_START_YEAR + 1

    def __init__(self,
                 gfactors=None,
                 parameter_dict=None,
                 start_year=JSON_START_YEAR,
                 num_years=DEFAULT_NUM_YEARS):
        super(Policy, self).__init__()

        if gfactors is None:
            self._gfactors = Growfactors()
        elif isinstance(gfactors, Growfactors):
            self._gfactors = gfactors
        else:
            raise ValueError('gfactors is not None or a Growfactors instance')

        if parameter_dict is None:  # read default parameters
            self._vals = self._params_dict_from_json_file()
        elif isinstance(parameter_dict, dict):
            self._vals = parameter_dict
        else:
            raise ValueError('parameter_dict is not None or a dictionary')

        if num_years < 1:
            raise ValueError('num_years cannot be less than one')

        syr = start_year
        lyr = start_year + num_years - 1
        self._inflation_rates = self._gfactors.price_inflation_rates(syr, lyr)
        self._apply_clp_cpi_offset(self._vals['_cpi_offset'], num_years)
        self._wage_growth_rates = self._gfactors.wage_growth_rates(syr, lyr)

        self.initialize(start_year, num_years)

        self.reform_warnings = ''
        self.reform_errors = ''
        self._ignore_errors = False

    def inflation_rates(self):
        """
        Returns list of price inflation rates starting with JSON_START_YEAR.
        """
        return self._inflation_rates

    def wage_growth_rates(self):
        """
        Returns list of wage growth rates starting with JSON_START_YEAR.
        """
        return self._wage_growth_rates

    def implement_reform(self, reform):
        """
        Implement multi-year policy reform and leave current_year unchanged.

        Parameters
        ----------
        reform: dictionary of one or more YEAR:MODS pairs
            see Notes to Parameters _update method for info on MODS structure

        Raises
        ------
        ValueError:
            if reform is not a dictionary.
            if each YEAR in reform is not an integer.
            if minimum YEAR in the YEAR:MODS pairs is less than start_year.
            if minimum YEAR in the YEAR:MODS pairs is less than current_year.
            if maximum YEAR in the YEAR:MODS pairs is greater than end_year.
            if Policy._validate_parameter_names generates any error messages.

        Returns
        -------
        nothing: void

        Notes
        -----
        Given a reform dictionary, typical usage of the Policy class
        is as follows::

            policy = Policy()
            policy.implement_reform(reform)

        In the above statements, the Policy() call instantiates a
        policy object (policy) containing current-law policy parameters,
        and the implement_reform(reform) call applies the (possibly
        multi-year) reform specified in reform and then sets the
        current_year to the value of current_year when implement_reform
        was called with parameters set for that pre-call year.

        An example of a multi-year, multi-parameter reform is as follows::

            reform = {
                2016: {
                    '_EITC_c': [[900, 5000, 8000, 9000]],
                    '_II_em': [7000],
                    '_SS_Earnings_c': [300000]
                },
                2017: {
                    '_SS_Earnings_c': [500000], '_SS_Earnings_c_cpi': False
                },
                2019: {
                    '_EITC_c': [[1200, 7000, 10000, 12000]],
                    '_II_em': [9000],
                    '_SS_Earnings_c': [700000], '_SS_Earnings_c_cpi': True
                }
            }

        Notice that each of the four YEAR:MODS pairs is specified as
        required by the private _update method, whose documentation
        provides several MODS dictionary examples.

        IMPORTANT NOTICE: when specifying a reform dictionary always group
        all reform provisions for a specified year into one YEAR:MODS pair.
        If you make the mistake of specifying two or more YEAR:MODS pairs
        with the same YEAR value, all but the last one will be overwritten,
        and therefore, not part of the reform.  This is because Python
        expects unique (not multiple) dictionary keys.  There is no way to
        catch this error, so be careful to specify reform dictionaries
        correctly.
        """
        # check that all reform dictionary keys are integers
        if not isinstance(reform, dict):
            raise ValueError('ERROR: YYYY PARAM reform is not a dictionary')
        if not reform:
            return  # no reform to implement
        reform_years = sorted(list(reform.keys()))
        for year in reform_years:
            if not isinstance(year, int):
                msg = 'ERROR: {} KEY {}'
                details = 'KEY in reform is not an integer calendar year'
                raise ValueError(msg.format(year, details))
        # check range of remaining reform_years
        first_reform_year = min(reform_years)
        if first_reform_year < self.start_year:
            msg = 'ERROR: {} YEAR reform provision in YEAR < start_year={}'
            raise ValueError(msg.format(first_reform_year, self.start_year))
        if first_reform_year < self.current_year:
            msg = 'ERROR: {} YEAR reform provision in YEAR < current_year={}'
            raise ValueError(msg.format(first_reform_year, self.current_year))
        last_reform_year = max(reform_years)
        if last_reform_year > self.end_year:
            msg = 'ERROR: {} YEAR reform provision in YEAR > end_year={}'
            raise ValueError(msg.format(last_reform_year, self.end_year))
        # validate reform parameter names and types
        self._validate_parameter_names_types(reform)
        if not self._ignore_errors and self.reform_errors:
            raise ValueError(self.reform_errors)
        # optionally apply cpi_offset to inflation_rates and re-initialize
        if Policy._cpi_offset_in_reform(reform):
            known_years = self._apply_reform_cpi_offset(reform)
            self.set_default_vals(known_years=known_years)
        # implement the reform year by year
        precall_current_year = self.current_year
        reform_parameters = set()
        for year in reform_years:
            self.set_year(year)
            reform_parameters.update(reform[year].keys())
            self._update({year: reform[year]})
        self.set_year(precall_current_year)
        # validate reform parameter values
        self._validate_parameter_values(reform_parameters)

    def current_law_version(self):
        """
        Return Policy object same as self except with current-law policy.
        """
        startyear = self.start_year
        numyears = self.num_years
        clv = Policy(self._gfactors,
                     parameter_dict=None,
                     start_year=startyear,
                     num_years=numyears)
        clv.set_year(self.current_year)
        return clv

    JSON_REFORM_SUFFIXES = {
        # MARS-indexed suffixes and list index numbers
        'single': 0,
        'joint': 1,
        'separate': 2,
        'headhousehold': 3,
        'widow': 4,
        # EIC-indexed suffixes and list index numbers
        '0kids': 0,
        '1kid': 1,
        '2kids': 2,
        '3+kids': 3,
        # idedtype-indexed suffixes and list index numbers
        'medical': 0,
        'statelocal': 1,
        'realestate': 2,
        'casualty': 3,
        'misc': 4,
        'interest': 5,
        'charity': 6
    }

    @staticmethod
    def translate_json_reform_suffixes(indict, growdiff_baseline_dict,
                                       growdiff_response_dict):
        """
        Replace any array parameters with suffixes in the specified
        JSON-derived "policy" dictionary, indict, and
        return a JSON-equivalent dictionary containing constructed array
        parameters and containing no parameters with suffixes, odict.
        """

        # define no_suffix function used only in this method
        def no_suffix(idict):
            """
            Return param_base:year dictionary having only no-suffix parameters.
            """
            odict = dict()
            suffixes = Policy.JSON_REFORM_SUFFIXES.keys()
            for param in idict.keys():
                param_pieces = param.split('_')
                suffix = param_pieces[-1]
                if suffix not in suffixes:
                    odict[param] = idict[param]
            return odict

        # define group_dict function used only in this method
        def suffix_group_dict(idict):
            """
            Return param_base:year:suffix dictionary with each idict value.
            """
            gdict = dict()
            suffixes = Policy.JSON_REFORM_SUFFIXES.keys()
            for param in idict.keys():
                param_pieces = param.split('_')
                suffix = param_pieces[-1]
                if suffix in suffixes:
                    del param_pieces[-1]
                    param_base = '_'.join(param_pieces)
                    if param_base not in gdict:
                        gdict[param_base] = dict()
                    for year in sorted(idict[param].keys()):
                        if year not in gdict[param_base]:
                            gdict[param_base][year] = dict()
                        gdict[param_base][year][suffix] = idict[param][year][0]
            return gdict

        # define with_suffix function used only in this method
        def with_suffix(gdict, growdiff_baseline_dict, growdiff_response_dict):
            """
            Return param_base:year dictionary having only suffix parameters.
            """
            if bool(growdiff_baseline_dict) or bool(growdiff_response_dict):
                gdiff_baseline = Growdiff()
                gdiff_baseline.update_growdiff(growdiff_baseline_dict)
                gdiff_response = Growdiff()
                gdiff_response.update_growdiff(growdiff_response_dict)
                growfactors = Growfactors()
                gdiff_baseline.apply_to(growfactors)
                gdiff_response.apply_to(growfactors)
            else:
                growfactors = None
            pol = Policy(gfactors=growfactors)
            pol.ignore_reform_errors()
            odict = dict()
            for param in gdict.keys():
                odict[param] = dict()
                for year in sorted(gdict[param].keys()):
                    odict[param][year] = dict()
                    for suffix in gdict[param][year].keys():
                        plist = getattr(pol, param).tolist()
                        dvals = plist[int(year) - Policy.JSON_START_YEAR]
                        odict[param][year] = [dvals]
                        idx = Policy.JSON_REFORM_SUFFIXES[suffix]
                        odict[param][year][0][idx] = gdict[param][year][suffix]
                        udict = {int(year): {param: odict[param][year]}}
                        pol.implement_reform(udict)
            return odict

        # high-level logic of translate_json_reform_suffixes method:
        # - construct odict containing just parameters without a suffix
        odict = no_suffix(indict)
        # - group params with suffix into param_base:year:suffix dictionary
        gdict = suffix_group_dict(indict)
        # - add to odict the consolidated values for parameters with a suffix
        if gdict:
            odict.update(
                with_suffix(gdict, growdiff_baseline_dict,
                            growdiff_response_dict))
        # - return policy dictionary containing constructed parameter arrays
        return odict

    def ignore_reform_errors(self):
        """
        Sets self._ignore_errors to True.
        """
        self._ignore_errors = True

    # ----- begin private methods of Policy class -----

    def _apply_clp_cpi_offset(self, cpi_offset_clp_data, num_years):
        """
        Call this method from Policy constructor
        after self._inflation_rates has been set and
        before base class initialize method is called.
        (num_years is number of years for which inflation rates are specified)
        """
        ovalues = cpi_offset_clp_data['value']
        if len(ovalues) < num_years:  # extrapolate last known value
            ovalues = ovalues + ovalues[-1:] * (num_years - len(ovalues))
        for idx in range(0, num_years):
            infrate = round(self._inflation_rates[idx] + ovalues[idx], 6)
            self._inflation_rates[idx] = infrate

    @staticmethod
    def _cpi_offset_in_reform(reform):
        """
        Return true if cpi_offset is in reform; otherwise return false.
        """
        for year in reform:
            for name in reform[year]:
                if name == '_cpi_offset':
                    return True
        return False

    def _apply_reform_cpi_offset(self, reform):
        """
        Call this method ONLY if _cpi_offset_in_reform returns True.
        Apply CPI offset to inflation rates and
        revert indexed parameter values in preparation for re-indexing.
        Also, return known_years which is
        (first cpi_offset year - start year + 1).
        """
        # extrapolate cpi_offset reform
        self.set_year(self.start_year)
        first_cpi_offset_year = 0
        for year in sorted(reform.keys()):
            self.set_year(year)
            if '_cpi_offset' in reform[year]:
                if first_cpi_offset_year == 0:
                    first_cpi_offset_year = year
                oreform = {'_cpi_offset': reform[year]['_cpi_offset']}
                self._update({year: oreform})
        self.set_year(self.start_year)
        assert first_cpi_offset_year > 0
        # adjust inflation rates
        cpi_offset = getattr(self, '_cpi_offset')
        for idx in range(0, self.num_years):
            infrate = round(self._inflation_rates[idx] + cpi_offset[idx], 6)
            self._inflation_rates[idx] = infrate
        # revert CPI-indexed parameter values to current_law_policy.json values
        for name in self._vals.keys():
            if self._vals[name]['cpi_inflated']:
                setattr(self, name, self._vals[name]['value'])
        # return known_years
        return first_cpi_offset_year - self.start_year + 1

    def _validate_parameter_names_types(self, reform):
        """
        Check validity of parameter names and parameter types used
        in the specified reform dictionary.
        """
        # pylint: disable=too-many-branches,too-many-nested-blocks
        data_names = set(self._vals.keys())
        for year in sorted(reform.keys()):
            for name in reform[year]:
                if name.endswith('_cpi'):
                    if isinstance(reform[year][name], bool):
                        pname = name[:-4]  # root parameter name
                        if pname not in data_names:
                            msg = '{} {} unknown parameter name'
                            self.reform_errors += ('ERROR: ' +
                                                   msg.format(year, name) +
                                                   '\n')
                        else:
                            # check if root parameter is cpi inflatable
                            if not self._vals[pname]['cpi_inflatable']:
                                msg = '{} {} parameter is not cpi inflatable'
                                self.reform_errors += (
                                    'ERROR: ' + msg.format(year, pname) + '\n')
                    else:
                        msg = '{} {} parameter is not true or false'
                        self.reform_errors += ('ERROR: ' +
                                               msg.format(year, name) + '\n')
                else:  # if name does not end with '_cpi'
                    if name not in data_names:
                        msg = '{} {} unknown parameter name'
                        self.reform_errors += ('ERROR: ' +
                                               msg.format(year, name) + '\n')
                    else:
                        # check parameter value type
                        bool_type = self._vals[name]['boolean_value']
                        int_type = self._vals[name]['integer_value']
                        assert isinstance(reform[year][name], list)
                        pvalue = reform[year][name][0]
                        if isinstance(pvalue, list):
                            scalar = False  # parameter value is a list
                        else:
                            scalar = True  # parameter value is a scalar
                            pvalue = [pvalue]  # make scalar a single-item list
                        for idx in range(0, len(pvalue)):
                            if scalar:
                                pname = name
                            else:
                                pname = '{}_{}'.format(name, idx)
                            pvalue_boolean = (
                                isinstance(pvalue[idx], bool)
                                or (isinstance(pvalue[idx], int) and
                                    (pvalue[idx] == 0 or pvalue[idx] == 1)) or
                                (isinstance(pvalue[idx], float) and
                                 (pvalue[idx] == 0.0 or pvalue[idx] == 1.0)))
                            if bool_type:
                                if not pvalue_boolean:
                                    msg = '{} {} value {} is not boolean'
                                    self.reform_errors += (
                                        'ERROR: ' +
                                        msg.format(year, pname, pvalue[idx]) +
                                        '\n')
                            elif int_type:
                                if not isinstance(pvalue[idx], int):
                                    msg = '{} {} value {} is not integer'
                                    self.reform_errors += (
                                        'ERROR: ' +
                                        msg.format(year, pname, pvalue[idx]) +
                                        '\n')
                            else:  # param is neither bool_type nor int_type
                                if not isinstance(pvalue[idx], (float, int)):
                                    msg = '{} {} value {} is not a number'
                                    self.reform_errors += (
                                        'ERROR: ' +
                                        msg.format(year, pname, pvalue[idx]) +
                                        '\n')

    def _validate_parameter_values(self, parameters_set):
        """
        Check values of parameters in specified parameter_set using
        range information from the current_law_policy.json file.
        """
        # pylint: disable=too-many-locals
        # pylint: disable=too-many-branches
        # pylint: disable=too-many-nested-blocks
        rounding_error = 100.0
        # above handles non-rounding of inflation-indexed parameter values
        clp = self.current_law_version()
        parameters = sorted(parameters_set)
        syr = Policy.JSON_START_YEAR
        for pname in parameters:
            if pname.endswith('_cpi'):
                continue  # *_cpi parameter values validated elsewhere
            pvalue = getattr(self, pname)
            for vop, vval in self._vals[pname]['range'].items():
                if isinstance(vval, six.string_types):
                    if vval == 'default':
                        vvalue = getattr(clp, pname)
                        if vop == 'min':
                            vvalue -= rounding_error
                        # the follow branch can never be reached, so it
                        # is commented out because it can never be tested
                        # (see test_range_infomation in test_policy.py)
                        # --> elif vop == 'max':
                        # -->    vvalue += rounding_error
                    else:
                        vvalue = getattr(self, vval)
                else:
                    vvalue = np.full(pvalue.shape, vval)
                assert pvalue.shape == vvalue.shape
                assert len(pvalue.shape) <= 2
                if len(pvalue.shape) == 2:
                    scalar = False  # parameter value is a list
                else:
                    scalar = True  # parameter value is a scalar
                for idx in np.ndindex(pvalue.shape):
                    out_of_range = False
                    if vop == 'min' and pvalue[idx] < vvalue[idx]:
                        out_of_range = True
                        msg = '{} {} value {} < min value {}'
                        extra = self._vals[pname]['out_of_range_minmsg']
                        if extra:
                            msg += ' {}'.format(extra)
                    if vop == 'max' and pvalue[idx] > vvalue[idx]:
                        out_of_range = True
                        msg = '{} {} value {} > max value {}'
                        extra = self._vals[pname]['out_of_range_maxmsg']
                        if extra:
                            msg += ' {}'.format(extra)
                    if out_of_range:
                        action = self._vals[pname]['out_of_range_action']
                        if scalar:
                            name = pname
                        else:
                            name = '{}_{}'.format(pname, idx[1])
                            if extra:
                                msg += '_{}'.format(idx[1])
                        if action == 'warn':
                            self.reform_warnings += ('WARNING: ' + msg.format(
                                idx[0] + syr, name, pvalue[idx], vvalue[idx]) +
                                                     '\n')
                        if action == 'stop':
                            self.reform_errors += ('ERROR: ' + msg.format(
                                idx[0] + syr, name, pvalue[idx], vvalue[idx]) +
                                                   '\n')
Exemple #6
0
 def __init__(self,
              data='puf.csv',
              exact_calculations=False,
              gfactors=Growfactors(),
              weights=WEIGHTS_PATH,
              adjust_ratios=ADJUST_RATIOS_PATH,
              start_year=PUFCSV_YEAR):
     # pylint: disable=too-many-arguments
     # read specified data
     self._read_data(data, exact_calculations)
     # check that three sets of split-earnings variables have valid values
     msg = 'expression "{0} == {0}p + {0}s" is not true for every record'
     tol = 0.020001  # handles "%.2f" rounding errors
     if not np.allclose(self.e00200, (self.e00200p + self.e00200s),
                        rtol=0.0, atol=tol):
         raise ValueError(msg.format('e00200'))
     if not np.allclose(self.e00900, (self.e00900p + self.e00900s),
                        rtol=0.0, atol=tol):
         raise ValueError(msg.format('e00900'))
     if not np.allclose(self.e02100, (self.e02100p + self.e02100s),
                        rtol=0.0, atol=tol):
         raise ValueError(msg.format('e02100'))
     # check that ordinary dividends are no less than qualified dividends
     other_dividends = np.maximum(0., self.e00600 - self.e00650)
     if not np.allclose(self.e00600, self.e00650 + other_dividends,
                        rtol=0.0, atol=tol):
         msg = 'expression "e00600 >= e00650" is not true for every record'
         raise ValueError(msg)
     # handle grow factors
     is_correct_type = isinstance(gfactors, Growfactors)
     if gfactors is not None and not is_correct_type:
         msg = 'gfactors is neither None nor a Growfactors instance'
         raise ValueError(msg)
     self.gfactors = gfactors
     # read sample weights
     self.WT = None
     self._read_weights(weights)
     self.ADJ = None
     self._read_adjust(adjust_ratios)
     # weights must be same size as tax record data
     if not self.WT.empty and self.dim != len(self.WT):
         # scale-up sub-sample weights by year-specific factor
         sum_full_weights = self.WT.sum()
         self.WT = self.WT.iloc[self.index]
         sum_sub_weights = self.WT.sum()
         factor = sum_full_weights / sum_sub_weights
         self.WT = self.WT * factor
     # specify current_year and FLPDYR values
     if isinstance(start_year, int):
         self._current_year = start_year
         self.FLPDYR.fill(start_year)
     else:
         msg = 'start_year is not an integer'
         raise ValueError(msg)
     # consider applying initial-year grow factors
     if gfactors is not None and start_year == Records.PUF_YEAR:
         self._blowup(start_year)
     # construct sample weights for current_year
     wt_colname = 'WT{}'.format(self.current_year)
     if wt_colname in self.WT.columns:
         self.s006 = self.WT[wt_colname] * 0.01
    def init(self, input_data, tax_year, reform, assump, growdiff_response,
             aging_input_data, exact_calculations):
        """
        TaxCalcIO class post-constructor method that completes initialization.

        Parameters
        ----------
        First four parameters are same as for TaxCalcIO constructor:
            input_data, tax_year, reform, assump.

        growdiff_response: Growdiff object or None
            growdiff_response Growdiff object is used only by the
            TaxCalcIO.growmodel_analysis method;
            must be None in all other cases.

        aging_input_data: boolean
            whether or not to extrapolate Records data from data year to
            tax_year.

        exact_calculations: boolean
            specifies whether or not exact tax calculations are done without
            any smoothing of "stair-step" provisions in the tax law.
        """
        # pylint: disable=too-many-arguments,too-many-locals
        # pylint: disable=too-many-statements,too-many-branches
        self.errmsg = ''
        # get parameter dictionaries from --reform and --assump files
        paramdict = Calculator.read_json_param_objects(reform, assump)
        # create Behavior object
        beh = Behavior()
        beh.update_behavior(paramdict['behavior'])
        self.behavior_has_any_response = beh.has_any_response()
        # create gdiff_baseline object
        gdiff_baseline = Growdiff()
        gdiff_baseline.update_growdiff(paramdict['growdiff_baseline'])
        # create Growfactors clp object that incorporates gdiff_baseline
        gfactors_clp = Growfactors()
        gdiff_baseline.apply_to(gfactors_clp)
        # specify gdiff_response object
        if growdiff_response is None:
            gdiff_response = Growdiff()
            gdiff_response.update_growdiff(paramdict['growdiff_response'])
        elif isinstance(growdiff_response, Growdiff):
            gdiff_response = growdiff_response
        else:
            gdiff_response = None
            msg = 'TaxCalcIO.more_init: growdiff_response is neither None '
            msg += 'nor a Growdiff object'
            self.errmsg += 'ERROR: {}\n'.format(msg)
        if gdiff_response is not None:
            some_gdiff_response = gdiff_response.has_any_response()
            if self.behavior_has_any_response and some_gdiff_response:
                msg = 'ASSUMP file cannot specify any "behavior" when using '
                msg += 'GrowModel or when ASSUMP file has "growdiff_response"'
                self.errmsg += 'ERROR: {}\n'.format(msg)
        # create Growfactors ref object that has both gdiff objects applied
        gfactors_ref = Growfactors()
        gdiff_baseline.apply_to(gfactors_ref)
        if gdiff_response is not None:
            gdiff_response.apply_to(gfactors_ref)
        # create Policy objects
        if self.specified_reform:
            pol = Policy(gfactors=gfactors_ref)
            try:
                pol.implement_reform(paramdict['policy'])
                self.errmsg += pol.reform_errors
            except ValueError as valerr_msg:
                self.errmsg += valerr_msg.__str__()
        else:
            pol = Policy(gfactors=gfactors_clp)
        clp = Policy(gfactors=gfactors_clp)
        # check for valid tax_year value
        if tax_year < pol.start_year:
            msg = 'tax_year {} less than policy.start_year {}'
            msg = msg.format(tax_year, pol.start_year)
            self.errmsg += 'ERROR: {}\n'.format(msg)
        if tax_year > pol.end_year:
            msg = 'tax_year {} greater than policy.end_year {}'
            msg = msg.format(tax_year, pol.end_year)
            self.errmsg += 'ERROR: {}\n'.format(msg)
        # any errors imply cannot proceed with calculations
        if self.errmsg:
            return
        # set policy to tax_year
        pol.set_year(tax_year)
        clp.set_year(tax_year)
        # read input file contents into Records objects
        if aging_input_data:
            if self.cps_input_data:
                recs = Records.cps_constructor(
                    gfactors=gfactors_ref,
                    exact_calculations=exact_calculations)
                recs_clp = Records.cps_constructor(
                    gfactors=gfactors_clp,
                    exact_calculations=exact_calculations)
            else:  # if not cps_input_data
                recs = Records(data=input_data,
                               gfactors=gfactors_ref,
                               exact_calculations=exact_calculations)
                recs_clp = Records(data=input_data,
                                   gfactors=gfactors_clp,
                                   exact_calculations=exact_calculations)
        else:  # input_data are raw data that are not being aged
            recs = Records(data=input_data,
                           gfactors=None,
                           exact_calculations=exact_calculations,
                           weights=None,
                           adjust_ratios=None,
                           start_year=tax_year)
            recs_clp = copy.deepcopy(recs)
        if tax_year < recs.data_year:
            msg = 'tax_year {} less than records.data_year {}'
            msg = msg.format(tax_year, recs.data_year)
            self.errmsg += 'ERROR: {}\n'.format(msg)
        # create Calculator objects
        con = Consumption()
        con.update_consumption(paramdict['consumption'])
        self.calc = Calculator(policy=pol,
                               records=recs,
                               verbose=True,
                               consumption=con,
                               behavior=beh,
                               sync_years=aging_input_data)
        self.calc_clp = Calculator(policy=clp,
                                   records=recs_clp,
                                   verbose=False,
                                   consumption=con,
                                   sync_years=aging_input_data)
        # remember parameter dictionary for reform documentation
        self.param_dict = paramdict
Exemple #8
0
 def __init__(self,
              data='puf.csv',
              exact_calculations=False,
              gfactors=Growfactors(),
              weights=PUF_WEIGHTS_FILENAME,
              adjust_ratios=PUF_RATIOS_FILENAME,
              benefits=None,
              start_year=PUFCSV_YEAR):
     # pylint: disable=too-many-arguments,too-many-locals
     self.__data_year = start_year
     # read specified data
     self._read_data(data, exact_calculations)
     # check that three sets of split-earnings variables have valid values
     msg = 'expression "{0} == {0}p + {0}s" is not true for every record'
     tol = 0.020001  # handles "%.2f" rounding errors
     if not np.allclose(
             self.e00200,
         (self.e00200p + self.e00200s), rtol=0.0, atol=tol):
         raise ValueError(msg.format('e00200'))
     if not np.allclose(
             self.e00900,
         (self.e00900p + self.e00900s), rtol=0.0, atol=tol):
         raise ValueError(msg.format('e00900'))
     if not np.allclose(
             self.e02100,
         (self.e02100p + self.e02100s), rtol=0.0, atol=tol):
         raise ValueError(msg.format('e02100'))
     # check that ordinary dividends are no less than qualified dividends
     other_dividends = np.maximum(0., self.e00600 - self.e00650)
     if not np.allclose(
             self.e00600, self.e00650 + other_dividends, rtol=0.0,
             atol=tol):
         msg = 'expression "e00600 >= e00650" is not true for every record'
         raise ValueError(msg)
     del other_dividends
     # check that total pension income is no less than taxable pension inc
     nontaxable_pensions = np.maximum(0., self.e01500 - self.e01700)
     if not np.allclose(
             self.e01500, self.e01700 + nontaxable_pensions, rtol=0.0,
             atol=tol):
         msg = 'expression "e01500 >= e01700" is not true for every record'
         raise ValueError(msg)
     del nontaxable_pensions
     # handle grow factors
     is_correct_type = isinstance(gfactors, Growfactors)
     if gfactors is not None and not is_correct_type:
         msg = 'gfactors is neither None nor a Growfactors instance'
         raise ValueError(msg)
     self.gfactors = gfactors
     # read sample weights
     self.WT = None
     self._read_weights(weights)
     self.ADJ = None
     self._read_ratios(adjust_ratios)
     # read extrapolated benefit variables
     self.BEN = None
     self._read_benefits(benefits)
     # weights must be same size as tax record data
     if not self.WT.empty and self.array_length != len(self.WT):
         # scale-up sub-sample weights by year-specific factor
         sum_full_weights = self.WT.sum()
         self.WT = self.WT.iloc[self.__index]
         sum_sub_weights = self.WT.sum()
         factor = sum_full_weights / sum_sub_weights
         self.WT *= factor
     # specify current_year and FLPDYR values
     if isinstance(start_year, int):
         self.__current_year = start_year
         self.FLPDYR.fill(start_year)
     else:
         msg = 'start_year is not an integer'
         raise ValueError(msg)
     # construct sample weights for current_year
     wt_colname = 'WT{}'.format(self.current_year)
     if wt_colname in self.WT.columns:
         self.s006 = self.WT[wt_colname] * 0.01
     # specify that variable values do not include behavioral responses
     self.behavioral_responses_are_included = False
Exemple #9
0
    def reform_documentation(params):
        """
        Generate reform documentation.

        Parameters
        ----------
        params: dict
            compound dictionary structured as dict returned from
            the static Calculator method read_json_param_objects()

        Returns
        -------
        doc: String
            the documentation for the policy reform specified in params
        """

        # pylint: disable=too-many-statements,too-many-branches

        # nested function used only in reform_documentation
        def param_doc(years, change, base):
            """
            Parameters
            ----------
            years: list of change years
            change: dictionary of parameter changes
            base: Policy or Growdiff object with baseline values
            syear: parameter start calendar year

            Returns
            -------
            doc: String
            """

            # nested function used only in param_doc
            def lines(text, num_indent_spaces, max_line_length=77):
                """
                Return list of text lines, each one of which is no longer
                than max_line_length, with the second and subsequent lines
                being indented by the number of specified num_indent_spaces;
                each line in the list ends with the '\n' character
                """
                if len(text) < max_line_length:
                    # all text fits on one line
                    line = text + '\n'
                    return [line]
                # all text does not fix on one line
                first_line = True
                line_list = list()
                words = text.split()
                while words:
                    if first_line:
                        line = ''
                        first_line = False
                    else:
                        line = ' ' * num_indent_spaces
                    while (words
                           and (len(words[0]) + len(line)) < max_line_length):
                        line += words.pop(0) + ' '
                    line = line[:-1] + '\n'
                    line_list.append(line)
                return line_list

            # begin main logic of param_doc
            # pylint: disable=too-many-nested-blocks
            assert len(years) == len(change.keys())
            basevals = getattr(base, '_vals', None)
            assert isinstance(basevals, dict)
            doc = ''
            for year in years:
                # write year
                base.set_year(year)
                doc += '{}:\n'.format(year)
                # write info for each param in year
                for param in sorted(change[year].keys()):
                    # ... write param:value line
                    pval = change[year][param]
                    if isinstance(pval, list):
                        pval = pval[0]
                        if basevals[param]['boolean_value']:
                            if isinstance(pval, list):
                                pval = [
                                    True if item else False for item in pval
                                ]
                            else:
                                pval = bool(pval)
                    doc += ' {} : {}\n'.format(param, pval)
                    # ... write optional param-index line
                    if isinstance(pval, list):
                        pval = basevals[param]['col_label']
                        pval = [str(item) for item in pval]
                        doc += ' ' * (4 + len(param)) + '{}\n'.format(pval)
                    # ... write name line
                    if param.endswith('_cpi'):
                        rootparam = param[:-4]
                        name = '{} inflation indexing status'.format(rootparam)
                    else:
                        name = basevals[param]['long_name']
                    for line in lines('name: ' + name, 6):
                        doc += '  ' + line
                    # ... write optional desc line
                    if not param.endswith('_cpi'):
                        desc = basevals[param]['description']
                        for line in lines('desc: ' + desc, 6):
                            doc += '  ' + line
                    # ... write baseline_value line
                    if isinstance(base, Policy):
                        if param.endswith('_cpi'):
                            rootparam = param[:-4]
                            bval = basevals[rootparam].get(
                                'cpi_inflated', False)
                        else:
                            bval = getattr(base, param[1:], None)
                            if isinstance(bval, np.ndarray):
                                # pylint: disable=no-member
                                bval = bval.tolist()
                                if basevals[param]['boolean_value']:
                                    bval = [
                                        True if item else False
                                        for item in bval
                                    ]
                            elif basevals[param]['boolean_value']:
                                bval = bool(bval)
                        doc += '  baseline_value: {}\n'.format(bval)
                    else:  # if base is Growdiff object
                        # all Growdiff parameters have zero as default value
                        doc += '  baseline_value: 0.0\n'
            return doc

        # begin main logic of reform_documentation
        # create Policy object with pre-reform (i.e., baseline) values
        # ... create gdiff_baseline object
        gdb = Growdiff()
        gdb.update_growdiff(params['growdiff_baseline'])
        # ... create Growfactors clp object that incorporates gdiff_baseline
        gfactors_clp = Growfactors()
        gdb.apply_to(gfactors_clp)
        # ... create Policy object containing pre-reform parameter values
        clp = Policy(gfactors=gfactors_clp)
        # generate documentation text
        doc = 'REFORM DOCUMENTATION\n'
        doc += 'Baseline Growth-Difference Assumption Values by Year:\n'
        years = sorted(params['growdiff_baseline'].keys())
        if years:
            doc += param_doc(years, params['growdiff_baseline'], gdb)
        else:
            doc += 'none: using default baseline growth assumptions\n'
        doc += 'Policy Reform Parameter Values by Year:\n'
        years = sorted(params['policy'].keys())
        if years:
            doc += param_doc(years, params['policy'], clp)
        else:
            doc += 'none: using current-law policy parameters\n'
        return doc
Exemple #10
0
 def __init__(
         self,
         input_data,
         tax_year,
         reform,
         assump,
         growdiff_response,  # =None in static analysis
         aging_input_data,
         exact_calculations):
     """
     TaxCalcIO class constructor.
     """
     # pylint: disable=too-many-arguments
     # pylint: disable=too-many-locals
     # pylint: disable=too-many-branches
     # pylint: disable=too-many-statements
     # check for existence of INPUT file
     if isinstance(input_data, six.string_types):
         # remove any leading directory path from INPUT filename
         fname = os.path.basename(input_data)
         # check if fname ends with ".csv"
         if fname.endswith('.csv'):
             inp = '{}-{}'.format(fname[:-4], str(tax_year)[2:])
         else:
             msg = 'INPUT file named {} does not end in .csv'
             raise ValueError(msg.format(fname))
         # check existence of INPUT file
         if not os.path.isfile(input_data):
             msg = 'INPUT file named {} could not be found'
             raise ValueError(msg.format(input_data))
     elif isinstance(input_data, pd.DataFrame):
         inp = 'df-{}'.format(str(tax_year)[2:])
     else:
         msg = 'INPUT is neither string nor Pandas DataFrame'
         raise ValueError(msg)
     # construct output_filename and delete old output file if it exists
     if reform is None:
         self._reform = False
         ref = ''
     elif isinstance(reform, six.string_types):
         self._reform = True
         # remove any leading directory path from REFORM filename
         fname = os.path.basename(reform)
         # check if fname ends with ".json"
         if fname.endswith('.json'):
             ref = '-{}'.format(fname[:-5])
         else:
             msg = 'REFORM file named {} does not end in .json'
             raise ValueError(msg.format(fname))
     else:
         msg = 'TaxCalcIO.ctor reform is neither None nor str'
         raise ValueError(msg)
     if assump is None:
         asm = ''
     elif isinstance(assump, six.string_types):
         # remove any leading directory path from ASSUMP filename
         fname = os.path.basename(assump)
         # check if fname ends with ".json"
         if fname.endswith('.json'):
             asm = '-{}'.format(fname[:-5])
         else:
             msg = 'ASSUMP file named {} does not end in .json'
             raise ValueError(msg.format(fname))
     else:
         msg = 'TaxCalcIO.ctor assump is neither None nor str'
         raise ValueError(msg)
     self._output_filename = '{}{}{}.csv'.format(inp, ref, asm)
     delete_file(self._output_filename)
     # get parameter dictionaries from --reform and --assump files
     param_dict = Calculator.read_json_param_files(reform, assump)
     # make sure no behavioral response is specified in --assump
     beh = Behavior()
     beh.update_behavior(param_dict['behavior'])
     if beh.has_any_response():
         msg = '--assump ASSUMP cannot assume any "behavior"'
         raise ValueError(msg)
     # make sure no growdiff_response is specified in --assump
     gdiff_response = Growdiff()
     gdiff_response.update_growdiff(param_dict['growdiff_response'])
     if gdiff_response.has_any_response():
         msg = '--assump ASSUMP cannot assume any "growdiff_response"'
         raise ValueError(msg)
     # create gdiff_baseline object
     gdiff_baseline = Growdiff()
     gdiff_baseline.update_growdiff(param_dict['growdiff_baseline'])
     # create Growfactors clp object that incorporates gdiff_baseline
     gfactors_clp = Growfactors()
     gdiff_baseline.apply_to(gfactors_clp)
     # specify gdiff_response object
     if growdiff_response is None:
         gdiff_response = Growdiff()
     elif isinstance(growdiff_response, Growdiff):
         gdiff_response = growdiff_response
     else:
         msg = 'TaxCalcIO.ctor growdiff_response is neither None nor {}'
         raise ValueError(msg.format('a Growdiff object'))
     # create Growfactors ref object that has both gdiff objects applied
     gfactors_ref = Growfactors()
     gdiff_baseline.apply_to(gfactors_ref)
     gdiff_response.apply_to(gfactors_ref)
     # create Policy object and implement reform if specified
     if self._reform:
         pol = Policy(gfactors=gfactors_ref)
         pol.implement_reform(param_dict['policy'])
         clp = Policy(gfactors=gfactors_clp)
     else:
         pol = Policy(gfactors=gfactors_clp)
     # check for valid tax_year value
     if tax_year < pol.start_year:
         msg = 'tax_year {} less than policy.start_year {}'
         raise ValueError(msg.format(tax_year, pol.start_year))
     if tax_year > pol.end_year:
         msg = 'tax_year {} greater than policy.end_year {}'
         raise ValueError(msg.format(tax_year, pol.end_year))
     # set policy to tax_year
     pol.set_year(tax_year)
     if self._reform:
         clp.set_year(tax_year)
     # read input file contents into Records object(s)
     if aging_input_data:
         if self._reform:
             recs = Records(data=input_data,
                            gfactors=gfactors_ref,
                            exact_calculations=exact_calculations)
             recs_clp = Records(data=input_data,
                                gfactors=gfactors_clp,
                                exact_calculations=exact_calculations)
         else:
             recs = Records(data=input_data,
                            gfactors=gfactors_clp,
                            exact_calculations=exact_calculations)
     else:  # input_data are raw data that are not being aged
         recs = Records(data=input_data,
                        exact_calculations=exact_calculations,
                        gfactors=None,
                        adjust_ratios=None,
                        weights=None,
                        start_year=tax_year)
         if self._reform:
             recs_clp = copy.deepcopy(recs)
     # create Calculator object(s)
     con = Consumption()
     con.update_consumption(param_dict['consumption'])
     self._calc = Calculator(policy=pol,
                             records=recs,
                             verbose=True,
                             consumption=con,
                             sync_years=aging_input_data)
     if self._reform:
         self._calc_clp = Calculator(policy=clp,
                                     records=recs_clp,
                                     verbose=False,
                                     consumption=con,
                                     sync_years=aging_input_data)