# Author: Simon-Pierre Boucher — simon-pierre.boucher@uqo.ca """Figure style: validated categorical palette (fixed slot order, never cycled), thin marks, single axis per panel.""" from __future__ import annotations import matplotlib import matplotlib.pyplot as plt matplotlib.use("Agg") SERIES = ["#2a78d6", "#eb6834", "#1baf7a", "#eda100", "#e87ba4", "#008300", "#6250d6", "#e34948"] INK = "#0b0b0b"; INK2 = "#52514e"; GRID = "#e4e3df" METHOD_COLORS = { # fixed assignment: colour follows the entity "S_sharp": SERIES[0], "C_pooled_td": SERIES[1], "A_median": SERIES[2], "H_repeat_sales_cs": SERIES[3], "D_rtd_w13_mean": SERIES[4], "E_hi_fisher_chained": SERIES[5], "F_geks_w25_movement": SERIES[7], "K_qhpi": SERIES[6], "G_hmts_w13": "#7a7a7a", "B_stratified_median": "#a0a0a0", } METHOD_LABELS = {"S_sharp": "SHARP-HPI", "C_pooled_td": "Pooled time dummy (Huber)", "A_median": "Median price", "H_repeat_sales_cs": "Repeat sales (Case–Shiller)", "D_rtd_w13_mean": "Rolling TD, 13 m, mean splice", "E_hi_fisher_chained": "Hedonic imputation Fisher (chained)", "F_geks_w25_movement": "GEKS-Fisher, 25 m", "K_qhpi": "QHPI v2.1 architecture", "G_hmts_w13": "HMTS, 13 m", "B_stratified_median": "Stratified median", "J_gbm_oot_residual": "GBM out-of-time residual", "S_sharp_filtered": "SHARP-HPI (real-time filtered)"} CLASS_LABELS = {"house": "Single-family houses", "condo": "Condominiums", "plex": "Plexes (2–5 units)"} def style() -> None: plt.rcParams.update({ "font.family": "serif", "font.size": 9, "axes.edgecolor": INK2, "axes.labelcolor": INK, "axes.spines.top": False, "axes.spines.right": False, "axes.grid": True, "grid.color": GRID, "grid.linewidth": 0.6, "xtick.color": INK2, "ytick.color": INK2, "legend.frameon": False, "legend.fontsize": 8, "lines.linewidth": 1.4, "figure.dpi": 150, "savefig.bbox": "tight", "axes.titlesize": 9.5, "axes.titleweight": "bold", }) def month_ticks(ax, months: list[str], every: int = 12) -> None: idx = [i for i, m in enumerate(months) if m.endswith("-01")][::max(1, every // 12)] ax.set_xticks(idx); ax.set_xticklabels([months[i][:4] for i in idx])