Introduction

Chapter 8 moves us from decomposition into a genuinely different way of thinking about a series: instead of splitting it into trend/season/remainder up front, exponential smoothing methods build the forecast directly out of weighted averages of past observations, where the weights decay exponentially the further back in time you go (Hyndman & Athanasopoulos, 2021). This chapter makes the progression really explicit, SES handles level only, Holt adds a trend term, and Holt-Winters adds a seasonal term on top of that. It’s basically a Lego-block build-up to the full ETS framework.

For the previous three weeks I’ve worked with MAUISA (Week 1), Victorian liquor retailing (Week 2), and vic_elec (Week 3). For this one I wanted a series where all three methods are actually meaningful to fit side by side, not just technically possible, but where you’d expect real separation in performance, so I picked aus_production’s Beer column: quarterly Australian beer production in megalitres, 1956 Q1 through 2010 Q2 (Australian Bureau of Statistics, Cat. 8301.0.55.001). It’s NSA, like my previous picks, and unlike MAUISA it doesn’t have the BEA-adjustment issue that flattened out seasonality in Week 1, this series has an unmistakable quarterly cycle (Q4 spikes, presumably tied to summer demand) layered on top of a trend that rises through the 1970s–80s and then gently declines as the industry consolidates.

beer <- aus_production %>%
  select(Quarter, Beer) %>%
  filter(!is.na(Beer))

range(beer$Quarter)
## <yearquarter[2]>
## [1] "1956 Q1" "2010 Q2"
## # Year starts on: January
beer %>%
  ggplot(aes(x = Quarter, y = Beer)) +
  geom_line(color = ink, linewidth = 0.5) +
  theme_bc() +
  labs(title = "Australian Quarterly Beer Production",
       subtitle = "1956 Q1 – 2010 Q2, megalitres",
       x = NULL, y = "Megalitres",
       caption = bc_badge)

You can see both ingredients right away: a trend that isn’t monotonic (it turns over around the mid-1970s), and seasonal swings that stay fairly constant in amplitude over time, which is why an additive seasonal component is the right call rather than multiplicative.

Applying Exponential Smoothing Models

I held out the last two years (8 quarters) as a test set and trained all three models on everything before that, so the accuracy comparison is genuinely out-of-sample rather than in-sample fit.

train <- beer %>% filter(Quarter <= yearquarter("2008 Q2"))
test  <- beer %>% filter(Quarter > yearquarter("2008 Q2"))

nrow(train); nrow(test)
## [1] 210
## [1] 8
fit <- train %>%
  model(
    SES  = ETS(Beer ~ error("A") + trend("N") + season("N")),
    Holt = ETS(Beer ~ error("A") + trend("A") + season("N")),
    HW   = ETS(Beer ~ error("A") + trend("A") + season("A"))
  )

fc <- fit %>% forecast(h = 8)
params <- tidy(fit) %>%
  filter(term %in% c("alpha", "beta", "gamma"))

params
## # A tibble: 6 × 3
##   .model term  estimate
##   <chr>  <chr>    <dbl>
## 1 SES    alpha   0.150 
## 2 Holt   alpha   0.0774
## 3 Holt   beta    0.0215
## 4 HW     alpha   0.206 
## 5 HW     beta    0.0298
## 6 HW     gamma   0.248

The three smoothing parameters each control how fast a different component of the model updates as new data arrives:

  • alpha governs the level - how much weight goes on the most recent observation versus the accumulated history. For SES, 0.15 is fairly low, meaning the level estimate barely reacts to any single quarter, which makes sense for a smoothing-only model that has to absorb both trend and seasonal swings into one number.
  • beta governs the trend — how quickly the slope estimate adapts. For both Holt and Holt-Winters this comes out quite small (0.021 and 0.03 respectively), which tells me the trend is being treated as close to fixed over the training window rather than something that whips around quarter to quarter, reasonable, given beer production trends move slowly.
  • gamma governs the seasonal indices — how fast the quarterly pattern itself can shift. Holt-Winters estimates this at 0.248, moderate relative to alpha and beta, which suggests the seasonal shape is fairly stable across the 50+ years of data but not perfectly rigid.

Evaluating Model Accuracy

acc <- fc %>% accuracy(beer)
acc %>% select(.model, RMSE, MAE, MAPE)
## # A tibble: 3 × 4
##   .model  RMSE   MAE  MAPE
##   <chr>  <dbl> <dbl> <dbl>
## 1 HW      13.2  11.6  2.73
## 2 Holt    41.0  28.0  6.16
## 3 SES     38.6  28.9  6.55
fc_df <- fc %>% as_tibble()

beer %>%
  filter(Quarter >= yearquarter("2000 Q1")) %>%
  ggplot(aes(x = Quarter, y = Beer)) +
  geom_line(color = "grey45", linewidth = 0.4) +
  geom_line(data = fc_df, aes(x = Quarter, y = .mean), color = ink, linewidth = 0.7) +
  facet_wrap(~.model, ncol = 1) +
  theme_bc() +
  labs(title = "Forecasts vs. Actuals by Method (last 8 quarters held out)",
       subtitle = "Grey = actual production · Navy = point forecast",
       x = NULL, y = "Megalitres",
       caption = bc_badge)

acc %>%
  select(.model, RMSE, MAE, MAPE) %>%
  pivot_longer(-.model, names_to = "metric", values_to = "value") %>%
  ggplot(aes(x = value, y = reorder(.model, value))) +
  geom_segment(aes(x = 0, xend = value, yend = .model), color = "grey75") +
  geom_point(color = ink, size = 3) +
  facet_wrap(~metric, scales = "free_x") +
  theme_bc() +
  labs(title = "Out-of-Sample Accuracy by Method",
       x = NULL, y = NULL,
       caption = bc_badge)

The gap here is not subtle. Holt-Winters comes in around 13.2 RMSE and 2.7% MAPE on the test set, while SES sits around 38.6 RMSE and Holt around 41. That’s roughly a two-to-threefold RMSE reduction just from adding the seasonal term. What actually surprised me a little is that Holt’s linear trend doesn’t clearly beat plain SES here, on some metrics it’s essentially tied or even slightly worse. My read is that because beta is estimated so close to zero, Holt isn’t really adding much beyond SES’s level-only forecast except a small linear drift, and that drift alone can’t do anything about the fact that both methods are still trying to average over a strong quarterly cycle. It’s a nice concrete illustration of a point that also came up in my Week 3 work: a model component that isn’t actually present in the data-generating process (real seasonality here, real curvature there) doesn’t just fail to help, it can leave you worse off than a simpler model, because you’re spending “trust” on a component that isn’t earning its keep. Holt-Winters is the only one of the three whose functional form actually matches what’s in the data.

Scenario Analysis: Sudden Shifts and Disruptions

This is where I think exponential smoothing’s biggest weakness shows up. All three models here update their state (level, trend, season) through a fixed set of smoothing weights that were estimated to fit the average behavior of the training window. If beer production had, say, dropped 30% in a single quarter due to a supply shock, none of these models has any mechanism to recognize “this is a break” versus “this is normal noise”, they’d just nudge the level down a little each period, governed by whatever alpha happened to be estimated, and take several quarters to catch up. A low alpha (which is what we got for SES) makes this even slower, since the whole point of a low alpha is to not overreact to any single observation.

This is really the same lesson I ran into with COVID-19 in my final project on beer/wine/liquor retail sales: the structural break fell entirely in the test set there too, and every model, ETS included, systematically under-forecast the demand surge because nothing in the training data had prepared the smoothing parameters for a shock of that size. Exponential smoothing methods are good at tracking gradual evolution in level, trend, and season; they are not built to detect regime changes, and I don’t think there’s a version of alpha/beta/gamma tuning that fixes that without also making the model twitchy in normal periods.

A few ways I’d think about improving this for a genuinely disruption-prone series:

  1. Dynamic regression with ARIMA errors, where a known intervention (a dummy variable for the disruption period) is modeled explicitly rather than asking the smoothing weights to absorb it implicitly.
  2. Rolling re-estimation on a shorter, more recent window after a suspected break, rather than re-fitting on the full history, so the model isn’t anchored to pre-shock behavior.
  3. Explicit structural-break monitoring (e.g., CUSUM-type checks on the residuals) that triggers a manual re-specification rather than trusting the automatic smoothing to catch up on its own.
  4. Where the shock is anticipated (a known policy change, a planned plant closure), just hand-adjusting the forecast for the affected periods and letting ETS handle the rest, sometimes the simplest fix is not to ask the statistical model to do something it fundamentally isn’t built for.

I’m still a little unsure where the line is between “this is worth building a dynamic regression for” and “this is rare enough that hand-adjusting after the fact is more practical”, that feels like it depends a lot on how often a given series actually gets hit with this kind of shock, which is hard to know in advance.

References

Hyndman, R. J., & Athanasopoulos, G. (2021). Forecasting: Principles and practice (3rd ed.). OTexts. https://otexts.com/fpp3/

Australian Bureau of Statistics. (n.d.). Manufacturing production, Australia (Cat. No. 8301.0.55.001).