TeX 55.1%
Python 44.2%
1#!/usr/bin/env python32# Author: Simon-Pierre Boucher — simon-pierre.boucher@uqo.ca3"""Phase 6 — revision analysis of the real-time vintage database (output of 40_vintages.py).4For each cell and month: 1st, 2nd, 3rd release vs the final retrospective panel (smoothed, full sample) and vs the latest5real-time vintage; revision magnitudes of monthly and 12-month changes; timeliness (first-release nowcast error of the newest6month); publication policies: full re-estimation (revising), movement splice (never revise history), revision-limited (freeze7after 3 releases). Writes results/vintages/revisions_*.csv."""8from __future__ import annotations910import sys11from pathlib import Path1213sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))14import numpy as np15import polars as pl1617from sharp_hpi import config as C1819OUT = C.RESULTS / "vintages"202122def main() -> None:23 v = pl.read_parquet(C.PROCESSED / "index_vintages.parquet")24 modes = v["mode"].unique().to_list() if "mode" in v.columns else ["filtered"]25 if "mode" not in v.columns:26 v = v.with_columns(pl.lit("filtered").alias("mode"))27 for mode in modes:28 _analyse(v.filter(pl.col("mode") == mode), panel_path(), mode)293031def panel_path():32 return pl.read_parquet(C.PROCESSED / "index_panel.parquet").select(33 pl.col("property_type").alias("class"), pl.col("geography_level").alias("level"), pl.col("geography_id").alias("geo_id"), "month", (pl.col("index_value") / 100).log().alias("li_final"))343536def _analyse(v: pl.DataFrame, panel: pl.DataFrame, mode: str) -> None:37 sfx = "" if mode == "smoothed" else "_" + mode38 # release number of month m in vintage vm: k = months between m and vm (+1)39 def mi(s):40 return (s.str.slice(0, 4).cast(pl.Int32) * 12 + s.str.slice(5, 2).cast(pl.Int32))41 v = v.with_columns((mi(pl.col("vintage")) - mi(pl.col("month")) + 1).alias("release"))42 # monthly change in each vintage (levels are only identified up to a constant per vintage → compare changes)43 v = v.sort(["class", "level", "geo_id", "vintage", "month"]).with_columns(44 (pl.col("log_index") - pl.col("log_index").shift(1)).over(["class", "level", "geo_id", "vintage"]).alias("d1"),45 (pl.col("log_index") - pl.col("log_index").shift(12)).over(["class", "level", "geo_id", "vintage"]).alias("d12"))46 final = panel.sort(["class", "level", "geo_id", "month"]).with_columns(47 (pl.col("li_final") - pl.col("li_final").shift(1)).over(["class", "level", "geo_id"]).alias("d1_final"),48 (pl.col("li_final") - pl.col("li_final").shift(12)).over(["class", "level", "geo_id"]).alias("d12_final"))49 last_v = v.filter(pl.col("vintage") == v["vintage"].max()).select(["class", "level", "geo_id", "month", pl.col("d1").alias("d1_last"), pl.col("d12").alias("d12_last")])50 j = v.join(final, on=["class", "level", "geo_id", "month"], how="left").join(last_v, on=["class", "level", "geo_id", "month"], how="left")51 j = j.with_columns((pl.col("d1") - pl.col("d1_final")).alias("rev1_final"), (pl.col("d12") - pl.col("d12_final")).alias("rev12_final"),52 (pl.col("d1") - pl.col("d1_last")).alias("rev1_last"), (pl.col("d12") - pl.col("d12_last")).alias("rev12_last"))53 summ = j.filter(pl.col("release") <= 12).group_by(["class", "level", "release"]).agg(54 pl.len().alias("obs"), pl.col("rev1_final").abs().mean().alias("mae_rev_m1_vs_final"), pl.col("rev1_final").mean().alias("bias_rev_m1_vs_final"),55 pl.col("rev12_final").abs().mean().alias("mae_rev_12m_vs_final"), pl.col("rev12_final").mean().alias("bias_rev_12m_vs_final"),56 pl.col("rev1_last").abs().mean().alias("mae_rev_m1_vs_last_rt"), pl.col("rev12_last").abs().mean().alias("mae_rev_12m_vs_last_rt"),57 (pl.col("rev1_final").abs() > 0.01).mean().alias("share_m1_rev_gt_1pct")).sort(["class", "level", "release"])58 summ.write_csv(OUT / f"revisions_by_release{sfx}.csv")59 # policies on the provincial monthly change: (a) full re-estimation = release k value; (b) movement splice = first release of each60 # month chained; (c) revision-limited = value at release 361 pol = j.filter(pl.col("level") == "province")62 first = pol.filter(pl.col("release") == 1).select(["class", "month", pl.col("d1").alias("d1_first"), pl.col("d1_final")])63 third = pol.filter(pl.col("release") == 3).select(["class", "month", pl.col("d1").alias("d1_third")])64 p = first.join(third, on=["class", "month"], how="left")65 pol_summ = p.group_by("class").agg(pl.len().alias("months"), (pl.col("d1_first") - pl.col("d1_final")).abs().mean().alias("mae_first_release_vs_final"),66 (pl.col("d1_first") - pl.col("d1_final")).mean().alias("bias_first_release_vs_final"),67 (pl.col("d1_third") - pl.col("d1_final")).abs().mean().alias("mae_third_release_vs_final"),68 ((pl.col("d1_first") - pl.col("d1_final")) ** 2).mean().sqrt().alias("rmse_first"), ((pl.col("d1_third") - pl.col("d1_final")) ** 2).mean().sqrt().alias("rmse_third"),69 pl.col("d1_final").abs().mean().alias("mean_abs_true_change"))70 pol_summ.write_csv(OUT / f"revision_policies_province{sfx}.csv")71 j.filter(pl.col("release") <= 6).select(["class", "level", "geo_id", "month", "vintage", "release", "n", "log_index", "se", "d1", "d12", "rev1_final", "rev12_final"]).write_parquet(OUT / f"revisions_long{sfx}.parquet")72 print(mode); print(summ); print(pol_summ)737475if __name__ == "__main__":76 main()77