#!/usr/bin/env python3 """Figure ch03 — Mises en chantier au Québec, données RÉELLES : Statistique Canada (SCHL), tableau 34-10-0135-01, toutes les régions, trimestriel non désaisonnalisé, lu dans data/raw/statcan_34100135/34100135.csv (téléchargé le 2026-10-31). Panneau A : totaux annuels 1990-2025 (2026 : trois trimestres, hachuré) et somme mobile sur quatre trimestres ; la zone grisée marque le resserrement monétaire (mars 2022 → juillet 2023, série V39079). Panneau B : composition par type d'unité. Sortie : assets/figures/ch03/ch03-fig-mises-chantier-quebec.pdf""" import sys, pathlib ROOT = pathlib.Path(__file__).resolve().parents[3] sys.path.insert(0, str(ROOT / "analysis/python")); sys.path.insert(0, str(ROOT / "analysis/python/cases")) import pandas as pd, matplotlib.pyplot as plt from traite.style import setup, palette, savefig, LARGEUR, fmt_milliers import ch03_cycles as cas def main(): setup() df = cas.mises_en_chantier_quebec() ann = cas.mises_en_chantier_annuelles(df) tot = df[df["type"] == "Total d'unités"].set_index("date")["unites"] mobile = tot.rolling(4).sum() fig, (a, b) = plt.subplots(2, 1, figsize=(LARGEUR, 4.9), height_ratios=[3, 2], sharex=False) yrs = ann.loc[1990:2026] complet = yrs[yrs["trimestres"] == 4]; partiel = yrs[yrs["trimestres"] < 4] a.bar(complet.index, complet["total"] / 1000, width=0.8, color=palette.cat[0], alpha=0.75, label="total annuel") a.bar(partiel.index, partiel["total"] / 1000, width=0.8, color=palette.cat[0], alpha=0.35, hatch="///", edgecolor=palette.cat[0], label=f"{int(partiel.index[0])} (3 trimestres)") mob = mobile[mobile.index >= "1990-10-01"] a.plot(mob.index.year + (mob.index.month - 1) / 12 + 0.25, mob / 1000, color=palette.cat[1], lw=1.4, label="somme mobile sur 4 trimestres") a.axvspan(2022 + 2 / 12, 2023 + 6.5 / 12, color=palette.grid, alpha=0.8, lw=0) a.text(2023.75, 74, "resserrement monétaire\n(mars 2022 – juillet 2023)", ha="left", va="top", fontsize=7, color=palette.ink2) for y, va in ((2021, "bottom"), (2023, "bottom"), (2025, "bottom")): a.annotate(f"{fmt_milliers(ann.loc[y, 'total'])}", xy=(y, ann.loc[y, "total"] / 1000), xytext=(0, 3), textcoords="offset points", ha="center", va=va, fontsize=7.5) a.set_ylabel("milliers de logements"); a.set_xlim(1989.3, 2026.9); a.set_ylim(0, 75) a.legend(loc="upper left", fontsize=7.5, ncol=1); a.set_title("A. Logements mis en chantier, Québec (toutes les régions)", loc="left") # B. composition comp = ann.loc[1990:2025] b.stackplot(comp.index, 100 * comp["part_individuelles"], 100 * comp["jumeles"] / comp["total"] + 100 * comp["bande"] / comp["total"], 100 * comp["part_appartements"], colors=[palette.cat[0], palette.cat[2], palette.cat[1]], alpha=0.85, labels=["maisons individuelles", "jumelés et en bande", "appartements et autres"]) b.set_ylim(0, 100); b.set_ylabel("% des unités"); b.set_xlim(1989.3, 2026.9) b.legend(loc="upper center", bbox_to_anchor=(0.5, -0.16), fontsize=7.5, ncol=3, frameon=False) b.set_title("B. Composition par type d'unité (années complètes)", loc="left") for y, ha in ((2005, "center"), (2025, "right")): b.annotate(f"{100*ann.loc[y,'part_individuelles']:.0f} %", xy=(y + (0.3 if ha == "right" else 0), 100 * ann.loc[y, "part_individuelles"] / 2), ha=ha, va="center", fontsize=7.5, color="white") fig.tight_layout(h_pad=1.4) out = savefig(fig, "ch03/ch03-fig-mises-chantier-quebec.pdf"); print("OK", out) if __name__ == "__main__": main()