Type at least 2 letters. Enter opens the first result, Esc closes.

    Time Series and Forecasting · Lesson 3 · First forecasts and honest evaluation

    Benchmark forecasts and honest evaluation: backtesting, metrics and leakage

    Beginner 75 min 4 interactive figures 4 videos Colab notebook

    Before this lesson: lesson 01 (trend, season, the seasonal period mm) and lesson 02 (white noise, random walk, the ACF and the Ljung–Box test). Cross-validation in ordinary machine learning helps (ml-fundamentals track). Time: about 75 minutes, plus the notebook.


    Why this matters #

    A forecast is only useful when it beats a simple rule. In the M4 competition (100,000 series), the six pure machine-learning methods were all less accurate than a simple combination of exponential smoothing methods, and only one of them beat a seasonally adjusted naive forecast (Makridakis, Spiliotis and Assimakopoulos 2020). Many published "improvements" disappear when they are compared with a seasonal naive forecast on the same backtest.

    Evaluation mistakes are common and they always flatter the model:

    • a random train/test split puts future values into the training set;
    • one test period of a few weeks can be lucky or unlucky;
    • a scaler, a decomposition or a hyperparameter search that sees the test period leaks information;
    • a percentage metric quietly rewards forecasts that are too low.

    Every later lesson of this track compares its method with the benchmarks of this lesson, using the rolling-origin backtest of this lesson.

    1. Predict first #

    2. The picture #

    Four benchmark forecasts #

    You stand at the forecast origin tt: you know y1,…,yty_1, \dots, y_t and nothing after. You want the values at t+1,t+2,…,t+Ht + 1, t + 2, \dots, t + H. The number of steps ahead, hh, is the horizon. We write the forecast of yt+hy_{t+h} made at origin tt as y^t+h∣t\hat y_{t+h\mid t}.

    Four rules need no model fitting:

    • mean: every future value equals the average of the past;
    • naive: every future value equals the last observed value yty_t;
    • seasonal naive: every future value equals the value one season earlier (for monthly data with a yearly pattern, next March equals the last March);
    • drift: draw a straight line through the first and the last observation, and extend it.

    These are the benchmark forecasts (also called baselines). They are not toys: each one is the best possible forecast for one simple kind of series (section 3.2).

    Interactive figure: loading…

    Drag in the chart to move the forecast origin (yellow line). The four benchmarks start there: mean, naive, seasonal naive and drift (dashed grey). The blue dots in the shaded window are the values they try to predict; the table scores them. Switch the series: on the seasonal "monthly demand" series the seasonal naive forecast usually wins, on a random walk the naive forecast wins, and on white noise the mean wins.

    Backtesting: many origins, not one #

    One test period is one sample. It can be lucky. A backtest (also called rolling-origin evaluation or time series cross-validation) repeats the forecast at many origins:

    1. Choose an origin t1t_1. Fit the method on the data up to t1t_1.
    2. Forecast t1+1,…,t1+Ht_1 + 1, \dots, t_1 + H and store the errors, labelled by their horizon hh.
    3. Move the origin forward by a step (for example 6 months) and repeat.
    4. Average the errors over all origins, separately for each horizon hh.

    The training data either grow (expanding window: always start at y1y_1) or keep a fixed length (sliding window: drop the oldest values). A gap leaves some values out between the end of the training data and the first test value (section 3.5 says when you need one).

    Interactive figure: loading…

    Each row is one fold. The training window (aqua) ends at the forecast origin (yellow); the test window (violet) holds the next HH values. Move the sliders and watch the rows. The bottom chart averages the error of the seasonal naive and the drift forecast over all folds, for each horizon hh: the errors are not the same at every horizon. Then choose "random K-fold": most test points now have training points after them.

    Quick check

    Monthly data, seasonal period m=12m = 12, forecast origin tt. Which observation is the seasonal naive forecast for horizon h=14h = 14?

    Quick check

    n=120n = 120 months, expanding window, initial training size 48, horizon H=12H = 12, step 6, no gap. How many folds does the backtest have?

    3. Definitions and main results #

    3.1 Forecasts, errors and residuals #

    • The forecast y^t+h∣t\hat y_{t+h\mid t} uses only y1,…,yty_1, \dots, y_t.
    • The forecast error is et+h=yt+h−y^t+h∣te_{t+h} = y_{t+h} - \hat y_{t+h\mid t}. It is measured on data the method did not see.
    • The residual is the one-step error on the training data, et=yt−y^t∣t−1e_t = y_t - \hat y_{t\mid t-1}. For a method with fitted parameters, the residuals use the parameters fitted on all the training data, so they are in-sample.

    Residuals tell you whether a method used all the information in the data. Forecast errors tell you how accurate it is. Do not report residuals as accuracy: they are almost always too small.

    3.2 The four benchmarks and when each one is optimal #

    Let nn be the number of observations known at the origin, mm the seasonal period and h=1,2,…h = 1, 2, \dots the horizon.

    Method Forecast y^n+h∣n\hat y_{n+h\mid n} Best forecast (smallest mean squared error) for
    mean yˉ=1n∑t=1nyt\bar y = \frac{1}{n}\sum_{t=1}^{n} y_t white noise around a constant: yt=μ+εty_t = \mu + \varepsilon_t
    naive yny_n a random walk: yt=yt−1+εty_t = y_{t-1} + \varepsilon_t
    seasonal naive yn+h−m(k+1)y_{n+h-m(k+1)}, k=⌊(h−1)/m⌋k = \lfloor (h-1)/m \rfloor a seasonal random walk: yt=yt−m+εty_t = y_{t-m} + \varepsilon_t
    drift yn+h yn−y1n−1y_n + h\,\dfrac{y_n - y_1}{n - 1} a random walk with drift: yt=c+yt−1+εty_t = c + y_{t-1} + \varepsilon_t

    Here εt\varepsilon_t is white noise with mean 0 and variance σ2\sigma^2 (lesson 02). In the seasonal naive formula, kk counts how many full years of the forecast period lie before the target: for h≤mh \le m, k=0k = 0 and the forecast is yn+h−my_{n+h-m}.

    Why the conditional mean is the best forecast under squared error. Let YY be the unknown future value and F\mathcal F the information at the origin. For any number cc computed from F\mathcal F,

    E[(Y−c)2∣F]=Var⁡(Y∣F)+(E[Y∣F]−c)2.\mathbb E\big[(Y - c)^2 \mid \mathcal F\big] = \operatorname{Var}(Y \mid \mathcal F) + \big(\mathbb E[Y \mid \mathcal F] - c\big)^2 .

    The first term does not depend on cc, so the smallest value is at c=E[Y∣F]c = \mathbb E[Y \mid \mathcal F]. (To get the identity, write Y−c=(Y−E[Y∣F])+(E[Y∣F]−c)Y - c = (Y - \mathbb E[Y \mid \mathcal F]) + (\mathbb E[Y \mid \mathcal F] - c) and expand; the cross term has conditional mean 0.)

    Now compute the conditional mean for each model.

    1. Random walk. yn+h=yn+εn+1+⋯+εn+hy_{n+h} = y_n + \varepsilon_{n+1} + \dots + \varepsilon_{n+h}. The future ε\varepsilon have mean 0 and are independent of the past, so E[yn+h∣y1,…,yn]=yn\mathbb E[y_{n+h} \mid y_1, \dots, y_n] = y_n: the naive forecast.
    2. Random walk with drift. yn+h=yn+hc+εn+1+⋯+εn+hy_{n+h} = y_n + hc + \varepsilon_{n+1} + \dots + \varepsilon_{n+h}, so the best forecast is yn+hcy_n + hc. The drift cc is unknown. Estimate it by the average change: c^=1n−1∑t=2n(yt−yt−1)=yn−y1n−1\hat c = \frac{1}{n-1}\sum_{t=2}^{n} (y_t - y_{t-1}) = \frac{y_n - y_1}{n - 1}, because the sum telescopes. This gives the drift forecast.
    3. Seasonal random walk. Each season jj is its own random walk, so the best forecast of a future value is the last observed value of the same season: the seasonal naive forecast.
    4. White noise around μ\mu. The best forecast is μ\mu; the sample mean yˉ\bar y estimates it.

    The fan. For the random walk, the forecast error is en+h=εn+1+⋯+εn+he_{n+h} = \varepsilon_{n+1} + \dots + \varepsilon_{n+h}, a sum of hh independent terms, so its variance is hσ2h\sigma^2. The error grows like h\sqrt h: the prediction interval opens like a fan. With normal errors, a 95 % prediction interval is y^n+h∣n±1.96 σ^h\hat y_{n+h\mid n} \pm 1.96\,\hat\sigma_h. The standard deviations σ^h\hat\sigma_h of the four benchmarks (FPP3, §5.5, Table 5.2; σ^\hat\sigma is the standard deviation of the residuals):

    Method σ^h\hat\sigma_h
    mean σ^1+1/n\hat\sigma\sqrt{1 + 1/n}
    naive σ^h\hat\sigma\sqrt{h}
    seasonal naive σ^k+1\hat\sigma\sqrt{k + 1}, k=⌊(h−1)/m⌋k = \lfloor (h-1)/m \rfloor
    drift σ^h (1+h/(n−1))\hat\sigma\sqrt{h\,(1 + h/(n-1))}

    For the drift forecast the error is ∑j=1hεn+j−h(c^−c)\sum_{j=1}^{h} \varepsilon_{n+j} - h(\hat c - c) with c^−c=1n−1∑t=2nεt\hat c - c = \frac{1}{n-1}\sum_{t=2}^{n}\varepsilon_t. The two parts are independent, so the variance is hσ2+h2σ2/(n−1)h\sigma^2 + h^2\sigma^2/(n-1): the extra term is the uncertainty of the estimated slope. Lesson 10 checks whether such intervals have the coverage they promise.

    3.3 Checking residuals #

    A good forecasting method leaves residuals that look like white noise. Check, in this order:

    1. Uncorrelated. If the residuals are correlated, the method missed information that is in the past values. Look at the ACF of the residuals and run the Ljung–Box test (lesson 02): Q=n(n+2)∑k=1ℓρ^(k)2/(n−k)Q = n(n+2)\sum_{k=1}^{\ell} \hat\rho(k)^2/(n-k), compared with a χ2\chi^2 distribution with ℓ\ell degrees of freedom (minus the number of fitted parameters, for models from lesson 05 on). FPP3 suggests ℓ=10\ell = 10 for non-seasonal data and ℓ=2m\ell = 2m for seasonal data.
    2. Mean zero. A nonzero mean is a bias: add the mean to the forecasts to remove it.
    3. Constant variance (homoscedastic). If the spread grows with the level, try a log transform (lesson 01).
    4. Roughly normal. Needed only for the normal prediction intervals above.

    Properties 1 and 2 are essential. Properties 3 and 4 only matter for intervals. A method can pass all four checks and still forecast badly; residual checks never replace a backtest.

    3.4 Split by time, never at random #

    A holdout split for time series is a split in time order: the training set is y1,…,yn0y_1, \dots, y_{n_0}, the test set is yn0+1,…,yny_{n_0+1}, \dots, y_n. A random split is wrong in general for two reasons:

    • Neighbours are correlated. A test point yty_t with training points yt−1y_{t-1} and yt+1y_{t+1} on both sides can be interpolated. A real forecast must extrapolate into the future, which is harder. The error estimate becomes too small.
    • The future is in the training set. Information after tt (a new level, a trend change) helps to predict yty_t. In real use that information does not exist yet.

    3.5 Rolling-origin evaluation #

    Definition. A rolling-origin evaluation (backtest, time series cross-validation) uses forecast origins t1<t2<⋯<tKt_1 < t_2 < \dots < t_K. At each origin tkt_k:

    • the training set is {1,…,tk}\{1, \dots, t_k\} (expanding window) or {tk−w+1,…,tk}\{t_k - w + 1, \dots, t_k\} for a fixed length ww (sliding window);
    • the test set is {tk+g+1,…,tk+g+H}\{t_k + g + 1, \dots, t_k + g + H\}, where g≥0g \ge 0 is the gap;
    • the origins are spaced by a step ss: tk+1=tk+st_{k+1} = t_k + s.

    The error at horizon hh is the average over the origins: MAE(h)=1K∑k=1K∣ytk+h−y^tk+h∣tk∣\text{MAE}(h) = \frac{1}{K}\sum_{k=1}^{K} |y_{t_k+h} - \hat y_{t_k+h\mid t_k}|.

    Expanding or sliding? An expanding window uses all the data and gives stable estimates. A sliding window follows a series whose behaviour changes, and it shows how the method works with a fixed amount of history. Use the scheme you will use in production.

    Report each horizon separately. A one-step error and a twelve-step error answer different questions: the error usually grows with hh. If you average all horizons into one number, say so, and also show the curve MAE(h)\text{MAE}(h).

    The gap (embargo). Leave gg values out between training and test when information near the origin is not really available at forecast time:

    • the target is known late (sales are corrected two weeks later; a medical outcome is known after 30 days);
    • features are built from windows that overlap the test period (a centred moving average, a label defined over the next 7 days);
    • you will really forecast from an older origin (data arrive with a delay of 3 days, so the forecast for "tomorrow" is a 4-step forecast).

    In finance this idea is called an embargo, together with purging training samples whose labels overlap the test period (López de Prado 2018).

    In scikit-learn. sklearn.model_selection.TimeSeriesSplit(n_splits, max_train_size, test_size, gap) makes expanding folds whose test windows have test_size points and follow each other without overlap (the step equals test_size). max_train_size turns it into a sliding window, and gap removes that many points from the end of each training set. The folds start from the end of the series: the last fold always tests the last test_size points.

    When is ordinary K-fold cross-validation acceptable?

    Bergmeir, Hyndman and Koo (2018) show that standard K-fold cross-validation is valid for a purely autoregressive model (the inputs are lagged values yt−1,…,yt−py_{t-1}, \dots, y_{t-p}) fitted to a stationary series, if the model's errors are uncorrelated. Then each row (yt−1,…,yt−p)→yt(y_{t-1}, \dots, y_{t-p}) \to y_t is a fair test example, and K-fold uses the data better than a single holdout. When the model underfits, its errors are correlated, and K-fold becomes too optimistic. The result does not cover non-stationary series (trend, level shifts) or models with other inputs. The last figure of this lesson shows both cases: on a series with a trend, random folds flatter a k-nearest-neighbour model a lot, but they agree with the truth for a linear model on lags.

    3.6 Point-forecast metrics #

    Let ej=yj−y^je_j = y_j - \hat y_j, j=1,…,Jj = 1, \dots, J, be the forecast errors of a test set (all origins and horizons you want to summarise).

    Metric Formula Units and remarks
    MAE (mean absolute error) 1J∑j∣ej∣\frac1J\sum_j \lvert e_j\rvert units of yy
    RMSE (root mean squared error) 1J∑jej2\sqrt{\frac1J\sum_j e_j^2} units of yy; large errors count more
    MAPE (mean absolute percentage error) 100J∑j∣ej∣/∣yj∣\frac{100}{J}\sum_j \lvert e_j\rvert / \lvert y_j\rvert percent; undefined if some yj=0y_j = 0, huge if yjy_j is near 0
    sMAPE (symmetric MAPE, M4 version) 100J∑j2∣ej∣∣yj∣+∣y^j∣\frac{100}{J}\sum_j \dfrac{2\lvert e_j\rvert}{\lvert y_j\rvert + \lvert\hat y_j\rvert} percent; between 0 and 200; not really symmetric
    WAPE (weighted absolute percentage error) ∑j∣ej∣/∑j∣yj∣\sum_j \lvert e_j\rvert \big/ \sum_j \lvert y_j\rvert a ratio; the same as MAE divided by the mean of ∣y∣\lvert y\rvert
    MASE (mean absolute scaled error) 1J∑j∣qj∣\frac1J\sum_j \lvert q_j\rvert with qj=ej/sq_j = e_j / s no units; ss is defined below
    RMSSE (root mean squared scaled error) 1J∑jej2/s2\sqrt{\frac1J\sum_j e_j^2 / s_2} no units; used in the M5 competition

    MAE and RMSE are scale-dependent: you cannot average them over series with different units or sizes. Percentage errors have no units, but they need a meaningful zero (they make no sense for temperatures in °C) and they explode near y=0y = 0.

    MASE (Hyndman and Koehler 2006) divides by the in-sample MAE of the one-step seasonal naive forecast on the training data y1,…,yny_1, \dots, y_n:

    s=1n−m∑t=m+1n∣yt−yt−m∣,qj=ejs,MASE=1J∑j∣qj∣.s = \frac{1}{n - m}\sum_{t=m+1}^{n} \lvert y_t - y_{t-m}\rvert , \qquad q_j = \frac{e_j}{s}, \qquad \text{MASE} = \frac1J\sum_j \lvert q_j\rvert .

    For non-seasonal data use m=1m = 1 (the naive forecast). For RMSSE, s2s_2 is the mean of the squared differences (yt−yt−m)2(y_t - y_{t-m})^2.

    • MASE < 1: the forecasts on the test set are, on average, better than the one-step seasonal naive forecasts were on the training set.
    • MASE > 1: they are worse. This is not the same as "worse than the seasonal naive method on the test set": the scale is in-sample and one-step, while the test errors are often several steps ahead. Even the seasonal naive method itself often has MASE > 1 on a test set.
    • The scale ss uses only training data, so it is known at the origin and it is positive unless the training data are exactly periodic.
    Quick check

    A method has MASE =1.3= 1.3 on a test set. What does this say?

    3.7 Which forecast does each metric reward? #

    A point forecast is one number, but the future value is uncertain: it has a distribution. Each metric is minimised by a different summary of that distribution. Let Y>0Y > 0 have the predictive distribution with CDF FF and density ff.

    Theorem.

    1. E[(Y−c)2]\mathbb E[(Y - c)^2] is smallest at the mean c=E[Y]c = \mathbb E[Y].
    2. E∣Y−c∣\mathbb E\lvert Y - c\rvert is smallest at the median of YY.
    3. E[∣Y−c∣/Y]\mathbb E\big[\lvert Y - c\rvert / Y\big] (the MAPE of a constant forecast) is smallest at the median of the distribution with density proportional to f(y)/yf(y)/y. This value is below the median of YY.

    Proof.

    1. This is the identity of section 3.2 without conditioning.
    2. Write E∣Y−c∣=∫−∞c(c−y)f(y) dy+∫c∞(y−c)f(y) dy\mathbb E\lvert Y - c\rvert = \int_{-\infty}^{c} (c - y) f(y)\,dy + \int_{c}^{\infty} (y - c) f(y)\,dy. Its derivative in cc is F(c)−(1−F(c))=2F(c)−1F(c) - (1 - F(c)) = 2F(c) - 1. This is negative for cc below the median and positive above it, so the minimum is at F(c)=1/2F(c) = 1/2.
    3. E[∣Y−c∣/Y]=∫∣y−c∣ f(y)y dy\mathbb E[\lvert Y - c\rvert / Y] = \int \lvert y - c\rvert\, \frac{f(y)}{y}\,dy. Up to a constant factor, this is E∣Y~−c∣\mathbb E\lvert \tilde Y - c\rvert for a variable Y~\tilde Y with density f(y)/yf(y)/y divided by its integral. By part 2 the minimum is the median of Y~\tilde Y. The weight 1/y1/y is larger for small values, so the distribution of Y~\tilde Y is shifted towards small values, and its median is below the median of YY. ■\blacksquare
    The log-normal case: the MAPE-optimal value is eμ−σ2e^{\mu - \sigma^2}

    Let ln⁡Y∼N(μ,σ2)\ln Y \sim \mathcal N(\mu, \sigma^2), so f(y)=1yσ2πexp⁡ ⁣(−(ln⁡y−μ)22σ2)f(y) = \frac{1}{y\sigma\sqrt{2\pi}}\exp\!\big(-\frac{(\ln y - \mu)^2}{2\sigma^2}\big). Then f(y)/y=1yσ2πexp⁡ ⁣(−(ln⁡y−μ)22σ2−ln⁡y)f(y)/y = \frac{1}{y\sigma\sqrt{2\pi}}\exp\!\big(-\frac{(\ln y - \mu)^2}{2\sigma^2} - \ln y\big). Complete the square in ln⁡y\ln y: (ln⁡y−μ)2+2σ2ln⁡y=(ln⁡y−(μ−σ2))2+2μσ2−σ4(\ln y - \mu)^2 + 2\sigma^2 \ln y = \big(\ln y - (\mu - \sigma^2)\big)^2 + 2\mu\sigma^2 - \sigma^4. The last two terms do not depend on yy. So f(y)/yf(y)/y is proportional to the log-normal density with parameters μ−σ2\mu - \sigma^2 and σ2\sigma^2. The median of that distribution is eμ−σ2e^{\mu - \sigma^2}. For μ=0\mu = 0, σ=1\sigma = 1: mean e1/2≈1.65e^{1/2} \approx 1.65, median 11, MAPE-optimal value e−1≈0.37e^{-1} \approx 0.37.

    Interactive figure: loading…

    Drag the orange forecast cc across the skewed data. The three curves are MSE(c)\text{MSE}(c), MAE(c)\text{MAE}(c) and MAPE(c)\text{MAPE}(c), each divided by its own minimum. Their minima sit at the mean, the median and a much smaller value. Switch to symmetric data: the mean and the median now agree.

    Consequence: choose the metric from the decision, then the forecast. If the cost of an error grows like its square (large errors are very expensive), report the mean and evaluate with RMSE. If the cost grows linearly, report the median and evaluate with MAE. Do not use MAPE to choose between methods for skewed positive data unless you want forecasts that are too low. When you need several quantiles, use the pinball loss of lesson 10.

    Quick check

    Daily demand is skewed: most days are small, a few days are very large. You must report one constant forecast and you are judged by MAE. Which constant is best?

    3.8 Leakage in evaluation #

    Leakage means that the model, or any step before it, used information that would not be available at the forecast origin. The backtest then reports an error that you will never see in real use. The general rule: at each origin, simulate exactly what was known at that time.

    Common forms in forecasting:

    • Preprocessing on the whole series. A scaler, a log-mean, a decomposition (STL, classical) or a missing-value fill computed on all data, before the split. Fit every step inside each fold, on the training window only.
    • Filling missing values from the future. Back-fill (bfill) and centred interpolation use values after the gap. Use forward-fill or a model of the past only.
    • Tuning on the test period. Choosing the smoothing parameter, the lag order or the network size by looking at test errors. Tune inside the training window (a nested backtest), and keep a final test period untouched.
    • Revised data (vintages). GDP, sales and sensor data are often corrected later. A backtest on today's corrected values uses information that did not exist at the origin. Use the data vintage that was available at each origin, when you have it.
    • Covariates not known at forecast time. Observed weather used as an input, as if it were a weather forecast; a promotion flag that is decided after the forecast. Use the forecast of the covariate, or its value at the origin.
    Interactive figure: loading…

    One model, three error estimates. The model predicts yty_t from yt−1,yt−2,yt−3y_{t-1}, y_{t-2}, y_{t-3}. Its error is estimated by random 5-fold cross-validation, by a rolling origin inside the history, and measured on the true future (the last 50 points). With k-nearest neighbours, random folds are far too optimistic: the neighbours of a test row come from both sides of it in time. In the true future, many values lie above the range of the training data, and k-NN cannot predict outside that range. Switch to linear regression on lags: averaged over 30 series, the three estimates now agree (the deeper box of section 3.5 says why).

    Quick check

    You standardise a series with the mean and standard deviation of all 10 years, then run TimeSeriesSplit with 5 folds. What is wrong?

    3.9 Comparing two methods #

    • Average over many origins and many series. With few origins the ranking of two methods is often noise.
    • Use the same folds for both methods. Then the differences are paired, and the comparison is much more precise.
    • Look at the curve per horizon, not only at the average.
    The Diebold–Mariano test

    To test whether two methods have the same expected loss at horizon hh, compute the loss differential dt=L(et(1))−L(et(2))d_t = L(e^{(1)}_t) - L(e^{(2)}_t) for the test errors of the two methods (for example L(e)=e2L(e) = e^2 or ∣e∣\lvert e\rvert), over TT origins. Under the null hypothesis E[dt]=0\mathbb E[d_t] = 0. The statistic is

    DM=dˉV^/T,V^=γ^d(0)+2∑k=1h−1γ^d(k),\mathrm{DM} = \frac{\bar d}{\sqrt{\hat V / T}}, \qquad \hat V = \hat\gamma_d(0) + 2\sum_{k=1}^{h-1} \hat\gamma_d(k),

    where γ^d(k)\hat\gamma_d(k) is the sample autocovariance of dtd_t at lag kk. For large TT, DM is approximately standard normal (Diebold and Mariano 1995). The sum goes up to lag h−1h - 1 because hh-step errors from origins one step apart share h−1h - 1 future shocks, so they are correlated. For small TT, Harvey, Leybourne and Newbold (1997) give a corrected version with a tt distribution. The test compares forecasts, not models: it says nothing about which model is "true".

    Answers to section 1. (1) The seasonal naive forecast. With a strong yearly pattern and no trend, "same month last year" keeps the pattern; "repeat the last value" gives a flat line and misses every seasonal swing. (2) Something below the median. MAPE divides by the true value, so errors on small values cost more; section 3.7 shows that the best constant for MAPE is the median of a distribution shifted towards small values (for a log-normal, eμ−σ2e^{\mu - \sigma^2}). (3) Too low. A random fold lets the model interpolate between neighbours and see the future level, so random K-fold is usually too optimistic. It is acceptable only in the special case of the deeper box in section 3.5.

    4. Worked example #

    (a) Benchmarks and metrics by hand. A quarterly series (m=4m = 4) has three years of training data and one year of test data:

    Q1 Q2 Q3 Q4
    year 1 (y1..y4y_1..y_4) 10 14 8 12
    year 2 (y5..y8y_5..y_8) 12 15 9 14
    year 3 (y9..y12y_9..y_{12}) 12 17 10 15
    year 4, test (y13..y16y_{13}..y_{16}) 14 19 11 16

    The origin is n=12n = 12 and H=4H = 4.

    Step 1: the four forecasts.

    • Mean: yˉ=(10+14+8+12+12+15+9+14+12+17+10+15)/12=148/12≈12.33\bar y = (10 + 14 + 8 + 12 + 12 + 15 + 9 + 14 + 12 + 17 + 10 + 15)/12 = 148/12 \approx 12.33 for every hh.
    • Naive: y12=15y_{12} = 15 for every hh.
    • Seasonal naive: for h=1,…,4h = 1, \dots, 4, k=0k = 0, so y^12+h∣12=y12+h−4=y8+h\hat y_{12+h\mid 12} = y_{12+h-4} = y_{8+h}: the values 12,17,10,1512, 17, 10, 15 of year 3.
    • Drift: slope (y12−y1)/(12−1)=(15−10)/11≈0.4545(y_{12} - y_1)/(12 - 1) = (15 - 10)/11 \approx 0.4545. Forecasts 15+0.4545 h15 + 0.4545\,h: 15.45,15.91,16.36,16.8215.45, 15.91, 16.36, 16.82.

    Step 2: errors e=y−y^e = y - \hat y on the test year (14,19,11,16)(14, 19, 11, 16).

    Method e13e_{13} e14e_{14} e15e_{15} e16e_{16}
    mean 1.67 6.67 −1.33 3.67
    naive −1 4 −4 1
    seasonal naive 2 2 1 1
    drift −1.45 3.09 −5.36 −0.82

    Step 3: the MASE scale. The in-sample seasonal naive errors are yt−yt−4y_t - y_{t-4} for t=5,…,12t = 5, \dots, 12: 12−10=212 - 10 = 2, 15−14=115 - 14 = 1, 9−8=19 - 8 = 1, 14−12=214 - 12 = 2, 12−12=012 - 12 = 0, 17−15=217 - 15 = 2, 10−9=110 - 9 = 1, 15−14=115 - 14 = 1. Their absolute values sum to 10, so s=10/8=1.25s = 10/8 = 1.25.

    Step 4: the metrics. For the seasonal naive forecast: MAE=(2+2+1+1)/4=1.5\text{MAE} = (2 + 2 + 1 + 1)/4 = 1.5; RMSE=(4+4+1+1)/4=2.5≈1.58\text{RMSE} = \sqrt{(4 + 4 + 1 + 1)/4} = \sqrt{2.5} \approx 1.58; MAPE=1004(214+219+111+116)≈10.0 %\text{MAPE} = \frac{100}{4}\big(\frac{2}{14} + \frac{2}{19} + \frac{1}{11} + \frac{1}{16}\big) \approx 10.0\,\%; WAPE=6/60=10 %\text{WAPE} = 6/60 = 10\,\%; MASE=1.5/1.25=1.2\text{MASE} = 1.5/1.25 = 1.2.

    Method MAE RMSE MAPE (%) MASE
    mean 3.33 3.95 20.5 2.67
    naive 2.50 2.92 17.7 2.00
    seasonal naive 1.50 1.58 10.0 1.20
    drift 2.68 3.21 20.1 2.15

    The seasonal naive forecast wins on every metric. Its MASE is still above 1: its test errors (one to four steps ahead, one year later, with a small upward trend) are larger than its in-sample one-step errors. The trend of about one unit per year is visible in its errors: all four are positive. A method that combines the season with a trend (lesson 04) can do better.

    (b) Listing the folds. n=20n = 20, expanding window, initial training size 12, H=4H = 4, step 4, gap 0. The origins are 12 and 16 (the next one, 20, has no test data left):

    Fold training indices test indices
    1 1–12 13–16
    2 1–16 17–20

    This is exactly TimeSeriesSplit(n_splits=2, test_size=4) on 20 points (with indices from 0 in Python). With gap=1, the training sets would end at 11 and 15 instead.

    5. In code #

    NumPy checks the worked example. The notebook has these cells and the exercises.

    import numpy as np
    
    y_train = np.array([10, 14, 8, 12, 12, 15, 9, 14, 12, 17, 10, 15.])
    y_test = np.array([14, 19, 11, 16.])
    m, n, H = 4, len(y_train), len(y_test)
    h = np.arange(1, H + 1)
    
    fc = {
        "mean": np.full(H, y_train.mean()),
        "naive": np.full(H, y_train[-1]),
        "snaive": y_train[n + h - m * ((h - 1) // m + 1) - 1],     # -1: Python indices start at 0
        "drift": y_train[-1] + h * (y_train[-1] - y_train[0]) / (n - 1),
    }
    scale = np.mean(np.abs(y_train[m:] - y_train[:-m]))
    print("scale", scale)                                          # scale 1.25
    for name, f in fc.items():
        e = y_test - f
        print(f"{name:7s} MAE {np.mean(np.abs(e)):.2f}  RMSE {np.sqrt(np.mean(e**2)):.2f}  "
              f"MAPE {100 * np.mean(np.abs(e) / y_test):.1f}  MASE {np.mean(np.abs(e)) / scale:.2f}")
    # mean    MAE 3.33  RMSE 3.95  MAPE 20.5  MASE 2.67
    # naive   MAE 2.50  RMSE 2.92  MAPE 17.7  MASE 2.00
    # snaive  MAE 1.50  RMSE 1.58  MAPE 10.0  MASE 1.20
    # drift   MAE 2.68  RMSE 3.21  MAPE 20.1  MASE 2.15
    
    from sklearn.model_selection import TimeSeriesSplit
    for tr, te in TimeSeriesSplit(n_splits=2, test_size=4).split(np.arange(20)):
        print(tr[0], tr[-1], te)
    # 0 11 [12 13 14 15]
    # 0 15 [16 17 18 19]
    

    6. Where it shows up #

    • Forecasting competitions. The M3, M4 and M5 competitions rank methods by sMAPE, MASE and (M5) a weighted RMSSE, always against naive and seasonal naive benchmarks. The M4 results showed that simple statistical benchmarks are hard to beat (Makridakis et al. 2020).
    • Demand and energy forecasting. Retailers report WAPE (it weights each product by its volume); electricity load forecasts are evaluated per horizon, because a day-ahead error and an hour-ahead error lead to different actions.
    • Finance. Trading strategies are tested with walk-forward backtests (a rolling origin with refitting). Purging and an embargo remove training samples whose labels overlap the test period (López de Prado 2018). Leakage here gives strategies that only work in the past.
    • Machine learning models for time series. Gradient boosting (lesson 08) and deep networks (lesson 09) are judged by the same backtests and by their MASE relative to seasonal naive. Foundation models for time series are compared on held-out series and periods; overlap between pretraining data and the test data is a form of leakage.
    • ML for science. Machine-learning weather models are trained on earlier years of reanalysis data and tested on later years they never saw (for example GraphCast, Lam et al. 2023); the skill score of a forecast is often reported relative to a reference forecast such as climatology or persistence, which play the roles of the mean and naive benchmarks. Drift monitoring of deployed models uses the same idea (mlops lesson 07).

    7. Common mistakes #

    • No benchmark. A complex model with MAPE 8 % sounds good until the seasonal naive forecast has 7 %. Always report the benchmarks on the same folds.
    • Shuffled cross-validation. KFold(shuffle=True) on time series lets the model interpolate and see the future. Use a time-ordered split, unless the special conditions of Bergmeir et al. hold.
    • One test period. One split of the last 3 months is one sample. Use many origins.
    • Residual error reported as accuracy. In-sample residuals are too small. Report errors on data that the method did not see.
    • Preprocessing before the split. Scaling, decomposition, feature selection and imputation must be fitted inside each training window.
    • Averaging errors over horizons without saying so. The error at h=1h = 1 and at h=12h = 12 are different quantities. Show MAE(h)\text{MAE}(h).
    • MAPE on data with zeros or values near zero. It is undefined or dominated by a few points, and it rewards forecasts that are too low. Use MASE or WAPE.
    • Averaging MAE over series with different scales. The series with the largest values dominate. Use a scaled metric (MASE, RMSSE) to average across series.

    8. Check yourself #

    1. A daily series has a weekly pattern (m=7m = 7). The origin is Sunday, day tt. What is the seasonal naive forecast for the next Wednesday, h=3h = 3, and for the Wednesday after that, h=10h = 10?
      AnswerFor h=3h = 3: k=⌊2/7⌋=0k = \lfloor 2/7 \rfloor = 0, so y^=yt+3−7=yt−4\hat y = y_{t+3-7} = y_{t-4}, the last observed Wednesday. For h=10h = 10: k=⌊9/7⌋=1k = \lfloor 9/7 \rfloor = 1, so y^=yt+10−14=yt−4\hat y = y_{t+10-14} = y_{t-4} again. The seasonal naive forecast repeats the last observed week.
    2. Show that the naive forecast is the best mean-squared-error forecast for a random walk, and give the variance of its hh-step error.
      Answeryt+h=yt+εt+1+⋯+εt+hy_{t+h} = y_t + \varepsilon_{t+1} + \dots + \varepsilon_{t+h}. The future shocks have mean 0 and are independent of the past, so E[yt+h∣y1,…,yt]=yt\mathbb E[y_{t+h} \mid y_1, \dots, y_t] = y_t. The conditional mean minimises the expected squared error. The error is the sum of hh independent shocks, so its variance is hσ2h\sigma^2.
    3. Compute MASE for the naive forecast of the worked example, and say what it means.
      AnswerThe naive errors are −1,4,−4,1-1, 4, -4, 1, so MAE =10/4=2.5= 10/4 = 2.5. The scale is s=1.25s = 1.25, so MASE =2.5/1.25=2.0= 2.5/1.25 = 2.0. Its test errors are twice as large as the in-sample one-step errors of the seasonal naive method. It ignores the strong season.
    4. A demand series has many days with zero sales. Your manager wants MAPE. What do you say, and what do you propose?
      AnswerMAPE divides by yjy_j, so it is undefined on days with zero sales and very large on days with small sales; it also rewards forecasts that are too low. Propose MASE (scale-free, defined unless the training data are exactly periodic) or WAPE (total absolute error divided by total sales, which is well defined as long as total sales are positive).
    5. Your features include a 7-day centred moving average of the target. You backtest with TimeSeriesSplit(gap=0). What leaks, and how large should the gap be?
      AnswerA centred 7-day average at time tt uses yt+1,yt+2,yt+3y_{t+1}, y_{t+2}, y_{t+3}. For the last training rows these values lie in the test window, so test targets leak into training features. A gap of at least 3 removes this overlap. Better: use a trailing average, which only uses past values, and keep the gap for delays in data arrival.
    6. Why does the seasonal naive method itself often have a MASE above 1 on the test set?
      AnswerThe MASE scale is the in-sample MAE of *one-step* seasonal naive forecasts. On the test set the seasonal naive method forecasts up to HH steps ahead, often with a trend or a change in level between the training and the test period. Its errors are then larger than its one-step in-sample errors.
    Recall cardsAnswer in your head first, then reveal. Add the cards to your review deck to see them again just before you would forget.
    1. Name the four benchmark forecasts.

    2. Write the seasonal naive forecast y^n+h∣n\hat y_{n+h\mid n}.

    3. For which model is the naive forecast optimal, and how fast does its error grow?

    4. What is rolling-origin evaluation?

    5. Expanding window or sliding window: what is the difference?

    6. Why does random K-fold cross-validation usually leak on time series?

    7. Define MASE.

    8. Which constant forecast minimises MSE, and which minimises MAE?

    9. Why does MAPE reward forecasts that are too low?

    10. What does a gap (embargo) between training and test data prevent?

    Open the review →

    9. Interview angle #

    • "How would you evaluate a forecasting model?" — A rolling-origin backtest with the same folds for all methods, refit (or update) at each origin, errors per horizon, and a comparison with naive and seasonal naive benchmarks using MASE.
    • "Can you use K-fold cross-validation on time series?" — Not in general: shuffled folds interpolate and see the future. Exception: a purely autoregressive model with uncorrelated errors on a stationary series (Bergmeir et al. 2018).
    • "MAPE or RMSE?" — It depends on the cost of errors. RMSE rewards the mean, MAE the median, MAPE a biased low value and fails near zero. For many series of different scales use MASE or WAPE.
    • "Your model has great backtest results but fails in production. Why?" — Leakage: preprocessing on all data, future-filled missing values, revised data, covariates not known in time, tuning on the test period; or a backtest that did not match the production delay (a missing gap).

    10. Practice #

    The notebook for this lesson runs in Colab. It has five exercises, each with a check cell and a folded solution:

    1. Write mean_forecast, naive, snaive and drift for a horizon HH, and check them on the worked example.
    2. Write rolling_origin_splits(n, initial, horizon, step, gap, expanding) and check it against TimeSeriesSplit where the two agree.
    3. Write mase(y_train, y_test, y_pred, m) and check it on the worked example.
    4. Backtest the four benchmarks on monthly Mauna Loa CO₂ data (1980–2001) and make a table of MASE by horizon.
    5. Reproduce the optimism of random K-fold with a k-nearest-neighbour model on lags, and compare it with a rolling origin and with a true future block.

    11. Watch and read #

    The mean, naive, seasonal naive and drift methods on real data.
    Open on YouTube · the player loads from youtube-nocookie.com when you press play
    Scale-dependent, percentage and scaled errors, and why MASE was proposed.
    Open on YouTube · the player loads from youtube-nocookie.com when you press play
    The rolling origin as a picture, and the error as a function of the horizon.
    Open on YouTube · the player loads from youtube-nocookie.com when you press play
    Why a single split is not enough, and how to move the origin.
    Open on YouTube · the player loads from youtube-nocookie.com when you press play