Time series forecasting in machine learning: start with persistence and seasonal baselines
Compare fixed-origin persistence and weekly seasonal-naive forecasts on chronological development and test windows, with explicit dates and errors.
A forecasting baseline predicts future values using a simple rule that was available at the forecast origin. Persistence repeats the latest observation. Seasonal naive repeats the corresponding value from the last observed seasonal cycle. Comparing both can reveal whether a complex model actually improves on the structure already present in the series. That is why they come first in any machine learning approach to time series: a gradient-boosted or neural forecaster is only worth its cost if it beats these two rules on the same chronological split.
We will create 42 invented daily workshop attendance counts with a repeating seven-day pattern and a small drift. The arithmetic series is deliberately simple so every forecast can be audited. It is not an attendance dataset or a claim about forecasting performance in practice.
Define the horizon and origin
Use the first 28 days as initial history, days 29–35 as a development window and days 36–42 as a final test window. At each origin we predict the entire next seven days using only values before that origin. We do not update the persistence prediction with observations from inside the horizon.
For weekly seasonal naive, the forecast for day t is the observation from t-7. With a seven-day horizon, every required value belongs to the preceding observed week. For a longer horizon, specify how the last observed cycle repeats rather than accidentally reading future values.
Compare both windows chronologically
import numpy as np
from datetime import date, timedelta
pattern = np.array([10., 12., 11., 13., 20., 25., 15.])
values = np.tile(pattern, 6) + .1*np.arange(42)
start = date(2026, 1, 1)
for label, origin in [('development', 28), ('test', 35)]:
history = values[:origin]
actual = values[origin:origin+7]
forecasts = {'persistence': np.repeat(history[-1], 7),
'seasonal_naive': history[-7:].copy()}
print(label, str(start + timedelta(days=origin)), 'through',
str(start + timedelta(days=origin+6)))
for name, predicted in forecasts.items():
mae = np.mean(np.abs(actual-predicted))
print(name, f'MAE={mae:.3f}',
'errors=' + str(np.round(actual-predicted, 1).tolist()))
assert len(history) == origin
assert np.allclose(actual - forecasts['seasonal_naive'], .7)
Checked output:
development 2026-01-29 through 2026-02-04
persistence MAE=4.257 errors=[-4.9, -2.8, -3.7, -1.6, 5.5, 10.6, 0.7]
seasonal_naive MAE=0.700 errors=[0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7]
test 2026-02-05 through 2026-02-11
persistence MAE=4.257 errors=[-4.9, -2.8, -3.7, -1.6, 5.5, 10.6, 0.7]
seasonal_naive MAE=0.700 errors=[0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7]
Interpret the error pattern
Seasonal naive captures the declared weekly pattern, but misses the drift: each value is 0.7 above the corresponding value seven days earlier. The residuals expose that systematic underprediction even when the average error is small relative to the series level.
Persistence misses the within-week variation because it repeats the final value of the preceding week. It is still a useful baseline for a series with little seasonality or with rapidly changing local levels. The appropriate baseline depends on the actual task and what information is available when predictions are issued.
The matching development and test errors follow from this perfectly repeated synthetic construction. Real time series have noise, holidays, interventions and changing seasonal patterns. Do not treat these results as evidence that one baseline will remain superior in a new population.
Keep tuning and uncertainty honest
Use the development window to choose a candidate rule. Reserve the final test window to assess the chosen forecasting procedure. If you compare many horizons, seasonal periods and transformations, account for that selection rather than presenting the best test score as an untouched evaluation.
A centered rolling average can use future observations and therefore leak information. So can scaling or imputing using full-series statistics. Construct each origin's features and fitted preprocessing from its available history.
This deterministic fixture provides no estimated prediction intervals. In a real application, inspect residuals by horizon and evaluate interval coverage on appropriate rolling origins. A single average absolute error does not describe tail risk, systematic bias or the cost of underprediction.
Extend the series
Add seeded noise and evaluate several nonoverlapping origins. Then introduce a level shift after day 28 and inspect which baseline adapts faster. Keep the same forecast issuance schedule for both rules; otherwise you are comparing different information budgets.
The Machine Learning course connects baselines to evaluation design. Review chronological cross-validation, then data leakage prevention before building lag features or rolling summaries.
References
Forecasting principles and practice: simple methods — Hyndman and Athanasopoulos describe naive, seasonal-naive and other simple forecasting methods.
Time-series cross-validation — Rolling origins, forecast horizons and evaluation on genuinely future observations.