Introduction to Data Analysis

9.1. Autocorrelation

Jay Ulfelder, a political scientist who has been credited for publishing rather accurate lists of most likely military coups in recent years, wrote on that topic: “Unsurprisingly, coups also turn out to be a recurrent problem; the risk is higher in countries that have experienced other coup attempts in the past several years, a factor common to the top eight countries on this list.”

This observation is applicable in many different contexts with relatively slow-moving quantities: the unemployment rate, for instance, is highly redundant from one month to another, modulo a small percent change; GDP approximates itself from one year to another; and so on. In brief, the best way to predict an event at time \(t\) is often to look at that same event at \(t-1, t - 2, …, t - k\), with \(k\) lags.

This form of temporal dependence is called autocorrelation, or serial correlation, in the context of time series. They can be shown in specific plot arrangements, like correlograms, or expressed through notions like marginal change (which mathematically relies on derivatives), lagged values, or detrended series. We will sample a few of these methods below.

# Load packages.
packages <- c("downloader", "ggplot2", "MASS", "reshape", "splines")
packages <- lapply(packages, FUN = function(x) {
    if (!require(x, character.only = TRUE)) {
        library(x, character.only = TRUE)

A time series of air pollution in Beijing

We used a [Twitter source][sw-ms] that logs pollution data in Beijing, where there was a pollution peak in January 2013 (the city looked like Blade Runner with more fog, and staying in Beijing then amounted to smoking 1.5 to 3 cigarettes a day).

Unfortunately, the Twitter API is now less open than it used to be, so we will not be able to access the data publicly. I have saved the results of a recent scrape for your viewing pleasure, as the U.S. Embassy has not turned the logging machine down even when pressured so. Read first on the Air Quality Index (AQI) used to measure pollution information.

# Target data source.
link = ""
file = "data/beijing.aqi.2013.csv"
if(!file.exists(file)) download(link, file)
# Read CSV file.
bp <- read.csv(file, stringsAsFactors = FALSE)
# Check result.
     PM                time
1 174.7 2013-01-06 12:00:00
2 150.3 2013-01-07 00:00:00
3 203.0 2013-01-07 12:00:00
4 118.8 2013-01-08 00:00:00
5  25.2 2013-01-08 12:00:00
6  78.5 2013-01-09 00:00:00
# Convert date.
bp$time <- strptime(bp$time, format = "%Y-%m-%d %T")
# Plot air pollution.
ggplot(data = bp, aes(x = time, y = PM)) +
  geom_line(color = "gray80") +
  geom_point(color = "blue", alpha = .5) +
  geom_smooth(fill = "lightblue") +
  labs(x = NULL, y = "Fine particles (PM2.5) 24hr avg")

plot of chunk twitter-pollution-auto

In this example, the main interest is the time series formed by the repeated measures of a single variable. Teetor, ch. 14, offers a quick introduction to the topic, using the dedicated zoo and xts packages. Our introduction will just show how to detrend and lag the series with base functions.

There's plenty of other ways to look at the data. Here's one way with cubic splines.

# Plot cubic spline with 2-length knots.
ggplot(data = bp, aes(x = time, y = PM)) +
  geom_line(color = "gray80") +
  geom_point(color = "blue", alpha = .5) +
  geom_smooth(method ="rlm", formula = y ~ ns(x, 12), alpha = .25, fill = "lightblue") +
  labs(x = NULL, y = "Fine particles (PM2.5) 24hr avg")

plot of chunk twitter-spline-auto,

Detrending a time series

Differencing a time series is to subtract \(x_{t-1}\) to all its values \(x_{t}\), that is, obtain the net difference between two values separated by one time period. This is equivalent to asking for the marginal change in \(x\) at every point \(t\) of the curve \(x(t)\). The diff function offers a very convenient way to obtain lagged differences:

# Plot a differenced time series.
qplot(x = bp$time[-1], 
      y = diff(bp$PM), 
      geom = "line") + 
  labs(x = "t")

plot of chunk ts-diff

Similarly, if you lag the series by \(k = 1, 2, …, n\) and then divide it by its original values, you will get an indication of how frequent the pollution spikes are within the overall trend. This is particularly useful to detect seasonal patterns, like weekly spikes and drops.

# Set vector of lagged values.
d = 1:9
# Create lags for one to eight days.
lags = sapply(d, FUN = function(i) { c(bp$PM[-1:-i], rep(NA, i)) } )
# Divide lagged values by series.
lags = lags / bp$PM
# Create lags dataset.
lags = data.frame(bp$time, lags)
# Fix variables names.
names(lags) = c("t", d)
# Melt data over days.
lags = melt(lags, id = "t", variable = "lag")
# Plot lagged dataset.
qplot(data = lags,
      x = t,
      y = value,
      colour = lag,
      geom = "line") + 
  labs(x = "t") + 
  scale_colour_brewer() + 
  theme(legend.position = "none")

plot of chunk ts-lags

Lagging a series is useful to control for autocorrelation, i.e. the series of correlation coefficients \(\rho_k\) for \(k = 1, 2, …, k\) lags. This is done by regressing the data onto its time index, for which Teetor, ch. 14.13-14.21, provides a fuller treatment. We will content ourselves with autocorrelation function plots, drawn with ggplot2:

# Correlogram function.
gglag <- function(x, method = "acf") {
  data =, list(x, plot = FALSE))
  qplot(y = 0,
        yend  = data$acf,
        size  = data$acf,
        color = data$acf,
        x     = data$lag,
        xend  = data$lag,
        geom  = "segment") +
  scale_y_continuous("", lim = c(ifelse(method == "acf", 0, -1), 1)) +
  scale_size(toupper(method)) +
                        low = "blue", 
                        mid = "white", 
                        high = "red", 
                        midpoint = 0) +
  labs(x = "Number of lags")

This function will draw the correlograms (plots of correlation coefficients) for the autocorrelation function (ACF) and partial autocorrelation function (PACF) of the time series. These indicate that a small amount of lags (probably just the first two) are relevant to detrend the series before analysis.

# Plot autocorrelation function.
gglag(bp$PM, "acf")

plot of chunk acf-plots

# Plot partial autocorrelation function.
gglag(bp$PM, "pacf")

plot of chunk acf-plots

Next: Smoothing.