ISAAC, SUPSI
IDSIA, SUPSI
2025-06-26
open-source software for probabilistic forecast reconciliation
same functionalities for both R and Python
contributors:
At time \(t+h\), given observations
up to time \(t\):
parametric distribution
At time \(t+h\), given observations
up to time \(t\):
samples
| \(i = 1\) | \(i = 2\) | \(\dots\) | \(i = N\) | ||||||
|---|---|---|---|---|---|---|---|---|---|
| \(\hat{Y}_{t+h|t}\) | 2 | 3 | \(\dots\) | 1 |
Constraints:
A = AA + AB, B = BA + BB, Z = A + B
AA, AB, BA, BB: bottom series
Z, A, B: upper series
\[ \mathcal{S} := \left\{\begin{bmatrix}z \\ a \\ \dots \\ bb \end{bmatrix} \;\text{such that }\; \begin{matrix} z = a + b,\\ a = aa+ab,\\ b = ba+bb \end{matrix} \right\} \]
coherence subspace
Base forecasts are incoherent:
Joint forecast distribution
Joint samples
| \(i = 1\) | \(i = 2\) | \(\dots\) | \(i = N\) | ||||||
|---|---|---|---|---|---|---|---|---|---|
| \(Z\) | 8 | 6 | \(\dots\) | 9 | |||||
| \(A \quad\) | 3 | 2 | \(\dots\) | 4 | |||||
| \(B\) | 4 | 3 | \(\dots\) | 3 | |||||
| \(AA\) | 1 | 1 | \(\dots\) | 2 | |||||
| \(AB\) | 1 | 2 | \(\dots\) | 2 | |||||
| \(BA\) | 2 | 0 | \(\dots\) | 1 | |||||
| \(BB\) | 3 | 2 | \(\dots\) | 1 |
Incoherent base forecasts
\[ \Big\Downarrow %\;\text{ reconciliation} \]
reconciled forecast distribution
reconciled samples
| \(i = 1\) | \(i = 2\) | \(\dots\) | \(i = N\) | ||||||
|---|---|---|---|---|---|---|---|---|---|
| \(Z\) | 8 | 6 | \(\dots\) | 9 | |||||
| \(A \quad\) | 3 | 3 | \(\dots\) | 5 | |||||
| \(B\) | 5 | 3 | \(\dots\) | 4 | |||||
| \(AA\) | 1 | 1 | \(\dots\) | 3 | |||||
| \(AB\) | 2 | 2 | \(\dots\) | 2 | |||||
| \(BA\) | 2 | 1 | \(\dots\) | 1 | |||||
| \(BB\) | 3 | 2 | \(\dots\) | 3 |
Reconciliation via conditioning
Condition the incoherent base forecast distribution
on the hierarchical constraints
Well-defined for both continuous and discrete distributions1
If base forecasts are Gaussian2:
reconciliation is analytic and coincides with MinT3
In general:
need to sample from the reconciled distribution
reconc_gaussian(A, base_forecasts_mu, base_forecasts_Sigma)
Input:
A: aggregation matrix (\(n_{upp} \times n_{bott}\))
base_forecasts_mu: list of length \(n\) representing the mean of the base forecast distribution
base_forecasts_Sigma : A \(n \times n\) covariance matrix representing the uncertainties in the base forecasts.
Output:
reconc_gaussian['bottom_reconciled_mean'] : mean of the bottom reconciled distribution
reconc_gaussian['bottom_reconciled_covariance'] : covariance of the bottom reconciled distribution.
Dataset considered: yearly counts of infant mortality in Australia, disaggregated by state and sex [‘hts’ package (Hyndman et al. 2021)]
python
recon_gauss = reconc_gaussian(A=A, base_forecasts_mu=mu, base_forecasts_Sigma=Sigma)
# Extract reconciled means and covariances
bottom_mu_reconc = recon_gauss['bottom_reconciled_mean']
bottom_Sigma_reconc = recon_gauss['bottom_reconciled_covariance']
upper_mu_reconc = A @ bottom_mu_reconc
upper_Sigma_reconc = A @ bottom_Sigma_reconc @ A.Tpython
recon_gauss = reconc_gaussian(A=A, base_forecasts_mu=mu, base_forecasts_Sigma=Sigma)
# Extract reconciled means and covariances
bottom_mu_reconc = recon_gauss['bottom_reconciled_mean']
bottom_Sigma_reconc = recon_gauss['bottom_reconciled_covariance']
upper_mu_reconc = A @ bottom_mu_reconc
upper_Sigma_reconc = A @ bottom_Sigma_reconc @ A.TBottom-up importance sampling (BUIS) algorithm 1
reconc_BUIS(A, base_forecasts, in_type, distr, num_samples)
Input:
A: aggregation matrix (\(n_{upp} \times n_{bott}\))
base_forecasts: list of length \(n\)
in_type: either “params” or “samples”
distr: specifies the parametric distribution (e.g. Poisson)
or the type of samples (continuous or discrete)
Output:
new dataset: we make available both data and base forecasts
data: counts of extreme market events in five economic sectors
daily time series, period: 2005-2018 (3508 trading days)
base forecasts: produced using score-driven model1
1-step-ahead, negative binomial distribution
r
j = 123 # pick one of the trading days
A <- matrix(1, ncol = 5, nrow = 1) # aggregation matrix (5 bottom, 1 upper)
# base forecasts: list of length n with the parameters of the negbin fc distr
base_fc_j <- c()
for (i in 1:6) {
base_fc_j[[i]] <- list(size = extr_mkt_events_basefc$size[[i]],
mu = extr_mkt_events_basefc$mu[[j,i]])
}
# Reconcile via importance sampling:
tic()
buis <- reconc_BUIS(A, base_fc_j, "params", "nbinom", num_samples = 1e4, seed = 42)
toc()0.03 sec elapsed
Daily sales of a single item from a Walmart store
Temporal hierarchy: 1 day - 1 week - 2 weeks - 4 weeks
Compute base forecasts using ‘smooth’ package (Svetunkov, 2023)
r
n_samples <- 1e4
base_fc_samples <- matrix(nrow = 0, ncol = n_samples)
model <- smooth::adam(aggr_ts[[1]], lags=c(1, 365%/%28), distribution=c("dgamma"))
fc <- forecast(model, h = 1, interval="simulated", scenarios=TRUE, nsim = n_samples)
base_fc_samples <- rbind(base_fc_samples, round((fc$scenarios)))
model <- smooth::adam(aggr_ts[[2]], lags=c(1, 365%/%14), distribution = c("dgamma"))
fc <- forecast(model, h = 2, interval="simulated", scenarios=TRUE, nsim = n_samples)
base_fc_samples <- rbind(base_fc_samples, round(fc$scenarios))
model <- smooth::adam(aggr_ts[[3]], lags=c(1, 365%/%7), distribution = c("dgamma"), occurrence="auto")
fc <- forecast(model, h = 4, interval="simulated", scenarios=TRUE, nsim = n_samples)
base_fc_samples <- rbind(base_fc_samples, round(fc$scenarios))
model <- smooth::adam(aggr_ts[[4]], lags=c(1,7,365), distribution=c("dgamma"), occurrence="auto")
fc <- forecast(model, h = 28, interval="simulated", scenarios=TRUE, nsim = n_samples)
base_fc_samples <- rbind(base_fc_samples, round(fc$scenarios))
dim(base_fc_samples)[1] 35 10000
r
0.11 sec elapsed
reconc_MixCond and reconc_TDcond 1 have the same input and output structure.reconc_MixCond(A, fc_bottom, fc_upper, bottom_in_type, distr, num_samples, return_type, seed)
Input:
A: aggregation matrix (\(n_{upp} \times n_{bott}\))
fc_bottom: base bottom forecasts
fc_upper : base upper forecasts, represented as parameters of a multivariate Gaussian
bottom_in_type : either “params”, “pmf”, or “samples”
distr : specifies the parametric distribution (only used for “params”)
return_type : specifies the return type - either “pmf”, “samples”, or “all”.
CA_1; forecast horizon: h=1; \(n_b=3049\) and \(n_u =11\).M5_CA1_basefc which were originally obtained in the R counterpart using ADAM [Svetunkov et. al 2023].python
from bayesreconpy.reconc_MixCond import reconc_MixCond
np.random.seed(seed)
start = time.time()
mix_cond = reconc_MixCond(A, fc_bottom_4rec, fc_upper_4rec, bottom_in_type="pmf",
num_samples=N_samples_IS, return_type="pmf", seed=seed)
stop = time.time()
rec_fc['Mixed_cond'] = {
'bottom': mix_cond['bottom_reconciled']['pmf'], # Bottom-level reconciled PMFs
'upper': mix_cond['upper_reconciled']['pmf'], # Upper-level reconciled PMFs
'ESS': mix_cond['ESS'] # Effective Sample Size (ESS)
}
MixCond_time = round(stop - start, 2)
print(f"Computational time for Mix-cond reconciliation: {MixCond_time} seconds")Computational time for Mix-cond reconciliation: 17.77 seconds
return_type="pmf" returns the pmf of the reconciled distribution with non-negative integer support in a memory-efficient way.python
Computational time for TD-cond reconciliation: 22.81 seconds
fully probabilistic: produces reconciled distribution
versatile: deals with many different cases: discrete, continuous, mixed
efficient: very fast, able to scale to large hierarchies
This is the initial version for both the packages as there might be changes in the future.
💡 This is an active research project on GitHub — we welcome collaboration and encourage you to report any issues or suggest new methods or algorithms.
Geometric intuition: slice the joint forecast distribution along the coherent subspace \(\mathcal{S}\)
Reconciled density/pmf can be expressed analytically (up to a normalizing constant):
\[ \tilde{\pi}(\mathbf{b}) \propto \hat{\pi}(\mathbf{A}\mathbf{b},\, \mathbf{b}) \]