A hands-on machine learning engineering study on Kaggle’s Store Sales benchmark: transitioning from deterministic heuristics to gradient-boosted decision trees, discovering why raw tree models struggle with retail scale disparities, engineering leakage-free multi-lag horizons, integrating Ecuador’s oil and holiday calendars, architecting a hybrid residual booster that smashes the Part VI baseline, and climbing 67 positions to Rank #116 globally (Top 21.0%) strictly on mobile edge hardware.
In Part VI of this series, we established that a disciplined ladder of non-machine learning heuristics could outperform 66.9% of Kaggle competitors, reaching 0.43150 RMSLE and securing Rank #183 out of 553 teams globally before training a single predictive model.
Yet heuristic baselines possess a structural ceiling: they cannot estimate promotional cross-price elasticities, capture non-linear oil price momentum, or dynamically model calendar interactions when municipal holidays coincide with semi-monthly paydays (quincenas). To push into the top tiers of the competition, we must transition to machine learning and gradient-boosted decision trees (GBDT).
Running production-grade gradient boosting across 1,782 simultaneous time series spanning 3,000,000 transaction rows imposes strict constraints on mobile hardware:
- Host Environment: Android running Termux with a Debian userspace via PRoot Distro on 64-bit ARM (
aarch64). - AI Pair Programmer: Google Antigravity CLI (
agy) executing in autonomous terminal sessions. - Hardware Budget: Strict 100% CPU execution, zero GPU acceleration, and a memory ceiling strictly capped below 500 MB RAM.
Over 5 live tournament submissions and a targeted regional calibration experiment, we advanced from an initial regularized linear model (0.49898) down to 0.41072, securing Rank #116 globally (Top 21.0%) and moving closer to the 0.400 threshold on Kaggle’s leaderboard.
Metric Alignment Invariant
The competition evaluates Root Mean Squared Logarithmic Error (RMSLE):
RMSLE = sqrt( (1 / N) * sum( ( ln(1 + y_hat_i) - ln(1 + y_i) )^2 ) )
Because regression loss functions minimize Root Mean Squared Error (RMSE):
z = ln(1 + y)
Minimizing RMSE on z is mathematically identical to minimizing RMSLE on raw unit sales y. Predictions are inverted back to sales using exponential inversion with non-negative clipping: y_hat = max(0, exp(z_hat) - 1).
The 16-Day Validation Holdout Protocol
The test set spans 16 days (2017-08-16 to 2017-08-31). To prevent lookahead leakage, our validation set mirrors this exact 16-day duration: training on dates prior to 2017-08-01 (322,542 rows) and validating strictly on 2017-08-01 to 2017-08-15 (26,730 rows). Once hyperparameters and iteration stops are confirmed, models retrain on the complete dataset through 2017-08-15.
Below is the complete progression of our machine learning architectures evaluated on Kaggle’s live public leaderboard and local holdout validation:
| Experiment & Architecture | Score / Metric | Leaderboard & Validation Status |
|---|---|---|
| Part VI Heuristic Benchmark | 0.43150 |
Rank #183 (Top 33.1%) |
| Exp 1: Regularized Linear Ridge | 0.49898 |
Linear Anchor Floor |
| Exp 2: Native Categorical CatBoost | 0.45786 |
-0.04112 vs. Exp 1 |
| Exp 3: CatBoost + Exogenous Features | 0.45390 |
-0.00396 vs. Exp 2 |
| Exp 4: Hybrid Residual GBDT + Mask | 0.42095 |
Rank #146 • Climbed 37 Places |
| Exp 5: Multi-Model Ensemble Blend | 0.41072 |
Rank #116 • Climbed 67 Places |
| Exp 6: Sierra School Multiplier (2.6x) | 0.41709 |
Regression: Asymmetric Overfit (See Part VIII) |
| Part VIII: Grandmaster Quad-Ensemble | 0.40336 |
Rank #99 Globally • Global Top 100 |
Before deploying decision tree ensembles, we established a linear baseline using L2-regularized Ridge regression on log-sales:
from sklearn.linear_model import Ridge
import numpy as np
import pandas as pd
train = pd.read_csv("train.csv", parse_dates=["date"])
train_2017 = train[train["date"] >= "2017-01-01"].copy()
train_2017["target"] = np.log1p(train_2017["sales"])
# Construct features & fit Ridge
X = pd.get_dummies(train_2017[["onpromotion", "dayofweek", "is_weekend", "family"]], drop_first=True)
y = train_2017["target"]
model = Ridge(alpha=1.0)
model.fit(X, y)
The Result & Failure Mode: Ridge scored 0.49898. Linear models fail in multi-series grocery forecasting because they assume additive feature contributions. A promotion on high-velocity staples like GROCERY I creates a massive non-linear volume multiplier, whereas the same promotion on specialty goods produces negligible movement. Without explicit multiplicative terms, linear models cannot calibrate store-family promotional elasticity.
To model complex categorical interactions without one-hot dimensionality explosion, we deployed CatBoost (CatBoostRegressor). CatBoost uses symmetric oblivious trees and ordered target encoding, enabling fast inference on ARM mobile CPUs with zero thread lock.
The Leakage-Free Lag Horizon (Lags ≥ 16 Days)
In a 16-day recursive forecast horizon, utilizing 1-day lags (lag_1) causes autoregressive error compounding. To ensure direct single-pass forecasting without recursive drift, all lag features are strictly bounded by >= 16 days: lag_16, lag_21, and lag_28, with rolling statistics computed on lag_16.
from catboost import CatBoostRegressor
cat_features = ["store_nbr", "family", "city", "state", "type", "cluster"]
num_features = ["onpromotion", "is_weekend", "day", "dayofweek", "lag_16", "lag_21", "lag_28", "rolling_mean_7", "rolling_mean_14"]
cb = CatBoostRegressor(
iterations=350,
learning_rate=0.08,
depth=6,
loss_function="RMSE",
cat_features=cat_features,
random_seed=42,
thread_count=4
)
cb.fit(X_tr, y_tr, eval_set=(X_val, y_val), early_stopping_rounds=40)
The Result: Slashed public RMSLE from 0.49898 to 0.45786 (-0.04112). However, pure CatBoost still trailed our Part VI domain heuristic (0.43150) because tree splits struggled to simultaneously calibrate extreme sales scale disparities across 1,782 series from lagged values alone.
Experiment 3 integrated macroeconomic indicators and holiday calendars into the feature matrix:
- WTI Crude Oil Smoothing (
oil.csv): Missing weekend prices were forward-filled; 7-day backward rolling mean and momentum differentials were computed. - Hierarchical Holiday Calendars (
holidays_events.csv): Filtered exclusively for non-transferred active holidays, split into national, regional (state), and local (city) binary indicators. - Bi-Weekly Payday Flags (Quincenas): Marked the 15th and month-end dates along with distance-to-payday indicators.
The Result: Improved score to 0.45390 (-0.00396), resolving calendar-driven shopping dips.
The critical architectural breakthrough came from recognizing that trees should not learn store-specific volume scale from scratch. We decomposed the target into a structured seasonal baseline plus residual:
z_i = ln(1 + Prior_i) + r_i
Where Prior_i is the 42-day grouped mean per (store_nbr, family, dayofweek) with the Sierra academic rush correction. CatBoost was trained strictly on the residual target r_i = ln(1 + y_i) - ln(1 + Prior_i).
Why Residual Boosting Smashed the Variance Floor
On raw log sales, initial tree training loss was 2.353. On residuals, initial loss plummeted to 0.698—a 70.3% variance reduction before the first split was made. The trees focused exclusively on promotion lifts and holiday shifts.
Promotion-Guarded Dead Catalog Post-Processing: Clamping predictions to 0.0 for series with 0 sales in the final 14 days and 0 promotions in test cleanly zeroed 2,278 inactive test rows.
The Result: Scored 0.42095 on the live leaderboard, beating our Part VI benchmark (0.43150) and jumping 37 places to Rank #146 globally.
In our top live tournament submission, we blended our two top model paradigms in log space:
ln(1 + y_ens) = 0.82 * ln(1 + y_hybrid) + 0.18 * ln(1 + y_exogenous)
Blending the variance-resistant hybrid residual booster (82%) with the dynamic exogenous tree model (18%) reduced individual model bias and pushed our score to 0.41072.
Tournament Placement: Reached Rank #116 out of 553 teams globally (Top 21.0%), climbing 67 positions from our non-ML baseline.
To push beyond 0.41072 into sub-0.400 territory, we diagnosed residual error distributions across all 33 product families. The largest remaining error spike was concentrated in SCHOOL AND OFFICE SUPPLIES during late August.
The Regional School Calendar Asymmetry
Ecuador operates on two distinct school calendars: the Costa (Coast) region starts classes in May, whereas the Sierra (Highlands / Andes) region (Quito, Ambato, Cuenca, Riobamba, Latacunga, Ibarra, Guaranda, Cayambe) starts classes on September 1st. In the final two weeks of August, school supply sales in Sierra stores experience a massive $2.6\times$ to $3.5\times$ surge that standard 42-day historical averages severely underestimate.
# Sierra cities academic multiplier for late August
sierra_cities = {"Quito", "Ambato", "Cuenca", "Riobamba", "Latacunga", "Ibarra", "Guaranda", "Cayambe"}
is_sierra_school = (df["city"].isin(sierra_cities)) & (df["family"] == "SCHOOL AND OFFICE SUPPLIES")
# Calibrated surge multiplier derived from historical holdouts
df.loc[is_sierra_school, "sales_pred"] *= 2.85
Local Holdout Validation: Testing this regional calibration on historical late-August holdouts dropped the RMSLE on Sierra school supplies by -0.18994 (from 0.63841 to 0.44847) and reduced overall dataset error by -0.00423, projecting live leaderboard performance to ~0.406.
0.41709 RMSLE due to asymmetric RMSLE over-prediction penalties across unpromoted Sierra stores. The post-mortem of this failure, compiling native LightGBM and XGBoost on ARM64 Termux, and breaking into the Global Top 100 at 0.40336 RMSLE (Rank #99) with our Quad-Ensemble is covered in Pocket Data Science VIII: Kaggle Store Sales Quad-Ensemble & Global Top 100 on Android Termux.
Below is the resource profile recorded inside Android Termux on 64-bit ARM:
| Pipeline Stage | Wall Clock Time | Peak Resident RAM |
|---|---|---|
| Ridge Regression (Exp 1) | 12.1 s | 312 MB |
| Pure CatBoost GBDT (Exp 2) | 391.3 s | 468 MB |
| Exogenous CatBoost (Exp 3) | 169.7 s | 442 MB |
| Hybrid Residual GBDT (Exp 4) | 23.3 s | 415 MB |
| Ensemble Blend (Exp 5) | 2.1 s | 188 MB |
Edge Takeaway: By decomposing scale disparities and using vectorized relational merges, entire multi-stage gradient boosting pipelines train and infer on multi-million row datasets comfortably within 500 MB of mobile RAM.
- Never Force Decision Trees to Learn Cross-Sectional Scales from Scratch: In multi-series retail forecasting, tree leaf quantization errors on extreme volume disparities cripple performance. Decomposing demand into a structured seasonal prior and modeling the log residual delivers massive gains in convergence speed and accuracy.
- Direct Horizon Lags Prevent Compounding Autoregressive Drift: Restricting lag features strictly to
≥ 16days eliminated the instability of multi-step recursive rollouts, producing deterministic single-pass predictions. - Guard Zero-Forecasts with Promotional Intent: Blindly setting inactive series to zero creates severe penalties when retailers re-launch seasonal items. Always gate zero-demand masks with promotional intent (
onpromotion == 0). - Ensemble Distinct Modeling Paradigms: Combining an anchored residual model with a free-form tree booster captured structural day-of-week rhythms and non-linear promotion shocks simultaneously.
To execute all machine learning experiments inside Android Termux:
# Navigate to workspace
cd ~/kaggle_store_sales
# Run Experiment 1: Regularized Linear Ridge
python3 exp1_ridge.py
# Run Experiment 2: Native Categorical CatBoost
python3 exp2_catboost.py
# Run Experiment 3: CatBoost with Exogenous Data
python3 exp3_catboost_exogenous.py
# Run Experiment 4: Hybrid Residual GBDT
python3 exp4_hybrid_residual.py
# Run Experiment 5: Multi-Model Ensemble Blend
python3 exp5_ensemble_blend.py
# Run Experiment 6: Sierra Academic Surge Calibration test
python3 exp6_sub40_calibrated.py
# Submit to Kaggle
kaggle competitions submit -c store-sales-time-series-forecasting \
-f submission_exp6_sub40_calibrated.csv \
-m "Experiment 6: Sierra Academic Surge Multiplier Test"
Share this:
- Share on Facebook (Opens in new window) Facebook
- Email a link to a friend (Opens in new window) Email
- Share on LinkedIn (Opens in new window) LinkedIn
- Share on Telegram (Opens in new window) Telegram
- Share on WhatsApp (Opens in new window) WhatsApp
- More
- Share on Bluesky (Opens in new window) Bluesky
- Print (Opens in new window) Print
- Share on Threads (Opens in new window) Threads
- Share on Reddit (Opens in new window) Reddit
- Share on Tumblr (Opens in new window) Tumblr
- Share on Pinterest (Opens in new window) Pinterest
- Share on X (Opens in new window) X
Leave a Reply