Forecasting algorithm

LightGBM forecasting

Gradient-boosted quantile regression for a single time series. Trains a point prediction plus a calibrated uncertainty band for every period in the forecast horizon. Sub-second on tens of rows; scales to millions.

What it does

You point it at a DataSource and pick a date column, a target column, and (optionally) some drivers. It outputs a new DataSource with one row per future period, in this exact shape:

ColumnTypeMeaning
<date_col>dateEchoes the input date column name
yhatfloatPoint prediction (median quantile)
yhat_lowerfloatLower bound at chosen interval
yhat_upperfloatUpper bound at chosen interval

The output DataSource is a first-class table the rest of the app can plot, pin to dashboards, or include in reports — there is no separate "forecast viewer".

How it works

Three quantile boosters

For each training run, three independent LightGBM models are fit, one per quantile derived from the chosen interval level:

Interval levelLower quantileMedianUpper quantile
80% (default)0.100.500.90
90%0.050.500.95
95%0.0250.500.975

LightGBM trains each booster with objective="quantile" against its respective quantile parameter — no calibration trick, just three direct fits.

After prediction the three values are sorted per row so the output always satisfies yhat_lower ≤ yhat ≤ yhat_upper, even if the upper booster slightly under-predicts on a particular row (quantile crossing fix).

The raw quantile bands tend to under-cover at longer horizons, so the trainer applies Conformalized Quantile Regression (CQR) on top: a slice of the training data is held out, residuals max(yhat_lower − y, y − yhat_upper) are computed on it, and the predicted intervals are widened by the interval_level-quantile of those residuals. The "80% interval" label then carries its intended meaning (marginal coverage ≥ 80%) rather than being a hopeful gesture.

When the input is too short to carve a calibration slice, the trainer falls back to raw quantile bands and flags the run; the forecast viz appends "(uncalibrated)" to its title so the looser guarantee is visible.

Engineered features

The trainer adds three families of features automatically — you don't fill these in. The feature set is cadence-aware so the lags cover the dominant seasonality at each frequency without exploding:

CadenceTarget lagsRolling meansExogenous lags
Daily1, 7, 14, 287, 281, 7
Weekly1, 2, 4, 84, 81, 2
Monthly1, 2, 3, 6, 123, 61, 3
Quarterly1, 2, 42, 41, 2
Yearly1, 2, 32, 31, 2

In addition, calendar features are derived from the date column: day-of-week, month, day-of-month, is_weekend. The rolling-mean window is shifted by one period before averaging so the model never peeks at its own current target during training.

Any driver columns you select are added as their own lagged copies (one entry per lag in the cadence's exog_lags row above). Drivers show up in the feature-importance panel alongside the engineered lag features.

Recursive multi-step forecast

Forecasting more than one period ahead is recursive: the median prediction for step t becomes part of the lag inputs for step t+1. The lower and upper bound predictions are produced the same way but with their respective booster.

This matches how real users want forecasts to behave ("show me the next 12 months") and keeps the feature engineering identical between training and prediction.

Configuration

The form maps directly to the spec — every input lands in either the model row or the spec, no UI-only flags:

Form inputStored as
SourcePredictionModel.source_id
Date columnspec.date_col
Target columnspec.target_col
Drivers (multi-select)spec.exogenous_cols
Cadencespec.frequency
Horizonspec.horizon (in periods)
Interval levelspec.interval_level
Validation horizonspec.validation_horizon
Seedspec.seed

algorithm, version, task, and hyperparams are server-defaulted. The seed is in the spec, not the run — same input + same spec ⇒ same output, deterministic by construction.

Metrics

A successful run writes these into PredictionRun.metrics:

MetricMeaning
maeMean absolute error against the held-out backtest period
mapeMean absolute % error (rows with target=0 excluded)
smapeSymmetric MAPE — robust to zero/near-zero targets
pi_coverageFraction of backtest rows where actual fell inside band
feature_importancesGain-based, descending — what the model leaned on

smape is the primary "is this any good" number. pi_coverage should be close to interval_level — see Limitations.

Good for

  • Single time series with regular cadence and clear seasonal patterns. Daily retail metrics, weekly demand, monthly KPIs.
  • Non-linear relationships and abrupt regime shifts. LightGBM handles step-changes, holidays, and threshold effects more gracefully than classical ARIMA.
  • Small or large data. Trains in well under a second on a few hundred rows; scales to millions without changing the code path.
  • Driver-informed forecasts. When you have leading indicators (marketing spend, weather, competitor pricing), adding them as drivers and reading the importance panel often pays off more than tuning hyperparameters.

Limitations

  • Per-horizon coverage drift. CQR applies a uniform widening across the entire forecast horizon, so coverage averages out correctly but step-1 predictions get the same band as step-12. The real forecast variance grows recursively; a per-horizon adjustment (separate quantile per step) is a future refinement.
  • No multi-series support. One time series in, one forecast out. Per-store / per-region forecasting is explicitly out of scope for V1 — split into one model per series in the meantime, or aggregate first via ETL.
  • No categorical outcomes. Forecasting "which category wins next month" is a classification task and isn't supported in V1.
  • Trailing-period edges. If your input data ends mid-period (e.g., the last week is truncated), the trainer doesn't drop the partial period and the final eval metric anchors against an incomplete observation. Trim before training.
  • No SHAP / partial dependence. The importance panel shows LightGBM's native gain-based importance with the sign of correlation between feature and target. True SHAP is a separate sprint.

When to use other algorithms

  • autoarima-v1.md — the classical seasonal-ARIMA baseline. Reach for it on regular, stationary series, when you want model-derived (not conformally-patched) prediction intervals, or simply as a yardstick — running both and comparing is the single most useful forecasting habit.

Future entries (Prophet, theta, etc.) will appear in the Algorithm dropdown automatically when added to the backend registry.

Not sure which to pick?

Choosing a forecasting algorithm

LightGBM forecasting vs AutoARIMA — when gradient-boosted forecasting wins, when the classical SARIMAX model is enough, and why it is worth running both.