SPB Git
7.0 KB · 97 lines python
1#!/usr/bin/env python32"""Figures du chapitre 9 (DONNÉES SYNTHÉTIQUES du cas 1, graine 20261031) :3  ch09-fig-ratios-distribution.pdf  — distribution des ratios rôle/prix, bruts et rajustés à la date de référence, bornes d'épuration du r. 104  ch09-fig-ratios-temps.pdf         — ratio médian par trimestre de vente : dérive des ratios bruts, stabilité après rajustement5  ch09-fig-prb.pdf                  — nuage (r − médiane)/médiane contre log2 de la valeur proxy et droite du PRB6  ch09-fig-sensibilite-contestation.pdf — gain net d'une contestation sur le cycle triennal selon la valeur réelle conclue et le taux de voisinage7Usage (racine) : PYTHONPATH=analysis/python python3 analysis/python/figures/ch09_ratios.py"""8from __future__ import annotations9import sys, pathlib10ROOT = pathlib.Path(__file__).resolve().parents[3]11sys.path.insert(0, str(ROOT / "analysis/python")); sys.path.insert(0, str(ROOT / "analysis/python/cases"))12import numpy as np, pandas as pd13import matplotlib.pyplot as plt14from matplotlib.ticker import FuncFormatter, PercentFormatter15from traite.style import setup, palette, savefig, LARGEUR, fmt_dollars16import ch09_ratios as cas171819def fig_distribution(et):20    v = et["v"]; ep = et["epuration"]21    bins = np.arange(0.64, 1.24, 0.02)22    fig, axes = plt.subplots(1, 2, figsize=(LARGEUR, 2.9), sharey=True)23    for ax, col, titre, c in [(axes[0], "r_brut", "Ratios bruts : rôle / prix de vente", palette.cat[0]), (axes[1], "r_aj", "Ratios rajustés au 1er juillet 2024", palette.cat[1])]:24        r = v[col]; ax.hist(r, bins=bins, color=c, alpha=0.85, edgecolor=palette.surface, linewidth=0.4)25        med = r.median(); ax.axvline(med, color=palette.ink, lw=1.2)26        ax.text(med + 0.01, ax.get_ylim()[1] * 0.93, f"médiane {med:.3f}".replace(".", ","), fontsize=8, color=palette.ink)27        ax.set_title(titre, loc="left", fontsize=8.5); ax.set_xlabel("Ratio valeur au rôle / prix")28        ax.set_xlim(0.64, 1.24)29    axes[0].set_ylabel("Nombre de ventes")30    ax = axes[1]31    for b in (ep["borne_basse"], ep["borne_haute"]):32        ax.axvline(b, color=palette.cat[3], lw=1.0, ls="--")33    ax.text(ep["borne_haute"] + 0.01, ax.get_ylim()[1] * 0.55, f"médiane ± 2σ\n{ep['n_retires']} ventes\nretirées", fontsize=7.5, color=palette.cat[3])34    for ax in axes:35        ax.axvspan(0.90, 1.10, color=palette.grid, alpha=0.35, lw=0, zorder=0)36    axes[0].text(1.13, axes[0].get_ylim()[1] * 0.55, "zone grise :\nniveau IAAO\n0,90–1,10", fontsize=7, color=palette.ink2, ha="center", va="center")37    fig.suptitle(f"COD {et['brut']['COD']:.1f} → {et['aj']['COD']:.1f} ; PRD {et['brut']['PRD']:.3f} → {et['aj']['PRD']:.3f} ; proportion médiane après épuration {et['PM']:.3f}".replace(".", ","),38                 fontsize=8, color=palette.ink2, x=0.02, ha="left", y=1.02)39    print("OK", savefig(fig, "ch09/ch09-fig-ratios-distribution.pdf"))404142def fig_temps(et):43    t = et["trimestres"].copy(); t.index = pd.PeriodIndex(t.index, freq="Q").to_timestamp()44    fig, ax = plt.subplots(figsize=(LARGEUR, 2.8))45    ax.plot(t.index, t["r_brut"], marker="o", ms=3.5, color=palette.cat[0], label="Ratio médian brut (rôle / prix)")46    ax.plot(t.index, t["r_aj"], marker="s", ms=3.5, color=palette.cat[1], label="Ratio médian après rajustement au 1er juillet 2024")47    ax.axhline(et["PM"], color=palette.ink2, lw=0.9, ls=":")48    ax.text(t.index[0], et["PM"] + 0.004, f"proportion médiane {et['PM']:.3f}".replace(".", ","), fontsize=7.5, color=palette.ink2)49    ax.axvline(cas.REF, color=palette.cat[3], lw=0.9, ls="--"); ax.text(cas.REF, 0.995, " date de référence", fontsize=7.5, color=palette.cat[3], va="top")50    ax.set_ylim(0.80, 1.0); ax.set_ylabel("Ratio médian du trimestre"); ax.set_xlabel("Trimestre de la vente")51    ax.legend(loc="lower left", fontsize=7.5)52    print("OK", savefig(fig, "ch09/ch09-fig-ratios-temps.pdf"))535455def fig_prb(et):56    v = et["v"]; V = v["valeur_role_2025"].to_numpy(); P = v["prix_ref"].to_numpy(); r = V / P; med = np.median(r)57    proxy = 0.5 * (V / med + P); x = np.log2(proxy); y = (r - med) / med58    b = np.polyfit(x, y, 1)59    fig, ax = plt.subplots(figsize=(LARGEUR, 3.0))60    ax.scatter(x, y, s=9, color=palette.cat[1], alpha=0.55, lw=0, label="Ventes (ratios rajustés)")61    xs = np.linspace(x.min(), x.max(), 50); ax.plot(xs, np.polyval(b, xs), color=palette.ink, lw=1.4, label=f"Droite du PRB : pente {et['aj']['PRB']:.3f} (e.-t. {et['aj']['PRB_se']:.3f})".replace(".", ","))62    ax.axhspan(-0.05, 0.05, color=palette.grid, alpha=0.5, lw=0, zorder=0)63    ax.text(x.min(), 0.052, "zone IAAO : pente attendue entre −0,05 et +0,05", fontsize=7.5, color=palette.ink2)64    ax.axhline(0, color=palette.ink2, lw=0.6)65    ax.set_xlabel("log₂ de la valeur proxy  ((rôle / médiane + prix rajusté) / 2)"); ax.set_ylabel("(ratio − médiane) / médiane")66    sec = ax.secondary_xaxis("top", functions=(lambda z: 2 ** z, lambda z: np.log2(np.maximum(z, 1))))67    sec.set_xlabel("Valeur proxy ($)", fontsize=8); sec.xaxis.set_major_formatter(FuncFormatter(fmt_dollars)); sec.tick_params(labelsize=7)68    ax.set_ylim(-0.3, 0.3); ax.yaxis.set_major_formatter(PercentFormatter(1.0, decimals=0))69    ax.legend(loc="lower right", fontsize=7.5)70    print("OK", savefig(fig, "ch09/ch09-fig-prb.pdf"))717273def fig_sensibilite(d, et, da):74    s = cas.sensibilite_acheteur(d, et, da); tv = cas.taux_voisinage_gatineau()75    fig, ax = plt.subplots(figsize=(LARGEUR, 2.9))76    for col, lab, c in [("gain_net_min", f"taux de voisinage minimal ({tv['min']:.3f} $/1 000 $)", palette.cat[2]), ("gain_net_mediane", f"taux médian ({tv['mediane']:.3f} $/1 000 $)", palette.cat[0]), ("gain_net_max", f"taux maximal ({tv['max']:.3f} $/1 000 $)", palette.cat[1])]:77        ax.plot(s["valeur_reelle"], s[col], marker="o", ms=3, color=c, label=lab.replace(".", ",").replace("$", "\\$"))78    ax.axhline(0, color=palette.ink2, lw=0.8)79    ax.axvline(da["prix_ref"], color=palette.cat[3], ls="--", lw=0.9); ax.text(da["prix_ref"] + 1500, ax.get_ylim()[1] * 0.97, "prix rajusté\nde la vente", fontsize=7.5, color=palette.cat[3], va="top")80    seuil = da["valeur_role"] - da["seuil_valeur"]81    ax.axvline(seuil, color=palette.ink2, ls=":", lw=0.9); ax.text(seuil - 1500, ax.get_ylim()[1] * 0.97, "seuil de\nrentabilité", fontsize=7.5, color=palette.ink2, ha="right", va="top")82    ax.set_xlabel("Valeur réelle conclue à la date de référence ($)"); ax.set_ylabel("Gain net sur trois exercices ($)")83    ax.xaxis.set_major_formatter(FuncFormatter(fmt_dollars)); ax.yaxis.set_major_formatter(FuncFormatter(fmt_dollars))84    ax.set_title(f"Valeur inscrite {fmt_dollars(da['valeur_role'])} ; frais de révision {da['frais_revision']:.0f} $ ; facteur comparatif {et['F']:.4f}".replace(".", ",").replace("$", "\\$"), loc="left", fontsize=8.5)85    ax.legend(loc="lower left", fontsize=7.5)86    print("OK", savefig(fig, "ch09/ch09-fig-sensibilite-contestation.pdf"))878889def main():90    setup()91    d = cas.charger(); et = cas.etude(d); da = cas.dossier_acheteur(d, et)92    fig_distribution(et); fig_temps(et); fig_prb(et); fig_sensibilite(d, et, da)939495if __name__ == "__main__":96    main()97
Files · ac7e5213a2af3676c22c1500a18fcfe86d2041bc
Menu

Theme