Views
No views yet
log1p(peak_customers_out);
predictions are back-transformed with expm1 and sorted per row so the bands never cross.quantile_10.txt, quantile_50.txt, quantile_90.txt — native LightGBM boosters.config.json — features, quantiles, the log transform, and the conformal offsets.metrics.json, loso_per_storm.parquet — evaluation results, in aggregate and per storm.| metric | value |
|---|---|
| 80% interval coverage | 0.79 (target 0.80) |
| coverage per tail | 0.11 below / 0.10 above |
| per-storm coverage | 0.56–0.92 (std 0.10) |
| mean interval width | ~6,800 customers |
| P50 mean absolute error | ~2,200 customers |
1import json
2import lightgbm as lgb
3import numpy as np
4
5cfg = json.load(open("config.json"))
6boosters = [lgb.Booster(model_file=f"quantile_{int(q * 100):02d}.txt") for q in cfg["quantiles"]]
7
8# X: array with columns cfg["features"], in that order
9log = np.sort(np.column_stack([b.predict(X) for b in boosters]), axis=1)
10lo = np.minimum(log[:, 0] - cfg["offsets_log"]["lower"], log[:, 1])
11hi = np.maximum(log[:, 2] + cfg["offsets_log"]["upper"], log[:, 1])
12p10, p50, p90 = (np.clip(np.expm1(v), 0, None) for v in (lo, log[:, 1], hi))