Forecasting Quickstart#

Train a GBLinear model on real energy data and generate probabilistic forecasts with confidence intervals — all in under a minute.

What you’ll learn:

  • Load the Liander 2024 benchmark dataset

  • Configure a forecasting workflow with ForecastingWorkflowConfig

  • Train a model and inspect evaluation metrics

  • Generate quantile forecasts (P10 / P50 / P90)

  • Visualize predictions against actuals

Note

This tutorial uses a small data slice for fast execution. See examples/benchmarks/ for production-scale runs.

Key API references: ForecastingWorkflowConfig create_forecasting_workflow · LeadTime · Q

Load the dataset#

The Liander 2024 benchmark dataset contains load measurements, versioned weather forecasts, EPEX prices, and load profiles for a medium-voltage feeder in the Netherlands.

We split the data into:

  • 45 days of training data

  • 7 days for forecasting

The predict window includes 14 days of history before the forecast start so that lag features (e.g. load_lag_P7D) can be computed during prediction.

from datetime import datetime, timedelta

from openstef_core.testing import load_liander_dataset

dataset = load_liander_dataset()

train_start = datetime.fromisoformat("2024-03-01T00:00:00Z")
train_end = train_start + timedelta(days=45)
forecast_end = train_end + timedelta(days=7)

train_dataset = dataset.filter_by_range(start=train_start, end=train_end)

# Include 14 days of history before forecast start for lag feature computation
predict_dataset = dataset.filter_by_range(
    start=train_end - timedelta(days=14),
    end=forecast_end,
)

print(
    f"Training:  {train_dataset.data.shape[0]:,} rows, "
    f"{train_dataset.data.index.min():%Y-%m-%d} to {train_dataset.data.index.max():%Y-%m-%d}"
)
print(
    f"Predict:   {predict_dataset.data.shape[0]:,} rows, "
    f"{predict_dataset.data.index.min():%Y-%m-%d} to {predict_dataset.data.index.max():%Y-%m-%d}"
)
Training:  4,320 rows, 2024-03-01 to 2024-04-14
Predict:   2,016 rows, 2024-04-01 to 2024-04-21

Hide code cell source

# Quick look at the target variable
fig = cast(Any, train_dataset.data[["load"]].plot(title="Training period — energy load"))
fig.update_layout(yaxis_title="Load (MW)", xaxis_title="Time")
fig.show()
../_images/a7eb5ca0a33afb372d5dee1cbdd0670cc2000c1cf8ff75b45c441c556fb96a17.png

Configure the workflow#

ForecastingWorkflowConfig bundles all settings — model type, horizons, quantiles, and feature columns — into a single object. create_forecasting_workflow turns it into a ready-to-use pipeline with preprocessing, training, and postprocessing.

We pick GBLinear (gradient-boosted linear model) for its speed and ability to extrapolate beyond training data.

from openstef_core.types import LeadTime, Q
from openstef_models.presets import ForecastingWorkflowConfig, create_forecasting_workflow
from openstef_models.presets.forecasting_workflow import GBLinearForecaster

workflow = create_forecasting_workflow(
    config=ForecastingWorkflowConfig(
        model_id="quickstart_gblinear",
        model="gblinear",
        horizons=[LeadTime.from_string("PT36H")],
        quantiles=[Q(0.5), Q(0.1), Q(0.9)],
        target_column="load",
        # Weather features available in the Liander dataset
        temperature_column="temperature_2m",
        relative_humidity_column="relative_humidity_2m",
        wind_speed_column="wind_speed_10m",
        radiation_column="shortwave_radiation",
        pressure_column="surface_pressure",
        verbosity=0,
        mlflow_storage=None,
        gblinear_hyperparams=GBLinearForecaster.HyperParams(n_steps=50),
    )
)

Train the model#

workflow.fit() runs the full pipeline: feature engineering, data validation, model training, and evaluation on a held-out test split.

result = workflow.fit(train_dataset)

if result is not None:
    print("Training metrics:")
    print(result.metrics_full.to_dataframe())

    if result.metrics_test is not None:
        print("\nTest-set metrics:")
        print(result.metrics_test.to_dataframe())
Training metrics:
   quantile        R2  observed_probability
0       0.5  0.801571              0.598380
1       0.1  0.487697              0.117824
2       0.9  0.470983              0.904861

Generate forecasts#

The trained workflow produces a ForecastDataset with point predictions and quantile bands. The P10-P90 interval covers 80 % of expected outcomes. To improve the reliability of these quantile estimates, see Quantile Calibration.

from openstef_core.datasets import ForecastDataset

forecast: ForecastDataset = workflow.predict(predict_dataset, forecast_start=train_end)

print(f"Forecast rows: {len(forecast.data)}")
print(f"Quantiles:     {forecast.quantiles}")
forecast.data.tail()
Forecast rows: 672
Quantiles:     [0.5, 0.1, 0.9]
quantile_P50 quantile_P10 quantile_P90 load stdev
timestamp
2024-04-21 22:45:00+00:00 384905.96875 327292.18750 435795.15625 353333.333333 32484.524950
2024-04-21 23:00:00+00:00 378329.21875 306992.93750 425346.84375 336666.666667 34686.091351
2024-04-21 23:15:00+00:00 370468.03125 301936.34375 414238.21875 323333.333333 34686.091351
2024-04-21 23:30:00+00:00 361670.59375 293272.46875 402527.71875 323333.333333 34686.091351
2024-04-21 23:45:00+00:00 353456.65625 287083.50000 392347.15625 316666.666667 34686.091351

Visualize the results#

ForecastTimeSeriesPlotter overlays measurements and predictions with shaded confidence bands in a single interactive chart.

Hide code cell source

from openstef_beam.analysis.plots import ForecastTimeSeriesPlotter

fig = (
    ForecastTimeSeriesPlotter()
    .add_measurements(measurements=predict_dataset.data["load"].loc[train_end:])
    .add_model(
        model_name="GBLinear",
        forecast=forecast.median_series,
        quantiles=forecast.quantiles_data,
    )
    .plot()
)
fig.update_layout(
    title="Forecast vs actuals",
    yaxis_title="Load (MW)",
    xaxis_title="Time",
    height=500,
)
fig.show()
../_images/d4e10820a3bc770c448ee55233dc1bad3a9993ad707d704f334e850fff36eef5.png

Next steps#