TeX 62.3%
Python 37.6%
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