34 Diagnosing Autocorrelated Errors

34.1 Diagnosing Autocorrelated Errors

In the last two lectures we concentrated on fitting the mean structure of a time series with regression. Now we ask whether the residuals behave independently after that mean structure has been removed.

By the end of this lecture, students should be able to:

  1. Explain what autocorrelation means in regression residuals.
  2. Describe why autocorrelation makes ordinary standard errors unreliable.
  3. Use residual plots, runs tests, Durbin-Watson, ACF plots, and Ljung-Box tests.
  4. Recognise when dependence remains after a reasonable mean model has been fitted.

34.2 What Is Autocorrelation?

In ordinary regression, uncorrelated errors mean that once the model has explained the systematic structure, a positive residual on one row tells us nothing about the sign of the next residual.

If nearby residuals tend to be similar, the errors are positively autocorrelated. This gives the familiar long, slow wandering pattern. If nearby residuals tend to alternate above and below zero, the errors are negatively autocorrelated.

Autocorrelation can appear because:

  • an important time-related variable has been omitted,
  • the response really does have carry-over behaviour,
  • or both.

Why does this matter? Least-squares coefficients can still be unbiased, but the reported standard errors are often too small. That makes confidence intervals too narrow and tests too optimistic.

34.3 A Classical Example: Consumer Expenditure and Money Stock

This example studies consumer expenditure (ConsExp) and the stock of money (MoneyStk) in billions of US dollars.

Download MoneyStock.csv

MoneyStock <- read.csv("../data/MoneyStock.csv", header = TRUE)
MoneyStock |>
    head() |>
    kable()
Year Quarter ConsExp MoneyStk Year.Q
1952 1 214.6 159.3 1952.00
1952 2 217.7 161.2 1952.25
1952 3 219.6 162.8 1952.50
1952 4 227.2 164.6 1952.75
1953 1 230.9 165.9 1953.00
1953 2 233.3 167.9 1953.25
pairs(MoneyStock[3:5])

unlabelled

34.3.1 Fit the Regression Model

MoneyStock_lm1 <- lm(ConsExp ~ MoneyStk, data = MoneyStock)
summary(MoneyStock_lm1)

Call:
lm(formula = ConsExp ~ MoneyStk, data = MoneyStock)

Residuals:
   Min     1Q Median     3Q    Max 
-7.176 -3.396  1.396  2.928  6.361 

Coefficients:
             Estimate Std. Error t value Pr(>|t|)    
(Intercept) -154.7192    19.8500  -7.794 3.54e-07 ***
MoneyStk       2.3004     0.1146  20.080 8.99e-14 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 3.983 on 18 degrees of freedom
Multiple R-squared:  0.9573,    Adjusted R-squared:  0.9549 
F-statistic: 403.2 on 1 and 18 DF,  p-value: 8.988e-14

At first glance this looks like a useful regression. The fitted line is strong, and the standard output suggests a precise estimate of the slope. But that depends on the residual assumptions being correct.

34.3.2 Residual Plot Over Time

plot(residuals(MoneyStock_lm1), type = "b", ylab = "Residuals", xlab = "Quarter")
abline(h = 0, lty = 2)

unlabelled

The residuals show the long, slow wandering pattern typical of positive autocorrelation. Extended sequences of positive or negative residuals should not be common if the errors are independent.

34.4 Diagnostic Tools

34.4.1 Runs Test

The runs test looks only at the signs of the residuals and asks whether the sequence of plus and minus signs looks random.

RunsTest(residuals(MoneyStock_lm1))

    Runs Test for Randomness

data:  residuals(MoneyStock_lm1)
runs = 5, m = 10, n = 10, p-value = 0.008985
alternative hypothesis: true number of runs is not equal the expected number
sample estimates:
median(x) 
 1.395704 

A small p-value suggests the residuals are not appearing in random order.

34.4.2 Durbin-Watson Test

The Durbin-Watson test is a classical test for first-order autocorrelation in regression residuals.

dwtest(MoneyStock_lm1, alternative = "greater")

    Durbin-Watson test

data:  MoneyStock_lm1
DW = 0.32821, p-value = 2.303e-08
alternative hypothesis: true autocorrelation is greater than 0

Here the one-sided alternative reflects the suspicion of positive autocorrelation.

34.4.3 ACF Plot

The autocorrelation function (ACF) shows the correlation between residuals separated by different time lags.

acf(residuals(MoneyStock_lm1), main = "ACF of MoneyStock residuals")

unlabelled

When the lag-1 autocorrelation is substantial, later lags often look large as well because nearby residuals carry information forward through time. A slow decay in the ACF is strong visual evidence that independence is failing.

34.4.4 Ljung-Box Test

The Ljung-Box test checks several autocorrelations at once rather than focusing only on lag 1.

Box.test(residuals(MoneyStock_lm1), lag = 10, type = "Ljung-Box")

    Box-Ljung test

data:  residuals(MoneyStock_lm1)
X-squared = 64.397, df = 10, p-value = 5.286e-10

If this test rejects, the residual series still contains dependence structure that the fitted regression has not explained.

34.5 A Modern Comparison: Seoul Bike Residuals

The same diagnostic logic applies to the daily bike model from the previous lecture.

Download seoul_bike_daily.csv

seoul_daily <- read_csv("../data/seoul_bike_daily.csv", show_col_types = FALSE) |>
    mutate(S1 = sin(2 * pi * day_index/365), C1 = cos(2 * pi * day_index/365), S2 = sin(4 *
        pi * day_index/365), C2 = cos(4 * pi * day_index/365))

seoul_mean <- lm(rented_bike_count ~ day_index + S1 + C1 + S2 + C2 + mean_temperature +
    total_rainfall + total_solar_radiation, data = seoul_daily)
plot(residuals(seoul_mean) ~ seoul_daily$day_index, type = "b", ylab = "Residuals",
    xlab = "Day index")
abline(h = 0, lty = 2)

unlabelled

RunsTest(residuals(seoul_mean))

    Runs Test for Randomness

data:  residuals(seoul_mean)
z = -5.5558, runs = 130, m = 183, n = 182, p-value = 2.763e-08
alternative hypothesis: true number of runs is not equal the expected number
sample estimates:
median(x) 
 474.7272 
dwtest(seoul_mean, alternative = "greater")

    Durbin-Watson test

data:  seoul_mean
DW = 1.7794, p-value = 0.007159
alternative hypothesis: true autocorrelation is greater than 0
Box.test(residuals(seoul_mean), lag = 14, type = "Ljung-Box")

    Box-Ljung test

data:  residuals(seoul_mean)
X-squared = 64.679, df = 14, p-value = 1.745e-08
acf(residuals(seoul_mean), main = "ACF of Seoul bike residuals")

unlabelled

Although the bike model has a much richer mean structure than the MoneyStock regression, the same pattern can still remain: long runs in the residual plot, slow decay in the ACF, and small p-values from formal tests.

34.6 Diagnostic Summary

When several of the following point in the same direction,

  • the residual plot shows long runs,
  • the runs test rejects randomness,
  • the Durbin-Watson test rejects independence,
  • the ACF decays slowly,
  • and the Ljung-Box test rejects white noise,

we should stop treating the errors as independent.

34.7 Looking Ahead

Diagnosis tells us there is a problem, but not yet how to fix it. The next lecture introduces a simple and important correction: a regression model with AR(1) errors fitted by generalized least squares.