Forecasting

Medium-range probabilistic forecasting of asset stress features. Requires the [ml] extra (pip install "climagrid[ml]"). See the forecasting guide for an overview.

climagrid.forecast

climagrid.forecasting.api.forecast(assets, model, *, history_end=None, run_fn=None)[source]

Forecast asset stress from previously saved model(s). Never trains.

Parameters:
  • assets (AssetRegistry | str | Path) – AssetRegistry or path to an asset CSV/GeoJSON (asset_id, lat, lon).

  • model (str | Path | LightGBMForecaster) – A directory containing manifest.json (and the per-factor model files), a path to a single saved .joblib, or a loaded LightGBMForecaster. Train and save these with the Kaggle training notebook (LightGBMForecaster.fit(...).save(...)).

  • history_end (datetime | None) – End of the recent window to fetch (UTC). Defaults to now. Only the recent min_inference_history_days (plus a small buffer) is fetched, since that is all the saved model needs to build its predictors.

  • run_fn (Callable[..., DataFrame] | None) – Injection point for climagrid.run (used by tests).

Returns:

Long form: one row per (asset_id, origin_date, target, horizon_day) with forecast_date, the quantile columns (p10/p50/p90), and a recommendation confidence flag when served from a manifest. Empty if no recent data could be fetched.

Return type:

DataFrame

ForecastConfig

class climagrid.forecasting.config.ForecastConfig(**data)[source]

Bases: BaseModel

Parameters controlling the stress-feature forecast pipeline.

Parameters:
  • targets (list[str]) – Stress-feature columns to forecast. Default is the single headline target feat_thermal_aging_factor (a per-row Arrhenius function of temperature, where an ML model has the most to add over naive baselines). Add more targets at the cost of more trained models.

  • horizon_days (int) – Maximum forecast lead time in days. One model is trained per horizon 1..horizon_days (direct multi-horizon strategy).

  • daily_agg (Literal['max', 'mean', 'min']) – How hourly stress features are reduced to one value per day. Default "max" matches how outputs.report.rank_assets ranks assets by peak stress.

  • history_years (int) – Default length of training history when explicit dates are not given. 15 years balances seasonal-cycle coverage against climate drift in the oldest years; the right value is confirmed by backtest.history_ablation.

  • lags (list[int]) – Target autoregressive lags (in days) used as predictors.

  • rolling_windows (list[int]) – Trailing-window sizes (in days) for rolling mean/std predictors.

  • quantiles (list[float]) – Quantiles to predict for prediction intervals. Must include 0.5.

  • model (Literal['lightgbm', 'persistence', 'climatology']) – Forecaster family. "lightgbm" is the trained model; the other two are the baselines the model is scored against.

  • per_asset (bool) – If True, fit one model per asset; otherwise a single global pooled model that generalizes to unseen asset locations (default).

  • embargo_days (int | None) – Gap in days between train and test in the rolling-origin backtest, so a training target window never overlaps a test predictor window. Defaults to horizon_days when None.

  • climatology_window_years (int | None) – If set, the climatology baseline uses only the most recent N years of history (a hedge against climate drift). None uses all training years.

  • calibrate_intervals (bool) – If True, conformally calibrate the prediction intervals: hold out the most recent calibration_days as a calibration set, fit on the rest, and adjust the interval so its coverage matches the nominal level.

  • calibration_method (Literal['normalized', 'constant', 'mondrian']) – "mondrian" (default): a separate width per meteorological season, which keeps the high-stress summer interval on target. "normalized": locally adaptive width (even overall, but summer under-covered). "constant": a single additive width per horizon.

  • calibration_days (int) – Size of the held-out calibration window. Should span a full seasonal cycle (default 365) so the calibration is not biased to one season.

  • sources (list[str]) – climagrid data sources to fetch. Default ["nasa_power"] (keyless, hourly back to 2001).

  • n_jobs (int) – LightGBM training parameters; defaults are conservative for a memory-constrained machine.

  • n_estimators (int) – LightGBM training parameters; defaults are conservative for a memory-constrained machine.

  • num_leaves (int) – LightGBM training parameters; defaults are conservative for a memory-constrained machine.

  • learning_rate (float) – LightGBM training parameters; defaults are conservative for a memory-constrained machine.

  • random_state (int) – LightGBM training parameters; defaults are conservative for a memory-constrained machine.

  • cache_dir (Path | None) – Directory for the cached daily panel parquet. None disables caching.

model_config: ClassVar[ConfigDict] = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

targets: list[str]
horizon_days: int
daily_agg: Literal['max', 'mean', 'min']
history_years: int
lags: list[int]
rolling_windows: list[int]
quantiles: list[float]
model: Literal['lightgbm', 'persistence', 'climatology']
per_asset: bool
embargo_days: int | None
climatology_window_years: int | None
calibrate_intervals: bool
calibration_method: Literal['normalized', 'constant', 'mondrian']
calibration_days: int
sources: list[str]
n_jobs: int
n_estimators: int
num_leaves: int
learning_rate: float
random_state: int
cache_dir: Path | None
property effective_embargo_days: int

Embargo gap, defaulting to the max horizon when unset.

property min_inference_history_days: int

Days of recent history needed to build predictors at inference time.

Equals the largest lag or rolling window. Forecasting forward from a saved model only needs this many recent days per asset, not the full training history, because the predictors are autoregressive lags and trailing rolling statistics that reach back at most this far.

required_features()[source]

climagrid feature names needed to compute the configured targets.

Return type:

list[str]

Dataset construction

climagrid.forecasting.dataset.build_training_panel(assets, history_start, history_end, config, *, run_fn=None)[source]

Build the daily training panel for a set of assets.

Parameters:
  • assets (AssetRegistry | str | Path) – An AssetRegistry or a path to an asset CSV/GeoJSON.

  • history_start (datetime) – Inclusive UTC date range of history to fetch.

  • history_end (datetime) – Inclusive UTC date range of history to fetch.

  • config (ForecastConfig) – Forecast configuration (targets, sources, daily aggregation, cache).

  • run_fn (Callable[..., DataFrame] | None) – Injection point for climagrid.run (used by tests to mock fetching).

Returns:

Columns: asset_id, date, lat, lon and one column per present target, with one row per (asset, day), sorted by (asset_id, date). Empty if no data was returned for any asset.

Return type:

DataFrame

climagrid.forecasting.dataset.build_supervised_frame(panel, target, config)[source]

Turn a daily panel into a supervised frame for one target.

Return type:

DataFrame

Parameters:
Predictors (all known at the forecast origin t):
  • y_t: the target value at the origin,

  • lag_k: the target value at t - k for each configured lag,

  • rollmean_w / rollstd_w: trailing rolling mean/std over the w days ending at t - 1 (shifted by 1 so the origin is excluded),

  • doy_sin / doy_cos: day-of-year harmonics of the origin date,

  • lat / lon: static location.

Targets: y_h{h} is the target value at t + h for each horizon.

Returns one row per (asset, origin date). Early rows have NaN lag/rolling predictors and late rows have NaN y_h* targets; both are retained here (LightGBM tolerates NaN predictors; NaN targets are dropped at fit time).

Model and baselines

class climagrid.forecasting.models.LightGBMForecaster(config)[source]

Bases: object

Direct multi-horizon quantile forecaster backed by LightGBM.

Parameters:

config (ForecastConfig)

fit(frame, target)[source]

Train one LGBMRegressor per (horizon, quantile).

Rows whose y_h{h} target is NaN (the tail of each asset’s series) are dropped per horizon. NaN predictors in early rows are kept; LightGBM handles them natively.

Return type:

LightGBMForecaster

Parameters:
calibrate(frame, target=None, *, method='normalized')[source]

Conformalize the outer prediction interval (Romano et al. 2019, CQR).

Uses a held-out calibration frame (rows NOT seen during fit) to adjust the lowest/highest quantile bounds per horizon so the interval attains its nominal coverage (q_hi - q_lo, e.g. 0.80 for p10-p90) out of sample. The calibration set should span a full seasonal cycle. The conformity score is E = max(p_lo - y, y - p_hi) and Q is its ceil((n + 1) * level) / n empirical quantile; predict() widens by Q.

Return type:

LightGBMForecaster

Parameters:
method:

"normalized" (default) scales the score by the model’s own interval width (p_hi - p_lo) + c so the widening adapts to local uncertainty. "constant" applies a single additive Q per horizon (marginal coverage only). "mondrian" fits a separate additive Q per (horizon, meteorological season) keyed on the forecast date, targeting per-season coverage (helps where a season, e.g. summer, is otherwise under-covered); seasons with too few calibration points fall back to the pooled per-horizon Q.

predict(frame, target=None)[source]

Produce long-form forecasts for every (row, horizon).

Returns one row per (asset_id, origin date, horizon) with columns: asset_id, origin_date, forecast_date, horizon_day, target and one column per quantile (p10, p50, p90), sorted so the quantile columns are non-decreasing. If the model has been conformally calibrated, the outer interval is widened accordingly.

Return type:

DataFrame

Parameters:
save(path)[source]

Persist the fitted forecaster (config, predictors, per-quantile models).

Uses joblib (a scikit-learn dependency, always present with the [ml] extra). Reload with load() for inference without retraining.

Return type:

Path

Parameters:

path (str | Path)

classmethod load(path)[source]

Reload a forecaster saved by save().

Return type:

LightGBMForecaster

Parameters:

path (str | Path)

class climagrid.forecasting.baselines.PersistenceForecaster(config)[source]

Bases: object

Predicts the origin value y_t for every horizon.

Parameters:

config (ForecastConfig)

fit(frame, target)[source]

No-op: persistence has nothing to learn.

Return type:

PersistenceForecaster

Parameters:
predict(frame, horizon)[source]

Return the origin value for each row (horizon-independent).

Return type:

ndarray

Parameters:
class climagrid.forecasting.baselines.ClimatologyForecaster(config)[source]

Bases: object

Predicts the historical day-of-year mean of the target.

Parameters:

config (ForecastConfig)

fit(frame, target)[source]

Build the day-of-year mean table from the training rows.

Return type:

ClimatologyForecaster

Parameters:
predict(frame, horizon)[source]

Return the day-of-year mean for each row’s target date (origin + h).

Return type:

ndarray

Parameters:

Backtesting

climagrid.forecasting.backtest.evaluate(panel, config, *, n_splits=3, test_size_days=90, event_threshold=0.0)[source]

Backtest the LightGBM model against baselines on a daily panel.

Returns one row per (fold, target, horizon) with point-accuracy metrics, skill scores versus both baselines, and interval calibration metrics.

skill_vs_persistence_events is the skill computed on only the active rows (where the actual value exceeds event_threshold), and event_fraction is their share. For mostly-zero, sparse targets (e.g. ice loading) the all-rows skill is inflated by the easy zero stretches; the event-conditioned skill reflects how well the model predicts the rare events that matter, and is the honest basis for a deploy decision.

Return type:

DataFrame

Parameters:
climagrid.forecasting.backtest.rolling_origin_splits(dates, config, *, n_splits=3, test_size_days=90)[source]

Build expanding-window (train, test) origin-date splits.

Test windows are placed back-to-back at the end of the timeline. Each fold’s training set expands to include everything up to embargo days before its test window starts, where embargo defaults to the max horizon (so a training target at t + H never reaches a test origin).

Return type:

list[tuple[set[Timestamp], set[Timestamp]]]

Parameters:
climagrid.forecasting.backtest.history_ablation(panel, config, *, windows_years=None, n_splits=3, test_size_days=90)[source]

Rerun evaluate across several history-window lengths.

Lets the default history length be chosen on measured skill and calibration rather than assumed. Returns the concatenated per-fold metrics with a history_years column.

Return type:

DataFrame

Parameters: