#!/usr/bin/env python3 # Author: Simon-Pierre Boucher — contact@spboucher.ai """Step 02 — Descriptive statistics and IAAO ratio-study diagnostics. Writes to ``results/reproduced/``: summary_stats.csv sample descriptives used in Table 1 iaao_overall.csv province-wide median ratio / COD / PRD / PRB with bootstrap CIs, overall and by sale year iaao_cities.csv the ten largest markets iaao_muni.csv every municipality with ≥ 100 sales (maps + histograms) Usage: python scripts/02_iaao_stats.py """ import sys from pathlib import Path import numpy as np import pandas as pd sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src")) from wp10 import config, iaao, sample # noqa: E402 def main() -> None: config.ensure_dirs() df = sample.load() out = config.REPRODUCED # ------------------------------------------------------------ Table 1 desc_vars = { "amount": "Sale price ($)", "role_valeur_immeuble": "Assessed value ($)", "ratio": "Assessment ratio AV/SP", "lag_months": "Roll lag (months)", "land_share": "Assessed land share", "age": "Building age (years)", "role_superficie_terrain_m2": "Lot area (m2)", "role_aire_etages_m2": "Floor area (m2)", } rows = [] for var, label in desc_vars.items(): s = df[var].dropna() rows.append({"variable": label, "n": len(s), "mean": s.mean(), "sd": s.std(), "p10": s.quantile(.10), "p50": s.median(), "p90": s.quantile(.90)}) pd.DataFrame(rows).to_csv(out / "summary_stats.csv", index=False) counts = {"n_sales": len(df), "n_munis": df["muni"].nunique(), "n_cells": df["cell"].nunique()} for k, v in df.groupby("prop_class").size().items(): counts[f"n_{k}"] = int(v) pd.Series(counts).to_csv(out / "sample_counts.csv") # ------------------------------------------------------------ overall + by year blocks = [("All sales 2021–2026", df)] blocks += [(str(y), g) for y, g in df.groupby("sale_year")] rows = [] for label, g in blocks: av = g["role_valeur_immeuble"].to_numpy(float) sp = g["amount"].to_numpy(float) r = av / sp b, se = iaao.prb(av, sp) row = {"group": label, "n": len(g), "median_ratio": float(np.median(r)), "cod": iaao.cod(r), "prd": iaao.prd(av, sp), "prb": b, "prb_se": se} cis = iaao.bootstrap_ci(av, sp, n_boot=200) for stat, (lo, hi) in cis.items(): row[f"{stat}_lo"], row[f"{stat}_hi"] = lo, hi rows.append(row) print(f" {label:<22} n={row['n']:>8,} med={row['median_ratio']:.3f} " f"COD={row['cod']:.1f} PRD={row['prd']:.3f} PRB={row['prb']:+.4f}") pd.DataFrame(rows).to_csv(out / "iaao_overall.csv", index=False) # --------------------------------------------------------------------- # Municipality-level statistics. Within a municipality × sale-year block # a single roll is in force, so the roll lag is (nearly) constant and the # COD/PRB are not inflated by market-time drift. Annual blocks are then # aggregated to one row per municipality (median across years, total n). def annual_then_aggregate(data: pd.DataFrame, min_n: int) -> pd.DataFrame: blocks = iaao.group_metrics(data, ["muni", "sale_year"], min_n=min_n) blocks["muni"] = blocks["group"].str.rsplit("_", n=1).str[0] agg = (blocks.groupby("muni") .agg(n=("n", "sum"), n_years=("n", "size"), median_ratio=("median_ratio", "median"), cod=("cod", "median"), prd=("prd", "median"), prb=("prb", "median")) .reset_index()) share_neg = (blocks.assign(neg=blocks["prb"] < 0) .groupby("muni")["neg"].mean().rename("share_years_prb_neg")) return agg.merge(share_neg, on="muni") # ------------------------------------------------------------ ten largest markets big = df[df["role_municipalite"].isin(config.BIG_CITIES)].copy() big["muni"] = big["role_municipalite"] # aggregate by display name tab = annual_then_aggregate(big, min_n=200) tab.to_csv(out / "iaao_cities.csv", index=False) # ------------------------------------------------------------ every muni ≥ 100 sales tab = annual_then_aggregate(df, min_n=50) tab = tab[tab["n"] >= config.MUNI_MIN_SALES] coords = df.groupby("muni")[["lat", "lng"]].median() names = df.groupby("muni")["role_municipalite"].first() tab = tab.merge(coords, left_on="muni", right_index=True) tab = tab.merge(names.rename("name"), left_on="muni", right_index=True) tab.to_csv(out / "iaao_muni.csv", index=False) print(f"\nMunicipality-level metrics: {len(tab)} municipalities " f"(median within-year COD {tab['cod'].median():.1f}, " f"share PRB<0: {(tab['prb'] < 0).mean():.1%})") if __name__ == "__main__": main()