Source code for demandlib.bdew.elec_slp

# -*- coding: utf-8 -*-
"""
Implementation of the standard load profiles

SPDX-FileCopyrightText: Birgit Schachler
SPDX-FileCopyrightText: Uwe Krien <krien@uni-bremen.de>
SPDX-FileCopyrightText: jnnr
SPDX-FileCopyrightText: Stephen Bosch
SPDX-FileCopyrightText: Patrik Schönfeldt

SPDX-License-Identifier: MIT
"""
import calendar
import datetime
import os
import warnings

import pandas as pd

from demandlib.tools import add_weekdays2df


[docs]class ElecSlp: """Generate electrical standardized load profiles based on the BDEW method. Parameters ---------- year : integer Year of the demand series. Optional Parameters ------------------- seasons : dictionary Describing the time ranges for summer, winter and transition periods. The seasons dictionary will update the existing one, so only changed keys have to be defined. Make sure not to create time gaps. The "h0_dyn" will not work with changed seasons, so you have to use your own smoothing curve to create a "h0_dyn" profile. holidays : dictionary or list The keys of the dictionary or the items of the list should be datetime objects of the days that are holidays. """ def __init__(self, year, seasons=None, holidays=None): if calendar.isleap(year): hoy = 8784 else: hoy = 8760 self._datapath = os.path.join(os.path.dirname(__file__), "bdew_data") self._date_time_index = pd.date_range( datetime.datetime(year, 1, 1, 0), periods=hoy * 4, freq="15Min" ) self._seasons = { "summer1": [5, 15, 9, 14], # summer: 15.05. to 14.09 "transition1": [3, 21, 5, 14], # transition1 :21.03. to 14.05 "transition2": [9, 15, 10, 31], # transition2 :15.09. to 31.10 "winter1": [1, 1, 3, 20], # winter1: 01.01. to 20.03 "winter2": [11, 1, 12, 31], # winter2: 01.11. to 31.12 } if seasons is not None: self._seasons.update(seasons) self._year = year # Create the default profiles self.slp_frame = self.all_load_profiles( self._date_time_index, holidays=holidays ) # Add the dynamic H0 profile self.create_dynamic_h0_profile() @property def date_time_index(self): return self._date_time_index
[docs] def all_load_profiles(self, time_df, holidays=None): slp_types = [ "h0", "g0", "g1", "g2", "g3", "g4", "g5", "g6", "l0", "l1", "l2", ] new_df = self.create_bdew_load_profiles( time_df, slp_types, holidays=holidays ) return new_df
[docs] def create_bdew_load_profiles(self, dt_index, slp_types, holidays=None): """ Calculates the hourly electricity load profile in MWh/h of a region. """ # define file path of slp csv data file_path = os.path.join(self._datapath, "selp_series.csv") # Read standard load profile series from csv file selp_series = pd.read_csv(file_path) tmp_df = selp_series # Create an index to merge. The year and month will be ignored only the # time index is necessary. index = pd.date_range( datetime.datetime(2007, 1, 1, 0), periods=2016, freq="15Min" ) tmp_df.set_index(index, inplace=True) # Create empty DataFrame to take the results. new_df = pd.DataFrame(index=dt_index, columns=slp_types).fillna(0) new_df = add_weekdays2df( new_df, holidays=holidays, holiday_is_sunday=True ) new_df["hour"] = dt_index.hour new_df["minute"] = dt_index.minute time_df = new_df[["date", "hour", "minute", "weekday"]].copy() tmp_df[slp_types] = tmp_df[slp_types].astype(float) # Inner join the slps on the time_df to the slp's for a whole year tmp_df["hour_of_day"] = tmp_df.index.hour tmp_df["minute_of_hour"] = tmp_df.index.minute left_cols = ["hour_of_day", "minute_of_hour", "weekday"] right_cols = ["hour", "minute", "weekday"] tmp_df = tmp_df.reset_index(drop=True) for p in self._seasons.keys(): a = datetime.datetime( self._year, self._seasons[p][0], self._seasons[p][1], 0, 0 ) b = datetime.datetime( self._year, self._seasons[p][2], self._seasons[p][3], 23, 59 ) merged_df = pd.DataFrame.merge( tmp_df[tmp_df["period"] == p[:-1]], time_df[a:b], left_on=left_cols, right_on=right_cols, how="inner", ).drop(["hour_of_day"], axis=1) merged_df.index = ( pd.to_datetime(merged_df["date"]) + pd.to_timedelta(merged_df["hour"], unit="h") + pd.to_timedelta(merged_df["minute"], unit="m") ) merged_df.sort_index(inplace=True) new_df.update(merged_df) new_df.drop( ["date", "minute", "hour", "weekday"], axis=1, inplace=True ) return new_df.div(new_df.sum(axis=0), axis=1)
[docs] def create_dynamic_h0_profile(self): r""" Use the dynamisation function of the BDEW to smoothen the seasonal edges. Functions resolution is daily. .. math:: F_t = -3,92\cdot10^{-10} \cdot t^4 + 3,2\cdot10^{-7} \cdot t^3– 7,02\cdot10^{-5}\cdot t^2 + 2,1\cdot10^{-3} \cdot t + 1,24 With `t` the day of the year as a decimal number. Adjustment of accuracy: from -3,92 to -3.916649251 """ # Create a Series with the day of the year as decimal number decimal_day = pd.Series( [((q + 1) / (24 * 4)) for q in range(len(self.slp_frame))], index=self.slp_frame.index, ) # Calculate the smoothing factor of the BDEW dynamic H0 profile smoothing_factor = ( -3.916649251 * 10 ** -10 * decimal_day ** 4 + 3.2 * 10 ** -7 * decimal_day ** 3 - 7.02 * 10 ** -5 * decimal_day ** 2 + 0.0021 * decimal_day + 1.24 ) # Multiply the smoothing factor with the default H0 profile self.slp_frame["h0_dyn"] = self.slp_frame["h0"].mul( smoothing_factor, axis=0 ) return self.slp_frame["h0_dyn"]
[docs] def get_profile(self, ann_el_demand_per_sector): """ DEPRECATED: Use :py:meth:`~get_scaled_power_profiles()` instead Parameters ---------- ann_el_demand_per_sector : dictionary Key: sector, value: annual value Returns ------- pandas.DataFrame : Table with all profiles """ msg = ( "This method is deprecated and will be removed in future " "versions\nUse the method get_scaled_power_profiles() instead." ) warnings.warn(msg, FutureWarning) return ( self.slp_frame.multiply( pd.Series(ann_el_demand_per_sector), axis=1 ).dropna(how="all", axis=1) * 4 )
[docs] def get_profiles(self, *args): """Get all or the selected profiles. To select profiles you can pass the name of the types as strings. The profiles are normalised to 1. Try `print(get_profiles().columns` to get all valid types. Returns ------- pandas.DataFrame : Table with all or the selected profiles. Examples -------- >>> from demandlib import bdew >>> e_slp = bdew.ElecSlp(year=2020) >>> ", ".join(sorted(e_slp.get_profiles().columns)) 'g0, g1, g2, g3, g4, g5, g6, h0, h0_dyn, l0, l1, l2' >>> e_slp.get_profiles("h0", "g0").head() h0 g0 2020-01-01 00:00:00 0.000017 0.000016 2020-01-01 00:15:00 0.000015 0.000015 2020-01-01 00:30:00 0.000014 0.000015 2020-01-01 00:45:00 0.000012 0.000014 2020-01-01 01:00:00 0.000012 0.000013 >>> e_slp.get_profiles("h0", "g0").sum() h0 1.0 g0 1.0 dtype: float64 """ if len(args) == 0: return self.slp_frame else: return self.slp_frame[list(args)]
[docs] def get_scaled_profiles(self, ann_el_demand_per_sector): """Get profiles scaled by there annual value. Parameters ---------- ann_el_demand_per_sector : dict The annual demand in an energy unit for each type. Returns ------- pandas.DataFrame : Table with scaled profiles. Examples -------- >>> from demandlib import bdew >>> e_slp = bdew.ElecSlp(year=2020) >>> e_slp.get_scaled_profiles({"h0": 3000, "g0": 5000}).head() g0 h0 2020-01-01 00:00:00 0.080084 0.050657 2020-01-01 00:15:00 0.076466 0.045591 2020-01-01 00:30:00 0.072897 0.041125 2020-01-01 00:45:00 0.069671 0.037408 2020-01-01 01:00:00 0.067030 0.034650 >>> e_slp.get_scaled_profiles({"h0": 3000, "g0": 5000}).sum() g0 5000.0 h0 3000.0 dtype: float64 """ return self.slp_frame.multiply( pd.Series(ann_el_demand_per_sector), axis=1 ).dropna(how="all", axis=1)
[docs] def get_scaled_power_profiles( self, ann_el_demand_per_sector, conversion_factor=4 ): """ Get profiles scaled by there annual value. Each value represents the average power of an interval. Therefore, it is not possible to sum up the array. A conversion factor is used to calculate power units from energy units. By default the conversion factor is `4`. As the interval of each profile is 15 minutes a conversion factor of `4` will convert energy units like Wh, kWh, MWh etc. to power units like W, kW, MW etc.. Parameters ---------- ann_el_demand_per_sector : dict The annual demand in an energy unit for each type. conversion_factor : float Factor to convert the energy unit of the annual value to the power unit of each interval. Returns ------- pandas.DataFrame : Table with scaled profiles. Examples -------- >>> from demandlib import bdew >>> e_slp = bdew.ElecSlp(year=2020) >>> e_slp.get_scaled_power_profiles({"h0": 3000, "g0": 5000}).head() g0 h0 2020-01-01 00:00:00 0.320338 0.202627 2020-01-01 00:15:00 0.305866 0.182365 2020-01-01 00:30:00 0.291590 0.164500 2020-01-01 00:45:00 0.278682 0.149633 2020-01-01 01:00:00 0.268122 0.138602 >>> cf = 4 >>> spp = e_slp.get_scaled_power_profiles({"h0": 3000, "g0": 5000}, ... conversion_factor=cf) >>> spp.sum() g0 20000.0 h0 12000.0 dtype: float64 >>> spp.div(cf).sum() g0 5000.0 h0 3000.0 dtype: float64 """ return ( self.get_scaled_profiles(ann_el_demand_per_sector) * conversion_factor )