#!/usr/bin/env python3 """Figures du chapitre 12 (DONNÉES SYNTHÉTIQUES, cas 1, graine 20261031) : ch12-fig-conventions-sujet.pdf : superficie du sujet selon cinq conventions et prix unitaire implicite ; ch12-fig-erreur-valeur.pdf : valeur hédonique du sujet en fonction de la superficie mesurée, tangente et erreur de ±5 % ; ch12-fig-attenuation.pdf : atténuation du coefficient de la superficie par une erreur de mesure (théorie et simulation). Usage : python3 analysis/python/figures/ch12_figures.py (depuis la racine).""" 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 ch12_mesurage as cas def fig_conventions(r): pu = r["pu"]; V = r["M"]["V"] noms = ["Aire au sol\n(MEFQ R0701A)", "Superficie nette\n(utile)", "Superficie habitable\n(convention de l'ouvrage)", "Aire des étages, brute\n(MEFQ R0702A)", "Habitable + sous-sol fini\n(fiches de courtage)"] fig, (a, b) = plt.subplots(1, 2, figsize=(LARGEUR, 3.0), gridspec_kw={"width_ratios": [1.1, 1]}, sharey=True) y = np.arange(len(pu)) cols = [palette.cat[1], palette.cat[1], palette.cat[0], palette.cat[1], palette.cat[1]] a.barh(y, pu["m2"], color=cols, height=0.6) for i, (m2, pi2) in enumerate(zip(pu["m2"], pu["pi2"])): a.text(m2 + 3, i, f"{m2:.1f} m² ({pi2:,.0f} pi²)".replace(",", " "), va="center", fontsize=7.5) a.set_yticks(y); a.set_yticklabels(noms, fontsize=7.5); a.invert_yaxis(); a.set_xlim(0, 320) a.set_xlabel("Superficie (m²)"); a.set_title("(a) Un bâtiment, cinq superficies", loc="left"); a.grid(axis="y", visible=False) b.barh(y, pu["dollars_m2"], color=cols, height=0.6) for i, (dm, dp) in enumerate(zip(pu["dollars_m2"], pu["dollars_pi2"])): b.text(dm + 80, i, (f"{dm:,.0f} " + r"\$/m² (" + f"{dp:,.0f} " + r"\$/pi²)").replace(",", " "), va="center", fontsize=7.5) b.set_xlim(0, 9500); b.set_xlabel("Prix unitaire implicite ($/m²)"); b.grid(axis="y", visible=False) b.set_title("(b) Cinq prix unitaires pour " + fmt_dollars(V).replace("$", r"\$"), loc="left") fig.tight_layout(w_pad=1.0) return savefig(fig, "ch12/ch12-fig-conventions-sujet.pdf") def fig_erreur(r): M = r["M"]; V, S, beta = M["V"], M["S"], M["beta"]; dVdS = r["dVdS"] s = np.linspace(135, 200, 200); Vs = V * (s / S) ** beta; tang = V + dVdS * (s - S) fig, (a, b) = plt.subplots(1, 2, figsize=(LARGEUR, 3.1)) a.plot(s, Vs, color=palette.cat[0], label=r"$V(S) = V_0\,(S/S_0)^{\beta}$, $\beta$ = 0,539") a.plot(s, tang, color=palette.cat[1], ls="--", lw=1.2, label="tangente en $S_0$ = 165 m²") a.axvspan(S * 0.95, S * 1.05, color=palette.cat[2], alpha=0.15, lw=0) a.plot([S], [V], "o", color=palette.ink, ms=4) for e in (-0.05, 0.05): se = S * (1 + e); ve = V * (1 + e) ** beta a.plot([se, se], [V, ve], color=palette.ink2, lw=0.8); a.plot([S, se], [ve, ve], color=palette.ink2, lw=0.8) a.annotate(("+" if e > 0 else "−") + f"{abs(ve - V):,.0f} $".replace(",", " "), (se, ve), xytext=(6 if e > 0 else -6, -12 if e > 0 else 8), textcoords="offset points", ha="left" if e > 0 else "right", fontsize=7.5) a.set_xlabel("Superficie habitable mesurée (m²)"); a.set_ylabel("Valeur indiquée ($)") a.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, p: f"{x/1000:.0f} k$")); a.legend(loc="upper left", fontsize=7.2) a.set_title("(a) Valeur du sujet et erreur de ±5 % sur S", loc="left") e = np.linspace(-0.15, 0.15, 121) b.plot(100 * e, 100 * ((1 + e) ** beta - 1), color=palette.cat[0], label="exact, β = 0,539") b.plot(100 * e, 100 * beta * e, color=palette.cat[1], ls="--", lw=1.2, label="linéaire, β·e") b.plot(100 * e, 100 * e, color=palette.cat[2], ls=":", lw=1.2, label="proportionnel (prix au m², β = 1)") mel = r["mel"] b.plot([mel["ecart_pct"]], [mel["sujet_brut_pct"]], "o", color=palette.ink, ms=4) b.annotate("sujet brut (190 m²)\nvs habitable (165 m²)", (mel["ecart_pct"], mel["sujet_brut_pct"]), xytext=(-10, -34), textcoords="offset points", ha="right", fontsize=7.2) b.axhline(0, color=palette.ink2, lw=0.6); b.axvline(0, color=palette.ink2, lw=0.6) b.set_xlabel("Erreur relative sur la superficie (%)"); b.set_ylabel("Erreur relative sur la valeur (%)") b.legend(loc="lower right", fontsize=7.2); b.set_title("(b) Propagation : erreur sur S → erreur sur V", loc="left") fig.tight_layout(w_pad=2.0) return savefig(fig, "ch12/ch12-fig-erreur-valeur.pdf") def fig_attenuation(r): att = r["att"]; beta = r["M"]["beta"]; var_e = att.attrs["var_e"] s = np.linspace(0, 0.22, 100); lam = var_e / (var_e + s ** 2) fig, (a, b) = plt.subplots(1, 2, figsize=(LARGEUR, 3.0)) a.plot(s, lam * beta, color=palette.cat[0], label=r"théorie : $\lambda\,\beta$, $\lambda = \sigma_{\tilde e}^2/(\sigma_{\tilde e}^2+\sigma_u^2)$") a.errorbar(att["sigma_u"], att["beta_moyen"], yerr=att["beta_ecart_type"], fmt="o", color=palette.cat[1], ms=4, capsize=2, lw=1, label="simulation (200 tirages)") a.axhline(beta, color=palette.ink2, lw=0.7, ls=":"); a.text(0.002, beta + 0.004, f"β sans erreur = {beta:.3f}", fontsize=7.2) a.set_xlabel(r"Écart-type de l'erreur de mesure sur $\ln S$, $\sigma_u$"); a.set_ylabel(r"Coefficient estimé $\hat\beta_S$") a.set_ylim(0.28, 0.58); a.legend(loc="lower left", fontsize=7.2); a.set_title("(a) Atténuation du coefficient", loc="left") b.plot(att["sigma_u"], att["V_sujet_165"] / 1000, "o-", color=palette.cat[0], ms=4, label="sujet, 165 m²") b.plot(att["sigma_u"], att["V_maison_120"] / 1000, "s-", color=palette.cat[1], ms=4, label="maison de 120 m²") b.set_xlabel(r"$\sigma_u$"); b.set_ylabel("Valeur prédite (k$)"); b.legend(loc="center right", fontsize=7.2) b.set_title("(b) Le gradient prix-taille s'aplatit", loc="left") fig.tight_layout(w_pad=2.0) return savefig(fig, "ch12/ch12-fig-attenuation.pdf") def main(): setup(); r = cas.tout() for f in (fig_conventions, fig_erreur, fig_attenuation): print("OK", f(r)) if __name__ == "__main__": main()