• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

IDSIA / bayesRecon / #13

05 Mar 2026 06:50PM UTC coverage: 76.645% (-7.8%) from 84.472%
#13

push

travis-pro

web-flow
Merge 35070ae15 into 599d7d457

671 of 891 new or added lines in 10 files covered. (75.31%)

11 existing lines in 4 files now uncovered.

955 of 1246 relevant lines covered (76.65%)

61846.67 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.0
/R/reconc_MixCond.R
1
###############################################################################
2
# Reconciliation with mixed-conditioning (Mix-Cond)
3
###############################################################################
4

5

6
#' @title Probabilistic forecast reconciliation of mixed hierarchies 
7
#'
8
#' @description
9
#'
10
#' `reconc_MixCond()` uses importance sampling to draw samples from the reconciled
11
#' forecast distribution, obtained via conditioning, in the case of a mixed hierarchy.
12
#'
13
#' `reconc_TDcond()` uses a top-down conditioning algorithm: first, upper base forecasts are
14
#' reconciled via conditioning using only the hierarchical constraints between the
15
#' upper; then, the bottom distributions are updated via a probabilistic top-down procedure.
16
#'
17
#' @details
18
#'
19
#' The base bottom forecasts `base_fc_bottom` must be a list of length n_bottom, where each element is either
20
#' * a PMF object (see details below), if `bottom_in_type='pmf'`;
21
#' * a vector of samples, if `bottom_in_type='samples'`;
22
#' * a list of parameters, if `bottom_in_type='params'`:
23
#'    * lambda for the Poisson base forecast if `distr`='poisson', see \link[stats]{Poisson};
24
#'    * size and prob (or mu) for the negative binomial base forecast if `distr`='nbinom',
25
#'      see \link[stats]{NegBinomial}.
26
#'
27
#' The base upper forecasts `base_fc_upper` must be a list containing the parameters of
28
#' the multivariate Gaussian distribution of the upper forecasts.
29
#' The list must contain only the named elements `mean` (vector of length n_upper)
30
#' and `cov` (n_upper x n_upper matrix).
31
#'
32
#' The order of the upper and bottom base forecasts must match the order of (respectively) the rows and the columns of A.
33
#'
34
#' A PMF object is a numerical vector containing the probability mass function of a discrete distribution.
35
#' Each element corresponds to the probability of the integers from 0 to the last value of the support.
36
#' See also [PMF] for functions that handle PMF objects.
37
#'
38
#' @section Warnings and errors:
39
#'
40
#' In `reconc_MixCond`, warnings are triggered from the importance sampling step if:
41
#' * weights are all zeros, then the upper forecast is ignored during reconciliation;
42
#' * the effective sample size is < 200;
43
#' * the effective sample size is < 1% of the sample size.
44
#' 
45
#' These warnings are an indication that the base forecasts might have issues.
46
#' Please check the base forecasts in case of warnings.
47
#' 
48
#' In `reconc_TDcond`, if some of the reconciled upper samples lie outside the support of the bottom-up
49
#' distribution, those samples are discarded; the remaining ones are resampled with
50
#' replacement, so that the number of output samples is equal to `num_samples`.
51
#' In this case, a warning is issued if `suppress_warnings=FALSE` (default is `TRUE`).
52
#' If the fraction of discarded samples is above 50%, the function returns an error.
53
#'
54
#' @param A Aggregation matrix (n_upper x n_bottom).
55
#' @param base_fc_bottom A list containing the bottom base forecasts, see details.
56
#' @param base_fc_upper A list containing the upper base forecasts, see details.
57
#' @param bottom_in_type A string with three possible values:
58
#'
59
#' * 'pmf' if the bottom base forecasts are in the form of pmf, see details;
60
#' * 'samples' if the bottom base forecasts are in the form of samples;
61
#' * 'params'  if the bottom base forecasts are in the form of estimated parameters.
62
#'
63
#' @param distr A string describing the type of bottom base forecasts ('poisson' or 'nbinom').
64
#'
65
#' This is only used if `bottom_in_type='params'`.
66
#'
67
#' @param num_samples Number of samples drawn from the reconciled distribution.
68
#'        This is ignored if `bottom_in_type='samples'`; in this case, the number of
69
#'        reconciled samples is equal to the number of samples of the base forecasts.
70
#'
71
#' @param return_type The return type of the reconciled distributions.
72
#'        A string with three possible values:
73
#'
74
#' * 'pmf' returns a list containing the reconciled marginal pmf objects;
75
#' * 'samples' returns a list containing the reconciled multivariate samples;
76
#' * 'all' returns a list with both pmf objects and samples.
77
#'
78
#' @param return_upper Logical, whether to return the reconciled parameters for the upper variables (default is TRUE).
79
#'
80
#' @param suppress_warnings Logical. If \code{TRUE}, no warnings about samples are triggered;
81
#'        if \code{FALSE}, warnings are generated. Default is \code{FALSE} for `reconc_MixCond`
82
#'        and \code{TRUE} for `reconc_TDcond`. See the respective sections above.
83
#' 
84
#' @param seed Seed for reproducibility.
85
#'
86
#' @return A list containing the reconciled forecasts. The list has the following named elements:
87
#'
88
#' * `bottom_rec`: a list containing the pmf, the samples (matrix n_bottom x `num_samples`) or both,
89
#'    depending on the value of `return_type`;
90
#' * `upper_rec`: a list containing the pmf, the samples (matrix n_upper x `num_samples`) or both,
91
#'    depending on the value of `return_type` (only if `return_upper = TRUE`).
92
#'
93
#' @references
94
#' Zambon, L., Azzimonti, D., Rubattu, N., Corani, G. (2024).
95
#' *Probabilistic reconciliation of mixed-type hierarchical time series*.
96
#' Proceedings of the Fortieth Conference on Uncertainty in Artificial Intelligence,
97
#' PMLR 244:4078-4095. <https://proceedings.mlr.press/v244/zambon24a.html>.
98
#'
99
#' @seealso [reconc_BUIS()], [reconc_gaussian()], [PMF]
100
#'
101
#' @name Mixed_reconciliation
102
NULL
103

104
#' @rdname Mixed_reconciliation
105
#' @examples
106
#'
107
#' library(bayesRecon)
108
#'
109
#' # Consider a simple hierarchy with two bottom and one upper
110
#' A <- matrix(c(1, 1), nrow = 1)
111
#' # The bottom forecasts are Poisson with lambda=15
112
#' lambda <- 15
113
#' n_tot <- 60
114
#' base_fc_bottom <- list()
115
#' base_fc_bottom[[1]] <- apply(matrix(seq(0, n_tot)), MARGIN = 1,
116
#'                              FUN = \(x) dpois(x, lambda = lambda))
117
#' base_fc_bottom[[2]] <- apply(matrix(seq(0, n_tot)), MARGIN = 1,
118
#'                              FUN = \(x) dpois(x, lambda = lambda))
119
#'
120
#' # The upper forecast is a Normal with mean 40 and std 5
121
#' base_fc_upper <- list(mean = 40, cov = matrix(5^2))
122
#'
123
#' # Reconcile with reconc_MixCond
124
#' res.mixCond <- reconc_MixCond(A, base_fc_bottom, base_fc_upper)
125
#'
126
#' # Note that the bottom distributions are slightly shifted to the right
127
#' PMF_summary(res.mixCond$bottom_rec$pmf[[1]])
128
#' PMF_summary(base_fc_bottom[[1]])
129
#'
130
#' PMF_summary(res.mixCond$bottom_rec$pmf[[2]])
131
#' PMF_summary(base_fc_bottom[[2]])
132
#'
133
#' # The upper distribution is slightly shifted to the left
134
#' PMF_summary(res.mixCond$upper_rec$pmf[[1]])
135
#' PMF_get_var(res.mixCond$upper_rec$pmf[[1]])
136
#'
137
#' @export
138
reconc_MixCond <- function(A, base_fc_bottom, base_fc_upper,
139
                           bottom_in_type = "pmf", distr = NULL,
140
                           num_samples = 2e4, return_type = "pmf",
141
                           return_upper = TRUE, suppress_warnings = FALSE, 
142
                           seed = NULL) {
143
  if (!is.null(seed)) set.seed(seed)
2✔
144

145
  # Check inputs
146
  .check_input_TD(
4✔
147
    A, base_fc_bottom, base_fc_upper,
4✔
148
    bottom_in_type, distr,
4✔
149
    return_type
4✔
150
  )
151

152
  # Prepare samples from the base bottom distribution
153
  if (bottom_in_type == "pmf") {
4✔
154
    B <- lapply(base_fc_bottom, PMF_sample, N_samples = num_samples)
1✔
155
    B <- do.call("cbind", B) # matrix of bottom samples (N_samples x n_bottom)
1✔
156
  } else if (bottom_in_type == "samples") {
3✔
157
    B <- do.call("cbind", base_fc_bottom)
1✔
158
    num_samples <- nrow(B)
1✔
159
  } else if (bottom_in_type == "params") {
2✔
160
    L_pmf <- lapply(base_fc_bottom, PMF_from_params, distr = distr)
2✔
161
    B <- lapply(L_pmf, PMF_sample, N_samples = num_samples)
2✔
162
    B <- do.call("cbind", B) # matrix of bottom samples (N_samples x n_bottom)
2✔
163
  }
164

165
  # Get mean and covariance matrix of the MVN upper base forecasts
166
  mean_upper <- base_fc_upper$mean
4✔
167
  cov_upper <- as.matrix(base_fc_upper$cov)
4✔
168

169
  out <- .core_reconc_MixCond(
4✔
170
    A, B, mean_upper, cov_upper, num_samples,
4✔
171
    return_type = return_type, 
4✔
172
    return_ESS = FALSE,
4✔
173
    return_upper = return_upper,
4✔
174
    suppress_warnings = suppress_warnings
4✔
175
  )
176

177
  return(out)
4✔
178
}
179

180

181
#' Core Reconciliation via Importance Sampling for Mixed Hierarchies
182
#'
183
#' Internal function that performs the core reconciliation logic using importance sampling
184
#' (IS) to reconcile mixed-type hierarchies. The base bottom forecasts (provided as samples)
185
#' are reweighted according to their fit to the upper multivariate Gaussian forecasts.
186
#'
187
#' @param A Matrix (n_upper x n_bottom) defining the hierarchy where upper = A %*% bottom.
188
#' @param B Matrix (n_samples x n_bottom) of bottom base forecast samples to be reconciled.
189
#' @param mean_upper Vector of upper level means.
190
#' @param cov_upper Covariance matrix of upper level.
191
#' @param num_samples Number of samples to draw/resample from.
192
#' @param return_type Character string specifying return format: 'pmf', 'samples', or 'all'.
193
#' @param return_ESS Logical, whether to return the Effective Sample Size (ESS) from importance sampling weights (default TRUE).
194
#' @param return_upper Logical, whether to return the reconciled parameters for the upper variables (default TRUE).
195
#' @param suppress_warnings Logical. If TRUE, suppresses warnings about sample quality (default FALSE).
196

197
#' @return A list containing:
198
#'   \itemize{
199
#'     \item `bottom_rec`: List with reconciled bottom forecasts (pmf and/or samples).
200
#'     \item `upper_rec`: (only if `return_upper = TRUE`) List with reconciled upper forecasts (pmf and/or samples).
201
#'     \item `ESS`: Effective Sample Size resulting from importance sampling reweighting (only if `return_ESS = TRUE`).
202
#'   }
203
#'
204
#' @keywords internal
205
#' @export
206
.core_reconc_MixCond <- function(A, B, mean_upper, cov_upper, num_samples, return_type, 
207
                                 return_ESS = TRUE, return_upper = TRUE,
208
                                 suppress_warnings = FALSE) {
209
  # Get dimensions
210
  n_u <- nrow(A)
4✔
211
  n_b <- ncol(A)
4✔
212

213
  # IS using MVN
214
  U <- B %*% t(A)
4✔
215
  weights <- .MVN_density(x = U, mu = mean_upper, Sigma = cov_upper)
4✔
216

217

218
  check_weights_res <- .check_weights(weights)
4✔
219
  if (check_weights_res$warning & !suppress_warnings) {
4✔
NEW
220
    warning_msg <- check_weights_res$warning_msg
×
UNCOV
221
    warning(warning_msg)
×
222
  }
223
  if (!(check_weights_res$warning & (1 %in% check_weights_res$warning_code))) {
4✔
224
    B <- .resample(B, weights, num_samples)
4✔
225
  }
226

227
  B <- t(B)
4✔
228
  U <- A %*% B
4✔
229

230
  # Prepare output: include the marginal pmfs and/or the samples (depending on "return" inputs)
231
  out <- list(bottom_rec = list(), upper_rec = list())
4✔
232
  if (return_type %in% c("pmf", "all")) {
4✔
233
    bottom_pmf <- lapply(1:n_b, function(i) PMF_from_samples(B[i, ]))
4✔
234
    out$bottom_rec$pmf <- bottom_pmf
4✔
235
    if (return_upper) {
4✔
236
      upper_pmf <- lapply(1:n_u, function(i) PMF_from_samples(U[i, ]))
4✔
237
      out$upper_rec$pmf <- upper_pmf
4✔
238
    }
239
  }
240
  if (return_type %in% c("samples", "all")) {
4✔
NEW
241
    out$bottom_rec$samples <- B
×
NEW
242
    if (return_upper) {
×
NEW
243
      out$upper_rec$samples <- U
×
244
    }
245
  }
246
  
247
  if (return_ESS) {
4✔
NEW
248
    out$ESS <- sum(weights)**2 / sum(weights**2)
×
249
  }
250

251
  return(out)
4✔
252
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc