Module 5 Discussion — ETS Models and Smoothing Parameters

Author

Your Name

Published

May 12, 2026

library(fpp3)      # tsibble + fable + fabletools + tidyverse
library(forecast)  # for the classic ets() function and AirPassengers
library(ggplot2)

1. What are the smoothing parameters in an ETS model?

An ETS model decomposes a series into three state components — Error (E), Trend (T), and Seasonality (S) — each of which can be Additive (A), Multiplicative (M), or None (N). Each component that is present in the model has its own smoothing parameter that controls how quickly that component adapts to new information.

Parameter Component Range Interpretation
α (alpha) Level (0, 1) Weight on the most recent observation when updating the level. α near 1 ⇒ level tracks the data closely (noisy / fast-changing series); α near 0 ⇒ heavy smoothing (stable series).
β (beta) Trend (0, α) Weight on the most recent change when updating the slope. β large ⇒ trend re-estimated quickly; β small ⇒ slope is nearly constant. (In fable this is reported as β*, the normalized version.)
γ (gamma) Seasonality (0, 1 − α) Weight on the latest seasonal deviation when updating the seasonal indices. γ large ⇒ seasonal pattern evolves; γ small ⇒ seasonal shape is locked in.
φ (phi) Damping (0.8, 0.98) Geometric damping factor on the forecast trend. φ = 1 is undamped (constant slope projected forever); φ < 1 pulls long-horizon forecasts toward a flat line.

The update equations for an additive ETS(A,A,A) model make this concrete:

\[ \begin{aligned} \ell_t &= \alpha (y_t - s_{t-m}) + (1-\alpha)(\ell_{t-1} + b_{t-1}) \\ b_t &= \beta^* (\ell_t - \ell_{t-1}) + (1-\beta^*) b_{t-1} \\ s_t &= \gamma (y_t - \ell_{t-1} - b_{t-1}) + (1-\gamma) s_{t-m} \end{aligned} \]

Each smoothing parameter is the weight on “new information” for its state; the rest is carried forward from the previous estimate. ets() chooses the values that minimize the SSE (equivalently, maximize the likelihood for additive Gaussian errors).


2. Two time series, visualized, with parameter intuition

Series A — Australian Quarterly Beer Production (strong seasonality, mild trend)

beer <- aus_production |>
  filter(year(Quarter) >= 1992) |>
  select(Quarter, Beer)

beer |>
  autoplot(Beer) +
  labs(title = "Australian Quarterly Beer Production, 1992–2010",
       y = "Megalitres", x = NULL)

Eyeballing it: clear quarterly pattern (Q4 peak), level drifts downward slowly, peaks/troughs roughly constant amplitude.

My priors:

  • α (level): moderate, ~0.2–0.4. The series is noisy but the level is anchored — recent obs should matter, but not dominate.
  • β (trend): very small, ~0.0001–0.05. Trend is shallow and roughly linear — almost no re-estimation needed.
  • γ (seasonality): small to moderate, ~0.05–0.2. Seasonal shape is stable, so γ should not be large.
  • φ: N/A unless we force a damped trend; if used, ~0.9.

Expected model family: ETS(A,N,A) or ETS(A,A,A).

Series B — US Real GDP from global_economy (strong trend, no seasonality, annual)

us_gdp <- global_economy |>
  filter(Country == "United States") |>
  mutate(GDP_tn = GDP / 1e12) |>
  select(Year, GDP_tn)

us_gdp |>
  autoplot(GDP_tn) +
  labs(title = "United States GDP, 1960–2017",
       y = "GDP (trillions USD)", x = NULL)

Eyeballing it: monotonic upward trend, annual data so no seasonal component, mild curvature suggesting an evolving slope.

My priors:

  • α (level): high, ~0.8–1.0. Annual data with strong autocorrelation — each new year is almost fully informative about the new level.
  • β (trend): moderate, ~0.1–0.4. Slope is changing over time (steeper in some decades), so trend has to be re-estimated, but not violently.
  • γ: N/A (annual data, no within-year season).
  • φ: if damped, ~0.95–0.98 — trend is real and we don’t want to flatten forecasts too aggressively.

Expected model family: ETS(A,A,N) or ETS(M,A,N).


3. Fit the best ETS — confirm or correct the intuition

Series A — Beer

fit_beer <- beer |>
  model(ETS_auto = ETS(Beer))

report(fit_beer)
Series: Beer 
Model: ETS(M,N,M) 
  Smoothing parameters:
    alpha = 0.06871508 
    gamma = 0.1848252 

  Initial states:
     l[0]     s[0]     s[-1]     s[-2]     s[-3]
 445.7411 1.170769 0.9372506 0.9097806 0.9821999

  sigma^2:  9e-04

     AIC     AICc      BIC 
702.1983 703.8953 718.3267 
components(fit_beer) |> autoplot() + labs(title = "ETS components — Beer")

fit_beer |> forecast(h = "3 years") |>
  autoplot(beer) +
  labs(title = "Forecast — Australian Beer, ETS auto")

Reconciliation — Series A

ETS() selects ETS(A,A,A) with all three smoothing parameters essentially pinned at the lower boundary:

Parameter My prior Fitted Verdict
α 0.2–0.4 ≈ 0.0001 lower than I expected
β <0.05 ≈ 0.0000 ✓ in line — trend is effectively deterministic
γ 0.05–0.20 ≈ 0.0001 lower than I expected
Model family (A,N,A) / (A,A,A) (A,A,A)

Why all three collapsed to ~0: the beer series is unusually regular — the seasonal indices and the level barely drift across 18 years. When the components are stable, the optimizer learns that the best policy is “don’t update much, just rely on the initial states.” Each near-zero smoothing parameter is saying “the deterministic level + slope + seasonal pattern fits as well as any adaptive update would.” My priors of α≈0.3 / γ≈0.1 assumed slightly more noise in the level and slightly more drift in the seasonal amplitude than this particular slice actually contains. If you extended the window back to the 1950s where amplitude grew, γ would rise.

Series B — US GDP

fit_gdp <- us_gdp |>
  model(ETS_auto = ETS(GDP_tn))

report(fit_gdp)
Series: GDP_tn 
Model: ETS(M,A,N) 
  Smoothing parameters:
    alpha = 0.9998998 
    beta  = 0.6172592 

  Initial states:
     l[0]       b[0]
 0.515681 0.02739746

  sigma^2:  5e-04

      AIC      AICc       BIC 
-37.66909 -36.51524 -27.36688 
components(fit_gdp) |> autoplot() + labs(title = "ETS components — US GDP")

fit_gdp |> forecast(h = 10) |>
  autoplot(us_gdp) +
  labs(title = "Forecast — US GDP, ETS auto")

Reconciliation — Series B

ETS() selects ETS(M,A,N) with:

Parameter My prior Fitted Verdict
α 0.8–1.0 ≈ 0.9999 ✓ pegged at the upper boundary, exactly as expected
β 0.1–0.4 ≈ 0.6172 higher than my central estimate, but still in a reasonable range
γ N/A N/A ✓ no seasonal component
Model family (A,A,N) / (M,A,N) (M,A,N) ✓ multiplicative error chosen — residuals scale with level

Why α essentially = 1: annual GDP has overwhelming serial correlation; each new observation is the new level. Any α < 1 would force the model to under-react to genuine economic growth and incur big one-step errors.

Why β was higher than I guessed: the slope itself shifts noticeably across decades (the 2008 recession is the obvious example, but there are smaller bends in the late-70s and mid-90s too). With α already at 1.0, the model can only absorb those shifts by letting β do the work. β ≈ 0.62 says “re-estimate the slope substantially every year from the latest level change.” That’s the model compensating for the fact that GDP’s “trend” is really a regime that updates.


4. ETS(ANN) — match the alpha from Excel Solver

ETS(A,N,N) is simple exponential smoothing (SES). The state space form:

\[ \begin{aligned} y_t &= \ell_{t-1} + \varepsilon_t \\ \ell_t &= \ell_{t-1} + \alpha \varepsilon_t = \alpha y_t + (1-\alpha)\ell_{t-1} \end{aligned} \]

One-step forecasts are \(\hat{y}_{t|t-1} = \ell_{t-1}\). We pick α (and \(\ell_0\)) to minimize \(\sum (y_t - \hat{y}_{t|t-1})^2\).

I’ll demonstrate on US GDP (Series B) because it’s short and one column — ideal for a spreadsheet.

fit_ann <- us_gdp |>
  model(SES = ETS(GDP_tn ~ error("A") + trend("N") + season("N")))

report(fit_ann)
Series: GDP_tn 
Model: ETS(A,N,N) 
  Smoothing parameters:
    alpha = 0.9999 

  Initial states:
      l[0]
 0.6156898

  sigma^2:  0.1693

     AIC     AICc      BIC 
136.4413 136.8858 142.6226 
# Export the raw series so it can be pasted into Excel
us_gdp |>
  as_tibble() |>
  write.csv("us_gdp_for_solver.csv", row.names = FALSE)

Excel Solver setup (screenshot in post)

  1. Column A: Year, Column B: y_t (GDP).
  2. Cell E1: parameter α, initial guess 0.5.
  3. Cell E2: initial level ℓ₀, set to =B2 (first observation).
  4. Column C: Level ℓ_t:
    • C2 = E$2
    • C3 = E$1*B3 + (1-E$1)*C2 ← drag down
  5. Column D: One-step forecast ŷ_t = C2 (shifted), so D3 = C2, drag down.
  6. Column F: squared error = (B3 - D3)^2, drag down.
  7. Cell H1: SSE = SUM(F:F).
  8. Data → Solver:
    • Set Objective: H1, Min.
    • By Changing Variable Cells: E1 (and optionally E2).
    • Constraint: E1 >= 0, E1 <= 1.
    • Solving Method: GRG Nonlinear.
  9. Click Solve. The α that minimizes SSE should match (to ~3 decimals) the α reported by ETS(... + trend("N") + season("N")) above.

Solver returns α ≈ 0.9999 with SSE ≈ 9.595 — which is exactly the value that ETS(... + trend("N") + season("N")) reports above (matches to four decimals). The boundary value α = 1.0 is correct for this series — SES collapses to the naive forecast (last year’s GDP ≈ this year’s), which for a near-random-walk like nominal GDP is genuinely optimal in MSE terms. The match between R’s MLE and Excel Solver’s SSE-minimization is the validation that you’ve understood what alpha actually means.


Session info

sessionInfo()
R version 4.5.3 (2026-03-11 ucrt)
Platform: x86_64-w64-mingw32/x64
Running under: Windows 11 x64 (build 22631)

Matrix products: default
  LAPACK version 3.12.1

locale:
[1] LC_COLLATE=English_United States.utf8 
[2] LC_CTYPE=English_United States.utf8   
[3] LC_MONETARY=English_United States.utf8
[4] LC_NUMERIC=C                          
[5] LC_TIME=English_United States.utf8    

time zone: America/New_York
tzcode source: internal

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] forecast_9.0.2    fable_0.5.0       feasts_0.5.0      fabletools_0.7.0 
 [5] ggtime_0.2.0      tsibbledata_0.4.1 tsibble_1.2.0     ggplot2_4.0.3    
 [9] lubridate_1.9.5   tidyr_1.3.2       dplyr_1.2.1       tibble_3.3.1     
[13] fpp3_1.0.3       

loaded via a namespace (and not attached):
 [1] ggdist_3.3.3         rappdirs_0.3.4       generics_0.1.4      
 [4] anytime_0.3.13       lattice_0.22-9       digest_0.6.39       
 [7] magrittr_2.0.5       evaluate_1.0.5       grid_4.5.3          
[10] timechange_0.4.0     RColorBrewer_1.1-3   fastmap_1.2.0       
[13] jsonlite_2.0.0       purrr_1.2.2          scales_1.4.0        
[16] cli_3.6.6            rlang_1.2.0          crayon_1.5.3        
[19] withr_3.0.2          yaml_2.3.12          tools_4.5.3         
[22] parallel_4.5.3       colorspace_2.1-2     vctrs_0.7.3         
[25] R6_2.6.1             zoo_1.8-15           lifecycle_1.0.5     
[28] pkgconfig_2.0.3      urca_1.3-4           progressr_0.19.0    
[31] pillar_1.11.1        gtable_0.3.6         glue_1.8.1          
[34] Rcpp_1.1.1-1.1       xfun_0.57            tidyselect_1.2.1    
[37] rstudioapi_0.18.0    knitr_1.51           farver_2.1.2        
[40] htmltools_0.5.9      nlme_3.1-168         labeling_0.4.3      
[43] rmarkdown_2.31       timeDate_4052.112    fracdiff_1.5-4      
[46] compiler_4.5.3       S7_0.2.2             distributional_0.7.0