
Lecture 6: Trend and Seasonality
24 September 2026
Last time, we ended with a taxonomy: trend, seasonality, cycles.
Today we model the first two components. Write
y_t = T_t + S_t + C_t + \varepsilon_t .
Trend and seasonality are the components we can treat as deterministic: fixed functions of the calendar, hence known exactly at every future date. That makes them the easiest things in this course to forecast, and the natural place to start.
Cycles, C_t, are the hard part. We leave them for later discussion.
Diebold, Forecasting in Economics, Business, Finance and Beyond, Chapter 5. The last section follows Hodrick and Prescott (1997) and Phillips and Shi (2021).
Consider a regression on time-series data,
y_t = x_t'\beta + \varepsilon_t, \qquad t = 1, \ldots, T, \qquad \varepsilon_t \sim \text{iid}\; N(0, \sigma^2).
The same equation holds in the period we care about, T+h. Under quadratic loss the optimal forecast is the conditional mean (Lecture 4), so
E(y_{T+h} \mid x_{T+h}) = x_{T+h}'\beta .
Suppose we even knew \beta. We would still need x_{T+h}, the regressors *at the . . .
This is the forecasting-the-right-hand-side-variables (FRV) problem. It is real, but far less damaging than it looks.
y_t = T_t + S_t + C_t + \varepsilon_t
This lecture: trend alone, y_t = T_t + \varepsilon_t; then seasonality alone, y_t = S_t + \varepsilon_t; then both together.
Two kinds of trend:
Sometimes a series rises or falls like a straight line:
T_t = \beta_0 + \beta_1\, TIME_t .

Trends need not be linear, but it needs to be smooth. A quadratic function of time captures a variable that grows at an increasing or decreasing rate:
T_t = \beta_0 + \beta_1\, TIME_t + \beta_2\, TIME_t^2 .

Left to right: \beta_1, \beta_2 > 0, increasing at an increasing rate; \beta_1 > 0 > \beta_2, inverted U; \beta_1, \beta_2 < 0, decreasing; \beta_1 < 0 < \beta_2, U-shaped. Linear trend is the case \beta_2 = 0. In practice these are local approximations: one rarely sees a whole U.
Many economic series grow at a roughly constant rate, say three percent a year. Constant growth at rate \beta_1 means
T_t = \beta_0\, e^{\beta_1\, TIME_t}, \qquad\text{equivalently}\qquad \ln T_t = \ln\beta_0 + \beta_1\, TIME_t .
Nonlinear in levels, linear in logarithms: exponential or log-linear trend.

Logarithms are natural logarithms throughout. Recall from Lecture 5 why we took logs of liquor sales: proportional movements become additive.
Both bend. Both can increase or decrease at an increasing or decreasing rate. Are they the same thing?
No. A quadratic and an exponential are different curves, and a series that is well approximated by one may be poorly approximated by the other.
Neither is “better” in general. Which fits a particular series is an empirical matter, settled by estimating both and comparing. We do that next.
Create TIME = (1, 2, \ldots, T) and TIME^2 = (1, 4, \ldots, T^2), then run a regression. In Diebold’s shorthand (c denotes an intercept):
| Trend | Regression | In R |
|---|---|---|
| linear | y \to c,\ TIME | lm(y ~ TIME) |
| quadratic | y \to c,\ TIME,\ TIME^2 | lm(y ~ TIME + I(TIME^2)) |
| exponential, in logs | \ln y \to c,\ TIME | lm(log(y) ~ TIME) |
The first two are ordinary OLS regressions and need no comment.
The third is OLS too, but on \ln y: the intercept estimates \ln \beta_0, not \beta_0, and the fitted values are fitted values of \ln y. Exponentiate both to get back to levels.
Or fit the exponential trend directly in levels:
(\hat\beta_0, \hat\beta_1) = \underset{\beta_0,\, \beta_1}{\arg\min}\ \sum_{t=1}^{T} \left( y_t - \beta_0\, e^{\beta_1\, TIME_t} \right)^2 .
No closed form: the computer searches numerically over (\beta_0, \beta_1). This is nonlinear least squares (NLS).
“Argmin” is the argument that minimizes. Numerical search tries many candidate values, so it is slower than OLS and can stop at a local rather than the global minimum. Good starting values, from the log regression for instance, matter.
At time T we hold \{y_1, \ldots, y_T\} and want y_{T+h}. The linear trend model holds at every date, in particular at T+h:
y_{T+h} = \beta_0 + \beta_1\, TIME_{T+h} + \varepsilon_{T+h} .
Two future quantities on the right-hand side.
y_{T+h,T} = \beta_0 + \beta_1 (T+h), \qquad\qquad \hat y_{T+h,T} = \hat\beta_0 + \hat\beta_1 (T+h) .
The subscript “T+h, T” reads: a forecast of T+h, made at T. The hats make it operational. Quadratic and exponential trends: identical steps.
Fit improves with every parameter; out-of-sample accuracy need not (Lecture 4: each parameter costs about \sigma^2 / T). Information criteria charge for parameters explicitly:
AIC = \ln\frac{SSR}{T} + \frac{2k}{T}, \qquad\qquad SIC = \ln\frac{SSR}{T} + \frac{k \ln T}{T},
with k the number of estimated coefficients. Smaller is better. The first term rewards fit, the second penalizes size; SIC’s penalty is the heavier one whenever T > e^2 \approx 7.4, so SIC leans toward parsimony.
Only compare models with the same dependent variable on the same sample: y and \ln y are not comparable. Diebold writes AIC = e^{2k/T} SSR/T and SIC = T^{k/T} SSR/T; those are the exponentials of the versions above, so the rankings coincide. DW is the Durbin-Watson statistic, explained on the next slides.
US retail sales, monthly, millions of current dollars, seasonally adjusted, 1955M1 to 1994M12. We estimate on 1955M1 to 1993M12 (T = 468) and hold out 1994 to check the forecasts.

“Seasonally adjusted”: a statistical agency removed the seasonal variation first. A clear nonlinear trend, and not much else.
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -16391.2480 1469.1767 -11.1568 0
## TIME 349.7731 5.4287 64.4307 0
## R2 SER DW AIC SIC
## 0.8991 15866.1151 0.0047 19.3481 19.3659
Serial correlation makes OLS standard errors and t-statistics untrustworthy; Newey-West (HAC) standard errors repair them. AIC and SIC remain valid. Later lectures model the correlation instead of just correcting for it.

The residuals are first all positive, then all negative for fifteen years, then all positive. The linear trend is simply inadequate: the true trend is curved.
Shaded bands are NBER recessions. fit_plot(), fc_plot() and dec_year() are short ggplot helpers defined in the source of these slides.
## (Intercept) TIME I(TIME^2)
## 18708.7003 -98.3113 0.9554
## R2 SER DW AIC SIC
## 0.9970 2728.2050 0.1511 15.8292 15.8558

R^2 is now 0.997 and the fitted curve tracks the series. The residuals are still persistent (DW = 0.15) but they are now recession-sized dips and boom-sized bumps: business cycle, not trend. No trend model can explain those.
## (Intercept) TIME
## 9.38997 0.00593
## R2 SER DW AIC SIC
## 0.9871 0.0919 0.0199 -4.7703 -4.7526

Growth of 0.59% per month, about 7.4% per year in nominal terms. The fit looks good. But every statistic here is in logs, so none of them can be compared with the linear and quadratic models in levels.
## b0 b1
## 12769.06695 0.00578
## R2 SER DW AIC SIC
## 0.9893 5168.3697 0.0423 17.1049 17.1226

Now in levels, so comparable: better than the linear trend, worse than the quadratic. The book’s EViews output reports \hat\beta_0 = 11968; it stopped after one iteration, while nls() continues to a lower sum of squares. Same conclusion either way.
## R2 SER DW AIC SIC
## linear 0.899 15866.115 0.005 19.348 19.366
## quadratic 0.997 2728.205 0.151 15.829 15.856
## exponential 0.989 5168.370 0.042 17.105 17.123
## log-linear (logs!) 0.987 0.092 0.020 -4.770 -4.753
Informal impressions, in-sample fit, and the criteria all agree here. They will not always. When they disagree, the criteria win: they are the only ones that price parsimony.
## t point lower upper
## 1 1994.00 182752.3 177405.0 188099.6
## 2 1994.08 183551.1 178203.8 188898.4
## 3 1994.17 184351.8 179004.6 189699.1

History 1990M1 to 1993M12, point forecast and 95% interval for 1994. The point forecasts look reasonable. The interval has constant width because the model treats deviations from trend as noise, which the residual plot said they are not.

Now add the realization (red). The forecast is quite good: the realization hugs the extrapolated trend, and all twelve months fall inside the 95% interval.

The point forecast is below the last observation, because by 1993 the linear trend sits far under the sample path. The interval is enormous (\hat\sigma = 15{,}866 versus 2{,}728) and still misses every month.
Forecasts are computed as if the model were the true process. A poor approximation to the trend gives confidently wrong forecasts, and the interval will not save you.
y_t = S_t + \varepsilon_t
Seasonality is a pattern that repeats every year, produced by technologies, preferences and institutions that are tied to the calendar.
You might guess that only a few obviously seasonal series are affected. On the contrary: seasonality is pervasive in business and economic data, and it is often the largest source of variation within a year.
Seasonality is impossible, and therefore a non-issue, in data recorded once a year or less often.
Let s be the number of observations per year: s = 4 for quarterly data, 12 for monthly, 52 for weekly. Create s dummies, one per season. With s = 4,
D_1 = (1,0,0,0,1,0,0,0,\ldots),\quad D_2 = (0,1,0,0,0,1,0,0,\ldots),\quad D_3 = (0,0,1,0,\ldots),\quad D_4 = (0,0,0,1,\ldots).
Exactly one of them equals 1 at any date.
The deterministic seasonal component is
S_t = \sum_{i=1}^{s} \gamma_i D_{it} ,
an intercept that changes with the season and repeats every year. The \gamma_i are the seasonal factors; together they are the seasonal pattern.
No seasonality means \gamma_1 = \cdots = \gamma_s: drop the dummies and keep an ordinary intercept. Like TIME, the dummies are known at every future date: deterministic seasonality is perfectly predictable.
Regress y on the full set of dummies, with no intercept:
y \to D_1, \ldots, D_s .
Why no intercept: the dummies sum to 1 at every date, so an intercept plus all s dummies is perfectly collinear (the dummy variable trap, Lecture 3). Keep the intercept and drop one dummy instead, and you get the same fit with a different parameterization: an intercept plus s - 1 differences from the omitted season.
Blend trend and seasonality by adding TIME and TIME^2: \ y \to TIME,\ TIME^2,\ D_1, \ldots, D_s.
When the intercept is dropped, R’s summary() reports an R^2 measured about zero instead of about the mean, and it comes out absurdly high. fit_stats() uses the centered version.
y_t = \sum_{i=1}^{s} \gamma_i D_{it} + \varepsilon_t \qquad\Longrightarrow\qquad y_{T+h} = \sum_{i=1}^{s} \gamma_i D_{i,T+h} + \varepsilon_{T+h} .
Project the right-hand side on \Omega_T. The dummies at T+h are known with certainty and the disturbance forecasts to zero, so
y_{T+h,T} = \sum_{i=1}^{s} \gamma_i D_{i,T+h}, \qquad\qquad \hat y_{T+h,T} = \sum_{i=1}^{s} \hat\gamma_i D_{i,T+h} .
In words: the forecast for next May is the estimated May factor, whatever the horizon. No FRV problem.
Density and interval forecasts exactly as for trends: N(\hat y_{T+h,T}, \hat\sigma^2) and \hat y_{T+h,T} \pm 1.96\,\hat\sigma.
US housing starts, monthly, thousands of units, 1946M1 to 1994M11. Estimation sample 1946M1 to 1993M12 (T = 576); holdout 1994M1 to 1994M11.

Housing starts is an economic indicator that reflects the number of privately owned new houses (technically housing units) on which construction has been started in a given period. Houses are started in spring so that they finish before winter: the same within-year shape every year, and no trend. A pure seasonal model, then.
## Estimate Std. Error t value Pr(>|t|)
## mJan 86.504 4.029 21.470 0
## mFeb 89.504 4.029 22.215 0
## mMar 122.883 4.029 30.499 0
## mApr 142.169 4.029 35.286 0
## mMay 147.500 4.029 36.609 0
## mJun 145.998 4.029 36.236 0
## mJul 139.113 4.029 34.527 0
## mAug 138.417 4.029 34.355 0
## mSep 130.563 4.029 32.405 0
## mOct 134.092 4.029 33.281 0
## mNov 111.833 4.029 27.757 0
## mDec 92.158 4.029 22.873 0
## R2 SER DW AIC SIC
## 0.384 27.914 0.154 6.679 6.770
R^2 = 0.38: the twelve dummies explain more than a third of the variation. The rest is largely cyclical (DW = 0.15, a measure of serial correlation (will be explained in the next lecture)), which the model was not built to capture. Every standard error is the same because every month has 48 observations: \hat\sigma / \sqrt{48}.

The fitted values go through the same twelve numbers every year; nothing else is in the model, yet that rigid pattern picks up a lot. What it misses is serially correlated: dips in the residuals in the recessions of 1975, 1980, 1982 and 1990 (shaded), peaks in booms.

Very low in January and February, a quick rise to a peak in May, then a slow decline until October and an abrupt drop in November and December.
With only dummies on the right-hand side, each \hat\gamma_i is simply the sample mean of season i. Regression on seasonal dummies is monthly averages with standard errors attached, and the factors are exactly the blue lines of gg_subseries() from Lecture 5.

The forecast is the seasonal pattern itself, and it looks reasonable. The interval is wide, because the seasonal factors account for only a third of the variation in the series.

With the realization added: the forecast is close throughout, and the realization stays well inside the 95% interval. The model did its job; the job was to predict the season, and the season is predictable.
Lecture 5’s liquor series has both components, and its seasonal swings grow with the level, so we work in logs. Log-quadratic trend plus twelve seasonal dummies, estimated on 1987M1 to 2012M12 with 2013 and 2014 held out for evaluation:
\ln y_t = \beta_1\, TIME_t + \beta_2\, TIME_t^2 + \sum_{i=1}^{12} \gamma_i D_{it} + \varepsilon_t .
liquor <- tibble(Sales = scan("data/liquor_sales.csv", skip = 1, quiet = TRUE)) |>
mutate(Month = yearmonth(seq(as.Date("1987-01-01"), by = "month", length.out = n()))) |>
as_tsibble(index = Month)
lest <- liquor |> filter(year(Month) <= 2012) |>
mutate(TIME = row_number(), m = factor(month(Month), labels = month.abb))
lq <- lm(log(Sales) ~ 0 + TIME + I(TIME^2) + m, data = lest)
signif(coef(lq)[1:2], 3); round(coef(lq)[-(1:2)], 3); round(fit_stats(lq), 4)## TIME I(TIME^2)
## 7.42e-03 -1.05e-05
## mJan mFeb mMar mApr mMay mJun mJul mAug mSep mOct mNov mDec
## 6.153 6.095 6.181 6.180 6.251 6.256 6.298 6.274 6.211 6.234 6.267 6.590
## R2 SER DW AIC SIC
## 0.9884 0.0444 0.6438 -6.1842 -6.0162

Trend and seasonal together explain 99% of the variation in log sales. The December factor sits about 0.37 above the average of the other eleven: December sales are e^{0.37} \approx 1.45 times a typical month. The residuals are persistent (DW = 0.64) and recession-shaped. The cycle, again.

Forecast in logs, then exponentiate the point forecast and both interval endpoints. The interval is asymmetric in levels. The two Decembers are forecast almost exactly; the forecast is a little high in 2013, when growth slowed more than the quadratic expected.
TSLM() builds TIME and the dummies for you, and forecast() returns the distribution, back-transformed to levels.

One difference from the hand version: fable’s point forecast is the mean of the back-transformed distribution, ours was the median e^{\hat p}; with \hat\sigma = 0.044 they differ by 0.1%. The interval here also includes parameter uncertainty.
Removing seasonality is called seasonal adjustment. With dummies it is a subtraction: take out the estimated seasonal factors, centered, and keep the rest.

Macroeconomists usually want the nonseasonal movements, and official series come pre-adjusted (Census X-13ARIMA-SEATS). Business forecasters usually want all the variation: if seasonality is most of what moves your series, the last thing to do is discard it.
Dummies are one basis for a periodic pattern. Sines and cosines are another:
S_t = \sum_{p=1}^{P} \left[ \delta_{c,p} \cos\!\left( \frac{2\pi p\, t}{s} \right) + \delta_{s,p} \sin\!\left( \frac{2\pi p\, t}{s} \right) \right].
Smooth, and parsimonious: 2P coefficients instead of s. With P = s/2 the two bases are identical; with small P you get a smoothed seasonal. Choose P with AIC or SIC. Indispensable for daily data, where s = 365 dummies is not appealing.
## R2 SER DW AIC SIC
## 12 dummies 0.384 27.914 0.154 6.679 6.770
## Fourier, P = 2 0.376 27.913 0.193 6.667 6.705
Five parameters instead of twelve, nearly the same fit, and both criteria prefer Fourier. In fable: fourier(K = 2).

Two harmonics reproduce the rise, the plateau and the fall; what they smooth away is the small kink at the September to October step, which is well within a standard error anyway.
Ravn and Uhlig (2002) argue for \lambda = 6.25 with annual and \lambda = 129{,}600 with monthly data.
The objective is quadratic in y^\star, so the solution is linear in the data:
\hat y^\star = (I_T + \lambda D'D)^{-1} y, \qquad D = \begin{pmatrix} 1 & -2 & 1 & & \\ & 1 & -2 & 1 & \\ & & \ddots & \ddots & \ddots \end{pmatrix}_{(T-2) \times T}.
hp_filter <- function(y, lambda) {
T <- length(y); D <- diff(diag(T), differences = 2) # second-difference matrix
trend <- as.numeric(solve(diag(T) + lambda * crossprod(D), y))
list(trend = trend, cycle = y - trend)
}
data("IRE", package = "bHP") # log Irish GDP, annual 1981-2016; used again below
max(abs(hp_filter(IRE, 100)$trend - bHP::BoostedHP(IRE, lambda = 100, iter = FALSE)$trend))## [1] 1.634248e-13

Quarterly, 1947Q1 to 2026Q2, from FRED (series GDPC1). The cycle turns down in every shaded recession; a typical swing is \pm 2\%, and 2020 is off that scale.
lam <- 100 # tuning parameter for the annual data
# raw HP filter
bx_HP <- bHP::BoostedHP(IRE, lambda = lam, iter = FALSE)
# stopping stands for the condition of the terminal of iteration
# by BIC
bx_BIC <- bHP::BoostedHP(IRE, lambda = lam, iter = TRUE, stopping = "BIC")
# by ADF
bx_ADF <- bHP::BoostedHP(IRE, lambda = lam, iter = TRUE, stopping = "adf")
c(BIC = bx_BIC$iter_num, ADF = bx_ADF$iter_num) # passes until the stopping rule fires## BIC ADF
## 5 19

One HP pass (\lambda = 100) leaves a trend that ignores the 2008 collapse and the 2015 jump, so both end up in the “cycle”. Boosting bends the trend toward the data: five passes under BIC, nineteen under the ADF rule, at which point the remaining cycle is stationary at the 5% level.

The HP trend at date t is a weighted average of the data on both sides of t. That is fine in the middle of the sample. At the end, where a forecaster lives, there is no future data to average over: the last trend values are the least reliable, and they get revised as new observations arrive.
So the filter is a tool for describing history, not for extrapolating it. Hamilton (2018) makes the case at length under the title “Why you should never use the Hodrick-Prescott filter”; Phillips and Shi’s reply is titled “Why you can use the HP filter”.
To forecast a trending series, model the trend and the cycle explicitly. That is what the rest of the course does.
library(fpp3)
d <- d |> mutate(TIME = row_number(), m = factor(month(Month), labels = month.abb))
# ---- trends -------------------------------------------------------------------
lm(y ~ TIME, data = d); lm(y ~ TIME + I(TIME^2), data = d); lm(log(y) ~ TIME, data = d)
nls(y ~ b0 * exp(b1 * TIME), data = d, start = list(b0 = 100, b1 = 0.01)) # exponential, in levels
# ---- seasonals ----------------------------------------------------------------
lm(y ~ 0 + m, data = d) # 12 dummies = monthly means
lm(y ~ 0 + TIME + I(TIME^2) + m, data = d) # trend and seasonal together
lm(y ~ cos(2*pi*t/12) + sin(2*pi*t/12), data = d) # Fourier, P = 1
# ---- forecasts ----------------------------------------------------------------
predict(fit, newdata = data.frame(TIME = T + 1:h, m = ...)) # point forecasts
sigma(fit) # SER: interval is point +- 1.96 * sigma
fit_stats(fit) # R2, SER, DW, AIC, SIC (defined in these slides)
model(d, TSLM(log(y) ~ trend() + I(trend()^2) + season())) |> forecast(h = 24) # fable
# ---- detrending ---------------------------------------------------------------
hp_filter(y, lambda = 1600) # trend and cycle (defined in these slides)
bHP::BoostedHP(y, lambda = 1600, iter = TRUE, stopping = "adf") # boosted HPTSLM(), trend(), season() and fourier().bHP.data/retail_sales.csv, data/housing_starts.csv — from the Elements of Forecasting companion files, converted from EViews.data/liquor_sales.csv — as in Lecture 5. data/GDPC1.csv — real GDP from FRED, downloaded September 2026.IRE — annual log GDP of Ireland, 1981–2016, shipped with bHP.