SPB Git forge

spb/wp23_uqo

Public
6commits 1branches 0releases
32.7 MBsize
maindefault branch
2 h agolast push
TeX 62.3% Python 37.6%
3.7 KB · 67 lines python
Raw Blame History
1#!/usr/bin/env python32# Author: Simon-Pierre Boucher — simon-pierre.boucher@uqo.ca3"""Task A — pseudo-real-time property-level forecasting. Quarterly origins from FIRST_ORIGIN; at each origin every model is4estimated on sales ≤ origin (features at their own month) and forecasts the log price of every sale in the next 12 months5(features at the origin). Predictions are stored per (origin, model, tx_id, h); scoring happens in 30_evaluate.py.6Usage: python3 scripts/10_task_a.py [--fast] [--origins 2022-12,2023-03,...]"""7from __future__ import annotations89import argparse10import sys11import time12from pathlib import Path1314sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))15import numpy as np16import polars as pl1718from wp23 import config as C19from wp23 import data as D20from wp23.models_a import TAUS, registry2122OUT = C.RESULTS / "task_a"; OUT.mkdir(parents=True, exist_ok=True)232425def origins_quarterly(first: str, last_origin: str) -> list[str]:26    o = first; out = []27    while o <= last_origin:28        out.append(o); o = D.month_add(o, 3)29    return out303132def main() -> None:33    ap = argparse.ArgumentParser(); ap.add_argument("--fast", action="store_true"); ap.add_argument("--family", type=str, default="trees"); ap.add_argument("--origins", type=str, default="")34    a = ap.parse_args()35    sales = D.load_sales()36    origins = a.origins.split(",") if a.origins else origins_quarterly(C.FIRST_ORIGIN, D.month_add(C.LAST_MONTH, -12))37    rows, timing = [], []38    for o in origins:39        tr, te = D.task_a_sets(sales, o)40        Xtr, ytr, cols, cats = D.design(tr); Xte, yte, _, _ = D.design(te, cats)41        meta_tr = {"mun_code": tr["mun_code"].to_list(), "mrc_code": tr["mrc_code"].to_list(), "region_code": tr["region_code"].to_list(), "h": tr["h"].to_numpy()}42        meta_te = {"mun_code": te["mun_code"].to_list(), "mrc_code": te["mrc_code"].to_list(), "region_code": te["region_code"].to_list(), "h": te["h"].to_numpy()}43        # naive benchmark: municipal median log price of the last 3 months at the origin, plus the class/subtype hedonic adjustment from OLS on the last 12 months44        base = pl.DataFrame({"tx_id": te["tx_id"], "h": te["h"], "y": yte, "naive_mun_median": te["mun_medlp_3"].fill_null(te["reg_q_3"]).to_numpy()})45        for m in registry(a.family, a.fast):46            t0 = time.time()47            try:48                m.fit(Xtr, ytr, meta_tr); p = m.predict(Xte, meta_te)49                Q = m.predict_quantiles(Xte, meta_te) if hasattr(m, "predict_quantiles") else None50            except Exception as ex:51                print("FAILED", o, m.name, repr(ex), flush=True); continue52            d = base.select(["tx_id", "h"]).with_columns(pl.lit(o).alias("origin"), pl.lit(m.name).alias("model"), pl.Series("pred", np.asarray(p, dtype=np.float64)))53            if Q is not None:54                for j, t in enumerate(TAUS):55                    d = d.with_columns(pl.Series(f"q{int(t*100):02d}", np.asarray(Q[:, j], dtype=np.float64)))56            rows.append(d); timing.append({"origin": o, "model": m.name, "seconds": round(time.time() - t0, 1), "n_train": len(ytr), "n_test": len(yte)})57            print(o, m.name, f"{time.time() - t0:.0f}s", f"MAE={np.mean(np.abs(p - yte)):.4f}", flush=True)58        if a.family == "trees":59            rows.append(base.select(["tx_id", "h"]).with_columns(pl.lit(o).alias("origin"), pl.lit("naive_mun_median").alias("model"), pl.Series("pred", base["naive_mun_median"])))60            base.select(["tx_id", "h", "y"]).with_columns(pl.lit(o).alias("origin")).write_parquet(OUT / f"targets_{o}.parquet")61        pl.concat(rows, how="diagonal").write_parquet(OUT / f"predictions_{a.family}.parquet"); pl.DataFrame(timing).write_csv(OUT / f"timing_{a.family}.csv")62    print("done")636465if __name__ == "__main__":66    main()67