CO₂ at Mauna Loa — R forecast::auto.arima, out-of-sample¶
Companion to the Python/PyMC notebooks. auto.arima on the additive series (no Box-Cox), forecast() for the held-out 5 years, plotted with ggplot/autoplot.
In [1]:
suppressMessages({library(forecast); library(ggplot2)})
d <- read.csv('co2.csv'); y <- ts(d$co2, start=c(1958,3), frequency=12)
H <- 60; train <- head(y, length(y)-H); test <- tail(y, H)
fit <- auto.arima(train, seasonal=TRUE, stepwise=TRUE, approximation=FALSE)
print(fit)
fc <- forecast(fit, h=H); acc <- accuracy(fc, test)
cat(sprintf('\nOUT-OF-SAMPLE (5 yr): RMSE = %.2f ppm MAPE = %.3f%%\n', acc['Test set','RMSE'], acc['Test set','MAPE']))
cat(sprintf('Ljung-Box residual p = %.3f\n', Box.test(residuals(fit), lag=12, type='Ljung-Box')$p.value))
Series: train
ARIMA(3,1,1)(0,1,1)[12]
Coefficients:
ar1 ar2 ar3 ma1 sma1
0.1650 0.0141 -0.1382 -0.5066 -0.8482
s.e. 0.1824 0.0766 0.0581 0.1805 0.0272
sigma^2 = 0.09504: log likelihood = -107.98
AIC=227.97 AICc=228.16 BIC=252.66
OUT-OF-SAMPLE (5 yr): RMSE = 0.95 ppm MAPE = 0.231%
Ljung-Box residual p = 0.907
In [2]:
options(repr.plot.width = 9, repr.plot.height = 5)
autoplot(fc, include=120) +
autolayer(test, series='actual (held out)') +
scale_colour_manual(values=c('actual (held out)'='red')) +
labs(title='auto.arima out-of-sample forecast — Mauna Loa CO₂', x='year', y='CO₂ (ppm)', colour='') +
theme_minimal(base_size=13) + theme(legend.position='top')
Results¶
auto.arimaselects a low-order SARIMA with white-noise residuals and forecasts the held-out 5 years with MAPE ≈ 0.23% — agreeing with statsmodels and the Bayesian structural model.- Three engines agree. Across the arc: AirPassengers (fixed seasonality, log/multiplicative), sunspots (variable cyclicality, damping forecast), and CO₂ (very stable trend+seasonality, near-perfect forecast) span the range of ARIMA behaviour — the groundwork before turning to GARCH / stochastic volatility, where the variance itself becomes the moving part.