This report forecasts US GDP Growth Rate using classical time series methods. GDP growth is one of the most closely watched economic indicators — understanding its trajectory helps businesses, policymakers, and investors make informed decisions.
Using quarterly data sourced from the Federal Reserve Economic Data (FRED), we apply and compare two industry-standard forecasting approaches:
ARIMA — captures autocorrelation structure in the series
ETS (Exponential Smoothing) — captures level, trend, and seasonality
Skills demonstrated: time series decomposition, stationarity testing, ARIMA/ETS modeling, forecast evaluation, and business interpretation.
autoplot(gdp_ts) +geom_hline(yintercept =0, color ="red", linetype ="dashed") +labs(title ="US GDP Growth Rate (Quarterly, 1960–2023)",subtitle ="Annualized % Change | Source: FRED",x =NULL, y ="GDP Growth (%)") +theme_minimal() +theme(plot.title =element_text(face ="bold"))
The series shows clear cyclical patterns tied to business cycles, with notable contractions during the 1970s oil shocks, the 2008 Financial Crisis, and the unprecedented COVID-19 collapse in 2020 Q2 (−31.2%), followed by a sharp rebound.
3 Exploratory Analysis
3.1 Decomposition
Show Code
gdp_ts |>decompose(type ="additive") |>autoplot() +labs(title ="Time Series Decomposition — US GDP Growth") +theme_minimal()
The decomposition separates the series into trend, seasonal, and remainder components. The trend component clearly captures major economic cycles, while the seasonal component shows mild but consistent quarterly patterns.
par(mfrow =c(1, 2))acf(gdp_ts, lag.max =24, main ="ACF — GDP Growth")pacf(gdp_ts, lag.max =24, main ="PACF — GDP Growth")
The ADF test confirms the series is stationary (p < 0.05) — meaning we can model it directly without differencing. The ACF and PACF plots guide the ARIMA order selection.
4 Train / Test Split
Show Code
n <-length(gdp_ts)train_ts <-ts(head(gdp_ts, n -8),start =start(gdp_ts),frequency =4)test_ts <-ts(tail(gdp_ts, 8),start =tsp(gdp_ts)[2] -7/4+1/4,frequency =4)cat("Training periods:", length(train_ts), "quarters\n")
fc_arima <-forecast(model_arima, h =length(test_ts))rmse_arima <-sqrt(mean((test_ts - fc_arima$mean)^2))mae_arima <-mean(abs(test_ts - fc_arima$mean))autoplot(fc_arima) +autolayer(test_ts, series ="Actual", color ="red", linewidth =0.8) +labs(title ="ARIMA Forecast vs Actual — US GDP Growth",subtitle =paste0("RMSE: ", round(rmse_arima, 3)," | MAE: ", round(mae_arima, 3)),x =NULL, y ="GDP Growth (%)") +theme_minimal() +theme(legend.position ="bottom")
Show Code
checkresiduals(model_arima)
Ljung-Box test
data: Residuals from ARIMA(0,1,2)
Q* = 2.7286, df = 6, p-value = 0.8421
Model df: 2. Total lags used: 8
Interpretation:auto.arima() selected the best-fitting ARIMA specification based on AIC. The residual diagnostics show residuals behaving close to white noise — no significant autocorrelation remaining, which validates the model specification.
6 Model 2 — ETS (Exponential Smoothing)
Show Code
model_ets <-ets(train_ts)data.frame(Parameter =c("Model", "AIC", "BIC", "Log-Likelihood"),Value =c( model_ets$method,round(model_ets$aic, 2),round(model_ets$bic, 2),round(model_ets$loglik, 2) )) |>kable(caption ="ETS — Selected Model & Fit Statistics")
ETS — Selected Model & Fit Statistics
Parameter
Value
Model
ETS(A,N,N)
AIC
1389.94
BIC
1399.45
Log-Likelihood
-691.97
Show Code
fc_ets <-forecast(model_ets, h =length(test_ts))rmse_ets <-sqrt(mean((test_ts - fc_ets$mean)^2))mae_ets <-mean(abs(test_ts - fc_ets$mean))autoplot(fc_ets) +autolayer(test_ts, series ="Actual", color ="red", linewidth =0.8) +labs(title ="ETS Forecast vs Actual — US GDP Growth",subtitle =paste0("RMSE: ", round(rmse_ets, 3)," | MAE: ", round(mae_ets, 3)),x =NULL, y ="GDP Growth (%)") +theme_minimal() +theme(legend.position ="bottom")
Show Code
checkresiduals(model_ets)
Ljung-Box test
data: Residuals from ETS(A,N,N)
Q* = 9.6568, df = 8, p-value = 0.2899
Model df: 0. Total lags used: 8
Interpretation: The ETS model automatically selects the optimal error, trend, and seasonality components. It weights recent observations more heavily — making it responsive to structural shifts like post-COVID recovery.
actual_df <-data.frame(Date =as.numeric(time(test_ts)),Value =as.numeric(test_ts),Model ="Actual")arima_df <-data.frame(Date =as.numeric(time(fc_arima$mean)),Value =as.numeric(fc_arima$mean),Model ="ARIMA")ets_df <-data.frame(Date =as.numeric(time(fc_ets$mean)),Value =as.numeric(fc_ets$mean),Model ="ETS")bind_rows(actual_df, arima_df, ets_df) |>ggplot(aes(x = Date, y = Value,color = Model, linewidth = Model)) +geom_line() +scale_color_manual(values =c("Actual"="black","ARIMA"="#2c7bb6","ETS"="#d7191c")) +scale_linewidth_manual(values =c("Actual"=1.2,"ARIMA"=0.9,"ETS"=0.9)) +labs(title ="Forecast Comparison — ARIMA vs ETS vs Actual",subtitle =paste0("Last ", length(test_ts)," quarters held out as test set"),x =NULL, y ="GDP Growth (%)",color =NULL, linewidth =NULL) +theme_minimal() +theme(legend.position ="bottom",plot.title =element_text(face ="bold"))
Show Code
results |>pivot_longer(cols =c(RMSE, MAE),names_to ="Metric",values_to ="Value") |>ggplot(aes(x = Model, y = Value, fill = Model)) +geom_col(show.legend =FALSE, width =0.5) +facet_wrap(~Metric, scales ="free_y") +scale_fill_manual(values =c("ARIMA"="#2c7bb6","ETS"="#d7191c")) +labs(title ="ARIMA vs ETS — Error Metrics",x =NULL, y =NULL) +theme_minimal()
8 Key Business Insights
GDP growth is mean-reverting — after sharp contractions (2008, 2020), the series reliably rebounds toward its long-run average of ~2.5%, supporting counter-cyclical investment strategies.
ARIMA captures autocorrelation better — GDP growth in one quarter is partially explained by the previous quarter, which ARIMA explicitly models.
ETS is more adaptive post-shock — its heavier weighting of recent data makes it respond faster to structural breaks like COVID recovery.
Best model for stable periods: ARIMA — lower error when the economy behaves predictably.
Best model for volatile periods: ETS — adapts faster when conditions shift rapidly.
Forecast uncertainty widens quickly — confidence intervals expand significantly beyond 2 quarters, reflecting the inherent difficulty of GDP forecasting.