Forecasting with machine learning: lag features, gradient boosting and multi-step strategies
Before this lesson: lesson 03 (rolling-origin backtesting, MASE, leakage in evaluation) and lesson 05 (an AR model is a regression on lags). Differencing is in lesson 02. Decision trees, gradient boosting, cross-validation and leakage in pipelines are in the ml-fundamentals track. Time: about 90 minutes, plus the notebook.
Why this matters #
When you forecast thousands of related series with many covariates (retail sales, electricity load, web traffic), gradient-boosted trees on lag and calendar features are among the strongest methods. In the M5 competition (2020, 42,840 Walmart sales series), "most of the methods examined utilized LightGBM", a gradient-boosting library, and essentially all of the 50 best methods used cross-learning: one model trained on many series. The winning entry averaged LightGBM models trained per store, per store and category, and per store and department, in a recursive and a non-recursive variant. It was 22.4 % more accurate than the best statistical benchmark (Makridakis, Spiliotis and Assimakopoulos 2022). The same "window to table" step is the input format of the deep networks of lesson 09 and of the quantile models of lesson 10.
The risk is just as large. A forecasting table is easy to build with a hidden leak, for example a rolling mean that contains today's value. Then the backtest looks excellent and the model fails in production. This lesson shows how to build the table correctly and how to test it.
1. Predict first #
2. The picture #
From a series to a table #
Take a series , a lookback of lags and a horizon . For each target time , one row of the table has
The row pretends that we stand at the forecast origin : we know everything up to and we forecast steps ahead. Slide the window one step to the right and you get the next row. A lag- feature of row is the value , steps before the target time. A rolling-mean feature is the mean of the last values that are known at the origin.
Move the target slider (or click a row) and watch one window become one row of the table: the blue points are the features, the orange point is the target, and the yellow line is the forecast origin. Turn on the rolling mean. With shift(h) it uses only known values (aqua). Set and choose shift(1), then no shift: the window crosses the origin and turns magenta. Each step of or removes one row from the table.
Which values are known at the origin? #
At the origin we forecast . The lag- value of the target time is . It is known only if , that is, if . So the set of usable lags depends on the horizon. Covariates (other variables) follow the same logic, but some of them are known in advance: the day of the week, a holiday, a price that was planned before the origin.
Click cells of the grid (rows: horizon ; columns: lag ). In the direct strategy the cells with are magenta: those values come after the origin. Switch to the recursive strategy: they become orange, because the model uses its own forecasts there. Then choose other variables: a calendar feature is known for every cell; the measured temperature is not, but a weather forecast issued before the origin is.
You use the direct strategy and train a separate model for horizon (daily data). Which lags of the target can this model use?
At the origin the model forecasts . Lag of that target time is , which is known only when , that is . Lags 1 to 6 are in the training table, but they do not exist yet when the forecast is made. Using them is leakage.
Trees flatten trends #
A regression tree splits the inputs into boxes and predicts, in each box, the mean of the training targets in that box. On a rising series the future inputs (recent lags) are larger than every training input. They all fall into the "largest values" boxes, so the forecast stays near the top of the training range and stops rising. Boosting adds many trees, but the sum is still constant outside the training range of the inputs.
Switch the target. On the raw level the boosted trees (orange) stay below the largest training value (muted dashed line) while the test data (blue dots) keep rising. Predict the differences instead, or the residual from a linear trend: the forecast now follows the trend. The linear model extrapolates even on the raw level. The data are the track's "monthly demand" series; the origin (yellow) is after month 96.
A random forest is trained on lags 1–12 of a series that rises from 100 to 150 during training. You forecast the next 24 months recursively. What do you get?
Each tree predicts a mean of training targets, and an average of such means stays in of the training data. New inputs larger than all training inputs land in the same leaves, so the forecast stays flat near the top. It does not fall to the overall mean, because the large lags still select the leaves with large targets.
3. Definitions and main results #
Reduction to supervised learning #
Let be the observed series. Fix a lookback and a horizon .
- The information set at the origin is everything known at time : the values , past values of the observed covariates, the known-in-advance covariates (also for future times) and the static features.
- A feature for forecasting is a function of only. Leakage is a feature that uses anything outside .
- The lag table for horizon has one row per target time : features and target . It has rows: the first target needs , so , and the last one is .
In pandas, y.shift(k) puts in row . So the lag- column is y.shift(k), and
every feature that is computed with a window must be computed after a shift by .
| Feature | Value in row (target , origin ) | pandas | Known at the origin? |
|---|---|---|---|
| lag | y.shift(k) |
only if | |
| rolling mean over | y.shift(h).rolling(w).mean() |
yes | |
| expanding mean | mean of | y.shift(h).expanding().mean() |
yes |
| month, day of week, holiday | calendar of time | index.month, ... |
yes (known in advance) |
| cyclic month | , | np.sin, np.cos |
yes |
| Fourier terms | , , | NumPy | yes |
y.rolling(w).mean() |
contains itself | — | no: leakage |
Calendar encodings. A tree can split an integer month (1 to 12) directly. A linear model cannot: it would treat December (12) as "12 times January". For linear models use one-hot columns, or the cyclic encoding , which puts the seasons on a circle. On the circle the distance between seasons and is , so December and January are as close as January and February (). Fourier terms with pairs give a smooth seasonal shape with columns (FPP3 §7.4).
Three kinds of covariates #
A covariate (exogenous variable) is any variable other than the target.
- Static: one value per series, for example the store id or the product category.
- Known in advance: future values are known at the origin, for example the calendar, holidays, planned prices and promotions, or a weather forecast issued before the origin.
- Observed only: values are known only up to the origin, for example the measured temperature, the sales of other products, or the number of visitors.
Proposition 1 (usable lags in the direct strategy). At the origin , for the target :
- lag of the target, or of an observed-only covariate, is usable if and only if ;
- a known-in-advance covariate is usable at every lag , including its value at the target time ;
- a static covariate is always usable.
Proof. Lag of the target time is the value at time . An observed-only variable is in exactly at times , and . A known-in-advance variable is in at all times, and a static one does not depend on time.
Trees cannot extrapolate #
Proposition 2.
- A regression tree fitted with squared loss predicts, in each leaf, the mean of the training targets in that leaf. So every prediction lies in , the range of the training targets. The same holds for a random forest, which averages trees.
- Let be a boosted ensemble of trees. For each feature , let be the largest split threshold on feature in all the trees. Then is constant on the region .
Proof.
- A leaf mean is an average of training targets. An average of numbers in is in . An average of tree predictions (a forest) is again in .
- Each internal node of a tree asks "is ?" for some . If , the answer is always "no". So all points of the region follow the same path in every tree, reach the same leaf in every tree, and get the same sum .
For boosting, the predictions can leave the range of the targets a little, but part 2 is what matters: the thresholds are always between training values of the features. On a trending series the future lags are larger than all training lags, so the forecast stops following the trend. The fixes change the target so that the future looks like the past:
- Differences. Predict from lags of (lesson 02). The forecast is .
- Detrending. Fit a trend on the training data (a line, or the level of an exponential smoothing model from lesson 04), let the trees model the residual, and add the trend back.
- Scaling by a level. Predict , where is the mean of the last few known values. Use the log first if the growth is multiplicative (lesson 01).
- A linear part. Use a linear model, or trees with linear models in the leaves (LightGBM's
linear_treeoption).
Multi-step strategies #
We want forecasts for at the origin . Write for the last known values.
- Recursive (iterated) strategy: train one model for . Then , , and so on: each forecast is fed back as an input.
- Direct strategy: train models, one per horizon: , using only values known at the origin.
- Multi-output (MIMO) strategy: one model predicts the whole vector at once (a multi-output tree, or a neural network with outputs).
- DirRec mixes the two: model uses and the forecasts of .
| Strategy | Models | Rows per model | Errors fed back? | Trained on the -step loss? |
|---|---|---|---|---|
| recursive | 1 | yes | no (one-step loss only) | |
| direct | no | yes | ||
| multi-output | 1 | no | yes (all together) |
Proposition 3 (recursive versus direct, one lag). Let be stationary with mean 0, variance and ACF . Fit linear models with one lag by least squares on infinitely many data. Then
- the recursive forecast is and the direct forecast is ;
- their mean squared errors satisfy
So the direct forecast is never worse. They are equal when for all , which is the ACF of an AR(1): then the one-lag model is correct.
Proof.
- For any number , .
- This parabola in is smallest at : this is the population least-squares coefficient of the direct model for horizon .
- The one-step model has . Applying it times gives , so the recursive forecast uses .
- Insert both values of into step 1 and subtract: .
The proposition is about a misspecified model. When the model is correct and the data are limited, the recursive strategy is often as good or better: it trains one model on the most rows and uses the model structure. The direct strategy needs models, and its forecasts at neighbouring horizons are not tied together, so the path can look jagged. On the 111 series of the NN5 competition, Taieb et al. (2012) found multi-output strategies the most accurate.
Compare the two strategies. The data come from an AR(2) process with cycles (, , lesson 05). With the model is wrong: the recursive RMSE rises above the direct RMSE after , as Proposition 3 predicts. Set : both curves meet the best possible RMSE of the true model (muted dashed, from the -weights of lesson 06). The top panel shows one forecast path; move the origin slider to see others.
Which multi-step strategy feeds its own forecasts back as inputs, so that the errors of early steps enter the later steps?
The recursive strategy uses in place of the unknown to forecast , and so on. Errors accumulate, and a wrong model is applied times. Direct and multi-output models use only values known at the origin.
Global and local models #
- A local model has its own parameters for each series , fitted on that series only (one ARIMA or one exponential smoothing model per series).
- A global model is one function with shared parameters, trained on the rows of all series together. Cross-learning in M5 means global models.
A global model sees many more rows: with 20 series of length 24 and , a local AR(3) fits 4 parameters to 21 rows, while one global AR(3) fits 4 parameters to rows. Montero-Manso and Hyndman (2021) show three things: (1) global methods are not more restrictive than local ones; both can produce the same forecasts, without any assumption that the series are similar; (2) the complexity of local methods grows with the number of series, while that of a global method stays constant, so a global model can afford to be more complex (for example, use more lags); (3) in their experiments, global linear models were competitive with local methods using two orders of magnitude fewer parameters.
Per-series scaling. Series often have very different levels (a small shop sells 10 units a day, a large one 1,000). Without scaling, a tree splits on absolute values and cannot share the shape between series, and the squared loss is dominated by the largest series. Divide each series by a scale , for example the mean of its training values (or of the last values at each origin), fit the global model on , and multiply the forecast by . Compute from training data only.
Move the training length. Twenty series share the same dynamics but have levels from 10 to 1000 (top, blue; switch to the scaled view). Each magenta dot is the MASE of one series (lesson 03); the black bar is the mean. With 12 points per series the local models overfit. The global model on raw values cannot fit all levels with one intercept. The global model on scaled values is the best. With 120 points the local models catch up.
You train one LightGBM model on the daily sales of 500 stores. Some stores sell about 10 units a day, others about 2,000. What should you do first?
Trees are invariant to a monotone transform of one feature over all rows, but here the levels differ between series: the same split value means "high" for one store and "low" for another, and the squared loss is dominated by the large stores. Per-series scaling (or a scale-free target such as a ratio or a difference) puts all series on a common scale so the model can share patterns.
Gradient boosting in practice #
- Libraries. scikit-learn's
HistGradientBoostingRegressor, LightGBM, XGBoost and CatBoost all build histogram-based boosted trees; the ideas of this lesson apply to all of them. - Loss. Squared error estimates the conditional mean; absolute error the median. For counts with many zeros use a Poisson or Tweedie loss (the M5 winner used the Tweedie loss). The quantile (pinball) loss gives quantile forecasts: lesson 10.
- Early stopping on a time-based block. Choose the number of trees on a validation block that
comes after the training rows, ideally with a gap of steps. In scikit-learn,
HistGradientBoostingRegressorturns on early stopping by itself when there are more than 10,000 rows (early_stopping="auto") and then holds out a random 10 % of the rows. For time series this mixes future rows into the training data. Setearly_stopping=Falseand choosemax_iteron your own time block, or (scikit-learn ≥ 1.7) pass a later block asX_val, y_valtofit. - Feature importance. Split-gain importance is computed on the training data and favours features with many possible split points; correlated lags share the importance between them. Use permutation importance on a time-based validation block, and do not read importance as a causal effect.
- Evaluation. Use the rolling origin of lesson 03. At each origin, recompute every feature with the data known at that origin and refit the model (or refit every few origins to save time). Choose hyperparameters inside the training part of each fold. Always compare with the benchmarks of lesson 03 (naive, seasonal naive).
A leakage checklist for features #
- A rolling or expanding window that includes the target time (no shift, or a shift smaller than ).
- Target encoding (for example the mean sales of each store) computed on all data, including the test period.
- Scaling or normalisation (mean, standard deviation, min–max, per-series scale) fitted on all data.
- An observed-only covariate used as if it were known in advance (measured weather; promotions decided late; data that were revised after the origin).
- Centred filters or decompositions of the whole series used as features.
- A random split for validation or early stopping.
A simple test catches the first five: change all values after an origin and recompute the features. No feature of a row with origin may change. The notebook uses this test.
Answers to section 1. (1) A flat line, near the largest training targets: trees predict means
of training targets (Proposition 2). Differencing or detrending fixes it. (2) No. For only
lags are known at the origin (Proposition 1). In the recursive strategy lag 1 would be a
forecast, not data. (3) No. rolling(7).mean() at time contains , the target itself; the
model partly copies the answer. Use y.shift(h).rolling(7).mean().
Hierarchical forecasting and reconciliation
Sales often form a hierarchy: products add up to stores, stores to regions, regions to the total. Write the bottom-level series as a vector and all series (every level) as , where the summing matrix has one row per series. For a total :
Forecasts made for each series separately ("base" forecasts ) usually do not add up. Three ways to make them coherent (FPP3 chapter 11):
- Bottom-up: forecast only the bottom series and add them: .
- Top-down: forecast the total and split it with historical proportions.
- MinT (minimum trace, Wickramasuriya, Athanasopoulos and Hyndman 2019): , where is the covariance matrix of the base forecast errors. With this is the orthogonal projection onto the coherent subspace (OLS reconciliation).
Example. Base forecasts: total 100, 55, 50; they do not add up (). With : and , so and the total becomes 101.67. The gap of 5 is shared equally: every series moves by . Bottom-up would give a total of 105; top-down with proportions and would give , .
4. Worked example #
Use the short series
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | |
|---|---|---|---|---|---|---|---|---|
| 10 | 11 | 13 | 12 | 14 | 16 | 15 | 17 |
(a) One-step table, , . The rows are , so
rows. Add the rolling mean of the two previous values,
y.shift(1).rolling(2).mean(), which is :
| roll2 (correct) | roll2 without shift (leaky) | target | ||||
|---|---|---|---|---|---|---|
| 4 | 13 | 11 | 10 | 12.0 | 12.5 | 12 |
| 5 | 12 | 13 | 11 | 12.5 | 13.0 | 14 |
| 6 | 14 | 12 | 13 | 13.0 | 15.0 | 16 |
| 7 | 16 | 14 | 12 | 15.0 | 15.5 | 15 |
| 8 | 15 | 16 | 14 | 15.5 | 16.0 | 17 |
The leaky column is : in row 6 it is , and it contains the target 16. To forecast the model gets the row and roll2 .
(b) Direct table for , . Row has features and target ; there are rows:
| target | |||
|---|---|---|---|
| 4 | 11 | 10 | 12 |
| 5 | 13 | 11 | 14 |
| 6 | 12 | 13 | 16 |
| 7 | 14 | 12 | 15 |
| 8 | 16 | 14 | 17 |
At the origin 8 this model forecasts from . It never sees .
(c) A tree on the level versus a tree on the differences. Fit a depth-1 tree (a stump) on the one-step table with the single feature . The 7 pairs are , , , , , , . The best split is :
- left leaf (): targets 11, 13, 12, 14, mean , squared error ;
- right leaf (): targets 16, 15, 17, mean , squared error .
The total squared error is the smallest of all splits. Forecast recursively from : gives ; then gives ; and so on. The forecast is flat at 16, below the last observed value, although the series rises by about 1 per step.
Now model the differences : , with mean . The simplest tree (a single leaf) predicts the mean difference, . Adding it up: , , . This is the drift method of lesson 03; a deeper tree on lags of would add corrections to it.
(d) Recursive versus direct with numbers. Take the AR(2) process , , and the (wrong) one-lag model. From the Yule–Walker equations (lesson 05):
- ;
- ;
- ;
- .
At : the recursive coefficient is ; the direct coefficient is . By step 1 of the proof,
The difference agrees with Proposition 3. At : versus . The recursive error at is even larger than , the error of forecasting the mean 0: the wrong model applied three times is worse than no model.
In part (d), the true model is AR(2) and we use one lag. What happens if we use two lags () instead?
With two lags the one-step model can learn and exactly (with enough data). Iterating the true model gives the best forecast at every horizon, and the direct models estimate the same best linear forecasts. The gap in Proposition 3 comes only from misspecification. The error is not zero: the future shocks cannot be predicted.
5. In code #
pandas builds the table with shift; scikit-learn's tree confirms part (c); a long simulation
confirms part (d).
import numpy as np
import pandas as pd
from sklearn.tree import DecisionTreeRegressor
# (a) series -> table: lags 1..3 and a rolling mean of the 2 previous values (h = 1)
y = pd.Series([10, 11, 13, 12, 14, 16, 15, 17.], index=range(1, 9))
X = pd.DataFrame({f"lag{k}": y.shift(k) for k in (1, 2, 3)})
X["roll2"] = y.shift(1).rolling(2).mean() # shift first: uses y_{s-1}, y_{s-2}
X["roll2_leak"] = y.rolling(2).mean() # WRONG: contains the target y_s
print(pd.concat([X, y.rename("target")], axis=1).dropna().to_string())
# lag1 lag2 lag3 roll2 roll2_leak target
# 4 13.0 11.0 10.0 12.0 12.5 12.0
# 5 12.0 13.0 11.0 12.5 13.0 14.0
# 6 14.0 12.0 13.0 13.0 15.0 16.0
# 7 16.0 14.0 12.0 15.0 15.5 15.0
# 8 15.0 16.0 14.0 15.5 16.0 17.0
# (c) a depth-1 tree (stump) on lag 1: the forecast stays flat
stump = DecisionTreeRegressor(max_depth=1).fit(X[["lag1"]].iloc[1:], y.iloc[1:])
print(stump.tree_.threshold[0], stump.tree_.value[1:, 0, 0]) # 13.5 [12.5 16. ]
x, path = 17.0, []
for h in range(3): # recursive: feed the forecast back
x = stump.predict(pd.DataFrame({"lag1": [x]}))[0]
path.append(float(x))
print("raw target:", path) # [16.0, 16.0, 16.0]
print("differences:", y.iloc[-1] + np.cumsum([np.diff(y).mean()] * 3)) # [18. 19. 20.]
# (d) recursive vs direct for a misspecified AR(1) model on AR(2) data
rng = np.random.default_rng(0)
z = np.zeros(200_000)
e = rng.normal(size=z.size)
for t in range(2, z.size):
z[t] = 1.2 * z[t - 1] - 0.5 * z[t - 2] + e[t]
z = z[1000:]
def ls_coef(h): # least squares of z_{t+h} on z_t
return np.dot(z[:-h], z[h:]) / np.dot(z[:-h], z[:-h])
a = ls_coef(1)
for h in (2, 3):
b = ls_coef(h)
mse_rec = np.mean((z[h:] - a**h * z[:-h])**2)
mse_dir = np.mean((z[h:] - b * z[:-h])**2)
print(h, round(a**h, 3), round(b, 3), round(mse_rec, 2), round(mse_dir, 2))
# 2 0.639 0.458 3.04 2.92
# 3 0.51 0.15 4.09 3.61
The simulated values match the theory: and at , and at ,
and MSEs up to simulation noise. The notebook builds the lag table with a
leak test, fits HistGradientBoostingRegressor on raw and differenced targets, compares the
strategies in a backtest, trains a global model, and repairs a leaky pipeline.
6. Where it shows up #
- Retail demand. M5 (Walmart, 2020): most methods used LightGBM on lags, rolling statistics, prices and calendar events, trained across many series. The winners of the earlier Kaggle competitions "Corporación Favorita Grocery Sales Forecasting" and "Recruit Restaurant Visitor Forecasting" also built on LightGBM (as reported in the M5 paper).
- Energy. Electricity load forecasts use lags of the load, calendar features (hour, day of week, holidays) and temperature. In production the temperature of the forecast period is a weather forecast: a known-in-advance covariate only if it was issued before the origin.
- Software. Libraries implement the reduction for you: sktime's
make_reduction(strategies"recursive","direct","multioutput"), skforecast, and Nixtla's mlforecast. They still need you to decide which features are known at the origin. - ML for science. In hydrology, Kratzert et al. (2019) trained one LSTM on 531 river basins of the CAMELS data set, with meteorological series and static basin attributes as inputs. This global model was more accurate than hydrological models calibrated basin by basin. The same global-model idea is used for air-quality stations and other sensor networks.
- Deep learning. The window-to-table step, per-series scaling and the multi-output strategy are the standard set-up of the neural forecasters in lesson 09.
7. Common mistakes #
- A rolling feature without a shift.
y.rolling(7).mean()contains . Usey.shift(h).rolling(7).mean(), with the shift equal to the horizon. - The same lags for every horizon. In the direct strategy the model for horizon can use only lags . A table built for leaks when it is reused for .
- Trees on a trending level. The forecast goes flat. Model differences, ratios or residuals from a trend instead, and check the forecast against the drift benchmark.
- Statistics computed on all data. Target encodings, per-series scales and normalisation constants must be computed from the training part of each fold only.
- Random validation split. A shuffled hold-out (also inside early stopping) puts future rows into training. Validate on a later time block.
- Measured covariates as if they were known. Train with what was known at the origin, for example a weather forecast, not the measured weather.
- One global model on raw levels. Large series dominate the loss and patterns are not shared. Scale each series first and undo the scaling after the forecast.
8. Check yourself #
- A series has values. How many rows has the lag table with lags for the
horizon ?
Answer
. The first target time is , the last is . - Write the pandas code for a 28-day rolling mean feature for a direct model at horizon .
Which values does it use for the target ?
Answer
`y.shift(7).rolling(28).mean()`. It is the mean of : 28 values, the most recent of which is at the origin . - The ACF of a series is , . With one lag, what are the recursive and the
direct coefficients at , and how large is the gap in MSE?
Answer
Recursive: . Direct: . Gap: . The one-lag model misses the strong lag-2 dependence, so iterating it is a poor 2-step forecast. - Which of these features are leaks for a one-day-ahead forecast of today's sales made last night:
(i) yesterday's sales; (ii) today's planned price; (iii) today's measured temperature; (iv) the
store's mean sales over the whole data set, test period included?
Answer
(iii) and (iv). Yesterday's sales are known (lag 1 with ). A planned price is known in advance. Today's measured temperature is observed only after the origin (use the weather forecast). The store mean over all data includes the test targets (compute it on the training part only). - Why does a global model need per-series scaling, while a local model does not?
Answer
A local linear model with an intercept adapts to its own series' level, and a local tree splits on that series' own values. A global model shares one function across series: on raw values the same split or intercept means different things for different series, and the loss is dominated by the series with the largest scale. Scaling puts all series on one scale so the shared patterns can be learned. - You fit
HistGradientBoostingRegressor()with default settings on 50,000 rows of a lag table. What is wrong with the early stopping, and how do you fix it?Answer
With more than 10,000 rows, `early_stopping="auto"` holds out a random 10 % of rows, so the validation rows are mixed in time with the training rows. Set `early_stopping=False` and choose `max_iter` on a later time block, or pass a later block as `X_val, y_val` (scikit-learn ≥ 1.7).
What is "reduction to supervised learning" in forecasting?
Cutting the series into windows: each window becomes a table row with features known at the origin (lags, rolling statistics, calendar) and a later value as the target. Any regression model can then be trained on the table.
How many rows has a lag table with values, lags and horizon ?
(target times ).
Write a leak-free 7-step rolling mean feature for horizon in pandas.
y.shift(h).rolling(7).mean(): shift by the horizon first, then roll.In the direct strategy, which lags of the target are usable at horizon ?
Only , because lag of time is , known only if .
Name the three kinds of covariates.
Static (store id), known in advance (calendar, planned prices, weather forecasts), observed only (measured weather, other sales).
Why can a tree ensemble not extrapolate a trend?
Each tree predicts a mean of training targets, and the ensemble is constant for inputs beyond the largest split thresholds. Future lags of a rising series all fall in the same leaves, so the forecast is flat.
Give two fixes for trees on a trending series.
Model the differences and add them up, or remove a trend fitted on the training data and model the residual (also: ratios to a level, a linear model).
Recursive versus direct: what is the main trade-off?
Recursive: one model, most data, but forecasts are fed back so errors and misspecification accumulate. Direct: one model per horizon, no feedback, fewer rows and more models.
For a one-lag linear model, what is ?
; zero when the series is AR(1).
Why does a global model need per-series scaling?
Series have different levels; without scaling the shared splits and intercepts cannot fit all of them and the loss is dominated by large series. Divide by a training scale, forecast, multiply back.
9. Interview angle #
- "How do you forecast with XGBoost or LightGBM?" — Build a lag table: lags , rolling
statistics after
shift(h), calendar and known-in-advance covariates. Choose a strategy (recursive, direct, multi-output). Make the target stationary (differences or scaling) because trees do not extrapolate. Backtest with a rolling origin against naive benchmarks. - "Your gradient-boosting forecast is flat on a growing series. Why?" — Trees predict means of training targets; inputs beyond the training range all land in the same leaves. Model differences or detrend.
- "Recursive or direct?" — Recursive: one model, errors fed back, good when the model is right. Direct: one model per , robust to misspecification, more models and fewer rows. Multi-output predicts the whole path at once. Know the formula .
- "Your backtest is much better than production. What do you check?" — Leaks: rolling windows without a shift, encodings or scalers fitted on all data, covariates that are only observed, random validation splits; then the perturbation test (change the future, features must not move).
- "One model for 10,000 products or 10,000 models?" — Usually one global model with per-series scaling (cross-learning, as in M5): more data per parameter and shared patterns; add series-level features (category, store) for differences between series.
10. Practice #
The notebook for this lesson runs in Colab. It has five exercises, each with a check cell and a folded solution:
- Write
make_lag_table(y, lags, horizon, rolling)with pandas; it is checked on the series of section 4 and with a leak test that changes the future. - Fit
HistGradientBoostingRegressoron lags of the "monthly demand" series, recursively, on the raw and on the differenced target; compare the test RMSE. - Write least-squares
recursiveanddirectforecasters and compare their RMSE by horizon on AR(2) data with one lag and with two lags. - Train one global linear model on 30 related series with per-series scaling and compare its MASE with 30 local AR models.
- Find and fix the three leaks in a feature pipeline (unshifted rolling mean, target encoding on all data, a same-day measured covariate).
11. Watch and read #
- Makridakis, Spiliotis and Assimakopoulos (2022), M5 accuracy competition: Results, findings, and conclusions, International Journal of Forecasting 38(4): LightGBM, cross-learning and the winning methods.
- Ben Taieb, Bontempi, Atiya and Sorjamaa (2012), A review and comparison of strategies for multi-step ahead time series forecasting based on the NN5 forecasting competition, Expert Systems with Applications.
- Montero-Manso and Hyndman (2021), Principles and algorithms for forecasting groups of time series: Locality and globality, International Journal of Forecasting.
- Bergmeir, Hyndman and Koo (2018), A note on the validity of cross-validation for evaluating autoregressive time series prediction, Computational Statistics and Data Analysis.
- Hyndman and Athanasopoulos, Forecasting: Principles and Practice (3rd ed.), free online: time series cross-validation and chapter 11, hierarchical forecasting.
- scikit-learn examples: Lagged features for time series forecasting
and Time-related feature engineering
(cyclic encodings, splines and
HistGradientBoostingRegressor). - Kratzert et al. (2019), Towards learning universal, regional, and local hydrological behaviors via machine learning applied to large-sample datasets, Hydrology and Earth System Sciences: one global LSTM for 531 basins.
Runs in your browser with nothing to install. The notebook repeats the key demos and has exercises with checks; solutions are inside, folded.
No Google account needed: Python runs inside your browser. The first start downloads about 40 MB; your changes are saved in this browser only.