Chapter 22 — Time Series Analysis and Forecasting

A time series is data ordered in time. This chapter covers date indexing, rolling statistics, decomposition into trend/seasonality/residual, and forecasting with ARIMA using the classic monthly airline passengers dataset.

Learning Objectives

Prerequisites / Imports

In [1]:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from statsmodels.tsa.seasonal import seasonal_decompose
from statsmodels.tsa.arima.model import ARIMA
from statsmodels.graphics.tsaplots import plot_acf
import warnings
warnings.filterwarnings('ignore')

1 Load and Index the Series

The seaborn flights dataset records monthly airline passengers from 1949–1960.

In [1]:
flights = sns.load_dataset('flights')
flights['date'] = pd.to_datetime(flights['year'].astype(str) + '-' + flights['month'].astype(str) + '-01')
ts = flights.set_index('date')['passengers'].astype(float)
print('start:', ts.index.min(), 'end:', ts.index.max(), 'n:', len(ts))
ts.head()
start: 1949-01-01 00:00:00 end: 1960-12-01 00:00:00 n: 144
date
1949-01-01    112.0
1949-02-01    118.0
1949-03-01    132.0
1949-04-01    129.0
1949-05-01    121.0
Name: passengers, dtype: float64

2 Visualize the Series

Airline travel shows clear trend and yearly seasonality.

In [1]:
plt.figure(figsize=(10,4))
ts.plot()
plt.title('Monthly airline passengers'); plt.ylabel('passengers (thousands)')
plt.show()

3 Rolling Statistics

A 12-month moving average smooths noise and reveals the trend.

In [1]:
plt.figure(figsize=(10,4))
ts.plot(label='original', alpha=0.5)
ts.rolling(12).mean().plot(label='12-month MA')
plt.title('Rolling mean'); plt.legend()
plt.show()

4 Decomposition

seasonal_decompose splits the series into trend, seasonal, and residual components.

In [1]:
decomp = seasonal_decompose(ts, model='multiplicative', period=12)
fig = decomp.plot()
fig.set_size_inches(10, 7)
plt.show()

5 Stationarity and Differencing

A stationary series has constant mean/variance. Differencing ($y_t - y_{t-1}$) helps stabilize trend.

In [1]:
diff = ts.diff().dropna()
fig, ax = plt.subplots(1, 2, figsize=(12,4))
ts.plot(ax=ax[0], title='Original')
diff.plot(ax=ax[1], title='First difference')
plt.tight_layout(); plt.show()

6 Autocorrelation

ACF shows how a series correlates with its own lags — useful for choosing ARIMA orders.

In [1]:
fig, ax = plt.subplots(figsize=(8,4))
plot_acf(ts, lags=24, ax=ax)
plt.title('Autocorrelation (ACF) of passengers')
plt.show()

7 Forecasting with ARIMA

We fit ARIMA(1,1,1) on the training portion and forecast the held-out months.

In [1]:
train = ts.iloc[:-24]
test = ts.iloc[-24:]
model = ARIMA(train, order=(1, 1, 1)).fit()
forecast = model.get_forecast(steps=len(test))
pred_mean = forecast.predicted_mean
ci = forecast.conf_int()

plt.figure(figsize=(10,4))
train.plot(label='train')
test.plot(label='actual')
pred_mean.plot(label='forecast')
plt.fill_between(ci.index, ci.iloc[:,0], ci.iloc[:,1], color='pink', alpha=0.3)
plt.title('ARIMA forecast vs actual'); plt.legend()
plt.show()

8 Evaluate the Forecast

Mean Absolute Error and Mean Absolute Percentage Error summarize forecast accuracy.

In [1]:
mae = np.mean(np.abs(pred_mean.values - test.values))
mape = np.mean(np.abs((pred_mean.values - test.values) / test.values)) * 100
print('MAE :', round(float(mae), 2))
print('MAPE:', round(float(mape), 2), '%')
MAE : 93.9
MAPE: 18.84 %

Case Study: Forecasting Web Traffic

We generate a synthetic daily series with trend and weekly seasonality, then forecast the next 30 days.

In [1]:
rng = np.random.default_rng(0)
dates = pd.date_range('2026-01-01', periods=180, freq='D')
trend = np.linspace(100, 200, 180)
season = 15 * np.sin(2 * np.pi * np.arange(180) / 7)
noise = rng.normal(0, 5, 180)
traffic = pd.Series(trend + season + noise, index=dates)

fit = ARIMA(traffic, order=(2, 1, 1)).fit()
fc = fit.get_forecast(steps=30).predicted_mean
plt.figure(figsize=(10,4))
traffic.plot(label='actual')
fc.index = pd.date_range(traffic.index[-1] + pd.Timedelta(days=1), periods=30, freq='D')
fc.plot(label='30-day forecast')
plt.title('Synthetic web traffic forecast'); plt.legend()
plt.show()

Exercises

  1. Load the flights data and set a DatetimeIndex.
  2. Plot the series and its 6-month rolling mean.
  3. Decompose the series and interpret the seasonal component.
  4. Plot the first difference and comment on stationarity.
  5. Produce an ACF plot up to 30 lags.
  6. Fit an ARIMA(1,1,1) model and forecast 12 steps.
  7. Compute MAPE for your forecast.
  8. Try ARIMA(2,1,2) and compare MAPE to ARIMA(1,1,1).
  9. Explain what the d parameter does in ARIMA.
  10. Generate a synthetic series with trend and monthly seasonality and forecast it.

Python Data Science: From Foundations to Applications — Chapter 22