Skip to content

Quick Start

This guide shows you how to download electricity data and run a simple forecast in just a few steps.

  1. Set up environment

    import os
    # Set your ENTSOE API key
    os.environ["ENTSOE_API_KEY"] = "your-api-key-here"
    # Optional: Configure multithreading (requires Python 3.13t+)
    os.environ["PYTHON_GIL"] = "0"
    os.environ["THREADS_PER_PROCESS"] = "16"
  2. Download data with DataPipeline

    from epftoolbox2.pipelines import DataPipeline
    from epftoolbox2.data.sources import EntsoeSource, OpenMeteoSource, CalendarSource
    from epftoolbox2.data.transformers import TimezoneTransformer, ResampleTransformer, LagTransformer
    from epftoolbox2.data.validators import CoverageValidator
    data_pipeline = (
    DataPipeline(timezone="Europe/Warsaw")
    .add_source(EntsoeSource(
    country_code="PL",
    api_key=os.environ["ENTSOE_API_KEY"],
    type=["load", "price"]
    ))
    .add_source(OpenMeteoSource(
    latitude=52.2297,
    longitude=21.0122,
    prefix="warsaw"
    ))
    .add_source(CalendarSource(
    country="PL",
    holidays="binary",
    weekday="onehot",
    daylight=True
    ))
    .add_transformer(TimezoneTransformer())
    .add_transformer(ResampleTransformer(freq="1h"))
    .add_transformer(LagTransformer(
    columns=["load_actual", "price"],
    lags=[1, 2, 7],
    freq="day"
    ))
    .add_transformer(LagTransformer(
    columns=[
    "is_monday", "is_tuesday", "is_wednesday", "is_thursday", "is_friday",
    "is_saturday", "is_sunday", "is_holiday", "daylight_hours",
    ],
    lags=range(-7, 1),
    freq="day"
    ))
    .add_validator(CoverageValidator(columns=["load_actual", "price"], min_coverage=1.0))
    )
    df = data_pipeline.run(start="2023-01-01", end="2024-04-01", cache=True)
    print(f"Downloaded {len(df)} rows with {len(df.columns)} columns")

    TimezoneTransformer turns the UTC index into Warsaw time, so a day lag is the same local hour on another calendar day: price_d-1 is yesterday’s price and is_monday_d+1 is the calendar of the day being forecast.

  3. Train models with ModelPipeline

    from epftoolbox2.pipelines import ModelPipeline
    from epftoolbox2.information_set import InformationSet
    from epftoolbox2.models import OLSModel, LassoCVModel
    from epftoolbox2.evaluators import MAEEvaluator
    from epftoolbox2.exporters import ExcelExporter, TerminalExporter
    predictors = [
    "is_monday_d+{horizon}",
    "is_tuesday_d+{horizon}",
    "is_wednesday_d+{horizon}",
    "is_thursday_d+{horizon}",
    "is_friday_d+{horizon}",
    "is_saturday_d+{horizon}",
    "is_sunday_d+{horizon}",
    "is_holiday_d+{horizon}",
    "daylight_hours_d+{horizon}",
    "load_actual_d-1",
    "load_actual_d-7",
    "price_d-1",
    "price_d-7",
    "warsaw_temperature_2m_d+{horizon}",
    ]
    # Forecasts are issued at 11:45, before the 12:00 CET day-ahead gate closure
    information_set = InformationSet("11:45", "Europe/Warsaw").with_source_rules(data_pipeline.sources)
    model_pipeline = (
    ModelPipeline(information_set=information_set)
    .add_model(OLSModel(predictors=predictors, training_window=365, name="OLS"))
    .add_model(LassoCVModel(predictors=predictors, training_window=365, cv=5, name="LassoCV"))
    .add_evaluator(MAEEvaluator())
    .add_exporter(TerminalExporter())
    .add_exporter(ExcelExporter("results.xlsx"))
    )
    report = model_pipeline.run(
    data=df,
    test_start="2024-02-01",
    test_end="2024-03-01",
    target="price",
    horizon=6, # as far ahead as the weather forecasts go
    save_dir="results",
    )

    Before any model is fitted, the information set checks that every predictor is known at 11:45 on the day the forecast is issued, which leaves 15 minutes before gate closure and is late enough for JMA’s 00 UTC run, available around 09:30 UTC, to count as known in summer too. Today’s load_actual would be rejected, which is why only its lags are used.

  4. Analyze results

    # Overall summary
    print(report.summary())
    # By forecast horizon
    print(report.by_horizon())
    # By hour of day
    print(report.by_hour())

Expected Output

Summary
┏━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━┓
┃ model ┃ forecasts ┃ MAE ┃
┡━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━┩
│ OLS │ 4320 │ 15.5850 │
│ LassoCV │ 4320 │ 15.5754 │
└─────────┴───────────┴─────────┘
By Horizon
┏━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━┓
┃ model ┃ horizon ┃ MAE ┃
┡━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━┩
│ OLS │ 1 │ 14.8547 │
│ LassoCV │ 1 │ 14.6445 │
│ OLS │ 2 │ 15.3472 │
│ LassoCV │ 2 │ 14.8396 │
│ OLS │ 3 │ 14.9454 │
│ LassoCV │ 3 │ 14.5638 │
│ OLS │ 4 │ 15.6970 │
│ LassoCV │ 4 │ 15.2951 │
│ OLS │ 5 │ 15.8217 │
│ LassoCV │ 5 │ 16.4123 │
│ OLS │ 6 │ 16.8437 │
│ LassoCV │ 6 │ 17.6971 │
└─────────┴─────────┴─────────┘

The MAE is in EUR/MWh. Each model made 4320 forecasts: 30 issue days, 24 hours and 6 horizons.

What’s Next?