Time-series forecasting of two backlist titles on Nielsen BookScan data — classical, ML, DL & hybrid models on a 32-week holdout. By Pierre Sutherland.
data/raw.dvc). To reproduce, supply your own copy under data/raw/ (course Drive link in the README), then run the pipeline. Everything else — code, parameters, the 12-stage DAG — is public.params.yaml# PACE Course 3 (C301) — Nielsen BookScan time-series forecasting.
# Knobs are read by every pipelines/forecast/*.py via yaml.safe_load.
seed: 42
horizon: 32 # 32-week test horizon (rubric)
seasonality: 52 # weekly data, annual cycle
focus_start: "2012-01-01" # restrict to post-2012 backlist regime
active_cutoff: "2024-07-01" # ISBNs with any non-zero sale after this = active
alchemist: "9780722532935"
caterpillar_candidates: # script picks the max-volume of these
- "9780241003008" # board book ISBN
- "9780140500875" # paperback ISBN
arima:
m: 52
max_p: 2
max_q: 2
max_d: 1
max_P: 1
max_Q: 1
max_D: 1
information_criterion: "aicc"
stepwise: true
dvc.yamlstages:
ingest:
cmd: .venv/bin/python pipelines/forecast/ingest.py
deps:
- pipelines/forecast/ingest.py
- data/raw
outs:
- data/processed/raw_long.csv
features:
cmd: .venv/bin/python pipelines/forecast/features.py
deps:
- pipelines/forecast/features.py
- data/processed/raw_long.csv
params:
- focus_start
- active_cutoff
- horizon
- alchemist
- caterpillar_candidates
outs:
- data/processed/train_alchemist.csv
- data/processed/holdout_alchemist.csv
- data/processed/train_caterpillar.csv
- data/processed/holdout_caterpillar.csv
- data/processed/active_isbns.csv
- data/processed/caterpillar_isbn.txt
train_arima:
cmd: .venv/bin/python pipelines/forecast/train_arima.py
deps:
- pipelines/forecast/train_arima.py
- data/processed/train_alchemist.csv
- data/processed/train_caterpillar.csv
params:
- horizon
- arima
outs:
- data/models/arima_alchemist.pkl
- data/models/arima_caterpillar.pkl
- data/models/forecast_alchemist.csv
- data/models/forecast_caterpillar.csv
- data/models/arima_orders.json
train_imputation:
cmd: .venv/bin/python pipelines/forecast/train_imputation.py
deps:
- pipelines/forecast/train_imputation.py
- data/processed/train_alchemist.csv
- data/processed/train_caterpillar.csv
- data/processed/holdout_alchemist.csv
- data/processed/holdout_caterpillar.csv
params:
- horizon
- arima
outs:
- data/models/imputation_sweep.json
train_xgb_lag:
cmd: .venv/bin/python pipelines/forecast/train_xgb_lag.py
deps:
- pipelines/forecast/train_xgb_lag.py
- data/processed/train_alchemist.csv
- data/processed/train_caterpillar.csv
- data/processed/holdout_alchemist.csv
- data/processed/holdout_caterpillar.csv
params:
- horizon
- seed
outs:
- data/models/forecast_xgb_lag_alchemist.csv
- data/models/forecast_xgb_lag_caterpillar.csv
- data/models/xgb_lag_orders.json
train_xgb_calendar:
cmd: .venv/bin/python pipelines/forecast/train_xgb_calendar.py
deps:
- pipelines/forecast/train_xgb_calendar.py
- data/processed/train_alchemist.csv
- data/processed/train_caterpillar.csv
- data/processed/holdout_alchemist.csv
- data/processed/holdout_caterpillar.csv
params:
- seed
- horizon
outs:
- data/models/forecast_xgb_calendar_alchemist.csv
- data/models/forecast_xgb_calendar_caterpillar.csv
- data/models/xgb_calendar_orders.json
train_lstm:
cmd: .venv/bin/python pipelines/forecast/train_lstm.py
deps:
- pipelines/forecast/train_lstm.py
- data/processed/train_alchemist.csv
- data/processed/train_caterpillar.csv
outs:
- data/models/lstm_status.json
train_hybrid_seq:
cmd: .venv/bin/python pipelines/forecast/train_hybrid_seq.py
deps:
- pipelines/forecast/train_hybrid_seq.py
- data/models/arima_alchemist.pkl
- data/models/arima_caterpillar.pkl
- data/models/forecast_alchemist.csv
- data/models/forecast_caterpillar.csv
- data/processed/train_alchemist.csv
- data/processed/train_caterpillar.csv
- data/processed/holdout_alchemist.csv
- data/processed/holdout_caterpillar.csv
outs:
- data/models/forecast_hybrid_seq_alchemist.csv
- data/models/forecast_hybrid_seq_caterpillar.csv
- data/models/hybrid_seq_status.json
train_lag_llama:
cmd: .venv-lagllama/bin/python pipelines/forecast/train_lag_llama.py
deps:
- pipelines/forecast/train_lag_llama.py
- data/processed/train_alchemist.csv
- data/processed/holdout_alchemist.csv
- data/processed/train_caterpillar.csv
- data/processed/holdout_caterpillar.csv
outs:
- data/models/forecast_lag_llama_alchemist.csv
- data/models/forecast_lag_llama_caterpillar.csv
- data/models/lag_llama_status.json
train_hybrid_par:
cmd: .venv/bin/python pipelines/forecast/train_hybrid_par.py
deps:
- pipelines/forecast/train_hybrid_par.py
- data/models/forecast_alchemist.csv
- data/models/forecast_caterpillar.csv
- data/processed/holdout_alchemist.csv
- data/processed/holdout_caterpillar.csv
outs:
- data/models/forecast_hybrid_par_alchemist.csv
- data/models/forecast_hybrid_par_caterpillar.csv
- data/models/parallel_sweep_alchemist.csv
- data/models/parallel_sweep_caterpillar.csv
- data/models/hybrid_par_status.json
charts:
cmd: .venv/bin/python pipelines/forecast/charts.py
deps:
- pipelines/forecast/charts.py
- data/processed/train_alchemist.csv
- data/processed/holdout_alchemist.csv
- data/processed/train_caterpillar.csv
- data/processed/holdout_caterpillar.csv
- data/models/forecast_alchemist.csv
- data/models/forecast_caterpillar.csv
- data/models/forecast_xgb_calendar_alchemist.csv
- data/models/forecast_xgb_calendar_caterpillar.csv
- data/models/forecast_hybrid_par_alchemist.csv
- data/models/forecast_hybrid_par_caterpillar.csv
- data/models/parallel_sweep_alchemist.csv
- data/models/parallel_sweep_caterpillar.csv
outs:
- charts/forecast_alchemist.png
- charts/forecast_caterpillar.png
- charts/parallel_sweep.png
evaluate:
cmd: .venv/bin/python pipelines/forecast/evaluate.py
deps:
- pipelines/forecast/evaluate.py
- data/processed/holdout_alchemist.csv
- data/processed/holdout_caterpillar.csv
- data/models/forecast_alchemist.csv
- data/models/forecast_caterpillar.csv
- data/models/arima_orders.json
- data/models/forecast_xgb_lag_alchemist.csv
- data/models/forecast_xgb_lag_caterpillar.csv
- data/models/xgb_lag_orders.json
- data/models/forecast_xgb_calendar_alchemist.csv
- data/models/forecast_xgb_calendar_caterpillar.csv
- data/models/xgb_calendar_orders.json
- data/models/lstm_status.json
- data/models/forecast_hybrid_seq_alchemist.csv
- data/models/forecast_hybrid_seq_caterpillar.csv
- data/models/hybrid_seq_status.json
- data/models/forecast_hybrid_par_alchemist.csv
- data/models/forecast_hybrid_par_caterpillar.csv
- data/models/parallel_sweep_alchemist.csv
- data/models/parallel_sweep_caterpillar.csv
- data/models/hybrid_par_status.json
- data/models/forecast_lag_llama_alchemist.csv
- data/models/forecast_lag_llama_caterpillar.csv
- data/models/lag_llama_status.json
- data/models/imputation_sweep.json
metrics:
- metrics.json:
cache: false
Every model on the same 32-week holdout (MAE; lower is better). The submission stands behind SARIMA; the rest is documented here as research, not as the headline.
| Model | Alch. MAE | Cat. MAE | Notes |
|---|---|---|---|
| SARIMA (auto-ARIMA) | 127.8 | 345.5 | submission baseline; near-best, cheapest |
| Hybrid (sequential) | 127.4 | 339.9 | LSTM on SARIMA residuals — marginal gain |
| XGBoost (lag-window) | 136.8 | 368.1 | in the report; trails on both |
| XGBoost + calendar features | 113.6 | 304.5 | lowest point MAE — but not significant (DM p=0.18 / 0.13) |
| Hybrid (parallel) | 125.5 | 351.2 | best blend weight collapses to SARIMA-only |
| LSTM (KerasTuner) | 176.0 | 883.3 | weakest, badly so on the board book |
| Lag-Llama (zero-shot) | 162.4 | 634.9 | time-series foundation model; lost to SARIMA |
Also explored (beyond the rubric): SARIMAX with explicit Christmas event-regressors; a Box-Cox / log transform to model the multiplicative shape directly (ruled out — no holdout gain); a monthly cross-check (aggregate to months, 8-month horizon — did not beat the weekly models); and the arithmetic cost / stocking model in the next tab.