Gridlock / src /splits.py
parvmittal07's picture
Initial Demo complete
01eb82e
Raw
History Blame Contribute Delete
2.14 kB
"""Train/test splitting and cross-validation that respect time order.
Forecasting is only honest if the model is evaluated on events that occurred
*after* everything it trained on. We therefore use a single chronological
holdout for final evaluation and time-series CV for tuning. A group-by-location
KFold is also provided as a robustness check against spatial memorisation.
"""
from __future__ import annotations
import numpy as np
import pandas as pd
from sklearn.model_selection import StratifiedKFold, TimeSeriesSplit
from . import config as C
def temporal_split(df: pd.DataFrame, test_fraction: float = C.TEST_FRACTION):
"""Return boolean train/test masks split on chronological ``order_time``.
Assumes ``df`` is already sorted by ``order_time`` (cleaning guarantees it).
"""
n = len(df)
cut = int(round(n * (1 - test_fraction)))
train_mask = np.zeros(n, dtype=bool)
test_mask = np.zeros(n, dtype=bool)
train_mask[:cut] = True
test_mask[cut:] = True
return train_mask, test_mask
def time_series_folds(n_train: int, n_splits: int = C.N_FOLDS):
"""Expanding-window CV indices for hyper-parameter tuning on the train set."""
tss = TimeSeriesSplit(n_splits=n_splits)
return list(tss.split(np.arange(n_train)))
def stratified_folds(y: np.ndarray, n_splits: int = C.N_FOLDS):
"""Stratified folds (used for the rare positive closure class during tuning)."""
skf = StratifiedKFold(n_splits=n_splits, shuffle=True, random_state=C.RANDOM_STATE)
return list(skf.split(np.zeros(len(y)), y))
def split_report(df: pd.DataFrame, train_mask, test_mask) -> str:
def span(mask):
t = df.loc[mask, "order_time"]
return f"{t.min():%Y-%m-%d} -> {t.max():%Y-%m-%d} (n={mask.sum()})"
return f"train: {span(train_mask)}\ntest : {span(test_mask)}"
if __name__ == "__main__": # pragma: no cover
d = pd.read_parquet(C.FEATURES_PARQUET)
tr, te = temporal_split(d)
print(split_report(d, tr, te))
for col in (C.TARGET_CLOSURE, C.TARGET_PRIORITY):
print(f"{col}: train+={d.loc[tr, col].mean():.3f} test+={d.loc[te, col].mean():.3f}")