Moving averages and classical decomposition

Today let's understand how a time series gets pulled apart into a trend and a repeating seasonal pattern, using one real series.

AirPassengers is a classic dataset built into R: the monthly totals of international airline passengers worldwide, from January 1949 to December 1960, 144 months in all. Monthly totals range from 104 in the quietest month to 622 in the busiest.

Here is the whole series, plotted in the order the months actually happened.

The line climbs across all twelve years, and every summer it rises to a peak, each one higher than the summer before. That climbing, repeating peak is the whole puzzle this lesson solves: how do you pull the steady climb apart from the repeating summer bump, so each can be looked at on its own?

What a centred moving average is, and how to compute one by hand

The trend in a series is its slow-moving overall level, with the season and the noise averaged out. The simplest way to estimate it is a moving average: replace each raw value with the average of the values around it.

A centred moving average of order m, written m-MA, averages the m raw values sitting symmetrically around a given month t. For m = 3, the 3-MA at month t averages month t-1, month t itself, and month t+1, one month on each side.

Take July 1949. Its raw value is 148. June 1949 is 135 and August 1949 is 148. Average those three and you pull the value down to about 143.67, a little below the raw 148 because June drags it lower.

Build this in R. First turn AirPassengers into a tsibble, the data structure feasts and tsibble expect, then compute the 3-MA by hand for the months around July 1949.

RInteractive R
# Compute a 3-month centred moving average by hand and compare it with the raw series library(tsibble) library(dplyr) ap <- AirPassengers air <- as_tsibble(ap) |> rename(Month = index, Passengers = value) raw <- air$Passengers n <- length(raw) ma3 <- rep(NA, n) for (t in 2:(n - 1)) { ma3[t] <- mean(raw[(t - 1):(t + 1)]) } air <- air |> mutate(MA3 = round(ma3, 2)) air |> filter(Month >= yearmonth("1949 May"), Month <= yearmonth("1949 Sep")) #> # A tsibble: 5 x 3 [1M] #> Month Passengers MA3 #> <mth> <dbl> <dbl> #> 1 1949 May 121 128. #> 2 1949 Jun 135 135. #> 3 1949 Jul 148 144. #> 4 1949 Aug 148 144 #> 5 1949 Sep 136 134. air$MA3[7] #> [1] 143.67

  

The table rounds its display to fit the column, so July's exact value shows up as 144. Pull that one number out on its own and the full figure comes back: 143.67. The loop above only runs from month 2 to month 143, one short of each end, because month 1 has no month before it to average with, and month 144 has no month after.