0
# Reconciliation Methods
1
2
Hierarchical reconciliation methods that ensure coherent forecasts across all levels of a hierarchy. Each method implements different approaches to resolving inconsistencies between forecasts at different aggregation levels, with both dense and sparse matrix variants for scalability.
3
4
## Capabilities
5
6
### Base Reconciler Interface
7
8
All reconciliation methods inherit from the `HReconciler` base class, providing a consistent interface across different algorithms.
9
10
```python { .api }
11
class HReconciler:
12
"""Abstract base class for all reconciliation methods."""
13
14
def fit(self, *args, **kwargs):
15
"""Fit the reconciliation method to data."""
16
17
def fit_predict(self, *args, **kwargs):
18
"""Fit and predict in one step."""
19
20
def predict(self, S, y_hat, level=None):
21
"""Generate reconciled predictions."""
22
23
def sample(self, num_samples: int):
24
"""Generate coherent samples from fitted model."""
25
26
def __call__(self, *args, **kwargs):
27
"""Alias for fit_predict."""
28
29
# Key attributes
30
fitted: bool # Whether model is fitted
31
is_sparse_method: bool # Whether method supports sparse matrices
32
insample: bool # Whether method requires historical data
33
```
34
35
### Bottom-Up Reconciliation
36
37
Bottom-up reconciliation aggregates forecasts from the most disaggregated level upward, ensuring perfect coherence by construction.
38
39
```python { .api }
40
class BottomUp:
41
"""
42
Bottom-up reconciliation method.
43
44
Aggregates forecasts from leaf nodes upward through the hierarchy.
45
This is the simplest reconciliation method that maintains perfect coherence.
46
"""
47
48
def __init__(self):
49
"""Initialize BottomUp reconciler."""
50
51
def fit(
52
self,
53
S: np.ndarray,
54
y_hat: np.ndarray,
55
idx_bottom: np.ndarray,
56
y_insample: Optional[np.ndarray] = None,
57
y_hat_insample: Optional[np.ndarray] = None,
58
sigmah: Optional[np.ndarray] = None,
59
intervals_method: Optional[str] = None,
60
num_samples: Optional[int] = None,
61
seed: Optional[int] = None,
62
tags: Optional[dict[str, np.ndarray]] = None
63
):
64
"""
65
Fit the bottom-up reconciliation method.
66
67
Parameters:
68
- S: summing matrix DataFrame
69
- y_hat: base forecasts array
70
- idx_bottom: indices of bottom-level series
71
- y_insample: historical data (not used for BottomUp)
72
- y_hat_insample: in-sample forecasts (not used)
73
- sigmah: forecast standard deviations
74
- intervals_method: method for generating intervals
75
- num_samples: number of samples for probabilistic reconciliation
76
- seed: random seed
77
- tags: hierarchy tags dictionary
78
"""
79
80
def fit_predict(
81
self,
82
S: np.ndarray,
83
y_hat: np.ndarray,
84
idx_bottom: np.ndarray,
85
**kwargs
86
) -> np.ndarray:
87
"""
88
Fit and generate bottom-up reconciled forecasts.
89
90
Returns:
91
Reconciled forecasts array with coherent predictions
92
"""
93
94
class BottomUpSparse:
95
"""Sparse matrix version of BottomUp for large hierarchies."""
96
# Same interface as BottomUp but optimized for sparse matrices
97
```
98
99
### Top-Down Reconciliation
100
101
Top-down reconciliation starts from the top level and disaggregates forecasts downward using historical proportions.
102
103
```python { .api }
104
class TopDown:
105
"""
106
Top-down reconciliation method.
107
108
Disaggregates forecasts from the root level downward using proportions
109
derived from historical data or base forecasts.
110
"""
111
112
def __init__(self, method: str = 'forecast_proportions'):
113
"""
114
Initialize TopDown reconciler.
115
116
Parameters:
117
- method: str, disaggregation method
118
'forecast_proportions': Use base forecast proportions
119
'average_proportions': Use average historical proportions
120
'proportion_averages': Use proportions of historical averages
121
"""
122
123
def fit(
124
self,
125
S: np.ndarray,
126
y_hat: np.ndarray,
127
y_insample: np.ndarray,
128
y_hat_insample: Optional[np.ndarray] = None,
129
sigmah: Optional[np.ndarray] = None,
130
intervals_method: Optional[str] = None,
131
num_samples: Optional[int] = None,
132
seed: Optional[int] = None,
133
tags: Optional[dict[str, np.ndarray]] = None,
134
idx_bottom: Optional[np.ndarray] = None
135
):
136
"""
137
Fit the top-down reconciliation method.
138
139
Parameters:
140
- S: summing matrix DataFrame
141
- y_hat: base forecasts array
142
- y_insample: historical data (required for proportion calculation)
143
- y_hat_insample: in-sample forecasts
144
- sigmah: forecast standard deviations
145
- intervals_method: method for generating intervals
146
- num_samples: number of samples
147
- seed: random seed
148
- tags: hierarchy tags dictionary
149
- idx_bottom: bottom-level series indices
150
"""
151
152
def fit_predict(
153
self,
154
S: np.ndarray,
155
y_hat: np.ndarray,
156
tags: dict[str, np.ndarray],
157
idx_bottom: Optional[np.ndarray] = None,
158
y_insample: Optional[np.ndarray] = None,
159
**kwargs
160
) -> np.ndarray:
161
"""
162
Fit and generate top-down reconciled forecasts.
163
164
Returns:
165
Reconciled forecasts array using top-down approach
166
"""
167
168
class TopDownSparse:
169
"""Sparse matrix version of TopDown for large hierarchies."""
170
# Same interface as TopDown
171
```
172
173
### Middle-Out Reconciliation
174
175
Middle-out reconciliation anchors at a specified middle level, applying bottom-up above and top-down below that level.
176
177
```python { .api }
178
class MiddleOut:
179
"""
180
Middle-out reconciliation method.
181
182
Anchors reconciliation at a middle level of the hierarchy, applying
183
bottom-up reconciliation above and top-down reconciliation below.
184
"""
185
186
def __init__(
187
self,
188
middle_level: str,
189
top_down_method: str = 'forecast_proportions'
190
):
191
"""
192
Initialize MiddleOut reconciler.
193
194
Parameters:
195
- middle_level: str, name of the middle level to anchor at
196
- top_down_method: str, method for top-down portion
197
"""
198
199
def fit_predict(
200
self,
201
S: np.ndarray,
202
y_hat: np.ndarray,
203
tags: dict[str, np.ndarray],
204
y_insample: Optional[np.ndarray] = None,
205
level: Optional[list[int]] = None,
206
intervals_method: Optional[str] = None,
207
**kwargs
208
) -> np.ndarray:
209
"""
210
Generate middle-out reconciled forecasts.
211
212
Returns:
213
Reconciled forecasts using middle-out approach
214
"""
215
216
class MiddleOutSparse:
217
"""Sparse matrix version of MiddleOut for large hierarchies."""
218
# Same interface as MiddleOut
219
```
220
221
### Minimum Trace Reconciliation
222
223
MinTrace uses generalized least squares to find reconciled forecasts that minimize the trace of the covariance matrix.
224
225
```python { .api }
226
class MinTrace:
227
"""
228
Minimum trace reconciliation using generalized least squares.
229
230
Finds reconciled forecasts that minimize the trace of the forecast
231
error covariance matrix subject to aggregation constraints.
232
"""
233
234
def __init__(
235
self,
236
method: str = 'ols',
237
nonnegative: bool = False,
238
mint_shr_ridge: float = 2e-8,
239
num_threads: int = 1
240
):
241
"""
242
Initialize MinTrace reconciler.
243
244
Parameters:
245
- method: str, estimation method
246
'ols': Ordinary least squares (identity covariance)
247
'wls_struct': Weighted least squares with structural scaling
248
'wls_var': Weighted least squares with variance scaling
249
'mint_cov': Use sample covariance matrix
250
'mint_shrink': Use shrinkage covariance estimator
251
- nonnegative: bool, enforce non-negative constraints
252
- mint_shr_ridge: float, ridge parameter for shrinkage estimator
253
- num_threads: int, number of threads for optimization
254
"""
255
256
def fit(
257
self,
258
S: np.ndarray,
259
y_hat: np.ndarray,
260
y_insample: Optional[np.ndarray] = None,
261
y_hat_insample: Optional[np.ndarray] = None,
262
sigmah: Optional[np.ndarray] = None,
263
intervals_method: Optional[str] = None,
264
num_samples: Optional[int] = None,
265
seed: Optional[int] = None,
266
tags: Optional[dict[str, np.ndarray]] = None,
267
idx_bottom: Optional[np.ndarray] = None
268
):
269
"""Fit the MinTrace reconciliation method."""
270
271
def fit_predict(
272
self,
273
S: np.ndarray,
274
y_hat: np.ndarray,
275
idx_bottom: Optional[np.ndarray] = None,
276
y_insample: Optional[np.ndarray] = None,
277
y_hat_insample: Optional[np.ndarray] = None,
278
**kwargs
279
) -> np.ndarray:
280
"""
281
Generate MinTrace reconciled forecasts.
282
283
Returns:
284
Reconciled forecasts using minimum trace approach
285
"""
286
287
class MinTraceSparse:
288
"""Sparse matrix version of MinTrace for large hierarchies."""
289
# Same interface as MinTrace
290
```
291
292
### Optimal Combination
293
294
OptimalCombination is equivalent to specific MinTrace variants, providing an alternative interface for classic optimal reconciliation.
295
296
```python { .api }
297
class OptimalCombination:
298
"""
299
Optimal combination method (equivalent to specific MinTrace variants).
300
301
Provides optimal linear combination of base forecasts to achieve coherence
302
while minimizing forecast error variance.
303
"""
304
305
def __init__(
306
self,
307
method: str = 'ols',
308
nonnegative: bool = False,
309
num_threads: int = 1
310
):
311
"""
312
Initialize OptimalCombination reconciler.
313
314
Parameters:
315
- method: str, combination method ('ols', 'wls_struct')
316
- nonnegative: bool, enforce non-negative constraints
317
- num_threads: int, number of threads for optimization
318
"""
319
320
# Same interface as MinTrace for fit/fit_predict methods
321
```
322
323
### Empirical Risk Minimization
324
325
ERM reconciliation minimizes empirical risk using in-sample errors, with options for regularization.
326
327
```python { .api }
328
class ERM:
329
"""
330
Empirical Risk Minimization reconciliation.
331
332
Minimizes in-sample reconciliation errors using various optimization
333
approaches including closed-form solutions and regularized methods.
334
"""
335
336
def __init__(
337
self,
338
method: str = 'closed',
339
lambda_reg: float = 1e-2
340
):
341
"""
342
Initialize ERM reconciler.
343
344
Parameters:
345
- method: str, optimization method
346
'closed': Closed-form solution
347
'reg': Regularized solution with lasso penalty
348
'reg_bu': Regularized bottom-up solution
349
- lambda_reg: float, regularization parameter for lasso methods
350
"""
351
352
def fit(
353
self,
354
S: pd.DataFrame,
355
y_hat: np.ndarray,
356
y_insample: pd.DataFrame,
357
y_hat_insample: np.ndarray,
358
sigmah: np.ndarray = None,
359
intervals_method: str = None,
360
num_samples: int = None,
361
seed: int = None,
362
tags: dict = None,
363
idx_bottom: np.ndarray = None
364
):
365
"""
366
Fit the ERM reconciliation method.
367
368
Note: ERM requires both historical data and in-sample forecasts.
369
"""
370
371
def fit_predict(
372
self,
373
S: np.ndarray,
374
y_hat: np.ndarray,
375
idx_bottom: Optional[np.ndarray] = None,
376
y_insample: Optional[np.ndarray] = None,
377
y_hat_insample: Optional[np.ndarray] = None,
378
**kwargs
379
) -> np.ndarray:
380
"""
381
Generate ERM reconciled forecasts.
382
383
Returns:
384
Reconciled forecasts using empirical risk minimization
385
"""
386
```
387
388
## Usage Examples
389
390
### Basic Method Usage
391
392
```python
393
from hierarchicalforecast.methods import BottomUp, TopDown, MinTrace
394
395
# Simple bottom-up reconciliation
396
bottom_up = BottomUp()
397
reconciled = bottom_up.fit_predict(S=summing_matrix, y_hat=forecasts, idx_bottom=bottom_indices)
398
399
# Top-down with forecast proportions
400
top_down = TopDown(method='forecast_proportions')
401
reconciled = top_down.fit_predict(
402
S=summing_matrix,
403
y_hat=forecasts,
404
tags=hierarchy_tags,
405
y_insample=historical_data
406
)
407
408
# MinTrace with shrinkage covariance
409
min_trace = MinTrace(method='mint_shrink', nonnegative=True)
410
reconciled = min_trace.fit_predict(
411
S=summing_matrix,
412
y_hat=forecasts,
413
y_insample=historical_data
414
)
415
```
416
417
### Using Sparse Methods for Large Hierarchies
418
419
```python
420
from hierarchicalforecast.methods import BottomUpSparse, MinTraceSparse
421
422
# For very large hierarchies, use sparse variants
423
bottom_up_sparse = BottomUpSparse()
424
min_trace_sparse = MinTraceSparse(method='ols')
425
426
# Same interface, but optimized for sparse matrices
427
reconciled = bottom_up_sparse.fit_predict(S=sparse_summing_matrix, y_hat=forecasts, idx_bottom=bottom_indices)
428
```
429
430
### Method Comparison
431
432
```python
433
# Compare multiple methods
434
methods = {
435
'BottomUp': BottomUp(),
436
'TopDown_FP': TopDown(method='forecast_proportions'),
437
'TopDown_AP': TopDown(method='average_proportions'),
438
'MinTrace_OLS': MinTrace(method='ols'),
439
'MinTrace_WLS': MinTrace(method='wls_struct'),
440
'ERM_Closed': ERM(method='closed')
441
}
442
443
results = {}
444
for name, method in methods.items():
445
results[name] = method.fit_predict(
446
S=summing_matrix,
447
y_hat=forecasts,
448
y_insample=historical_data,
449
y_hat_insample=insample_forecasts
450
)
451
```