Upload submission.py with huggingface_hub

#1
Files changed (1) hide show
  1. submission.py +76 -112
submission.py CHANGED
@@ -1,133 +1,97 @@
1
- """Example submission — a reference for how a model submission should look.
2
-
3
- This is a *complete, self-contained* example for the ``swedish-temperatures:ar``
4
- problem. Copy it, rename ``ExamplePredictor`` to your own model, and replace the
5
- ``train`` / ``predict`` bodies. The only thing the verifier needs is a module-level
6
- ``emflow.Predictor`` instance:
7
-
8
- model = ExamplePredictor() # a FRESH, UNTRAINED model
9
-
10
- The verifier (``scripts/verify_submission.py``) will:
11
- 1. pick up your ``model`` (untrained),
12
- 2. train it on the official pre-2026 split via ``model.train(train_df)``,
13
- 3. score it with **strict walk-forward** on the held-out 2026 hours.
14
-
15
- (If you prefer, you can instead expose a ``get_model() -> emflow.Predictor``
16
- factory — the verifier accepts either form.)
17
-
18
- You never see the test targets and you cannot train on them, so the score is
19
- trustworthy. Self-check before submitting::
20
-
21
- python scripts/verify_submission.py submissions/example_submission.py
22
-
23
- ----------------------------------------------------------------------------
24
- The model
25
- ----------------------------------------------------------------------------
26
- A least-squares autoregression on the most recent hours:
27
-
28
- y_t = c + sum_i w_i * y_{t-lag_i} (lags = 1, 2, 3 hours)
29
-
30
- Fit per column with ``numpy.linalg.lstsq`` — no scikit-learn / statsmodels
31
- needed. Short lags are deliberately strong for *1-step-ahead* hourly
32
- temperature, and this comfortably beats the persistence and seasonal-naive
33
- baselines.
34
-
35
- Want to do better? This is where the intern earns their keep — e.g. add the
36
- daily/weekly lags (24, 168), a per-hour-of-day or per-month offset, exogenous
37
- NWP features, or swap the OLS for a gradient-boosted / neural model. Keep the
38
- same ``train`` / ``predict`` contract and the verifier scores it the same way.
39
- """
40
-
41
  from __future__ import annotations
42
-
43
  import numpy as np
44
  import pandas as pd
45
-
46
  import emflow as ef
47
 
48
 
49
- class ExamplePredictor(ef.Predictor):
50
- """OLS autoregression on recent lags, fit independently per column.
51
-
52
- Parameters
53
- ----------
54
- lags : iterable of int
55
- Lag orders (hours) used as regressors. Default: 1, 2, 3.
56
- name : str
57
- Submission name shown on the scorecard / leaderboard.
58
- """
59
-
60
- def __init__(self, lags=(1, 2, 3), name="example-ar"):
61
- # IMPORTANT: set self.name so the scorecard / leaderboard can label you.
62
  self.name = name
63
- self.lags = sorted(lags)
64
- self.max_lag = max(self.lags)
65
- # column -> (intercept: float, weights: ndarray) or None if unfit
66
- self.coefs: dict = {}
67
-
68
- # -- feature construction -------------------------------------------------
69
- def _design_matrix(self, series: pd.Series) -> np.ndarray:
70
- """Build an (n_obs, n_lags) matrix of lagged values.
71
-
72
- Row ``t`` holds the values at ``t-lag`` for each lag — i.e. only the
73
- past, never the value at ``t`` itself. That is what keeps the model
74
- honest under walk-forward evaluation.
75
- """
76
- return np.column_stack([series.shift(lag).to_numpy() for lag in self.lags])
77
-
78
- # -- training -------------------------------------------------------------
79
- def train(self, train_df):
80
- """Fit one AR model per column via least squares on complete rows only.
81
-
82
- Rows with any NaN in the target or its required lags are dropped before
83
- fitting. A column with too little data is left unfit (predicts NaN).
84
- """
 
 
 
85
  if isinstance(train_df, pd.Series):
86
  train_df = train_df.to_frame()
87
 
88
- self.coefs = {}
 
 
89
  for col in train_df.columns:
90
- series = train_df[col]
91
- X = self._design_matrix(series)
92
- y = series.to_numpy()
93
- mask = np.isfinite(y) & np.isfinite(X).all(axis=1)
94
- if mask.sum() <= len(self.lags) + 1: # need more rows than params
95
- self.coefs[col] = None
 
96
  continue
97
- X_fit = np.column_stack([np.ones(mask.sum()), X[mask]])
98
- beta, *_ = np.linalg.lstsq(X_fit, y[mask], rcond=None)
99
- self.coefs[col] = (float(beta[0]), beta[1:])
100
- return self
101
 
102
- # -- prediction -----------------------------------------------------------
103
- def predict(self, input):
104
- """1-step-ahead forecast for every timestamp in ``input``.
 
 
 
 
 
 
 
 
 
 
105
 
106
- Returns a DataFrame sharing ``input``'s index/columns. The verifier reads
107
- the value at the LAST timestamp of ``input`` (whose actual is hidden as
108
- NaN) as your forecast for that hour. Rows whose lags are missing — or
109
- unfit columns — stay NaN.
110
- """
111
- if isinstance(input, pd.Series):
112
- input = input.to_frame()
113
 
114
  preds = {}
115
- for col in input.columns:
116
- series = input[col]
117
- coef = self.coefs.get(col)
118
- if coef is None:
119
- preds[col] = np.full(len(series), np.nan)
120
  continue
121
- intercept, weights = coef
122
- X = self._design_matrix(series)
123
- out = np.full(len(series), np.nan)
124
- finite = np.isfinite(X).all(axis=1) # only score rows with all lags present
125
- with np.errstate(divide="ignore", over="ignore", invalid="ignore"):
126
- out[finite] = intercept + X[finite] @ weights
 
 
 
 
 
 
 
 
 
 
127
  preds[col] = out
128
 
129
- return pd.DataFrame(preds, index=input.index, columns=input.columns)
130
 
131
 
132
- # The submission: a FRESH, UNTRAINED predictor for the verifier to train + score.
133
- model = ExamplePredictor()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  from __future__ import annotations
 
2
  import numpy as np
3
  import pandas as pd
4
+ from sklearn.ensemble import GradientBoostingRegressor
5
  import emflow as ef
6
 
7
 
8
+ class QuantileRegressionPredictor(ef.Predictor):
9
+ def __init__(self, name="quantile-regression-ar"):
 
 
 
 
 
 
 
 
 
 
 
10
  self.name = name
11
+ self.lags = [1, 24]
12
+ self.quantile = 0.5
13
+ self.models = {}
14
+
15
+ def _prepare_features(self, df: pd.DataFrame, col: str):
16
+ series = df[col]
17
+ # Use timezone-normalized timestamps to derive time features
18
+ # Note: input_df index is expected to be a DatetimeIndex
19
+ hour = df.index.hour
20
+ day_of_week = df.index.dayofweek
21
+ month = df.index.month
22
+
23
+ X = pd.DataFrame(
24
+ {
25
+ "hour": hour,
26
+ "day_of_week": day_of_week,
27
+ "month": month,
28
+ "lag_1h": series.shift(1),
29
+ "lag_24h": series.shift(24),
30
+ },
31
+ index=df.index,
32
+ )
33
+ return X
34
+
35
+ def train(self, train_df: pd.DataFrame):
36
  if isinstance(train_df, pd.Series):
37
  train_df = train_df.to_frame()
38
 
39
+ # Filter for Stockholm-Observatoriekullen A as per user's specific interest
40
+ # though the contract implies handling all columns passed.
41
+ # Given the "swedish-temperatures:ar" context, we handle all.
42
  for col in train_df.columns:
43
+ X = self._prepare_features(train_df, col)
44
+ y = train_df[col]
45
+
46
+ # Combine and drop NaNs
47
+ data = pd.concat([X, y], axis=1).dropna()
48
+ if len(data) < 100: # Heuristic for sufficient data
49
+ self.models[col] = None
50
  continue
 
 
 
 
51
 
52
+ X_train = data.drop(columns=[col])
53
+ y_train = data[col]
54
+
55
+ model = GradientBoostingRegressor(
56
+ loss="quantile",
57
+ alpha=self.quantile,
58
+ n_estimators=100,
59
+ max_depth=5,
60
+ random_state=42,
61
+ )
62
+ model.fit(X_train, y_train)
63
+ self.models[col] = model
64
+ return self
65
 
66
+ def predict(self, input_df: pd.DataFrame):
67
+ if isinstance(input_df, pd.Series):
68
+ input_df = input_df.to_frame()
 
 
 
 
69
 
70
  preds = {}
71
+ for col in input_df.columns:
72
+ model = self.models.get(col)
73
+ if model is None:
74
+ preds[col] = np.full(len(input_df), np.nan)
 
75
  continue
76
+
77
+ X = self._prepare_features(input_df, col)
78
+ # We can't dropna here because we need a value for the last row
79
+ # even if it has NaNs in current values (but lags should be there)
80
+ # The contract says last timestamp is the target to forecast.
81
+ # Only the current value at last timestamp is NaN. Lags should be fine.
82
+
83
+ # Fill NaNs in features with a neutral value or previous if necessary
84
+ # but usually for the last row, shift(1) of NaN is the value at T-1.
85
+ out = np.full(len(input_df), np.nan)
86
+
87
+ # Identify rows where we have all features
88
+ valid_mask = X.notna().all(axis=1)
89
+ if valid_mask.any():
90
+ out[valid_mask] = model.predict(X[valid_mask])
91
+
92
  preds[col] = out
93
 
94
+ return pd.DataFrame(preds, index=input_df.index, columns=input_df.columns)
95
 
96
 
97
+ model = QuantileRegressionPredictor()