From acde47a58f390c5960c17d15b97df04ee984dcb1 Mon Sep 17 00:00:00 2001 From: samgdotson Date: Wed, 13 Mar 2024 16:03:55 -0500 Subject: [PATCH] major update to ATBe class --- nrelpy/atb.py | 274 ++++++++++++++++++++++++++------------------------ 1 file changed, 141 insertions(+), 133 deletions(-) diff --git a/nrelpy/atb.py b/nrelpy/atb.py index 5b0cd1d..b8c3ea7 100644 --- a/nrelpy/atb.py +++ b/nrelpy/atb.py @@ -3,6 +3,8 @@ import numpy as np from nrelpy.utils.data_io import check_stored_data, save_local +pd.set_option('display.max_columns', None) + def as_dataframe(year, database, verbose=False, **kwargs): """ @@ -11,11 +13,11 @@ def as_dataframe(year, database, verbose=False, **kwargs): Parameters ---------- year : int - The ATB year - * ATB Electricity (ATBe) accepts: [2019,2022] -- inclusive + The ATB year * ATB Electricity (ATBe) accepts: [2019,2023] -- inclusive * ATB Transportation (ATBt) accepts: [2020] database : string The desired ATB dataset. Accepts: 'electricity', 'transportation'. + Default is `electricity`. Returns ------- @@ -62,17 +64,12 @@ def as_dataframe(year, database, verbose=False, **kwargs): class ATBe(object): """ - A class that allows simplified access to the various - cases and data values. + A class that allows simplified access to the various cases and data values. """ def __init__( self, year, - database='electricity', - case='R&D', - scenario='Moderate', - crp='20', **kwargs) -> None: """ Initializes the ATB class. @@ -81,138 +78,149 @@ def __init__( ---------- year : int Specifies the ATB year - database : string - Specifies which ATB database. - Accepts ['electricity', 'transportation'] - Default is 'electricity'. - case : string - Specifies the technology case. - Accepts ['Market', 'R&D'] - scenario : string - Specifies the technology scenario. - Accepts ['Conservative', 'Moderate', 'Advanced'] - crp : string or int - `crp` stands for "cost recovery period." This parameter only - influences the levelized cost of electricity (LCOE) - calculation. Default is 20 years. + + Examples + -------- + The ATBe class makes it easy to access data from the ATBe. + + >>> from nrelpy.atb import ATBe + >>> atbe = ATBe(2023) + >>> atbe.get_index_values('technology') + ['Battery', + 'AEO', + 'Biopower', + 'CSP', + 'Coal', + 'CommPV', + 'Geothermal', + 'Hydropower', + 'LandbasedWind', + 'NaturalGas', + 'Nuclear', + 'OffShoreWind', + 'ResPV', + 'UtilityPV'] + + You can filter data from the ATBe simply passing an index and value + + >>> atbe(technology='Nuclear') + + or passing an unpacked dictionary + + >>> opts = {'technology':'Nuclear', + >>> 'core_metric_parameter':'LCOE', + >>> 'core_metric_variable':2024} + >>> atbe(**opts) """ - self.year = year - self.database = database - self.case = case - self.scenario = scenario - self.crp = crp - - @property - def dataframe(self): - """ - Creates a subset of the ATBe according to the settings - stored as class attributes. Updates whenever a setting - is changed. - - >>> atbe = ATBe(2022) - >>> print(atbe.dataframe.scenario.unique()) - ['Moderate'] - >>> atbe.scenario = 'Advanced' - >>> print(atbe.dataframe.scenario.unique()) - ['Advanced'] - """ - df = as_dataframe(year=self.year, database=self.database) - # breakpoint() - df = df[(df.core_metric_case == self.case) & - (df.scenario == self.scenario) & - (df.crpyears == str(self.crp))] - return df - - @property - def available_tech(self): - """ - The list of available technologies for the current ATBe. - """ - - return (self.dataframe.technology.unique()) + self.database = 'electricity' + self.raw_dataframe = as_dataframe(year=self.year, database=self.database) + self.dataframe = _atbe_formatter(self.raw_dataframe, self.year) + + self.index_names = list(self.dataframe.index.names) + + def __call__(self, **kwargs): + cases = {key:slice(None) for key in self.index_names} + for k, v in kwargs.items(): + cases[k] = v + data_slice = tuple(cases.values()) + + selection = self.dataframe.xs(data_slice).dropna(axis=1, how='all') + + return selection + + def get_index_values(self, key): + + try: + key_list = self.dataframe.index.get_level_values(key).unique().to_list() + except KeyError as err: + msg = f"Key not found. Try one of {print(self.index_names)}" + raise KeyError(msg) + + return key_list @property - def available_metric(self): - """ - The list of available metrics for the current ATBe. - """ - - return np.sort(self.dataframe.core_metric_parameter.unique()) - - def get_data(self, - technology, - core_metric_param, - techdetail, - projection_year=2020, - value_only=True): + def acronyms(self): """ - Retrieves a specific piece of technology data. - - Parameters - ---------- - technology : string - Specifies the technology. - core_metric_param : string - The parameter of interest. - Accepts ['CAPEX', 'Fixed O&M', 'Variable O&M', - 'Fuel', 'CF', 'LCOE'] - techdetail : string - A more descriptive parameter to narrow down - technology selection. For example, `LandbasedWind` - has data for multiple wind classes (e.g. `Class9`). - This must be specified in order to distinguish values. - projection_year : int or list of int - The desired data year or set of years. Default is 2020. - value_only : boolean - An optional parameter that indicates whether the - value is returned as a Unyt object or as the value - only. Default is True. + Retrieves a dataset with the long form of acronyms in the `ATBe`. + Only valid for years in [2021, 2022, 2023]. Returns ------- - value : unyt.Quantity or float - The technology datum of interest - unit : string - Only returned if `value_only` is set to False. - Currently a string representation of the unit. + acronyms : :class:`pandas.DataFrame` + A dataframe of acronyms. """ - - df = self.dataframe.copy() - - tech_fail_str = (f"{technology} not in list of available techs. \n" + - f"Try one of \n {self.available_tech}") - param_fail_str = (f"{core_metric_param} not in list of metrics. \n" + - f"Try one of \n {self.available_metric}") - assert technology in self.available_tech, tech_fail_str - assert core_metric_param in self.available_metric, param_fail_str - - # mask the techs and params - df = df[(df['technology'] == technology) & - (df['core_metric_parameter'] == core_metric_param)] + acro_df = pd.read_html(f"https://atb.nrel.gov/electricity/{self.year}/acronyms")[0] + acro_df.cols = ['acronym','long name'] + acro_df.set_index("acronym", inplace=True, drop=True) - detail_list = df['techdetail'].unique() - detail_fail_str = (f"{techdetail} not in list of details for " + - f"technology {technology}. \n" + - f"Try one of \n {detail_list}") - assert techdetail in detail_list, detail_fail_str - - # mask the detail - detail_mask = df['techdetail'] == techdetail - df = df[detail_mask] - - # mask the year - if isinstance(projection_year, int): - df = df[df['core_metric_variable'] == projection_year] - assert len(df) > 0, f"{projection_year} not in ATBe." - elif isinstance(projection_year, list): - df = df[df['core_metric_variable'].isin(projection_year)] - assert len(df) == len(projection_year), "Some years not in ATBe" - - unit = df.units.values[0] - value = df.value.values[0] - - if value_only: - return value - else: - return value, unit + return acro_df + + +def _atbe_formatter(df, year): + """ + Creates a pivot table for the ATBe + + Parameters + ---------- + df : :class:`pandas.DataFrame` + raw ATBe dataframe. + year : int + The ATBe year. + + Returns + ------- + pivoted : :class:`pandas.DataFrame` + A pivoted dataframe. + """ + + pivoted = df.pivot_table(index=ATBe_INDEXES[year], + columns=ATBe_COLUMNS[year], + values='value' + ) + + return pivoted + +ATBe_INDEXES = { + 2019:['core_metric_case', + 'crpyears', + 'scenario', + 'technology', + 'core_metric_parameter', + 'core_metric_variable'], + 2020:['core_metric_case', + 'crpyears', + 'scenario', + 'technology', + 'core_metric_parameter', + 'core_metric_variable'], + 2021:['core_metric_case', + 'crpyears', + 'scenario', + 'technology', + 'core_metric_parameter', + 'core_metric_variable'], + 2022:['core_metric_case', + 'crpyears', + 'scenario', + 'technology', + 'core_metric_parameter', + 'core_metric_variable'], + 2023:['core_metric_case', + 'crpyears', + 'maturity', + 'scale', + 'scenario', + 'technology', + 'core_metric_parameter', + 'core_metric_variable', + ], + } + +ATBe_COLUMNS = { + 2019:'techdetail', + 2020:'techdetail', + 2021:'display_name', + 2022:'display_name', + 2023:'display_name', + } \ No newline at end of file