Skip to content

Commit

Permalink
Merge pull request #77 from rl-institut/features/annuisation
Browse files Browse the repository at this point in the history
Features/annuisation
  • Loading branch information
jnnr authored Nov 16, 2021
2 parents d1dadb3 + 349c0bb commit 0f90212
Show file tree
Hide file tree
Showing 3 changed files with 255 additions and 3 deletions.
9 changes: 9 additions & 0 deletions Snakefile
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,15 @@ rule prepare_electricity_demand:
shell:
"python {input.script} {input.opsd_url} {output}"

rule prepare_scalars:
input:
raw_scalars="raw/base-scenario.csv",
script="scripts/prepare_scalars.py",
output:
"results/_resources/base-scenario.csv"
shell:
"python {input.script} {input.raw_scalars} {output}"

rule build_datapackage:
input:
"scenarios/{scenario}.yml"
Expand Down
136 changes: 133 additions & 3 deletions oemof_b3/tools/data_processing.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ def save_df(df, path):
print(f"User info: The DataFrame has been saved to: {path}.")


def filter_df(df, column_name, values):
def filter_df(df, column_name, values, inverse=False):
"""
This function filters a DataFrame.
Expand All @@ -162,6 +162,9 @@ def filter_df(df, column_name, values):
The column's name to filter.
values : str/numeric/list
String, number or list of strings or numbers to filter by.
inverse : Boolean
If True, the entries for `column_name` and `values` are dropped
and the rest of the DataFrame be retained.
Returns
-------
Expand All @@ -171,10 +174,15 @@ def filter_df(df, column_name, values):
_df = df.copy()

if isinstance(values, list):
df_filtered = _df.loc[df[column_name].isin(values)]
where = _df[column_name].isin(values)

else:
df_filtered = _df.loc[df[column_name] == values]
where = _df[column_name] == values

if inverse:
where = ~where

df_filtered = _df.loc[where]

return df_filtered

Expand Down Expand Up @@ -438,3 +446,125 @@ def unstack_timeseries(df):
df_unstacked.index.name = _df["timeindex_start"].index.name

return df_unstacked


def unstack_var_name(df):
r"""
Given a DataFrame in oemof_b3 scalars format, this function will unstack
the variables. The returned DataFrame will have one column for each var_name.
Parameters
----------
df : pd.DataFrame
Stacked scalar data.
Returns
-------
unstacked : pd.DataFrame
Unstacked scalar data.
"""
_df = df.copy()

_df = format_header(_df, HEADER_B3_SCAL, "id_scal")

_df = _df.set_index(
["scenario", "name", "region", "carrier", "tech", "type", "var_name"]
)

unstacked = _df.unstack("var_name")

return unstacked


def stack_var_name(df):
r"""
Given a DataFrame, this function will stack the variables.
Parameters
----------
df : pd.DataFrame
DataFrame with one column per variable
Returns
-------
stacked : pd.DataFrame
DataFrame with a column "var_name" and "var_value"
"""
assert isinstance(df, pd.DataFrame)

_df = df.copy()

_df.columns.name = "var_name"

stacked = _df.stack("var_name")

stacked.name = "var_value"

stacked = pd.DataFrame(stacked).reset_index()

return stacked


class ScalarProcessor:
r"""
This class allows to filter and unstack scalar data in a way that makes processing simpler.
"""

def __init__(self, scalars):
self.scalars = scalars

def get_unstacked_var(self, var_name):
r"""
Filters the scalars for the given var_name and returns the data in unstacked form.
Parameters
----------
var_name : str
Name of the variable
Returns
-------
result : pd.DataFrame
Data in unstacked form.
"""
_df = filter_df(self.scalars, "var_name", var_name)

if _df.empty:
raise ValueError(f"No entries for {var_name} in df.")

_df = unstack_var_name(_df)

result = _df.loc[:, "var_value"]

return result

def drop(self, var_name):

self.scalars = filter_df(self.scalars, "var_name", var_name, inverse=True)

def append(self, var_name, data):
r"""
Accepts a Series or DataFrame in unstacked form and appends it to the scalars.
Parameters
----------
var_name : str
Name of the data to append
data : pd.Series or pd.DataFrame
Data to append
Returns
-------
None
"""
_df = data.copy()

if isinstance(_df, pd.Series):
_df.name = var_name

_df = pd.DataFrame(_df)

_df = stack_var_name(_df)

_df = format_header(_df, HEADER_B3_SCAL, "id_scal")

self.scalars = self.scalars.append(_df)
113 changes: 113 additions & 0 deletions scripts/prepare_scalars.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# coding: utf-8
r"""
Inputs
-------
in_path1 : str
path incl. file name of input file with raw scalar data as .csv
out_path : str
path incl. file name of output file with prepared scalar data as .csv
Outputs
---------
pandas.DataFrame
with scalar data prepared for parametrization
Description
-------------
The script performs the following steps to prepare scalar data for parametrization:
* Calculate annualized investment cost from overnight cost, lifetime and wacc.
"""
import sys

import pandas as pd

from oemof.tools.economics import annuity

from oemof_b3.tools.data_processing import ScalarProcessor, load_b3_scalars


def fill_na(df):
key = "scenario"

value = "None"

_df = df.copy()

# save index and columns before resetting index
id_names = _df.index.names

columns = _df.columns

_df.reset_index(inplace=True)

# separate data where NaNs should be filled and base
df_fill_na = _df.loc[_df[key] != value]

base = _df.loc[_df[key] == value]

# merge data on the columns of the data to update
df_merged = df_fill_na.drop(columns, 1).merge(base.drop(key, 1), "left")

# update dataframe NaNs
df_fill_na.update(df_merged)

# combine the filled data with the base data
df_fill_na = pd.concat([df_fill_na, base])

# set index as before
df_fill_na = df_fill_na.set_index(id_names)

return df_fill_na


def annuise_investment_cost(sc):

for var_name_cost in ["capacity_cost_overnight", "storage_capacity_cost_overnight"]:

invest_data = sc.get_unstacked_var([var_name_cost, "lifetime"])

# if some value is None in some scenario key, use the values from Base scenario to fill NaNs
invest_data = fill_na(invest_data)

wacc = sc.get_unstacked_var("wacc").iloc[0, 0]

assert isinstance(wacc, float)

invest_data["wacc"] = wacc

annuised_investment_cost = invest_data.apply(
lambda x: annuity(x[var_name_cost], x["lifetime"], x["wacc"]), 1
)

sc.append(var_name_cost.replace("_overnight", ""), annuised_investment_cost)

sc.drop(
[
"wacc",
"lifetime",
"capacity_cost_overnight",
"storage_capacity_cost_overnight",
"fixom_cost",
"storage_fixom_cost",
]
)


if __name__ == "__main__":
in_path = sys.argv[1] # path to raw scalar data
out_path = sys.argv[2] # path to destination

df = load_b3_scalars(in_path)

sc = ScalarProcessor(df)

annuise_investment_cost(sc)

sc.scalars = sc.scalars.sort_values(by=["carrier", "tech", "var_name", "scenario"])

sc.scalars.reset_index(inplace=True, drop=True)

sc.scalars.index.name = "id_scal"

sc.scalars.to_csv(out_path)

0 comments on commit 0f90212

Please sign in to comment.