Pipeline Examples
Each example adds to the one before it. The last two are complete pipelines you can copy as they are.
1. ENTSO-E and calendar
import osfrom epftoolbox2.pipelines import DataPipelinefrom epftoolbox2.data.sources import EntsoeSource, CalendarSourcefrom epftoolbox2.data.transformers import TimezoneTransformer, ResampleTransformer
pipeline = ( DataPipeline(timezone="Europe/Warsaw") .add_source(EntsoeSource(country_code="PL", api_key=os.environ["ENTSOE_API_KEY"], type=["load", "price"])) .add_source(CalendarSource(country="PL", holidays="binary", weekday="number")) .add_transformer(TimezoneTransformer()) .add_transformer(ResampleTransformer(freq="1h")))
df = pipeline.run(start="2024-01-01", end="2024-12-31", cache=True)end is inclusive, so this covers all of 31 December in Warsaw time. The result is an hourly frame with a Warsaw
index and the columns load_actual, load_forecast, load_forecast_daily_min, load_forecast_daily_max,
price, is_holiday and weekday.
2. Lagged features
from epftoolbox2.data.transformers import LagTransformer
pipeline.add_transformer(LagTransformer(columns=["load_actual", "price"], lags=[1, 2, 7], freq="day"))pipeline.add_transformer(LagTransformer(columns=["load_forecast", "is_holiday", "weekday"], lags=[-1], freq="day"))A positive lag looks back, so price_d-1 is the price at the same local hour yesterday. A negative lag looks
forward, so is_holiday_d+1 is the calendar of the day being forecast and load_forecast_d+1 is its day-ahead
load forecast.
3. Weather for the whole country
from epftoolbox2.data.sources import OpenMeteoSource
pipeline.add_source(OpenMeteoSource( locations={ "warsaw": {"latitude": 52.2297, "longitude": 21.0122, "weight": 1.86}, "krakow": {"latitude": 50.0647, "longitude": 19.9450, "weight": 0.80}, "gdansk": {"latitude": 54.3520, "longitude": 18.6466, "weight": 0.47}, }, agg="mean", columns=["temperature_2m", "wind_speed_10m", "cloud_cover"], prefix="pl",))The three cities become one population-weighted series per variable, with the columns
pl_temperature_2m_mean_d+1 to pl_temperature_2m_mean_d+6 and the same for the other variables. They already
hold the forecast for the target day, so they need no LagTransformer. See
OpenMeteoSource for the other options.
4. A neighbouring zone
pipeline.add_source(EntsoeSource(country_code="DE_LU", api_key=os.environ["ENTSOE_API_KEY"], type=["price"], prefix="de"))pipeline.add_transformer(LagTransformer(columns=["de_price"], lags=[1, 2, 7], freq="day"))The prefix keeps the German price (de_price) apart from the Polish one. Day-ahead prices of all zones are
published together, so the neighbour’s price is used only through its lags.
5. Validators
from epftoolbox2.data.validators import ( CoverageValidator, DSTStructureValidator, PlausibilityValidator,)
pipeline.add_validator(CoverageValidator(columns=["load_actual", "price"], min_coverage=1.0))pipeline.add_validator(DSTStructureValidator())pipeline.add_validator(PlausibilityValidator())Validators do not change the data. A failure is logged as a warning, and DataPipeline(on_invalid="raise") makes it
raise DataValidationError instead.
6. A 15-minute grid
pipeline = ( DataPipeline(timezone="Europe/Warsaw") .add_source(EntsoeSource( country_code="PL", api_key=os.environ["ENTSOE_API_KEY"], type=["load", "price"], prefer_15min=True, )) .add_source(CalendarSource(country="PL", holidays="binary", weekday="number", freq="15min")) .add_transformer(TimezoneTransformer()) .add_transformer(ResampleTransformer(freq="15min")) .add_transformer(LagTransformer(columns=["load_actual", "price"], lags=[1, 7], freq="day")))
df = pipeline.run(start="2025-10-01", end="2025-12-31", cache=True)prefer_15min=True gives a 15-minute price, and freq="15min" on the calendar and the resample puts everything on
the same grid, 96 rows per local day (92 or 100 on DST days). See EntsoeSource for
the price resolution.
7. A full Polish pipeline
import osfrom epftoolbox2.pipelines import DataPipelinefrom epftoolbox2.data.sources import EntsoeSource, CalendarSource, OpenMeteoSourcefrom epftoolbox2.data.transformers import TimezoneTransformer, ResampleTransformer, LagTransformerfrom epftoolbox2.data.validators import ( CoverageValidator, DSTStructureValidator, PlausibilityValidator,)
api_key = os.environ["ENTSOE_API_KEY"]
pipeline = ( DataPipeline(timezone="Europe/Warsaw") .add_source(EntsoeSource(country_code="PL", api_key=api_key, type=["load", "price"])) .add_source(EntsoeSource(country_code="DE_LU", api_key=api_key, type=["price"], prefix="de")) .add_source(CalendarSource( country="PL", holidays="binary", weekday="number", bridge_days=True, daylight=True, school_holidays="all", )) .add_source(OpenMeteoSource( locations={ "warsaw": {"latitude": 52.2297, "longitude": 21.0122, "weight": 1.86}, "krakow": {"latitude": 50.0647, "longitude": 19.9450, "weight": 0.80}, "lodz": {"latitude": 51.7592, "longitude": 19.4560, "weight": 0.65}, "wroclaw": {"latitude": 51.1079, "longitude": 17.0385, "weight": 0.67}, "poznan": {"latitude": 52.4064, "longitude": 16.9252, "weight": 0.54}, "gdansk": {"latitude": 54.3520, "longitude": 18.6466, "weight": 0.47}, }, agg="mean", columns=["temperature_2m", "wind_speed_10m", "cloud_cover", "relative_humidity_2m"], prefix="pl", )) .add_transformer(TimezoneTransformer()) .add_transformer(ResampleTransformer(freq="1h")) .add_transformer(LagTransformer(columns=["load_actual", "price", "de_price"], lags=range(1, 8), freq="day")) .add_transformer(LagTransformer(columns=["load_forecast"], lags=[-1], freq="day")) .add_transformer(LagTransformer( columns=["is_holiday", "is_bridge_day", "weekday", "daylight_hours", "school_holiday_share"], lags=range(-5, 0), freq="day", )) .add_validator(CoverageValidator(columns=["load_actual", "price", "de_price"], min_coverage=1.0)) .add_validator(DSTStructureValidator()) .add_validator(PlausibilityValidator()))
df = pipeline.run(start="2023-01-01", end="2025-12-31", cache=True)pipeline.save("data_pipeline.yaml")The saved YAML keeps the API key as ${ENTSOE_API_KEY}, so it can be committed and reloaded with
DataPipeline.load("data_pipeline.yaml"). See Serialization.