library(fpp3)
library(fredr)
fredr_set_key(Sys.getenv("FRED_API_KEY"))
setwd("~/Library/Mobile Documents/com~apple~CloudDocs/Boston College/Spring 2026/Forecasting/Discussions/Module 9")
retail_raw <- fredr(
series_id = "RSXFSN",
observation_start = as.Date("2010-01-01"))
retail <- retail_raw |>
mutate(Month = yearmonth(date)) |>
select(Month, value) |>
rename(Sales = value) |>
as_tsibble(index = Month)
retail |> autoplot(Sales) +
labs(title = "US Retail Sales",
y = "Millions of dollars", x = NULL)Module 9
ARIMA vs ETS
Data + Plot
Split into Train/Test
train <- retail |> slice(1:floor(0.8 * n()))
test <- retail |> slice((floor(0.8 * n()) + 1):n())
train# A tsibble: 155 x 2 [1M]
Month Sales
<mth> <dbl>
1 2010 Jan 273665
2 2010 Feb 269889
3 2010 Mar 314812
4 2010 Apr 310622
5 2010 May 318467
6 2010 Jun 312635
7 2010 Jul 314597
8 2010 Aug 315974
9 2010 Sep 301406
10 2010 Oct 308709
# ℹ 145 more rows
test# A tsibble: 39 x 2 [1M]
Month Sales
<mth> <dbl>
1 2022 Dec 637438
2 2023 Jan 535580
3 2023 Feb 516582
4 2023 Mar 589560
5 2023 Apr 574331
6 2023 May 615939
7 2023 Jun 597476
8 2023 Jul 591150
9 2023 Aug 613511
10 2023 Sep 579306
# ℹ 29 more rows
Fitting both Models
fit <- train |>
model(
ets = ETS(Sales),
arima = ARIMA(Sales)
)
fit |> select(ets) |> report()Series: Sales
Model: ETS(M,A,M)
Smoothing parameters:
alpha = 0.5421074
beta = 0.000108813
gamma = 0.0001060196
Initial states:
l[0] b[0] s[0] s[-1] s[-2] s[-3] s[-4] s[-5]
319974.6 1850.523 1.145351 1.013066 0.9911094 0.9630888 1.031154 1.013457
s[-6] s[-7] s[-8] s[-9] s[-10] s[-11]
1.013306 1.042289 0.982475 1.016705 0.8888571 0.8991418
sigma^2: 8e-04
AIC AICc BIC
3697.537 3702.004 3749.276
fit |> select(arima) |> report()Series: Sales
Model: ARIMA(0,1,2)(0,1,1)[12]
Coefficients:
ma1 ma2 sma1
-0.2226 -0.3221 -0.7293
s.e. 0.0832 0.0845 0.0751
sigma^2 estimated as 164263882: log likelihood=-1547.8
AIC=3103.61 AICc=3103.9 BIC=3115.43
Interpretation: The ETS selected was ETS(M, A, M) and the ARIMA selected was ARIMA(0, 1, 2)(0, 1, 1)[12]. The models are not equivalent because ETS models with multiplicative components have no exact ARIMA counterpart, and the selected ETS contains multiplicative errors and multiplicative seasonality, while ARIMA is a strictly additive linear framework.
ARIMAX
sent_raw <- fredr(
series_id = "UMCSENT",
observation_start = as.Date("2010-01-01")
)
sent <- sent_raw |>
mutate(Month = yearmonth(date)) |>
select(Month, value) |>
rename(Sentiment = value)
#merge sentiment into retail tsibble
retail_x <- retail |>
left_join(sent, by = "Month")|>
mutate(Sentiment_lag1 = lag(Sentiment)) #lag sentiment by 1 month
retail_x# A tsibble: 194 x 4 [1M]
Month Sales Sentiment Sentiment_lag1
<mth> <dbl> <dbl> <dbl>
1 2010 Jan 273665 74.4 NA
2 2010 Feb 269889 73.6 74.4
3 2010 Mar 314812 73.6 73.6
4 2010 Apr 310622 72.2 73.6
5 2010 May 318467 73.6 72.2
6 2010 Jun 312635 76 73.6
7 2010 Jul 314597 67.8 76
8 2010 Aug 315974 68.9 67.8
9 2010 Sep 301406 68.2 68.9
10 2010 Oct 308709 67.7 68.2
# ℹ 184 more rows
Split and Fitting the Model
train_x <- retail_x |> slice(1:floor(0.8 * n()))
test_x <- retail_x |> slice((floor(0.8 * n()) + 1):n())
fit_all <- train_x |>
model(
ets = ETS(Sales),
arima = ARIMA(Sales),
arimax = ARIMA(Sales ~ Sentiment_lag1) #ADDED a lagged term
)
fit_all |> select(arima) |> report()Series: Sales
Model: ARIMA(0,1,2)(0,1,1)[12]
Coefficients:
ma1 ma2 sma1
-0.2226 -0.3221 -0.7293
s.e. 0.0832 0.0845 0.0751
sigma^2 estimated as 164263882: log likelihood=-1547.8
AIC=3103.61 AICc=3103.9 BIC=3115.43
fit_all |> select(arimax) |> report()Series: Sales
Model: LM w/ ARIMA(4,1,0) errors
Coefficients:
ar1 ar2 ar3 ar4 Sentiment_lag1 intercept
-0.6877 -0.7339 -0.4781 -0.3594 -600.7734 1811.8125
s.e. 0.0756 0.0852 0.0847 0.0746 432.0283 750.7964
sigma^2 estimated as 924091793: log likelihood=-1794.48
AIC=3602.96 AICc=3603.73 BIC=3624.22
fit_all |> select(ets) |> report()Series: Sales
Model: ETS(M,A,M)
Smoothing parameters:
alpha = 0.5421074
beta = 0.000108813
gamma = 0.0001060196
Initial states:
l[0] b[0] s[0] s[-1] s[-2] s[-3] s[-4] s[-5]
319974.6 1850.523 1.145351 1.013066 0.9911094 0.9630888 1.031154 1.013457
s[-6] s[-7] s[-8] s[-9] s[-10] s[-11]
1.013306 1.042289 0.982475 1.016705 0.8888571 0.8991418
sigma^2: 8e-04
AIC AICc BIC
3697.537 3702.004 3749.276
Accuracy Table
fc_all <- fit_all |> forecast(new_data = test_x)
accuracy(fc_all, retail_x) |>
select(.model, RMSE, MAE, MAPE, MASE) |>
arrange(RMSE)# A tibble: 3 × 5
.model RMSE MAE MAPE MASE
<chr> <dbl> <dbl> <dbl> <dbl>
1 ets 18003. 14353. 2.33 0.613
2 arimax 38812. 28340. 4.80 1.21
3 arima 38816. 34788. 5.78 1.49
Conclusion:
I would choose the ETS model because it has the best accuracy metrics. It’s multiplicative error and multiplicative seasonal components capture the proportional holiday spikes in retail sales better than the ARIMA models. The ARIMAX outperformed the plain ARIMA on every metric, confirming the expectation that a good external regressor should improve the performance of a basic ARIMA model. Lagged consumer sentiment added forecasting power on top of the pure time-series structure, but not enough to beat the ETS model.
Plot
zoom_start <- min(test_x$Month) - 12 # show 12 months of training before test starts
fc_all |>
autoplot(train_x, level = 95) +
autolayer(test_x, Sales, colour = "black", linewidth = 0.5) +
coord_cartesian(xlim = c(zoom_start, max(test_x$Month))) +
labs(title = "ETS vs ARIMA vs ARIMAX Forecasts: Retail Sales",
subtitle = "Test period with 12 months of prior training context",
y = "Millions of dollars", x = NULL) +
theme_minimal()