Playground: Chapter 16
Time Series Forecasting
This page practises the ideas of Chapter 16 in four parts. Practise the chapter works through the book’s exercises, with starter code, hints, and solutions. Go further goes beyond the book, with new questions and real time series datasets. Check your understanding asks short questions whose answers open with a click. Do it yourself gives open tasks with an empty code space and no answers: you forecast and report yourself, as you will in your own thesis.
In the exercises below, replace each ______ with your own code and press Run Code.
Practise the chapter
These are the exercises at the end of Chapter 16, with the same numbers.
Exercise 1: Benchmarks for a series with a trend
Predict which of the three benchmarks (mean, naive, seasonal naive) would be best for a series with a trend but no seasonality. Then test your prediction on the chapter’s clinic series after adding a steady increase of one visit a day.
The seasonal naive method repeats the last full cycle: for daily data with weekly seasonality, that is the last seven days (indices 7 to 14 in 0-based Python slicing).
snaive_fc = clinic_trend[7:14]For a series with a trend and no seasonality, the naive method is best of the three: the last value is closer to the future than the average of a rising series. Because this clinic series has strong weekly seasonality, seasonal naive achieves an MAE of 7.0 (vs 10.7 for naive and 11.0 for mean), but it misses the steady growth.
Exercise 2: The seasonally adjusted series
Decompose the counselling series, calculate the seasonally adjusted series (the series minus its seasonal component), and print its yearly averages.
The series is the sum of trend, season, and remainder; removing the seasonal component means subtracting it (-).
adjusted = visits['visits'] - stl_res.seasonalWithout the large seasonal swings, the gradual upward trend is clear: average demand rises steadily from 30.7 visits/week in 2020/21 up to 37.6 in 2024/25.
Exercise 3: Fewer or more waves
Refit the Fourier model with \(K = 2\), \(K = 6\), and \(K = 12\), and explain how and why the test accuracy changes.
The third model uses \(K = 12\) harmonic pairs.
for K_val in [2, 6, 12]:With \(K = 2\), the waves are too smooth and broad. With \(K = 6\) and \(K = 12\), the model bends more to capture peaks, but each pair adds two parameters. In all cases, smooth sinusoidal waves struggle with the sudden step drop in summer.
Exercise 4: Three years of training
Train models on the first three academic years and test on the fourth (2023/24).
For Year 4, the seasonal naive forecast copies Year 3 (year_of(3)).
snaive_fc = year_of(3)Seasonal naive reaches an MAE of 6.6 visits/week. Averaging Years 2 and 3 and adding 6% annual growth gives 6.0 visits/week, smoothing out single-year idiosyncrasies.
Exercise 5: Visits before the first exam period
Simulate 1,000 futures to estimate how many visits to expect in the four weeks before the first exam period of 2025/26 (weeks 12 to 15), with a 95% interval.
The simulated futures indicate a median expectation of ~193 visits across the 4 pre-exam weeks, with a 95% prediction interval spanning roughly 151 to 247 visits.
Exercise 6: Explaining a prediction interval
Write a short paragraph for the counselling service explaining what a 95% prediction interval means. Write your answer first, then open the model answer.
“For each week, the forecast gives a most likely number of visits and a range. The 95% prediction interval is the range within which we expect the actual number of visits to fall in 19 weeks out of 20, if the patterns of the past five years continue. It is wide because demand varies from week to week for reasons no forecast can know, such as an awareness campaign or a difficult exam. Plan for the middle of the range, and keep in mind that about one week in 20 will fall outside it.”
Exercise 7: Twice the noise
In the artificial series at the start of the chapter, double the standard deviation of the noise. Describe how the sum changes.
Double the original noise standard deviation of 3.0 to 6.0.
for noise_sd in [3.0, 6.0]:With twice the noise, the weekly fluctuations are doubled, obscuring the seasonal wave and doubling the width of forecast prediction intervals.
Go further
These exercises go beyond the book.
Exercise 8: Build a series and forecast with growth
Build a weekly series of your own for four years from a trend (50 visits rising by 0.1 a week), a seasonal wave of 15, and noise with an SD of 4. Forecast the fourth year from the first three.
The trend adds 0.1 per week; over 52 weeks, the growth is 0.1 * 52 = 5.2.
growth_52 = 0.1 * 52Seasonal naive with growth brings MAE down from 7.5 to 4.5, close to the irreducible random noise standard deviation.
Exercise 9: Autocorrelation by hand
Calculate the autocorrelation of the counselling series at lags 1 and 52 by hand using np.corrcoef(), and compare with acf().
For lag 52, pair elements starting from index 52 (x[52:]) with elements up to x[:-52].
r52 = np.corrcoef(x[52:], x[:-52])[0, 1]At lag 1 both methods agree (0.69). At lag 52 the correlation is strong (~0.68 to 0.87), proving the pronounced annual seasonality.
Exercise 10: Airline passengers and growing swings
The flights.csv dataset contains monthly passenger numbers (in thousands) from 1949 to 1960. Compare seasonal swings on the raw scale and on the logarithmic scale.
On the raw scale, seasonal swings expand from 44 to 232 thousand passengers as the total volume grows. On the log scale, swings remain nearly constant (0.35 to 0.47), demonstrating why log transformations are required for multiplicative time series.
Exercise 11: Carbon dioxide and the missing trend
The co2.csv dataset contains monthly atmospheric CO₂ concentrations from 1959 to 1997. Forecast the final two years (24 months) using benchmarks.
Annual growth in monthly data is the difference across 12 months (tr_co2[-1] - tr_co2[-13]).
annual_growth = tr_co2[-1] - tr_co2[-13]Accounting for annual growth reduces the forecasting error from 2.3 ppm down to ~0.4 ppm.
Check your understanding
Answer each question in your own words first, then click to see a model answer.
1. Why are the observations of a time series not independent?
Because each observation carries over much of what shaped the one before it: the same students, the same academic term, and ongoing circumstances. This serial correlation is what makes forecasting possible.
2. What is the difference between trend, season, and noise?
- Trend: the long-term direction of the series (growth or decline over years).
- Season: a periodic pattern that repeats over a fixed interval (e.g. 52 weeks or 12 months).
- Noise: irregular, unpredictable variation that cannot be forecast.
3. Why must the test period come after the training period?
Because a real forecast can only use past data. Random splitting across time would allow future information to leak into past estimates.
4. What is a prediction interval?
A range within which a future observation is expected to fall with a given probability (e.g. 95%), reflecting both model estimation uncertainty and future random noise.
5. Why should every forecast be compared with benchmarks?
Benchmarks show what the simplest rules (like copying last year’s value) can achieve. A complex forecasting model is only justified if it beats these baselines.
Do it yourself
These tasks have no starter code and no answers. Write your own code, as you would for a thesis.
1. Using flights.csv or co2.csv, plot the series, fit an exponential smoothing or SARIMAX model, evaluate on a held-out test year, and report your forecasts with prediction intervals.
2. Write a short report for the counselling service summarising next year’s expected demand, identifying peak periods, and quantifying uncertainty.
3. Describe three real-world changes that could violate your model’s assumptions, and how an analyst would detect them early in the academic year.
Work on your own computer
The project contains the datasets and all four parts of this page as a Python script (exercises.py). Answers to the questions are in solutions.py, and the open tasks have space to write your code.
- Download chapter16.zip, unzip it, and open it in your favourite Python IDE.