Hierarchical and Grouped Time Series

Author

Arvind Sharma

Published

April 17, 2026

1 Introduction

In many real-world applications, time-series data are organized in structured forms — for example, products within categories, stores within regions, or states within a country. When these series are related through aggregation, they are referred to as hierarchical or grouped time series.

These structures arise naturally in:

  • Retail forecasting (products, stores, regions)
  • Tourism demand (states, purposes, demographics)
  • Supply chain management (warehouses, distribution centers)
  • Economic data (national, regional, sectoral aggregates)

Understanding the difference between hierarchical and grouped structures is crucial for forecast reconciliation — keeping forecasts consistent across aggregation levels.


2 Hierarchical Time Series (HTS)

2.1 Definition

A hierarchical time series is organized in a tree-like structure, where observations at higher levels are obtained by summing the series at lower levels.

Key characteristics:

  • Each parent node is the aggregate of its child nodes.
  • Aggregation flows along a single path, from the bottom (most detailed level) to the top (overall total).
  • Strict tree structure — no cross-classification.

2.2 Example: Retail Sales Hierarchy

Consider a retail company tracking sales across a geographic hierarchy:

Total (Country)
 ├── State A
 │    ├── Region A1
 │    │    ├── Store A1a
 │    │    └── Store A1b
 │    └── Region A2
 │         ├── Store A2a
 │         └── Store A2b
 └── State B
      ├── Region B1
      │    └── Store B1a
      └── Region B2
           └── Store B2a

Store-level sales sum to region totals, region totals sum to state totals, and state totals sum to the national total.

2.3 Mathematical Representation

Let \(\mathbf{b}_t\) denote the vector of bottom-level series at time \(t\). The hierarchy can be expressed through a summing matrix \(\mathbf{S}\):

\[ \mathbf{y}_t = \mathbf{S} \mathbf{b}_t \]

where \(\mathbf{y}_t\) contains all series (from top to bottom) and \(\mathbf{S}\) encodes the aggregation structure.

2.3.1 Example with 3 bottom-level series

Three stores (A, B, C) in two regions: Region 1 = {A, B}, Region 2 = {C}.

\[ \mathbf{S} = \begin{bmatrix} 1 & 1 & 1 \\ 1 & 1 & 0 \\ 0 & 0 & 1 \\ 1 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \end{bmatrix}, \qquad \mathbf{y}_t = \begin{bmatrix} \text{Total} \\ \text{Region 1} \\ \text{Region 2} \\ \text{Store A} \\ \text{Store B} \\ \text{Store C} \end{bmatrix} = \mathbf{S} \begin{bmatrix} y_{A,t} \\ y_{B,t} \\ y_{C,t} \end{bmatrix} \]


3 Grouped Time Series (GTS)

3.1 Definition

A grouped time series does not follow a single hierarchical tree. The same observations can be aggregated along multiple independent dimensions — for example, region, product type, or customer segment.

Key characteristics:

  • Each dimension gives a different aggregation view of the same base data.
  • Cross-classification: a single observation can belong to several groups simultaneously.
  • Multiple valid aggregation paths exist.

3.2 Example: Tourism Data

A tourism company tracks visitor numbers by both state and purpose of travel:

State Purpose Visitors
NSW Business 1,200
NSW Leisure 3,400
VIC Business 800
VIC Leisure 2,100

Two aggregation dimensions exist simultaneously:

  1. By State (NSW, VIC) — geographical aggregation
  2. By Purpose (Business, Leisure) — functional aggregation

The same base data can be summarized in multiple ways: state totals, purpose totals, and the grand total.

3.3 Grouped Structure Visualization

A GTS cannot be drawn as a single tree because each bottom cell has two parents — one in each aggregation dimension. We show it as two parallel aggregation paths on top, sharing a common bottom grid:

                            Total
                           /     \
                   ┌──────┘       └──────┐
              By State                 By Purpose
               /    \                   /     \
             NSW    VIC              Business  Leisure
              │      │                  │        │
              └──┐   └──────┐    ┌──────┘   ┌────┘
                 │          │    │          │
                 ▼          ▼    ▼          ▼

    Bottom level — each cell rolls up BOTH ways:

      ┌─────────┬───────────┬────────────┐
      │         │  Business │   Leisure  │
      ├─────────┼───────────┼────────────┤
      │  NSW    │  NSW×Bus  │  NSW×Leis  │
      │  VIC    │  VIC×Bus  │  VIC×Leis  │
      └─────────┴───────────┴────────────┘

The bottom grid is shared across both aggregation paths — NSW×Bus contributes to the NSW total and to the Business total. Only the intermediate levels differ. This is exactly why the summing matrix \(\mathbf{S}\) has extra rows compared to an HTS: it must encode both aggregation views simultaneously.

3.4 Mathematical Representation

As with HTS, a grouped series uses a summing matrix:

\[ \mathbf{y}_t = \mathbf{S} \mathbf{b}_t \]

but here \(\mathbf{S}\) carries rows for each independent grouping path.

3.4.1 Example: Two-way Grouping

For 2 states × 2 purposes = 4 bottom-level series, the relationship \(y = S \cdot b\) writes out as:

\[ \begin{bmatrix} \text{Total} \\ \text{NSW} \\ \text{VIC} \\ \text{Business} \\ \text{Leisure} \\ \text{NSW×Bus} \\ \text{NSW×Leis} \\ \text{VIC×Bus} \\ \text{VIC×Leis} \end{bmatrix} = \begin{bmatrix} 1 & 1 & 1 & 1 \\ 1 & 1 & 0 & 0 \\ 0 & 0 & 1 & 1 \\ 1 & 0 & 1 & 0 \\ 0 & 1 & 0 & 1 \\ 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} \text{NSW×Bus} \\ \text{NSW×Leis} \\ \text{VIC×Bus} \\ \text{VIC×Leis} \end{bmatrix} \]

Do the matrix multiplication row-by-row to read off each aggregation rule:

  • Row 1 (Total): \(1 \cdot \text{NSW×Bus} + 1 \cdot \text{NSW×Leis} + 1 \cdot \text{VIC×Bus} + 1 \cdot \text{VIC×Leis}\) = grand total.
  • Row 2 (NSW): \(1,1,0,0\) picks out the two NSW cells → NSW = NSW×Bus + NSW×Leis.
  • Row 4 (Business): \(1,0,1,0\) picks out the two Business cells → Business = NSW×Bus + VIC×Bus.
  • Rows 6–9 each have a single 1 → the bottom series equal themselves (identity block).

3.4.2 Reading the Matrix: Terminology

Using the 2×2 example to anchor each term:

Term Meaning In this example
Bottom-level series (\(\mathbf{b}_t\)) The most disaggregated observations — the “atoms” of the structure. Every other series is a sum of these. NSW×Bus, NSW×Leis, VIC×Bus, VIC×Leis — 4 series (columns of \(\mathbf{S}\))
Top-level series The fully aggregated series at the root of the structure. Total — 1 series (row 1 of \(\mathbf{S}\))
Intermediate / middle-level series Any aggregate series that sits between the top and the bottom. In a GTS, these are the partial aggregations along each grouping dimension. NSW, VIC (by State); Business, Leisure (by Purpose) — 4 series
Aggregate series Any series above the bottom level — includes both the top and all intermediate series. All rows of \(\mathbf{y}_t\) except the last 4
Full series vector (\(\mathbf{y}_t\)) All series stacked: top first, intermediate next, bottom last. Length \(n\). 9 series (1 top + 4 intermediate + 4 bottom)
Summing matrix (\(\mathbf{S}\)) An \(n \times m\) 0/1 matrix that expresses each series as a sum of bottom-level series. Each row = one series in \(\mathbf{y}_t\); each column = one bottom-level series. \(9 \times 4\)
Identity block (\(\mathbf{I}_m\)) The bottom \(m \times m\) block of \(\mathbf{S}\) — the trivial “a bottom series equals itself” rows. Rows 6–9 of \(\mathbf{S}\)

How to read a row of \(\mathbf{S}\):

  • Row 1 = Total: all four 1’s → Total is the sum of every bottom cell.
  • Row 2 = NSW: 1’s in the NSW columns only → NSW = NSW×Bus + NSW×Leis.
  • Row 4 = Business: 1’s in the Business columns only → Business = NSW×Bus + VIC×Bus.
  • Row 6 = NSW×Bus: a single 1 → it is a bottom series (this is the \(\mathbf{I}_m\) block).

Counting the series. For a GTS with \(p\) grouping dimensions of sizes \(k_1, k_2, \dots, k_p\):

\[ m = \prod_{i=1}^{p} k_i \quad (\text{bottom series}), \qquad n = 1 + \sum_{i=1}^{p} k_i + m \quad (\text{total series in } \mathbf{y}_t) \]

In the tourism example (\(k_{\text{state}} = 2\), \(k_{\text{purpose}} = 2\)): \(m = 4\), \(n = 1 + 2 + 2 + 4 = 9\) — matching the 9 rows of \(\mathbf{S}\).

3.4.3 Forecasting Vocabulary

Two more terms you’ll meet once you start forecasting:

  • Base forecasts (\(\hat{\mathbf{y}}_h\)) — the independent forecasts produced by your base model (ETS, ARIMA, TSLM, …) for every series in \(\mathbf{y}_t\), before any adjustment. These are usually incoherent: the forecast for NSW does not equal NSW×Bus + NSW×Leis.
  • Reconciled forecasts (\(\tilde{\mathbf{y}}_h = \mathbf{S}\mathbf{G}\hat{\mathbf{y}}_h\)) — the adjusted forecasts that satisfy the aggregation structure, i.e., they are coherent across every level and every grouping path.

Coherent vs incoherent: a set of forecasts is coherent if every aggregate equals the sum of its parts — for example, \(\tilde{y}_{\text{Total}} = \tilde{y}_{\text{NSW}} + \tilde{y}_{\text{VIC}} = \tilde{y}_{\text{Business}} + \tilde{y}_{\text{Leisure}} = \sum \tilde{y}_{\text{bottom}}\). That is exactly what reconciliation enforces.


4 HTS vs GTS at a Glance

Feature Hierarchical (HTS) Grouped (GTS)
Structure Pure tree, one parent per series Cross-classification, multiple valid groupings
Aggregation path Unique (e.g., Country → State → Region) Multiple (State and Purpose both roll up to Total)
Summing matrix S Smaller, fewer constraints Larger, overlapping constraints
Computational cost Lower Higher
Example Country → State → City State × Purpose (two-way grouping)
Typical notation a / b / c a * b
Important

Choosing HTS vs GTS is not a different research question. You are asking the same economic question (e.g., forecasting tourism demand), but you are telling the software how the data are organized and should be reconciled.

That structure determines:

  • how forecasts aggregate or cross-classify,
  • which reconciliation methods are valid, and
  • how is_aggregated() recognizes levels for plotting or filtering.

You are defining what belongs to what, not yet what to forecast. The base model (ETS, ARIMA, …) doesn’t “know” whether it’s HTS or GTS — the reconciliation step uses the structure.


5 Forecast Reconciliation

5.1 The Coherence Problem

When forecasting hierarchical or grouped time series, independent forecasts made at different levels may be incoherent — upper-level forecasts do not equal the sum of lower-level forecasts.

Example of incoherence:

  • Forecast for Total = 1,000 units
  • Forecast for State A = 600 units
  • Forecast for State B = 500 units
  • Problem: 600 + 500 = 1,100 ≠ 1,000

Reconciliation adjusts forecasts so they are internally consistent across all levels.

Caution

Base models vs. reconciliation methods — do not confuse these two steps.

Step Type Examples Purpose
Base forecasting Statistical / ML models ETS(), ARIMA(), TSLM(), NN Produce initial (unreconciled) forecasts for each series
Reconciliation Coherence adjustment bottom_up(), top_down(), middle_out(), min_trace() Enforce coherence across the structure

Reconciliation happens after base models produce forecasts.

5.2 The Four Reconciliation Methods

All four methods apply to the general form

\[ \tilde{\mathbf{y}}_{h} = \mathbf{S}\mathbf{G}\,\hat{\mathbf{y}}_{h}, \]

where \(\hat{\mathbf{y}}_h\) are the base forecasts and \(\mathbf{G}\) is the matrix that maps base forecasts to reconciled bottom-level forecasts. What differs across methods is the choice of \(\mathbf{G}\).

5.2.1 Bottom-Up (BU)

Forecast at the bottom level, then aggregate upward.

\[ \hat{y}_{\text{total}} = \sum \hat{y}_{\text{bottom}} \]

  • Use when bottom-level data are rich and reliable.
  • Works for both HTS and GTS.
  • Always coherent by construction; noise at the bottom propagates upward.

5.2.2 Top-Down (TD)

Forecast only the top, then disaggregate using proportions.

  • Use when the data form a strict, single-tree hierarchy and totals are stable.
  • method = "forecast_proportions" (dynamic) or "average_proportions" (historical).
  • HTS only — not valid for grouped or crossed structures. reconcile() will error on GTS.

5.2.3 Middle-Out (MO)

Forecast at an intermediate level, then aggregate up and disaggregate down.

  • Use when there are three or more levels (e.g., Country → State → Region) and the middle level is the most stable signal.
  • With only 2 levels, middle-out defaults to bottom-up (redundant but safe).
  • Not defined for grouped/crossed structures.

5.2.4 Optimal Reconciliation — MinT

Uses all base forecasts and finds the optimal \(\mathbf{G}\) that minimizes total forecast-error variance subject to the coherence constraint \(\mathbf{S}\mathbf{G}\mathbf{S} = \mathbf{S}\):

\[ \mathbf{G} = (\mathbf{S}' \mathbf{\Sigma}^{-1} \mathbf{S})^{-1} \mathbf{S}' \mathbf{\Sigma}^{-1} \]

where \(\mathbf{\Sigma}\) is the forecast-error covariance matrix.

Common variants in fable:

  • method = "ols" — assumes equal forecast-error variance (simple, ignores covariances).
  • method = "mint_shrink" — shrinkage estimator for \(\mathbf{\Sigma}\); more stable in practice.

Use when you want the statistically efficient answer. Works for any structure — HTS, GTS, or mixed — and borrows strength across correlated series.

5.3 Method Selection Summary

Method Works For Needs Strict Hierarchy? Behavior at 2 Levels Strength Limitation
Bottom-Up HTS + GTS No Simple summation Coherent, interpretable Propagates bottom-level noise
Top-Down HTS only Yes Valid (splits total) Uses stable totals Fails for grouped structures
Middle-Out HTS (3+ levels) Yes Collapses to Bottom-Up Balances top & bottom Not meaningful with only 2 levels
MinT (OLS / Shrink) HTS + GTS No Weighted optimum Statistically efficient Requires covariance estimation

Quick rule: Bottom-Up for simplicity, Top-Down for stable aggregates, Middle-Out for deep hierarchies, and MinT when you want statistical optimality.


6 Software Implementation

Both HTS and GTS are handled in R with the fable and fabletools packages.

6.1 Australian domestic overnight trips: (State / Region) * Purpose

Quarterly overnight trips from 1998 Q1 to 2016 Q4 across Australia — a tsibble with 23,408 rows and 5 variables:

  • Quarter — year quarter (index)
  • Region — tourism regions formed by aggregating Statistical Local Areas
  • State — Australian states and territories
  • Purpose — visit purpose: Holiday, Visiting friends and relatives, Business, Other reason
  • Trips — overnight trips (thousands)

In a tsibble (tidy time series), each observation is uniquely identified by a time index (Quarter) and a key (one or more variables that define individual series).

The attribute key = tsibble[304x4] tells us there are 304 unique series, each defined by Region + State + Purpose.

remove(list = ls())

library(fable)
library(fabletools)
library(tsibble)
library(skimr)
library(dplyr)
library(ggplot2)

# Explore structure — (State / Region) * Purpose
head(tourism)

6.2 Confirming the Key

key_vars(tourism)
[1] "Region"  "State"   "Purpose"
index_var(tourism)
[1] "Quarter"
# Brute-force confirmation: how many unique series?
tsibble::tourism %>%
  distinct(Region, State, Purpose) %>%
  count()
table(tourism$State)

               ACT    New South Wales Northern Territory         Queensland 
               320               4160               2240               3840 
   South Australia           Tasmania           Victoria  Western Australia 
              3840               1600               6720               1600 
table(tourism$Region, tourism$State)
                                 
                                  ACT New South Wales Northern Territory
  Adelaide                          0               0                  0
  Adelaide Hills                    0               0                  0
  Alice Springs                     0               0                320
  Australia's Coral Coast           0               0                  0
  Australia's Golden Outback        0               0                  0
  Australia's North West            0               0                  0
  Australia's South West            0               0                  0
  Ballarat                          0               0                  0
  Barkly                            0               0                320
  Barossa                           0               0                  0
  Bendigo Loddon                    0               0                  0
  Blue Mountains                    0             320                  0
  Brisbane                          0               0                  0
  Bundaberg                         0               0                  0
  Canberra                        320               0                  0
  Capital Country                   0             320                  0
  Central Coast                     0             320                  0
  Central Highlands                 0               0                  0
  Central Murray                    0               0                  0
  Central NSW                       0             320                  0
  Central Queensland                0               0                  0
  Clare Valley                      0               0                  0
  Darling Downs                     0               0                  0
  Darwin                            0               0                320
  East Coast                        0               0                  0
  Experience Perth                  0               0                  0
  Eyre Peninsula                    0               0                  0
  Fleurieu Peninsula                0               0                  0
  Flinders Ranges and Outback       0               0                  0
  Fraser Coast                      0               0                  0
  Geelong and the Bellarine         0               0                  0
  Gippsland                         0               0                  0
  Gold Coast                        0               0                  0
  Goulburn                          0               0                  0
  Great Ocean Road                  0               0                  0
  High Country                      0               0                  0
  Hobart and the South              0               0                  0
  Hunter                            0             320                  0
  Kakadu Arnhem                     0               0                320
  Kangaroo Island                   0               0                  0
  Katherine Daly                    0               0                320
  Lakes                             0               0                  0
  Lasseter                          0               0                320
  Launceston, Tamar and the North   0               0                  0
  Limestone Coast                   0               0                  0
  MacDonnell                        0               0                320
  Macedon                           0               0                  0
  Mackay                            0               0                  0
  Mallee                            0               0                  0
  Melbourne                         0               0                  0
  Melbourne East                    0               0                  0
  Murray East                       0               0                  0
  Murraylands                       0               0                  0
  New England North West            0             320                  0
  North Coast NSW                   0             320                  0
  North West                        0               0                  0
  Northern                          0               0                  0
  Outback                           0               0                  0
  Outback NSW                       0             320                  0
  Peninsula                         0               0                  0
  Phillip Island                    0               0                  0
  Riverina                          0             320                  0
  Riverland                         0               0                  0
  Snowy Mountains                   0             320                  0
  South Coast                       0             320                  0
  Spa Country                       0               0                  0
  Sunshine Coast                    0               0                  0
  Sydney                            0             320                  0
  The Murray                        0             320                  0
  Tropical North Queensland         0               0                  0
  Upper Yarra                       0               0                  0
  Western Grampians                 0               0                  0
  Whitsundays                       0               0                  0
  Wilderness West                   0               0                  0
  Wimmera                           0               0                  0
  Yorke Peninsula                   0               0                  0
                                 
                                  Queensland South Australia Tasmania Victoria
  Adelaide                                 0             320        0        0
  Adelaide Hills                           0             320        0        0
  Alice Springs                            0               0        0        0
  Australia's Coral Coast                  0               0        0        0
  Australia's Golden Outback               0               0        0        0
  Australia's North West                   0               0        0        0
  Australia's South West                   0               0        0        0
  Ballarat                                 0               0        0      320
  Barkly                                   0               0        0        0
  Barossa                                  0             320        0        0
  Bendigo Loddon                           0               0        0      320
  Blue Mountains                           0               0        0        0
  Brisbane                               320               0        0        0
  Bundaberg                              320               0        0        0
  Canberra                                 0               0        0        0
  Capital Country                          0               0        0        0
  Central Coast                            0               0        0        0
  Central Highlands                        0               0        0      320
  Central Murray                           0               0        0      320
  Central NSW                              0               0        0        0
  Central Queensland                     320               0        0        0
  Clare Valley                             0             320        0        0
  Darling Downs                          320               0        0        0
  Darwin                                   0               0        0        0
  East Coast                               0               0      320        0
  Experience Perth                         0               0        0        0
  Eyre Peninsula                           0             320        0        0
  Fleurieu Peninsula                       0             320        0        0
  Flinders Ranges and Outback              0             320        0        0
  Fraser Coast                           320               0        0        0
  Geelong and the Bellarine                0               0        0      320
  Gippsland                                0               0        0      320
  Gold Coast                             320               0        0        0
  Goulburn                                 0               0        0      320
  Great Ocean Road                         0               0        0      320
  High Country                             0               0        0      320
  Hobart and the South                     0               0      320        0
  Hunter                                   0               0        0        0
  Kakadu Arnhem                            0               0        0        0
  Kangaroo Island                          0             320        0        0
  Katherine Daly                           0               0        0        0
  Lakes                                    0               0        0      320
  Lasseter                                 0               0        0        0
  Launceston, Tamar and the North          0               0      320        0
  Limestone Coast                          0             320        0        0
  MacDonnell                               0               0        0        0
  Macedon                                  0               0        0      320
  Mackay                                 320               0        0        0
  Mallee                                   0               0        0      320
  Melbourne                                0               0        0      320
  Melbourne East                           0               0        0      320
  Murray East                              0               0        0      320
  Murraylands                              0             320        0        0
  New England North West                   0               0        0        0
  North Coast NSW                          0               0        0        0
  North West                               0               0      320        0
  Northern                               320               0        0        0
  Outback                                320               0        0        0
  Outback NSW                              0               0        0        0
  Peninsula                                0               0        0      320
  Phillip Island                           0               0        0      320
  Riverina                                 0               0        0        0
  Riverland                                0             320        0        0
  Snowy Mountains                          0               0        0        0
  South Coast                              0               0        0        0
  Spa Country                              0               0        0      320
  Sunshine Coast                         320               0        0        0
  Sydney                                   0               0        0        0
  The Murray                               0               0        0        0
  Tropical North Queensland              320               0        0        0
  Upper Yarra                              0               0        0      320
  Western Grampians                        0               0        0      320
  Whitsundays                            320               0        0        0
  Wilderness West                          0               0      320        0
  Wimmera                                  0               0        0      320
  Yorke Peninsula                          0             320        0        0
                                 
                                  Western Australia
  Adelaide                                        0
  Adelaide Hills                                  0
  Alice Springs                                   0
  Australia's Coral Coast                       320
  Australia's Golden Outback                    320
  Australia's North West                        320
  Australia's South West                        320
  Ballarat                                        0
  Barkly                                          0
  Barossa                                         0
  Bendigo Loddon                                  0
  Blue Mountains                                  0
  Brisbane                                        0
  Bundaberg                                       0
  Canberra                                        0
  Capital Country                                 0
  Central Coast                                   0
  Central Highlands                               0
  Central Murray                                  0
  Central NSW                                     0
  Central Queensland                              0
  Clare Valley                                    0
  Darling Downs                                   0
  Darwin                                          0
  East Coast                                      0
  Experience Perth                              320
  Eyre Peninsula                                  0
  Fleurieu Peninsula                              0
  Flinders Ranges and Outback                     0
  Fraser Coast                                    0
  Geelong and the Bellarine                       0
  Gippsland                                       0
  Gold Coast                                      0
  Goulburn                                        0
  Great Ocean Road                                0
  High Country                                    0
  Hobart and the South                            0
  Hunter                                          0
  Kakadu Arnhem                                   0
  Kangaroo Island                                 0
  Katherine Daly                                  0
  Lakes                                           0
  Lasseter                                        0
  Launceston, Tamar and the North                 0
  Limestone Coast                                 0
  MacDonnell                                      0
  Macedon                                         0
  Mackay                                          0
  Mallee                                          0
  Melbourne                                       0
  Melbourne East                                  0
  Murray East                                     0
  Murraylands                                     0
  New England North West                          0
  North Coast NSW                                 0
  North West                                      0
  Northern                                        0
  Outback                                         0
  Outback NSW                                     0
  Peninsula                                       0
  Phillip Island                                  0
  Riverina                                        0
  Riverland                                       0
  Snowy Mountains                                 0
  South Coast                                     0
  Spa Country                                     0
  Sunshine Coast                                  0
  Sydney                                          0
  The Murray                                      0
  Tropical North Queensland                       0
  Upper Yarra                                     0
  Western Grampians                               0
  Whitsundays                                     0
  Wilderness West                                 0
  Wimmera                                         0
  Yorke Peninsula                                 0

The data structure is confirmed as (State / Region) * Purpose. Read the data dictionary, eyeball with View() or glimpse(), then use the commands above to confirm whether the data are HTS or GTS — and only then choose the matching reconciliation step.

6.3 Build, Model, Reconcile, Forecast

6.3.1 Why We Model This as GTS (not HTS)

The tourism dataset has the full structure (State / Region) * Purpose — geography is strictly nested and crossed with Purpose. So we have a real choice of how to model it:

  • HTS on State / Region — a clean nested tree. All four reconciliation methods (BU, TD, MO, MinT) are valid, but we lose the Purpose dimension entirely.
  • HTS on State / Purpose — forces Purpose into a single tree under State. Works, but treats Purpose as subordinate to geography, which is not how tourism analysts actually think about it.
  • GTS on State * Purpose — keeps State totals and Purpose totals as first-class aggregation views at the same time. This matches the real business question (“what’s happening by state?” and “what’s happening by purpose?”) and it’s the case where method choice actually matters — top_down() and middle_out() are not valid here, so you are forced to think about which reconciliation is appropriate.

We go with GTS because (i) it reflects how the data are actually consumed by decision-makers, and (ii) it’s the pedagogically interesting case — the one where “just pick any reconciliation method” fails.

The pipeline below is written so you only change one line (step 1) to swap between HTS and GTS. Everything downstream — fit, reconcile, forecast — stays the same.

# ──────────────────────────────────────────────────────────────
# 1. DECLARE THE STRUCTURE  ← change this ONE line to experiment
# ──────────────────────────────────────────────────────────────
# Uncomment exactly ONE option. The code below it works for all three.

tourism_struct <- tourism %>%
  aggregate_key(State * Purpose,  Trips = sum(Trips))    # GTS (used in this lecture)
  # aggregate_key(State / Region,  Trips = sum(Trips))   # HTS — strict geographic tree
  # aggregate_key(State / Purpose, Trips = sum(Trips))   # HTS — State then Purpose

# ──────────────────────────────────────────────────────────────
# 2. FIT BASE MODELS (creates a mable)
# ──────────────────────────────────────────────────────────────
fit <- tourism_struct %>%
  model(ets = ETS(Trips))

# ──────────────────────────────────────────────────────────────
# 3. RECONCILE
#    If you stay on GTS: leave top_down() commented (it errors on GTS).
#    If you switch to an HTS above: feel free to uncomment td_fp and/or td_ap.
# ──────────────────────────────────────────────────────────────
recon <- fit %>%
  reconcile(
    bu          = bottom_up(ets),                                    # HTS + GTS
    # td_fp     = top_down(ets, method = "forecast_proportions"),   # HTS only
    # td_ap     = top_down(ets, method = "average_proportions"),    # HTS only
    mint_shrink = min_trace(ets, method = "mint_shrink"),            # HTS + GTS
    mint_ols    = min_trace(ets, method = "ols")                     # HTS + GTS
  )

# ──────────────────────────────────────────────────────────────
# 4. FORECAST
# ──────────────────────────────────────────────────────────────
fc <- recon %>% forecast(h = 8)   # 8 quarters = 2 years

head(fc)
TipTry It Yourself

Play with the pipeline — the one-line swap at step 1 is the whole point.

  1. Swap to HTS by uncommenting one of the State / Region or State / Purpose lines. Rerun everything. Does the pipeline still work?
  2. Now uncomment td_fp = top_down(...) in step 3. On HTS it runs fine; on GTS it errors. Read the error — that’s fable telling you reconciliation validity is structure-dependent, not a bug.
  3. Change the base model — swap ETS(Trips) for ARIMA(Trips) or TSLM(Trips ~ trend() + season()). The reconciliation menu doesn’t change (it’s a property of the structure, not the model).
  4. Change the horizon — try h = 4 (1 year) vs h = 16 (4 years). Do MinT and Bottom-Up diverge more at longer horizons?

Note: the visualizations below use filter(... Purpose ...). If you switch to State / Region, you’ll need to swap Purpose for Region in those filters to see non-empty plots.


7 Visualizing Reconciled Forecasts

After generating the reconciled forecasts (fc), we visualize them to confirm:

  1. Coherence — forecasts add up correctly across hierarchy levels.
  2. Behavior — different reconciliation methods produce consistent, realistic trends.
# 1. Compare all reconciliation methods
autoplot(fc, tourism_struct) +
  labs(
    title    = "Forecast Reconciliation Methods (8-Quarter Horizon)",
    subtitle = "Comparing Bottom-Up, MinT (OLS), and MinT (Shrinkage)",
    y = "Trips", x = "Year"
  ) +
  facet_wrap(~ .model, scales = "free_y") +
  theme_minimal()

# 2. Focus on one state
fc %>%
  filter(State == "Victoria") %>%
  autoplot(tourism_struct) +
  labs(
    title    = "Reconciled Forecasts for Victoria",
    subtitle = "Comparison across Bottom-Up and MinT methods",
    y = "Trips", x = "Year"
  ) +
  facet_wrap(~ .model) +
  theme_minimal()

# 3. Top-level total (aggregated over State AND Purpose)
fc %>%
  filter(is_aggregated(State), is_aggregated(Purpose)) %>%
  autoplot(tourism_struct) +
  labs(
    title    = "Total Tourism Trips — Coherent Forecasts",
    subtitle = "Top-level series after reconciliation",
    y = "Trips", x = "Year"
  ) +
  facet_wrap(~ .model) +
  theme_minimal()

# 4. State-level totals (aggregated over Purpose)
fc %>%
  filter(!is_aggregated(State), is_aggregated(Purpose)) %>%
  autoplot(tourism_struct) +
  labs(
    title    = "State-Level Total Tourism Trips (Aggregated over Purpose)",
    subtitle = "Useful for comparing geographic performance",
    y = "Trips", x = "Year"
  ) +
  facet_wrap(~ State) +
  theme_minimal()

# 5. Purpose-level totals (aggregated over State)
fc %>%
  filter(is_aggregated(State), !is_aggregated(Purpose)) %>%
  autoplot(tourism_struct) +
  labs(
    title    = "Purpose-Level Total Tourism Trips (Aggregated over State)",
    subtitle = "Useful for analyzing trends by travel purpose",
    y = "Trips", x = "Year"
  ) +
  facet_wrap(~ Purpose) +
  theme_minimal()

Plot Aggregation Level Key Insight
1 All series Compare reconciliation methods visually
2 Victoria only Assess local forecast coherence
3 Total Check top-level consistency
4 State totals Compare states geographically
5 Purpose totals Understand motivation trends
TipForecasting Level — Your Choice

Once the structure is declared and reconciled, you can forecast at any level — bottom, middle, or top — because coherence is guaranteed.

HTS:

  • forecast at Region → sums automatically to State and Total
  • forecast at State → middle-out
  • forecast at Total → top-down

GTS:

  • forecast at State × Purpose → bottom-up gives totals
  • forecast at State (aggregated over Purpose) or Purpose (aggregated over State)

You choose the forecast level at the end — the structure keeps every level coherent.


8 Step-by-Step Workflow

When starting with hierarchical or grouped time series (e.g., the tourism data):

Step Task Example Command Purpose
1 Read the data dictionary documentation / metadata Understand variables (State, Region, Purpose)
2 Eyeball the data View(tourism), glimpse(tourism) Inspect the organization
3 Confirm structure type State / Region (HTS) or State * Purpose (GTS)? Hierarchical or grouped?
4 Build structure aggregate_key(State / Region, Trips = sum(Trips)) Define aggregation
5 Model model(ETS(Trips)) Fit base forecasting models
6 Reconcile reconcile(bu = bottom_up(...), mint = min_trace(...)) Enforce coherence
7 Forecast forecast(h = 8) Produce coherent forecasts at any level

9 Summary

Note
  • Hierarchical Time Series (HTS) — single tree; each level aggregates the ones below it. Simpler reconciliation.
  • Grouped Time Series (GTS) — multiple, possibly intersecting hierarchies; several valid aggregation paths. More complex reconciliation.
  • Reconciliation (BU, TD, MO, MinT) is essential for coherence; the valid choice depends on the structure declared via aggregate_key().
  • Base models (ETS, ARIMA, TSLM, …) are unchanged across HTS and GTS — only the reconciliation rule changes.

10 Further Reading

  • Hyndman, R.J., & Athanasopoulos, G. (2021). Forecasting: Principles and Practice (3rd ed.), Chapter 11: Forecasting hierarchical and grouped time series. OTexts.
  • Wickramasuriya, S.L., Athanasopoulos, G., & Hyndman, R.J. (2019). Optimal forecast reconciliation for hierarchical and grouped time series through trace minimization. Journal of the American Statistical Association, 114(526), 804-819.
  • Athanasopoulos, G., Hyndman, R.J., Kourentzes, N., & Petropoulos, F. (2017). Forecasting with temporal hierarchies. European Journal of Operational Research, 262(1), 60-74.