spb/traite-evaluation-qc
Public
1#!/usr/bin/env python32"""Figure ch42 — grille de comparaison de la fraction F4 : prix observé, prix ajusté et décomposition des ajustements des cinq comparables3fictifs ; valeur pondérée (DONNÉES SYNTHÉTIQUES).4Sortie : assets/figures/ch42/ch42-fig-grille-fraction.pdf"""5import sys, pathlib6ROOT = pathlib.Path(__file__).resolve().parents[3]7sys.path.insert(0, str(ROOT / "analysis/python")); sys.path.insert(0, str(ROOT / "analysis/python/cases"))8import numpy as np, matplotlib.pyplot as plt9from traite.style import setup, palette, savefig, LARGEUR, fmt_dollars10import ch42_copro as cas1112ORDRE = ["marché", "étage", "orientation", "stationnement", "superficie", "charges", "syndicat"]13COUL = {k: c for k, c in zip(ORDRE, palette.cat[:6] + [palette.ink2])}1415def main():16 setup()17 g = cas.grille_fraction(); comps, aj, w = g["comparables"], g["ajustes"], g["poids"]; rec = g["reconciliation"]18 y = np.arange(len(aj))19 fig, ax = plt.subplots(figsize=(LARGEUR, 3.6))20 for yi, a, c in zip(y, aj, comps):21 pos = a.prix; neg = a.prix22 for k in ORDRE:23 v = a.ajustements[k]24 if v >= 0:25 ax.barh(yi, v, left=pos, height=0.5, color=COUL[k]); pos += v26 else:27 neg += v; ax.barh(yi, -v, left=neg, height=0.5, color=COUL[k], alpha=0.55, label=None)28 ax.plot([a.prix], [yi], marker="o", color=palette.ink, ms=5, zorder=5)29 ax.plot([a.prix_ajuste], [yi], marker="D", color=palette.ink, ms=5, zorder=5)30 ax.text(max(pos, a.prix_ajuste) + 3000, yi, f"{a.prix_ajuste:,.0f} $ (w = {wi:.2f})".replace(",", " ").replace("0.", "0,") if (wi := w[yi]) is not None else "", va="center", fontsize=7.2)31 ax.axvline(rec["valeur_ponderee"], color=palette.ink2, ls="--", lw=1.1)32 ax.text(rec["valeur_ponderee"], -0.75, f"valeur pondérée {rec['valeur_ponderee']:,.0f} $".replace(",", " "), ha="center", fontsize=7.6, color=palette.ink)33 ax.set_yticks(y); ax.set_yticklabels([f"{c['nom']} — {c['immeuble']}" for c in comps]); ax.invert_yaxis()34 ax.xaxis.set_major_formatter(plt.FuncFormatter(fmt_dollars))35 ax.set_xlim(300_000, 450_000); ax.set_xlabel("Prix observé (cercle) → prix ajusté (losange) ; ajustements négatifs en teinte pâle")36 ax.grid(axis="y", visible=False)37 from matplotlib.patches import Patch38 handles = [Patch(color=COUL[k], label=k) for k in ORDRE]39 ax.legend(handles=handles, loc="upper center", bbox_to_anchor=(0.45, -0.22), ncol=4, fontsize=7.2, title="Ajustements (du point de vue du sujet)", title_fontsize=7.4)40 out = savefig(fig, "ch42/ch42-fig-grille-fraction.pdf"); print("OK", out)4142if __name__ == "__main__":43 main()44