Skip to content

Commit

Permalink
major update to ATBe class
Browse files Browse the repository at this point in the history
  • Loading branch information
samgdotson committed Mar 13, 2024
1 parent 068faf7 commit acde47a
Showing 1 changed file with 141 additions and 133 deletions.
274 changes: 141 additions & 133 deletions nrelpy/atb.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
"""
Expand All @@ -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
-------
Expand Down Expand Up @@ -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.
Expand All @@ -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',
}

0 comments on commit acde47a

Please sign in to comment.