AR vs ADL vs VAR — Predicting Unemployment with Inflation
The Two Questions, Answered Up Front
Q1. I want to predict unemployment using past unemployment only. Is this an ADL model?
No. That’s an AR(p) (or ARIMA) model. ADL — Auto-regressive Distributed Lag — requires lags of the dependent variable and lags of an external regressor. With only one variable, there is no “distributed lag” of anything outside.
Q2. I add inflation as a second variable to predict unemployment. Is this a VAR model?
Not necessarily. If you write one equation with \(U_t\) on the left and lags of \(U\) + lags of \(\pi\) on the right, that’s an ADL model. A VAR is a system: every variable appears on the LHS of its own equation, and every equation has lags of every variable on the RHS. ADL = one equation; VAR = a system of equations.
| Model | Equations | LHS variables | RHS (each equation) |
|---|---|---|---|
| AR(p) | 1 | \(U_t\) | lags of \(U\) |
| ADL(p,q) | 1 | \(U_t\) | lags of \(U\) + lags of \(\pi\) |
| VAR(p) | 2 | \((U_t, \pi_t)\) | lags of \((U, \pi)\) in both equations |
Compactly:
\[ \text{AR:}\quad U_t = c + \sum_{i=1}^{p} \phi_i U_{t-i} + \varepsilon_t \]
\[ \text{ADL:}\quad U_t = c + \sum_{i=1}^{p} \phi_i U_{t-i} + \sum_{j=1}^{q} \beta_j \pi_{t-j} + \varepsilon_t \]
\[ \text{VAR(p):}\quad \begin{bmatrix} U_t \\ \pi_t \end{bmatrix} = \mathbf{c} + \sum_{i=1}^{p} \begin{bmatrix} \phi_{11,i} & \phi_{12,i} \\ \phi_{21,i} & \phi_{22,i} \end{bmatrix} \begin{bmatrix} U_{t-i} \\ \pi_{t-i} \end{bmatrix} + \begin{bmatrix} \varepsilon_{1,t} \\ \varepsilon_{2,t} \end{bmatrix} \]
We will build all three on the same data and check whether adding inflation actually helps forecast unemployment out-of-sample.
Setup and Data
Pull monthly unemployment rate (UNRATE) and the CPI (CPIAUCSL) from FRED, aggregate to quarters, and compute year-over-year inflation.
unrate_raw <- fredr(series_id = "UNRATE",
observation_start = as.Date("1960-01-01"))
cpi_raw <- fredr(series_id = "CPIAUCSL",
observation_start = as.Date("1960-01-01"))
# Combine, aggregate to quarterly, compute YoY inflation.
# `obs_date` rename avoids any clash with base::date().
quarterly <- bind_rows(unrate_raw, cpi_raw) %>%
rename(obs_date = date) %>%
mutate(Quarter = yearquarter(obs_date)) %>%
group_by(series_id, Quarter) %>%
summarise(value = mean(value, na.rm = TRUE), .groups = "drop") %>%
pivot_wider(names_from = series_id, values_from = value) %>%
rename(Unemployment = UNRATE, CPI = CPIAUCSL) %>%
mutate(Inflation = 100 * (CPI / lag(CPI, 4) - 1)) %>% # YoY % change
select(Quarter, Unemployment, Inflation) %>%
drop_na() %>%
as_tsibble(index = Quarter)
tail(quarterly)quarterly %>%
pivot_longer(c(Unemployment, Inflation), names_to = "Series", values_to = "Value") %>%
autoplot(Value) +
facet_wrap(~ Series, scales = "free_y", ncol = 1) +
labs(
title = "U.S. Unemployment Rate and YoY CPI Inflation",
subtitle = "Quarterly, FRED (UNRATE and CPIAUCSL)",
x = NULL, y = "Percent"
) +
theme_minimal()Train / Test Split
We hold out the last 4 quarters and train on everything before. Using a 4-quarter horizon keeps the ADL forecast ex-ante valid: the inflation lags it needs (lag 1 to lag 4) all sit inside the training set when forecasting up to 4 quarters ahead.
h <- 4
# Pre-compute lagged inflation columns so they live in both train and test
df <- quarterly %>%
mutate(
Inflation_l1 = lag(Inflation, 1),
Inflation_l2 = lag(Inflation, 2),
Inflation_l3 = lag(Inflation, 3),
Inflation_l4 = lag(Inflation, 4)
) %>%
drop_na()
cutoff <- max(df$Quarter) - h
train <- df %>% filter(Quarter <= cutoff)
test <- df %>% filter(Quarter > cutoff)
cat("Training: ", as.character(min(train$Quarter)), "to", as.character(max(train$Quarter)),
"(", nrow(train), "quarters)\n")Training: 1962 Q1 to 2025 Q1 ( 253 quarters)
cat("Test: ", as.character(min(test$Quarter)), "to", as.character(max(test$Quarter)),
"(", nrow(test), "quarters)\n")Test: 2025 Q2 to 2026 Q1 ( 4 quarters)
Model 1 — AR(p) on Unemployment Alone
This is the AR / ARIMA answer to Q1. The model knows nothing about inflation; all signal must come from unemployment’s own past.
fit_ar <- train %>%
model(AR = ARIMA(Unemployment))
report(fit_ar)Series: Unemployment
Model: ARIMA(1,0,0) w/ mean
Coefficients:
ar1 constant
0.9092 0.5265
s.e. 0.0254 0.0427
sigma^2 estimated as 0.5015: log likelihood=-271.57
AIC=549.14 AICc=549.23 BIC=559.74
Model 2 — ADL: Unemployment with Lagged Inflation
A single equation with \(U_t\) on the LHS and four lags of inflation on the RHS, plus the AR component (handled implicitly by ARIMA()). This is the ADL answer to Q2.
fit_adl <- train %>%
model(
ADL = ARIMA(Unemployment ~ Inflation_l1 + Inflation_l2 +
Inflation_l3 + Inflation_l4)
)
report(fit_adl)Series: Unemployment
Model: LM w/ ARIMA(1,0,0) errors
Coefficients:
ar1 Inflation_l1 Inflation_l2 Inflation_l3 Inflation_l4 intercept
0.9032 -0.0287 0.0340 -0.0156 0.0917 5.5022
s.e. 0.0266 0.0655 0.0703 0.0702 0.0657 0.5314
sigma^2 estimated as 0.5047: log likelihood=-270.3
AIC=554.61 AICc=555.07 BIC=579.34
Model 3 — VAR: Joint System of Unemployment and Inflation
A system in which both unemployment and inflation are endogenous: each is regressed on lags of both. Notice we never wrote Inflation ~ ... — VAR() does that for us.
Series: Unemployment, Inflation
Model: VAR(5) w/ mean
Coefficients for Unemployment:
lag(Unemployment,1) lag(Inflation,1) lag(Unemployment,2)
0.8377 -0.0223 0.0387
s.e. 0.0672 0.0698 0.0875
lag(Inflation,2) lag(Unemployment,3) lag(Inflation,3)
0.0584 0.0640 -0.0413
s.e. 0.1135 0.0875 0.1157
lag(Unemployment,4) lag(Inflation,4) lag(Unemployment,5)
0.0140 0.0673 -0.0635
s.e. 0.0878 0.1134 0.0662
lag(Inflation,5) constant
-0.0100 0.4351
s.e. 0.0698 0.1840
Coefficients for Inflation:
lag(Unemployment,1) lag(Inflation,1) lag(Unemployment,2)
-0.0256 1.3518 -0.0799
s.e. 0.0603 0.0627 0.0786
lag(Inflation,2) lag(Unemployment,3) lag(Inflation,3)
-0.3812 0.0940 0.2400
s.e. 0.1020 0.0786 0.1039
lag(Unemployment,4) lag(Inflation,4) lag(Unemployment,5)
0.0787 -0.5937 -0.0795
s.e. 0.0788 0.1019 0.0595
lag(Inflation,5) constant
0.3440 0.2295
s.e. 0.0627 0.1652
Residual covariance matrix:
Unemployment Inflation
Unemployment 0.5042 -0.1314
Inflation -0.1314 0.4068
log likelihood = -485.23
AIC = 1022.45 AICc = 1028.81 BIC = 1113.8
Forecast the Holdout Period
fc_ar <- fit_ar %>% forecast(h = h)
fc_adl <- fit_adl %>% forecast(new_data = test) # needs lagged inflation in test
fc_var <- fit_var %>% forecast(h = h)
# For VAR, as_tibble() returns .mean as a matrix with one column per response.
# Pull out the Unemployment column by name and stack with AR / ADL forecasts.
var_fc_tbl <- as_tibble(fc_var)
fc_combined <- bind_rows(
fc_ar %>% as_tibble() %>% transmute(Quarter, .mean, Model = "AR"),
fc_adl %>% as_tibble() %>% transmute(Quarter, .mean, Model = "ADL"),
tibble(
Quarter = var_fc_tbl$Quarter,
.mean = var_fc_tbl$.mean[, "Unemployment"],
Model = "VAR"
)
)
ggplot(fc_combined, aes(x = Quarter, y = .mean, colour = Model)) +
geom_line(linewidth = 1) +
geom_line(data = test, aes(x = Quarter, y = Unemployment),
inherit.aes = FALSE, colour = "black", linetype = "dashed") +
labs(
title = "Out-of-Sample Forecasts of U.S. Unemployment",
subtitle = "Dashed black = actual; coloured lines = each model",
y = "Unemployment rate (%)", x = NULL
) +
theme_minimal()Accuracy Comparison
Now the punchline. Compute RMSE, MAE, MAPE, and MASE on the holdout, then compare across the three models.
acc_ar <- fc_ar %>%
accuracy(test, measures = list(RMSE = RMSE, MAE = MAE, MAPE = MAPE, MASE = MASE)) %>%
mutate(Model = "AR (univariate)")
acc_adl <- fc_adl %>%
accuracy(test, measures = list(RMSE = RMSE, MAE = MAE, MAPE = MAPE, MASE = MASE)) %>%
mutate(Model = "ADL (U + lagged Inflation)")
acc_var <- fc_var %>%
accuracy(test, measures = list(RMSE = RMSE, MAE = MAE, MAPE = MAPE, MASE = MASE)) %>%
filter(.response == "Unemployment") %>%
mutate(Model = "VAR (joint U, Inflation)")
acc_table <- bind_rows(acc_ar, acc_adl, acc_var) %>%
select(Model, RMSE, MAE, MAPE, MASE) %>%
arrange(RMSE)
kable(
acc_table,
digits = 3,
caption = "Holdout Accuracy on the Last 4 Quarters of U.S. Unemployment"
)| Model | RMSE | MAE | MAPE | MASE |
|---|---|---|---|---|
| VAR (joint U, Inflation) | 0.142 | 0.117 | 2.710 | NaN |
| ADL (U + lagged Inflation) | 0.170 | 0.133 | 3.087 | NaN |
| AR (univariate) | 0.182 | 0.150 | 3.455 | NaN |
acc_table %>%
pivot_longer(c(RMSE, MAE, MAPE, MASE), names_to = "Metric", values_to = "Value") %>%
ggplot(aes(x = reorder(Model, Value), y = Value, fill = Model)) +
geom_col(show.legend = FALSE) +
coord_flip() +
facet_wrap(~ Metric, scales = "free_x") +
labs(
title = "Forecast Accuracy by Model and Metric",
subtitle = "Lower is better — ADL and VAR add inflation; AR uses only unemployment's own past",
x = NULL, y = NULL
) +
theme_minimal()Reading the Result
The intuition behind the comparison is straightforward:
- AR treats unemployment as a closed system. Its only signal is the inertia in \(U_t\) itself. It will track persistence well but misses any leading information that lives elsewhere in the macro economy.
- ADL lets the model “listen” to lagged inflation. To the extent that past inflation has any leading content for the next 4 quarters of unemployment (a Phillips-curve-style relationship), ADL captures it.
- VAR goes one step further: it models inflation jointly with unemployment, so feedback in both directions is allowed. For a unidirectional forecast of unemployment, VAR and ADL are often close in accuracy; the bigger payoff from VAR shows up when you also need a coherent inflation forecast.
If the table above shows AR with the highest RMSE/MAE and ADL/VAR lower, that backs the intuition: adding inflation as a predictor improves out-of-sample forecasts of unemployment. If the gap is small (or AR wins narrowly), the honest reading is that over the holdout window the inflation signal was weak relative to the autoregressive inertia — a useful negative result to discuss with students.
- Change
hto 8 or 12 and rerun. Does ADL/VAR’s edge over AR grow or shrink at longer horizons? - Restrict the sample to a single regime (e.g., post-1985) by filtering
quarterly. Does the inflation→unemployment signal change? - Replace YoY inflation with the quarter-over-quarter annualized rate. Does the noisier inflation series help or hurt?
- Add the Fed Funds rate (
FEDFUNDSfrom FRED) as a third variable and grow the VAR to 3 equations. Does the bigger system beat the 2-variable VAR?
Key Takeaways
- AR / ARIMA uses only the variable’s own lags — one equation, one input series.
- ADL is one equation but two input series: the dependent variable’s own lags + lags of an external regressor.
- VAR is a system of equations, with every variable both endogenous and used as a predictor.
- Choose AR when no external information is available; ADL when one regressor matters and only one variable needs forecasting; VAR when you need coherent forecasts of multiple variables that feed into each other.
References
- Hyndman, R.J., & Athanasopoulos, G. (2021). Forecasting: Principles and Practice (3rd ed.), Chapters 10 (Dynamic regression) and 12.3 (Vector autoregressions). OTexts.
- Stock, J.H., & Watson, M.W. (2001). Vector autoregressions. Journal of Economic Perspectives, 15(4), 101–115.