Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Passing on the principle of netting generation & consumption for generation of specific units. #119

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion entsoe/entsoe.py
Original file line number Diff line number Diff line change
Expand Up @@ -1678,7 +1678,7 @@ def query_generation_per_plant(
area = lookup_area(country_code)
text = super(EntsoePandasClient, self).query_generation_per_plant(
country_code=area, start=start, end=end, psr_type=psr_type)
df = parse_generation(text, per_plant=True)
df = parse_generation(text, per_plant=True, nett = nett)
df.columns = df.columns.set_levels(df.columns.levels[0].str.encode('latin-1').str.decode('utf-8'), level=0)
df = df.tz_convert(area.tz)
# Truncation will fail if data is not sorted along the index in rare
Expand Down
2 changes: 2 additions & 0 deletions entsoe/mappings.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ def code(self):
# List taken directly from the API Docs
DE_50HZ = '10YDE-VE-------2', '50Hertz CA, DE(50HzT) BZA', 'Europe/Berlin',
AL = '10YAL-KESH-----5', 'Albania, OST BZ / CA / MBA', 'Europe/Tirane',
AZ = '10Y1001A1001B05V', 'Azerbaijan', 'Asia/Baku',
DE_AMPRION = '10YDE-RWENET---I', 'Amprion CA', 'Europe/Berlin',
AT = '10YAT-APG------L', 'Austria, APG BZ / CA / MBA', 'Europe/Vienna',
BY = '10Y1001A1001A51S', 'Belarus BZ / CA / MBA', 'Europe/Minsk',
Expand All @@ -70,6 +71,7 @@ def code(self):
MK = '10YMK-MEPSO----8', 'Former Yugoslav Republic of Macedonia, MEPSO BZ / CA / MBA', 'Europe/Skopje',
FR = '10YFR-RTE------C', 'France, RTE BZ / CA / MBA', 'Europe/Paris',
DE = '10Y1001A1001A83F', 'Germany', 'Europe/Berlin'
GE = '10Y1001A1001B012', 'Georgia', 'Asia/Tbilisi',
GR = '10YGR-HTSO-----Y', 'Greece, IPTO BZ / CA/ MBA', 'Europe/Athens',
HU = '10YHU-MAVIR----U', 'Hungary, MAVIR CA / BZ / MBA', 'Europe/Budapest',
IS = 'IS', 'Iceland', 'Atlantic/Reykjavik',
Expand Down
82 changes: 49 additions & 33 deletions entsoe/parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
GENERATION_ELEMENT = "inBiddingZone_Domain.mRID"
CONSUMPTION_ELEMENT = "outBiddingZone_Domain.mRID"

CONSUMPTION = 'Actual Consumption'
GENERATION = 'Actual Aggregated'


def _extract_timeseries(xml_text):
"""
Expand Down Expand Up @@ -125,39 +128,52 @@ def parse_generation(
return df


def _get_aggregation_level(df: pd.DataFrame) -> int:
"""
Returns the dataframe's column level corresponding to the aggregation.
Parameters
----------
df : pd.DataFrame

Returns
-------
int
"""
for ind, level in enumerate(df.columns.levels):
if set(level) <= set([CONSUMPTION, GENERATION]):
return ind
return -1


def _calc_nett_and_drop_redundant_columns(
df: pd.DataFrame, nett: bool) -> pd.DataFrame:
def _calc_nett(_df):
try:
if set(['Actual Aggregated']).issubset(_df):
if set(['Actual Consumption']).issubset(_df):
_new = _df['Actual Aggregated'].fillna(0) - _df[
'Actual Consumption'].fillna(0)
else:
_new = _df['Actual Aggregated'].fillna(0)
else:
_new = -_df['Actual Consumption'].fillna(0)

except KeyError:
print ('Netting production and consumption not possible. Column not found')
return _new

if hasattr(df.columns, 'levels'):
if len(df.columns.levels[-1]) == 1:
# Drop the extra header, if it is redundant
df = df.droplevel(axis=1, level=-1)
elif nett:
frames = []
for column in df.columns.levels[-2]:
new = _calc_nett(df[column])
new.name = column
frames.append(new)
df = pd.concat(frames, axis=1)
else:
if nett:
df = _calc_nett(df)
elif len(df.columns) == 1:
df = df.squeeze()
"""
Calculates the net generation if needed.
Parameters
----------
df : pd.DataFrame
nett: bool
whether or not to net

Returns
-------
df : pd.DataFrame
"""
aggr_level = _get_aggregation_level(df)
if aggr_level >= 0 and nett:
gen = df.xs(GENERATION, 1, aggr_level).fillna(0) \
if GENERATION in df.columns.levels[aggr_level] \
else None
cons = df.xs(CONSUMPTION, 1, aggr_level).fillna(0) \
if CONSUMPTION in df.columns.levels[aggr_level] \
else 0

if gen is None:
# No generation is defined, only consumption (eg PSP in ES)
df = -cons
else:
# Net generation = generation - consumption
df = gen.add(-cons, fill_value = 0)

return df

Expand Down Expand Up @@ -467,9 +483,9 @@ def _parse_generation_timeseries(soup, per_plant: bool = False) -> pd.Series:
# If OUT, this means Consumption is measured.
# OUT means Consumption of a generation plant, eg. charging a pumped hydro plant
if soup.find(CONSUMPTION_ELEMENT.lower()):
metric = 'Actual Consumption'
metric = CONSUMPTION
else:
metric = 'Actual Aggregated'
metric = GENERATION

name = [metric]

Expand Down