Source code for bca_tool_code.general_modules.annual_summary

import pandas as pd


[docs]class AnnualSummary: """ The AnnualSummary class creates a summary of results by year_id along with present values and annualized values for use in the benefit-cost analysis. """
[docs] def __init__(self): self.results = dict()
[docs] def annual_summary(self, settings, data_object, options, year_ids): """ Parameters: settings: object; the SetInputs class object.\n data_object: object; the fleet data object to summarize.\n options: object; the options object associated with the data_object.\n year_ids: range; the min_year_id thru max_year_id as set in settings. Returns: Updates the annual summary dictionary with annual, present and annualized values based on the data_object. """ print(f'\nCalculating Annual Values, Present Values and Annualized Values...') source_dict = data_object.results num_option_ids = len(options._dict) costs_start = settings.general_inputs.get_attribute_value('costs_start') discount_to_year = pd.to_numeric(settings.general_inputs.get_attribute_value('discount_to_yearID')) discount_offset = 0 annualized_offset = 1 if costs_start == 'end-year': discount_offset = 1 annualized_offset = 0 # get cost attributes but only totals, per vehicle or mile costs are not relevant here nested_dict = [n_dict for key, n_dict in source_dict.items()][0] all_costs = tuple([k for k, v in nested_dict.items() if 'Cost' in k and 'Per' not in k]) emission_cost_args_25 = tuple([item for item in all_costs if '_0.025' in item]) emission_cost_args_3 = tuple([item for item in all_costs if '_0.03' in item]) emission_cost_args_5 = tuple([item for item in all_costs if '_0.05' in item]) emission_cost_args_7 = tuple([item for item in all_costs if '_0.07' in item]) non_emission_cost_args = tuple([item for item in all_costs if '_0.0' not in item]) social_rates = tuple([settings.general_inputs.get_attribute_value('social_discount_rate_1'), settings.general_inputs.get_attribute_value('social_discount_rate_2')]) social_rates = tuple([pd.to_numeric(rate) for rate in social_rates]) # build the destination dictionary to house data # first undiscounted annual values for option_id in range(0, num_option_ids): rate = 0 series = 'AnnualValue' for year_id in year_ids: self.results.update({ (series, option_id, year_id, rate): { 'optionID': option_id, 'optionName': options.get_option_name(option_id), 'yearID': year_id, 'DiscountRate': rate, 'Series': series, 'Periods': 1, } } ) # then for discounted values for series in ('AnnualValue', 'PresentValue', 'AnnualizedValue'): for option_id in range(0, num_option_ids): for social_rate in social_rates: for year_id in year_ids: self.results.update({ (series, option_id, year_id, social_rate): { 'optionID': option_id, 'optionName': options.get_option_name(option_id), 'yearID': year_id, 'DiscountRate': social_rate, 'Series': series, 'Periods': 1, } } ) # first sum by year for each cost arg series = 'AnnualValue' for option_id in range(0, num_option_ids): for social_rate in (0, *social_rates): for year_id in year_ids: temp_dict = {k: v for k, v in source_dict.items() if v['yearID'] == year_id and v['DiscountRate'] == social_rate and v['optionID'] == option_id} for arg in all_costs: arg_sum = sum(v[arg] for k, v in temp_dict.items()) self.results[(series, option_id, year_id, social_rate)][arg] = arg_sum # now do a cumulative sum year-over-year for each cost arg - these will be present values # (note change to destination_dict in arg_value calc and removal of rate=0) series = 'PresentValue' for option_id in range(0, num_option_ids): for social_rate in social_rates: for arg in all_costs: for year_id in year_ids: periods = year_id - discount_to_year + discount_offset if (series, option_id, year_id - 1, social_rate) not in self.results: arg_value = self.results[('AnnualValue', option_id, year_id, social_rate)][arg] else: arg_value = self.results[(series, option_id, year_id - 1, social_rate)][arg] arg_value += self.results[('AnnualValue', option_id, year_id, social_rate)][arg] self.results[(series, option_id, year_id, social_rate)][arg] = arg_value self.results[(series, option_id, year_id, social_rate)]['Periods'] = periods # now annualize those present values series = 'AnnualizedValue' for option_id in range(0, num_option_ids): for social_rate in social_rates: for arg in non_emission_cost_args: for year_id in year_ids: periods = year_id - discount_to_year + discount_offset present_value = self.results['PresentValue', option_id, year_id, social_rate][arg] arg_annualized = self.calc_annualized_value(present_value, social_rate, periods, annualized_offset) self.results[(series, option_id, year_id, social_rate)][arg] = arg_annualized self.results[(series, option_id, year_id, social_rate)]['Periods'] = periods emission_rate = 0.025 for arg in emission_cost_args_25: for year_id in year_ids: periods = year_id - discount_to_year + discount_offset present_value = self.results['PresentValue', option_id, year_id, social_rate][arg] arg_annualized = self.calc_annualized_value(present_value, emission_rate, periods, annualized_offset) self.results[(series, option_id, year_id, social_rate)][arg] = arg_annualized emission_rate = 0.03 for arg in emission_cost_args_3: for year_id in year_ids: periods = year_id - discount_to_year + discount_offset present_value = self.results['PresentValue', option_id, year_id, social_rate][arg] arg_annualized = self.calc_annualized_value(present_value, emission_rate, periods, annualized_offset) self.results[(series, option_id, year_id, social_rate)][arg] = arg_annualized emission_rate = 0.05 for arg in emission_cost_args_5: for year_id in year_ids: periods = year_id - discount_to_year + discount_offset present_value = self.results['PresentValue', option_id, year_id, social_rate][arg] arg_annualized = self.calc_annualized_value(present_value, emission_rate, periods, annualized_offset) self.results[(series, option_id, year_id, social_rate)][arg] = arg_annualized emission_rate = 0.07 for arg in emission_cost_args_7: for year_id in year_ids: periods = year_id - discount_to_year + discount_offset present_value = self.results['PresentValue', option_id, year_id, social_rate][arg] arg_annualized = self.calc_annualized_value(present_value, emission_rate, periods, annualized_offset) self.results[(series, option_id, year_id, social_rate)][arg] = arg_annualized
[docs] def get_attribute_value(self, key, attribute_name): """ Parameters: key: tuple; (series, option_id, year_id, rate), where series is 'AnnualValue', 'PresentValue' or 'AnnualizedValue'.\n attribute_name: str; the attribute for which the value is sought. Returns: The value associated with the attribute for the given key. """ return self.results[key][attribute_name]
[docs] def update_object_dict(self, key, update_dict): """ Parameters: key: tuple; ((vehicle_id), option_id, modelyear_id, age_id, discount_rate).\n update_dict: Dictionary; represents the attribute-value pairs to be updated. Returns: Updates the object dictionary with each attribute updated with the appropriate value. """ if key in self.results: for attribute_name, attribute_value in update_dict.items(): self.results[key][attribute_name] = attribute_value else: self.results.update({key: {}}) for attribute_name, attribute_value in update_dict.items(): self.results[key].update({attribute_name: attribute_value})
[docs] @staticmethod def calc_annualized_value(present_value, rate, periods, annualized_offset): """ Parameters: present_value: Numeric; the present value to be annualized.\n rate: Numeric; the discount rate to use.\n periods: int; the number of periods over which to annualize present_value.\n annualized_offset: int; 0 or 1 reflecting whether costs are assumed to occur at the start of the year or the end of the year. Returns: A single annualized value of present_value discounted at rate over periods number of year_ids. """ return present_value * rate * (1 + rate) ** periods \ / ((1 + rate) ** (periods + annualized_offset) - 1)