Reading the ACF and PACF to tell AR from MA

Today let's understand two plots that, read together, tell you what kind of pattern is still hiding in a time series once the trend and the season are gone: the ACF, and a second plot called the PACF.

The running example is AirPassengers, R's built-in monthly count of international airline passengers, in thousands, from January 1949 through December 1960. One ordinary difference and one seasonal difference, at lag 12, already removed its climbing trend and its yearly season, leaving 131 months behind.

Here is what remains.

It wanders around zero with no obvious climb and no obvious yearly repeat left in it. But flat to the eye is not the same thing as free of pattern, and telling those two apart is exactly what the ACF and the PACF, read side by side, let you do.

What the ACF leaves out, and what the PACF adds

The ACF, short for autocorrelation function, measures how strongly a series is related to its own past. Its value at lag k is the correlation between the series and that same series shifted back k steps. A significance band, roughly plus or minus 2 divided by the square root of how many values you have, marks how far that correlation can wander by pure chance when there is really no dependence at that lag.

But the ACF cannot separate a direct effect from an indirect one. Its value at lag 2 mixes the direct pull of lag 2 on today's value with an indirect pull that arrives secondhand: lag 2 helps shape lag 1, and lag 1 helps shape today, so part of what the ACF reports at lag 2 is really lag 1's influence passed along the chain, not lag 2 acting on its own.

The partial autocorrelation function, the PACF, strips that indirect part out. The PACF at lag k is the coefficient on the k-th lag when today's value is regressed on lags 1 through k all at once. With lags 1 through k-1 already sitting in that same regression, whatever coefficient lag k earns is its direct pull alone, once every indirect path through the closer lags has been accounted for.

Write that regression out for lag k:

\[ y_t = \alpha_1 y_{t-1} + \alpha_2 y_{t-2} + \cdots + \alpha_k y_{t-k} + \varepsilon_t \]

The PACF at lag k is \(\alpha_k\), the coefficient on the k-th lag in that particular regression. Check a different lag and you run a different regression, one more lag long, and read off its own last coefficient.

See that difference on a real series instead of in the abstract. Build a simulated series 200 points long, with a known rule linking each value to its own last two lags. Then compare the ACF's raw value at lag 2 against the lag-2 coefficient from regressing today's value on its first two lags together.

RInteractive R
# Compare the ACF at lag 2 with the lag-2 coefficient from a two-lag regression library(tsibble) library(feasts) library(dplyr) set.seed(6) ar2_y <- as.numeric(arima.sim(list(ar = c(0.6, -0.3)), n = 200)) ar2 <- tsibble(t = 1:200, y = ar2_y, index = t) round((ar2 |> ACF(y, lag_max = 2))$acf[2], 3) #> [1] -0.062 y_now <- ar2_y[3:200] y_lag1 <- ar2_y[2:199] y_lag2 <- ar2_y[1:198] round(coef(lm(y_now ~ y_lag1 + y_lag2)), 3) #> (Intercept) y_lag1 y_lag2 #> -0.079 0.619 -0.350 round((ar2 |> PACF(y, lag_max = 2))$pacf[2], 3) #> [1] -0.343

  

The raw ACF at lag 2 comes out at -0.062, small. But once lag 1 sits in the regression alongside it, lag 2's own coefficient comes out at -0.350, close to what PACF() reports directly for lag 2, -0.343 (the small gap between -0.350 and -0.343 is just two different ways of estimating the same thing, a plain regression here against PACF's own method, not a real disagreement). Lag 2's raw correlation was hiding most of its real, direct pull, because a large share of it was arriving secondhand, through lag 1.