#!/usr/bin/env python3 """Figure ch42 — grille de comparaison de la fraction F4 : prix observé, prix ajusté et décomposition des ajustements des cinq comparables fictifs ; valeur pondérée (DONNÉES SYNTHÉTIQUES). Sortie : assets/figures/ch42/ch42-fig-grille-fraction.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 numpy as np, matplotlib.pyplot as plt from traite.style import setup, palette, savefig, LARGEUR, fmt_dollars import ch42_copro as cas ORDRE = ["marché", "étage", "orientation", "stationnement", "superficie", "charges", "syndicat"] COUL = {k: c for k, c in zip(ORDRE, palette.cat[:6] + [palette.ink2])} def main(): setup() g = cas.grille_fraction(); comps, aj, w = g["comparables"], g["ajustes"], g["poids"]; rec = g["reconciliation"] y = np.arange(len(aj)) fig, ax = plt.subplots(figsize=(LARGEUR, 3.6)) for yi, a, c in zip(y, aj, comps): pos = a.prix; neg = a.prix for k in ORDRE: v = a.ajustements[k] if v >= 0: ax.barh(yi, v, left=pos, height=0.5, color=COUL[k], label=k if yi == 0 else None); pos += v else: neg += v; ax.barh(yi, -v, left=neg, height=0.5, color=COUL[k], alpha=0.55, label=None) ax.plot([a.prix], [yi], marker="o", color=palette.ink, ms=5, zorder=5) ax.plot([a.prix_ajuste], [yi], marker="D", color=palette.ink, ms=5, zorder=5) 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) ax.axvline(rec["valeur_ponderee"], color=palette.ink2, ls="--", lw=1.1) 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) ax.set_yticks(y); ax.set_yticklabels([f"{c['nom']} — {c['immeuble']}" for c in comps]); ax.invert_yaxis() ax.xaxis.set_major_formatter(plt.FuncFormatter(fmt_dollars)) ax.set_xlim(300_000, 450_000); ax.set_xlabel("Prix observé (cercle) → prix ajusté (losange) ; ajustements positifs en couleur pleine, négatifs en teinte pâle") ax.grid(axis="y", visible=False) ax.legend(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) out = savefig(fig, "ch42/ch42-fig-grille-fraction.pdf"); print("OK", out) if __name__ == "__main__": main()