IDSIA, SUPSI
2025-08-21
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 (\(h=1\))
At time \(t+h\), given observations
up to time \(t\):
samples (\(h=1\))
| \(i = 1\) | \(i = 2\) | \(\dots\) | \(i = N\) | ||||||
|---|---|---|---|---|---|---|---|---|---|
| \({\widehat{Y}}_{t+1|t}\) | 2 | 3 | \(\dots\) | 1 |
Constraints: \(U_2 = B_1 + B_2\), \(U_3 = B_3 + B_4\), \(U_1 = U_2 + U_3\)
Let \(\mathbf{Y} = \begin{bmatrix}\mathbf{U}^T \quad \mathbf{B}^T\end{bmatrix}^T = \begin{bmatrix}U_1 \\ U_2 \\ \dots \\ B_4 \end{bmatrix}\) then \(\mathbf{U} = A \mathbf{B} \quad \text{ where } A= \begin{bmatrix}1 & 1 & 1 & 1\\ 1 & 1 & 0 & 0 \\ 0 & 0 & 1 & 1\end{bmatrix}\)
bottom series: \(B_1\), \(B_2\), \(B_3\), \(B_4\)
upper series: \(U_1\), \(U_2\), \(U_3\)
coherent subspace \[ \mathcal{S} := \left\{\begin{bmatrix}u_1 \\ u_2 \\ \dots \\ b_4 \end{bmatrix} \;\text{such that }\; \begin{matrix} u_1 = u_2 + u_3,\\ u_2 = b_1 + b_2,\\ u_3 = b_3 + b_4 \end{matrix} \right\} \]
Base forecasts are incoherent:
Joint forecast distribution
Joint samples
| \(i = 1\) | \(i = 2\) | \(\dots\) | \(i = N\) | ||||||
|---|---|---|---|---|---|---|---|---|---|
| \(U_1\) | 8 | 6 | \(\dots\) | 9 | |||||
| \(U_2 \quad\) | 3 | 2 | \(\dots\) | 4 | |||||
| \(U_3\) | 4 | 3 | \(\dots\) | 3 | |||||
| \(B_1\) | 1 | 1 | \(\dots\) | 2 | |||||
| \(B_2\) | 1 | 2 | \(\dots\) | 2 | |||||
| \(B_3\) | 2 | 0 | \(\dots\) | 1 | |||||
| \(B_4\) | 3 | 2 | \(\dots\) | 1 |
Incoherent base forecasts
\[ \Big\Downarrow %\;\text{ reconciliation} \]
reconciled forecast distribution
reconciled samples
| \(i = 1\) | \(i = 2\) | \(\dots\) | \(i = N\) | ||||||
|---|---|---|---|---|---|---|---|---|---|
| \(U_1\) | 8 | 6 | \(\dots\) | 9 | |||||
| \(U_2 \quad\) | 3 | 3 | \(\dots\) | 5 | |||||
| \(U_3\) | 5 | 3 | \(\dots\) | 4 | |||||
| \(B_1\) | 1 | 1 | \(\dots\) | 3 | |||||
| \(B_2\) | 2 | 2 | \(\dots\) | 2 | |||||
| \(B_3\) | 2 | 1 | \(\dots\) | 1 | |||||
| \(B_4\) | 3 | 2 | \(\dots\) | 3 |
Reconciliation via conditioning
Condition the incoherent base forecast distribution on the hierarchical constraints
Given \(\widehat{\mathbf{Y}} = \big[\widehat{\mathbf{U}}^T,\; \widehat{\mathbf{B}}^T \big]^T\), the reconciled distribution of the bottom series is: \[\widetilde{\mathbf{B}} \;\;\sim\;\; \widehat{\mathbf{B}} \;\Big|\; \big(\widehat{\mathbf{U}} - \mathbf{A}\widehat{\mathbf{B}} = 0 \big).\]
Well-defined for both continuous and discrete distributions1
Gaussian base forecasts1: reconciliation is analytic and coincides with MinT2
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 : \(n \times n\) covariance matrix for the base forecast distribution
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)]
3.13.5 | packaged by conda-forge | (main, Jun 16 2025, 08:25:50) [Clang 18.1.8 ]
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\) representing the base forecast distribution
in_type: either “params” or “samples”
distr: specifies the parametric distribution (e.g. Poisson)
or the type of samples (continuous or discrete)
Output:
base forecasts: produced using score-driven model1
1-step-ahead, negative binomial distribution
data and base forecasts available inside the package.
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.018 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.046 sec elapsed
reconc_MixCond and reconc_TDcond 1 have the same input and output structure.reconc_mix_cond(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_mix_cond import reconc_mix_cond
np.random.seed(seed)
start = time.time()
mix_cond = reconc_mix_cond(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: 9.5 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: 10.28 seconds
fully probabilistic: produces reconciled distribution
versatile: deals with many different cases: discrete, continuous, mixed
efficient: very fast, able to scale to large hierarchies
Note
Both packages are at version 0.x, there might be changes in the future.
A fable wrapper for the R package might come soon!
fully probabilistic: produces reconciled distribution
versatile: deals with many different cases: discrete, continuous, mixed
efficient: very fast, able to scale to large hierarchies
💡 Those are active research projects on GitHub — please report any issues or suggestions.
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}) \]
dario.azzimonti@supsi.ch