---
title: "Data Science Methods for Forecasting in Energy and Economics"
date: 30 June 2025
author:
- name: Jonathan Berrisch
affiliations:
- ref: hemf
affiliations:
- id: hemf
name: University of Duisburg-Essen, House of Energy, Climate and Finance
format:
revealjs:
embed-resources: false
footer: ""
width: 1280
height: 720
logo: assets/logos_combined.png
theme: [default, sydney.scss, custom.scss]
smaller: true
fig-format: svg
slide-number: true
self-contained-math: true
crossrefs-hover: true
pagetitle: "De-Fence"
html-math-method: mathjax
pointer:
color: "#1B5E20"
include-in-header:
- file: custom.html
execute:
daemon: false
highlight-style: github
bibliography: assets/library.bib
csl: apa-old-doi-prefix.csl
revealjs-plugins:
- pointer
---
## Outline
::: {.hidden}
$$
\newcommand{\A}{{\mathbb A}}
$$
:::
:::: {style="font-size: 150%;"}
[Research Motivation](#motivation)
Overview of the Thesis
Online Aggregation
Probabilistic Forecasting of European Carbon and Energy Prices
Contributions & Outlook
:::
```{r, setup, include=FALSE}
# Compile with: rmarkdown::render("crps_learning.Rmd")
library(latex2exp)
library(ggplot2)
library(dplyr)
library(tidyr)
library(purrr)
library(kableExtra)
library(RefManageR)
knitr::opts_chunk$set(
dev = "svglite" # Use svg figures
)
BibOptions(
check.entries = TRUE,
bib.style = "authoryear",
cite.style = "authoryear",
style = "html",
hyperlink = TRUE,
dashed = FALSE
)
source("assets/01_common.R")
col_lightgray <- "#e7e7e7"
col_blue <- "#000088"
col_smooth_expost <- "#a7008b"
col_constant <- "#dd9002"
col_optimum <- "#666666"
col_smooth <- "#187a00"
col_pointwise <- "#008790"
col_green <- "#61B94C"
col_orange <- "#ffa600"
col_yellow <- "#FCE135"
```
## Motivation
## Overview of the Thesis {transition="fade" transition-speed="slow"}
|
|
Berrisch, J., & Ziel, F. [-@BERRISCH2023105221]. CRPS learning. Journal of Econometrics, 237(2), 105221.
|
|
|
Berrisch, J., & Ziel, F. [-@BERRISCH20241568]. Multivariate probabilistic CRPS learning with an application to day-ahead electricity prices. International Journal of Forecasting, 40(4), 1568–1586.
|
|
|
Hirsch, S., Berrisch, J., & Ziel, F. [-@hirsch2024online]. Online Distributional Regression. arXiv preprint arXiv:2407.08750.
|
|
|
Berrisch, J., & Ziel, F. [-@berrisch2022distributional]. Distributional modeling and forecasting of natural gas prices. Journal of Forecasting, 41(6), 1065–1086.
|
|
|
Berrisch, J., Pappert, S., Ziel, F., & Arsova, A. [-@berrisch2023modeling]. Modeling volatility and dependence of European carbon and energy prices. Finance Research Letters, 52, 103503.
|
|
|
Berrisch, J., Narajewski, M., & Ziel, F. [-@BERRISCH2023100236]. High-resolution peak demand estimation using generalized additive models and deep neural networks. Energy and AI, 13, 100236.
|
|
|
Berrisch, J. [-@berrisch2025rcpptimer]. rcpptimer: Rcpp Tic-Toc Timer with OpenMP Support. arXiv preprint arXiv:2501.15856.
|
## Overview of the Thesis {transition="fade" transition-speed="slow"}
|
|
Berrisch, J., & Ziel, F. [-@BERRISCH2023105221]. CRPS learning. Journal of Econometrics, 237(2), 105221.
|
|
|
Berrisch, J., & Ziel, F. [-@BERRISCH20241568]. Multivariate probabilistic CRPS learning with an application to day-ahead electricity prices. International Journal of Forecasting, 40(4), 1568–1586.
|
|
|
Hirsch, S., Berrisch, J., & Ziel, F. [-@hirsch2024online]. Online Distributional Regression. arXiv preprint arXiv:2407.08750.
|
|
|
Berrisch, J., & Ziel, F. [-@berrisch2022distributional]. Distributional modeling and forecasting of natural gas prices. Journal of Forecasting, 41(6), 1065–1086.
|
|
|
Berrisch, J., Pappert, S., Ziel, F., & Arsova, A. [-@berrisch2023modeling]. Modeling volatility and dependence of European carbon and energy prices. Finance Research Letters, 52, 103503.
|
|
|
Berrisch, J., Narajewski, M., & Ziel, F. [-@BERRISCH2023100236]. High-resolution peak demand estimation using generalized additive models and deep neural networks. Energy and AI, 13, 100236.
|
|
|
Berrisch, J. [-@berrisch2025rcpptimer]. rcpptimer: Rcpp Tic-Toc Timer with OpenMP Support. arXiv preprint arXiv:2501.15856.
|
## Overview of the Thesis {transition="fade" transition-speed="slow"}
|
|
Berrisch, J., & Ziel, F. [-@BERRISCH2023105221]. CRPS learning. Journal of Econometrics, 237(2), 105221.
|
|
|
Berrisch, J., & Ziel, F. [-@BERRISCH20241568]. Multivariate probabilistic CRPS learning with an application to day-ahead electricity prices. International Journal of Forecasting, 40(4), 1568–1586.
|
|
|
Hirsch, S., Berrisch, J., & Ziel, F. [-@hirsch2024online]. Online Distributional Regression. arXiv preprint arXiv:2407.08750.
|
|
|
Berrisch, J., & Ziel, F. [-@berrisch2022distributional]. Distributional modeling and forecasting of natural gas prices. Journal of Forecasting, 41(6), 1065–1086.
|
|
|
Berrisch, J., Pappert, S., Ziel, F., & Arsova, A. [-@berrisch2023modeling]. Modeling volatility and dependence of European carbon and energy prices. Finance Research Letters, 52, 103503.
|
|
|
Berrisch, J., Narajewski, M., & Ziel, F. [-@BERRISCH2023100236]. High-resolution peak demand estimation using generalized additive models and deep neural networks. Energy and AI, 13, 100236.
|
|
|
Berrisch, J. [-@berrisch2025rcpptimer]. rcpptimer: Rcpp Tic-Toc Timer with OpenMP Support. arXiv preprint arXiv:2501.15856.
|
## Overview
:::: {.columns}
::: {.column width="48%"}
#### Online Distributional Regression
:::
::: {.column width="4%"}
:::
::: {.column width="48%"}
#### Distributional Modeling and Forecasting of Natural Gas Prices
```{r, echo=FALSE, fig.width = 12, fig.height = 6, fig.align="center"}
load("assets/ngas/residuals.RData")
clr_day_ahead <- cols[5, "green"]
clr_month_ahead <- cols[5, "blue"]
line_da <- da_data %>%
ggplot(aes(y = sresids, x = obs)) +
geom_point(col = clr_day_ahead) +
geom_line(col = clr_day_ahead) +
theme_minimal() +
xlab(NULL) +
ylab(expression(z[t])) +
theme(text = element_text(size = text_size))
acf_da <- forecast::ggAcf(da_data$sresids, size = 1, col = clr_day_ahead) +
theme_minimal() +
ggtitle("") +
theme(text = element_text(size = text_size))
# Density Plot DA
den_da <- da_data %>%
ggplot(aes(x = sresids)) +
geom_histogram(aes(y = ..density..), fill = clr_day_ahead) +
geom_density(aes(y = ..density..), size = 1) +
theme_minimal() +
ylab("Density") +
xlab(NULL) +
theme(text = element_text(size = text_size))
line_ma <- ma_data %>%
ggplot(aes(y = sresids, x = obs)) +
geom_point(col = clr_month_ahead) +
geom_line(col = clr_month_ahead) +
theme_minimal() +
xlab(NULL) +
ylab(expression(z[t])) +
theme(text = element_text(size = text_size))
acf_ma <- forecast::ggAcf(ma_data$sresids, size = 1, col = clr_month_ahead) +
theme_minimal() +
ggtitle("") +
theme(text = element_text(size = text_size))
den_ma <- ma_data %>%
ggplot(aes(x = sresids)) +
geom_histogram(aes(y = after_stat(density)), fill = clr_month_ahead) +
geom_density(aes(y = after_stat(density)), size = 1) +
theme_minimal() +
ylab("Density") +
xlab(NULL) +
theme(text = element_text(size = text_size))
plots_da <- cowplot::align_plots(line_da, acf_da, line_ma, acf_ma, align = "v", axis = "l")
bottom_row_da <- cowplot::plot_grid(plots_da[[2]], den_da, align = "hv")
plots_ma <- cowplot::align_plots(line_ma, acf_ma, line_ma, acf_ma, align = "v", axis = "l")
bottom_row_ma <- cowplot::plot_grid(plots_ma[[2]], den_ma, align = "hv")
cols_ngas <- c(
clr_day_ahead, clr_day_ahead, clr_day_ahead, clr_day_ahead,
clr_month_ahead, clr_month_ahead, clr_month_ahead, clr_month_ahead
)
pacfs <- map2(pacfs, cols_ngas, .f = ~ .x %>%
mutate(Col = .y)) %>%
purrr::reduce(.f = rbind)
var_labs <- c(
"Residuals", "Absolute", "Positive", "Negative",
"Residuals", "Absolute", "Positive", "Negative"
)
lvls <- unique(pacfs$Var)
names(var_labs) <- lvls
pacfs <- transform(pacfs,
Var = factor(Var,
levels = lvls,
labels = c(
"paste(Residuals, ':',~ z[t])",
"paste(Absolute, ':',~ '|', z[t] ,'|')",
"paste(Positive, ':',~ '(', z[t],')'^'+')",
"paste(Negative, ':',~ '(', z[t],')'^'-')"
)
)
)
acfs <- pacfs %>%
dplyr::filter(Lag != 0) %>%
ggplot(aes(x = Lag)) +
geom_linerange(aes(ymin = ymin, ymax = ymax, color = Col), size = 1) +
geom_line(aes(y = upper), linetype = "longdash", alpha = 0.5) +
geom_line(aes(y = lower), linetype = "longdash", alpha = 0.5) +
scale_color_identity() +
ylab("ACF") +
theme_minimal() +
facet_grid(Product ~ Var, labeller = label_parsed) +
theme(
plot.margin = unit(c(0, 0, 0, 0.2), "cm"),
text = element_text(size = text_size),
strip.background = element_rect(fill = "grey95", colour = "grey95")
)
acfs_da <- pacfs %>%
dplyr::filter(Lag != 0 & Product == "Day-Ahead") %>%
ggplot(aes(x = Lag)) +
geom_linerange(aes(ymin = ymin, ymax = ymax, color = Col), size = 1) +
geom_line(aes(y = upper), linetype = "longdash", alpha = 0.5) +
geom_line(aes(y = lower), linetype = "longdash", alpha = 0.5) +
scale_color_identity() +
ylab("ACF") +
theme_minimal() +
facet_grid(. ~ Var, labeller = label_parsed) +
theme(
plot.margin = unit(c(0, 0, 0, 0.2), "cm"),
text = element_text(size = text_size),
strip.background = element_rect(
fill = "grey95", colour = "grey95"
),
axis.title.x = element_blank(),
axis.text.x = element_blank(),
axis.ticks.x = element_blank()
)
acfs_ma <- pacfs %>%
dplyr::filter(Lag != 0 & Product == "Month-Ahead") %>%
ggplot(aes(x = Lag)) +
geom_linerange(aes(ymin = ymin, ymax = ymax, color = Col), size = 1) +
geom_line(aes(y = upper), linetype = "longdash", alpha = 0.5) +
geom_line(aes(y = lower), linetype = "longdash", alpha = 0.5) +
scale_color_identity() +
ylab("ACF") +
theme_minimal() +
scale_x_continuous(breaks = c(1, 10, 20, 30, 40)) +
facet_grid(. ~ Var, labeller = label_parsed) +
theme(
plot.margin = unit(c(0, 0, 0, 0.2), "cm"),
text = element_text(size = text_size),
strip.background = element_blank(), strip.text = element_blank()
)
plots_da <- cowplot::align_plots(acfs_da, line_da, line_ma, acf_ma, align = "hv", axis = "l")
bottom_row_da <- cowplot::plot_grid(
plots_da[[2]], den_da,
align = "hv",
rel_widths = c(0.66, 0.33)
)
da_plots <- cowplot::plot_grid(plots_da[[1]], bottom_row_da, nrow = 2)
plots_ma <- cowplot::align_plots(acfs_ma, line_ma, line_ma, acf_ma, align = "hv", axis = "l")
bottom_row_ma <- cowplot::plot_grid(
plots_ma[[2]],
den_ma,
align = "hv",
rel_widths = c(0.66, 0.33)
)
cowplot::plot_grid(
bottom_row_da,
bottom_row_ma,
acfs_da,
acfs_ma,
ncol = 1,
rel_heights = c(0.2, 0.2, 0.3, 0.3)
)
```
:::
::::
## Overview
:::: {.columns}
::: {.column width="48%"}
#### High-Resolution Peak Demand Estimation Using Generalized Additive Models and Deep Neural Networks
```{r, echo=FALSE, fig.width = 12, fig.height = 6, fig.align="center"}
load("assets/minmaxload/plot_overview.rds")
# linesize <- 1.2
ggplot() +
geom_line(
data = plot_df[plot_df$var == "1high_res_load", ],
aes(
x = time,
y = value,
colour = var
),
linewidth = linesize
) +
geom_line(
data = plot_df[plot_df$var == "2low_res", ],
aes(
x = time,
y = value,
colour = var
),
linewidth = linesize
) +
geom_line(
data = plot_df[plot_df$var == "4max", ],
aes(
x = time,
y = value,
colour = var
),
linewidth = linesize
) +
geom_line(
data = plot_df[plot_df$var == "3min", ],
aes(
x = time,
y = value,
colour = var
),
linewidth = linesize
) +
scale_color_manual(
values = as.character(c(
cols[4, col_load],
cols[9, col_load],
cols[8, col_min],
cols[8, col_max]
)),
labels = c(
"High-Resolution Load",
"Low-Resolution Load",
"Minimum",
"Maximum"
)
) +
guides(color = guide_legend(
override.aes = list(size = 2)
)) +
theme_minimal() +
ylab("Load [MW]") +
xlab("Time") +
theme(
zoom.x = element_rect(fill = cols[4, "grey"], colour = NA),
legend.position = "bottom",
legend.title = element_blank(),
text = element_text(
size = text_size + 2,
),
validate = FALSE
) +
scale_y_continuous(breaks = seq(-1.5, 1.0, 0.5)) +
ggforce::facet_zoom(
xlim = c(
as.POSIXct("2021-07-24 12:00:00", tz = "UTC"),
as.POSIXct("2021-07-24 19:00:00", tz = "UTC")
),
zoom.size = 2,
ylim = c(-.9, 1),
split = FALSE,
horizontal = FALSE,
show.area = TRUE
)
```
:::
::: {.column width="4%"}
:::
::: {.column width="48%"}
#### rcpptimer: Rcpp Tic-Toc Timer with OpenMP Support
:::
::::
# CRPS Learning
Berrisch, J., & Ziel, F. [-@BERRISCH2023105221]. *Journal of Econometrics*, 237(2), 105221.
## Introduction
:::: {.columns}
::: {.column width="48%"}
The Idea:
- Combine multiple forecasts instead of choosing one
- Combination weights may vary over **time**, over the **distribution** or **both**
2 Popular options for combining distributions:
- Combining across quantiles (this paper)
- Horizontal aggregation, vincentization
- Combining across probabilities
- Vertical aggregation
:::
::: {.column width="2%"}
:::
::: {.column width="48%"}
::: {.panel-tabset}
## Time
```{r, echo = FALSE, fig.height=6, cache = TRUE}
par(mfrow = c(3, 3), mar = c(2, 2, 2, 2))
set.seed(1)
# Data
X <- matrix(ncol = 3, nrow = 15)
X[, 1] <- seq(from = 8, to = 12, length.out = 15) + 0.25 * rnorm(15)
X[, 2] <- 10 + 0.25 * rnorm(15)
X[, 3] <- seq(from = 12, to = 8, length.out = 15) + 0.25 * rnorm(15)
# Weights
w <- matrix(ncol = 3, nrow = 15)
w[, 1] <- sin(0.1 * 1:15)
w[, 2] <- cos(0.1 * 1:15)
w[, 3] <- seq(from = -2, 0.25, length.out = 15)^2
w <- (w / rowSums(w))
# Vis
plot(X[, 1],
lwd = 4,
type = "l",
ylim = c(8, 12),
xlab = "",
ylab = "",
xaxt = "n",
yaxt = "n",
bty = "n",
col = "#2050f0"
)
plot(w[, 1],
lwd = 4, type = "l",
ylim = c(0, 1),
xlab = "",
ylab = "", xaxt = "n", yaxt = "n", bty = "n", col = "#2050f0"
)
text(6, 0.5, TeX("$w_1(t)$"), cex = 2, col = "#2050f0")
arrows(13, 0.25, 15, 0.0, , lwd = 4, bty = "n")
plot.new()
plot(X[, 2],
lwd = 4,
type = "l", ylim = c(8, 12),
xlab = "", ylab = "", xaxt = "n", yaxt = "n", bty = "n", col = "purple"
)
plot(w[, 2],
lwd = 4, type = "l",
ylim = c(0, 1),
xlab = "",
ylab = "", xaxt = "n", yaxt = "n", bty = "n", col = "purple"
)
text(6, 0.6, TeX("$w_2(t)$"), cex = 2, col = "purple")
arrows(13, 0.5, 15, 0.5, , lwd = 4, bty = "n")
plot(rowSums(X * w), lwd = 4, type = "l", xlab = "", ylab = "", xaxt = "n", yaxt = "n", bty = "n", col = "#298829")
plot(X[, 3],
lwd = 4,
type = "l", ylim = c(8, 12),
xlab = "", ylab = "", xaxt = "n", yaxt = "n", bty = "n", col = "#e423b4"
)
plot(w[, 3],
lwd = 4, type = "l",
ylim = c(0, 1),
xlab = "",
ylab = "", xaxt = "n", yaxt = "n", bty = "n", col = "#e423b4"
)
text(6, 0.25, TeX("$w_3(t)$"), cex = 2, col = "#e423b4")
arrows(13, 0.75, 15, 1, , lwd = 4, bty = "n")
```
## Distribution
```{r, echo = FALSE, fig.height=6, cache = TRUE}
par(mfrow = c(3, 3), mar = c(2, 2, 2, 2))
set.seed(1)
# Data
X <- matrix(ncol = 3, nrow = 31)
X[, 1] <- dchisq(0:30, df = 10)
X[, 2] <- dnorm(0:30, mean = 15, sd = 5)
X[, 3] <- dexp(0:30, 0.2)
# Weights
w <- matrix(ncol = 3, nrow = 31)
w[, 1] <- sin(0.05 * 0:30)
w[, 2] <- cos(0.05 * 0:30)
w[, 3] <- seq(from = -2, 0.25, length.out = 31)^2
w <- (w / rowSums(w))
# Vis
plot(X[, 1],
lwd = 4,
type = "l",
xlab = "",
ylab = "",
xaxt = "n",
yaxt = "n",
bty = "n",
col = "#2050f0"
)
plot(X[, 2],
lwd = 4,
type = "l",
xlab = "", ylab = "", xaxt = "n", yaxt = "n", bty = "n", col = "purple"
)
plot(X[, 3],
lwd = 4,
type = "l",
xlab = "", ylab = "", xaxt = "n", yaxt = "n", bty = "n", col = "#e423b4"
)
plot(w[, 1],
lwd = 4, type = "l",
ylim = c(0, 1),
xlab = "",
ylab = "", xaxt = "n", yaxt = "n", bty = "n", col = "#2050f0"
)
text(12, 0.5, TeX("$w_1(x)$"), cex = 2, col = "#2050f0")
arrows(26, 0.25, 31, 0.0, , lwd = 4, bty = "n")
plot(w[, 2],
lwd = 4, type = "l",
ylim = c(0, 1),
xlab = "",
ylab = "", xaxt = "n", yaxt = "n", bty = "n", col = "purple"
)
text(15, 0.5, TeX("$w_2(x)$"), cex = 2, col = "purple")
arrows(15, 0.25, 15, 0, , lwd = 4, bty = "n")
plot(w[, 3],
lwd = 4, type = "l",
ylim = c(0, 1),
xlab = "",
ylab = "", xaxt = "n", yaxt = "n", bty = "n", col = "#e423b4"
)
text(20, 0.5, TeX("$w_3(x)$"), cex = 2, col = "#e423b4")
arrows(5, 0.25, 0, 0, , lwd = 4, bty = "n")
plot.new()
plot(rowSums(X * w),
lwd = 4, type = "l", xlab = "", ylab = "", xaxt = "n",
yaxt = "n", bty = "n", col = "#298829"
)
```
:::
:::
::::
## The Framework of Prediction under Expert Advice
### The sequential framework
:::: {.columns}
::: {.column width="48%"}
Each day, $t = 1, 2, ... T$
- The **forecaster** receives predictions $\widehat{X}_{t,k}$ from $K$ **experts**
- The **forecaster** assings weights $w_{t,k}$ to each **expert**
- The **forecaster** calculates her prediction:
\begin{equation}
\widetilde{X}_{t} = \sum_{k=1}^K w_{t,k} \widehat{X}_{t,k}.
\label{eq_forecast_def}
\end{equation}
- The realization for $t$ is observedilities
- Vertical aggregation
:::
::: {.column width="2%"}
:::
::: {.column width="48%"}
- The experts can be institutions, persons, or models
- The forecasts can be point-forecasts (i.e., mean or median) or full predictive distributions
- We do not need any assumptions concerning the underlying data
- @cesa2006prediction
:::
::::
---
## The Regret
Weights are updated sequentially according to the past performance of the $K$ experts.
That is, a loss function $\ell$ is needed. This is used to compute the **cumulative regret** $R_{t,k}$
\begin{equation}
R_{t,k} = \widetilde{L}_{t} - \widehat{L}_{t,k} = \sum_{i = 1}^t \ell(\widetilde{X}_{i},Y_i) - \ell(\widehat{X}_{i,k},Y_i)\label{eq:regret}
\end{equation}
The cumulative regret:
- Indicates the predictive accuracy of the expert $k$ until time $t$.
- Measures how much the forecaster *regrets* not having followed the expert's advice
Popular loss functions for point forecasting @gneiting2011making:
:::: {.columns}
::: {.column width="48%"}
$\ell_2$ loss:
\begin{equation}
\ell_2(x, y) = | x -y|^2 \label{eq:elltwo}
\end{equation}
Strictly proper for *mean* prediction
:::
::: {.column width="4%"}
:::
::: {.column width="48%"}
$\ell_1$ loss:
\begin{equation}
\ell_1(x, y) = | x -y| \label{eq:ellone}
\end{equation}
Strictly proper for *median* predictions
:::
::::
## Popular Algorithms and the Risk