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
def growmodel_analysis(input_data, tax_year, baseline, reform, assump, aging_input_data, exact_calculations, writing_output_file=False, output_tables=False, output_graphs=False, output_ceeu=False, dump_varset=None, output_dump=False, output_sqldb=False): """ High-level logic for dynamic analysis using GrowModel class. Parameters ---------- First five parameters are same as the first five parameters of the TaxCalcIO.init method. Last seven parameters are same as the first seven parameters of the TaxCalcIO.analyze method. Returns ------- Nothing """ # pylint: disable=too-many-arguments,too-many-locals progress = 'STARTING ANALYSIS FOR YEAR {}' gdiff_dict = {Policy.JSON_START_YEAR: {}} for year in range(Policy.JSON_START_YEAR, tax_year + 1): print(progress.format(year)) # pylint: disable=superfluous-parens # specify growdiff_response using gdiff_dict growdiff_response = Growdiff() growdiff_response.update_growdiff(gdiff_dict) gd_dict = TaxCalcIO.annual_analysis(input_data, tax_year, baseline, reform, assump, aging_input_data, exact_calculations, growdiff_response, year, writing_output_file, output_tables, output_graphs, output_ceeu, dump_varset, output_dump, output_sqldb) gdiff_dict[year + 1] = gd_dict
def dynamic_analysis(input_data, tax_year, reform, assump, aging_input_data, exact_calculations, writing_output_file=False, output_graph=False, output_ceeu=False, output_dump=False): """ High-level logic for dyanamic tax analysis. Parameters ---------- First six parameters are same as the first six parameters of the TaxCalcIO constructor. Last four parameters are same as the first four parameters of the TaxCalcIO static_analysis method. Returns ------- Nothing """ # pylint: disable=too-many-arguments # pylint: disable=superfluous-parens progress = 'STARTING ANALYSIS FOR YEAR {}' gdiff_dict = {Policy.JSON_START_YEAR: {}} for year in range(Policy.JSON_START_YEAR, tax_year + 1): print(progress.format(year)) # specify growdiff_response using gdiff_dict growdiff_response = Growdiff() growdiff_response.update_growdiff(gdiff_dict) gd_dict = TaxCalcIO.annual_analysis( input_data, tax_year, reform, assump, aging_input_data, exact_calculations, growdiff_response, year, writing_output_file, output_graph, output_ceeu, output_dump) gdiff_dict[year + 1] = gd_dict
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
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
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)