SPB Git
9.2 KB · 128 lines python
1#!/usr/bin/env python32"""Figures du chapitre 25 — Évaluation du terrain (DONNÉES SYNTHÉTIQUES, graine 20261031).3Sorties (assets/figures/ch25/) :4  ch25-fig-valeur-unitaire-superficie.pdf — valeur unitaire ($/m²) des terrains vacants selon la superficie, desservis ou non, et courbes du modèle ;5  ch25-fig-carte-terrains.pdf — carte synthétique des 200 ventes de terrains, du sujet et des cinq comparables retenus ;6  ch25-fig-lotissement.pdf — flux annuels du lotissement et valeur actualisée cumulée ; tornade de sensibilité ;7  ch25-fig-residuelle-sensibilite.pdf — technique résiduelle du terrain (cas 3) : V_L selon le taux du terrain pour trois coûts dépréciés.8Usage (racine) : python3 analysis/python/figures/ch25_figures.py"""9from __future__ import annotations10import sys, pathlib11import numpy as np12ROOT = pathlib.Path(__file__).resolve().parents[3]13sys.path.insert(0, str(ROOT / "analysis/python")); sys.path.insert(0, str(ROOT / "analysis/python/cases"))14import matplotlib.pyplot as plt15from matplotlib.lines import Line2D16from traite.style import setup, palette, savefig, LARGEUR, fmt_dollars, fmt_milliers17import ch25_terrain as cas181920def fig_valeur_unitaire(S):21    c = S["comparaison"]; df = c["terrains"]; m = c["modele"]; b = m["beta"]22    fig, ax = plt.subplots(figsize=(LARGEUR, 3.4))23    for val, lab, col, mk in [(1, "Terrain desservi (aqueduc et égout)", palette.cat[0], "o"), (0, "Terrain non desservi", palette.cat[1], "^")]:24        d = df[df["desservi"] == val]25        ax.scatter(d["superficie_m2"], d["prix_m2"], s=14, alpha=0.65, color=col, marker=mk, label=lab, linewidths=0)26    T = np.linspace(320, 9000, 300)27    base = np.exp(b["const"] + b["ln_T"] * np.log(T / 560.0)) * m["duan"]   # Hull, plat, régulier, façade 18 m, 1 logement, 2024-07-0128    ax.plot(T, base / T, color=palette.cat[0], lw=1.6, label=f"Modèle, desservi (élasticité {b['ln_T']:.2f})")29    ax.plot(T, base * np.exp(b["non_desservi"]) / T, color=palette.cat[1], lw=1.6, ls="--", label="Modèle, non desservi")30    vr = S["valeur_retenue"] / 620.031    ax.scatter([620], [vr], marker="*", s=120, color=palette.cat[3], zorder=5, edgecolor=palette.ink, linewidths=0.5)32    ax.annotate(f"Sujet du cas 1 : 620 m², {fmt_dollars(vr)}/m² (valeur retenue)", (620, vr), xytext=(1000, vr + 90), fontsize=8,33                arrowprops=dict(arrowstyle="-", color=palette.ink2, lw=0.6), color=palette.ink)34    ax.set_xscale("log"); ax.set_xlim(300, 10000); ax.set_ylim(0, 800)35    ax.set_xticks([300, 500, 1000, 2000, 5000, 10000]); ax.set_xticklabels(["300", "500", "1 000", "2 000", "5 000", "10 000"])36    ax.set_xlabel("Superficie du terrain (m², échelle logarithmique)"); ax.set_ylabel("Prix de vente par m² ($)")37    ax.legend(loc="upper right", fontsize=7.5)38    savefig(fig, "ch25/ch25-fig-valeur-unitaire-superficie.pdf")394041def fig_carte(S):42    c = S["comparaison"]; df = c["terrains"]; comps = c["selection"]["comparables"]; suj = c["sujet"]43    fig, ax = plt.subplots(figsize=(LARGEUR, 4.2))44    d1 = df[df["desservi"] == 1]; d0 = df[df["desservi"] == 0]45    ax.scatter(d1["lon"], d1["lat"], s=12, color=palette.ink2, alpha=0.45, linewidths=0, label="Vente de terrain desservi")46    ax.scatter(d0["lon"], d0["lat"], s=16, facecolors="none", edgecolors=palette.ink2, alpha=0.7, linewidths=0.7, label="Vente de terrain non desservi")47    ax.scatter(comps["lon"], comps["lat"], s=42, color=palette.cat[1], edgecolor=palette.ink, linewidths=0.5, zorder=4, label="Comparables retenus (5)")48    ax.scatter([suj["lon"]], [suj["lat"]], marker="*", s=170, color=palette.cat[3], edgecolor=palette.ink, linewidths=0.6, zorder=5, label="Sujet (cas 1)")49    for _, r in comps.iterrows():50        ax.annotate(r["id_vente"], (r["lon"], r["lat"]), xytext=(4, 3), textcoords="offset points", fontsize=7, color=palette.ink)51    # rayon de recherche de 4 km (approximation locale : 1° lat ≈ 111 km, 1° lon ≈ 78 km)52    th = np.linspace(0, 2 * np.pi, 200)53    ax.plot(suj["lon"] + 4 / 78 * np.cos(th), suj["lat"] + 4 / 111 * np.sin(th), color=palette.cat[3], lw=0.8, ls=":")54    for sct, (la, lo) in cas.CENTROIDES.items():55        dy = -0.036 if sct == "Masson-Angers" else 0.03056        ax.text(lo, la + dy, sct, ha="center", fontsize=8, color=palette.ink, fontweight="semibold")57    ax.set_xlabel("Longitude (coordonnées fictives)"); ax.set_ylabel("Latitude (coordonnées fictives)")58    ax.set_aspect(111 / 78); ax.legend(loc="lower right", fontsize=7.5)59    ax.set_xlim(-75.92, -75.33); ax.set_ylim(45.34, 45.64)60    savefig(fig, "ch25/ch25-fig-carte-terrains.pdf")616263def fig_lotissement(S):64    L = S["lotissement"]; det = L["detail"]; p = L["params"]; m = S["comparaison"]["modele"]65    fig, (a1, a2) = plt.subplots(1, 2, figsize=(LARGEUR, 3.6), gridspec_kw={"width_ratios": [1.0, 1.0], "wspace": 0.95})66    yrs = det["annee"].values67    couts = -(det["infrastructure"] + det["honoraires"] + det["taxes"] + det["frais_vente"] + det["profit"]).values68    a1.bar(yrs, det["revenus"].values, color=palette.cat[0], width=0.6, label="Produit des ventes de lots")69    a1.bar(yrs, couts, color=palette.cat[1], width=0.6, label="Coûts, frais et profit du promoteur")70    r = p["taux"]; cum = np.cumsum([f / (1 + r) ** t for t, f in zip(yrs, L["flux"])])71    a1.plot(yrs, cum, color=palette.cat[3], marker="o", lw=1.6, label="Valeur actualisée cumulée (10 %)")72    for t, v in zip(yrs, cum):73        a1.annotate(fmt_milliers(v / 1000), (t, v), xytext=(0, 6), textcoords="offset points", ha="center", fontsize=7, color=palette.ink)74    a1.axhline(0, color=palette.ink2, lw=0.6)75    a1.set_xticks(yrs); a1.set_xticklabels([str(t) for t in yrs]); a1.set_xlabel("Année (flux de fin d'année)"); a1.set_ylim(-1_250_000, 2_300_000); a1.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, p_: fmt_milliers(x / 1000)))76    a1.set_ylabel("Milliers de dollars"); a1.set_title("(a) Flux du lotissement de 12 lots", loc="left", fontsize=9)77    a1.legend(fontsize=6.5, loc="upper left", handlelength=1.2)78    sl = cas.sensibilite_lotissement(m); base = sl["base"]79    items = [("Absorption\n2 → 6 lots/an", sl["absorption"][2], sl["absorption"][6]),80             ("Prix des lots\n−10 % → +10 %", sl["prix"][-0.10], sl["prix"][0.10]),81             ("Infrastructures\n80 000 → 50 000 $/lot", sl["infrastructure"][80_000], sl["infrastructure"][50_000]),82             ("Profit du promoteur\n20 % → 10 %", sl["profit"][0.20], sl["profit"][0.10]),83             ("Taux d'actualisation\n15 % → 8 %", sl["taux"][0.15], sl["taux"][0.08])]84    items.sort(key=lambda x: abs(x[2] - x[1]), reverse=True)85    for k, (lab, lo, hi) in enumerate(items):86        a2.barh(k, lo - base, left=base, color=palette.cat[1], height=0.55)87        a2.barh(k, hi - base, left=base, color=palette.cat[0], height=0.55)88        a2.text(min(lo, hi) - 8000, k, fmt_milliers((min(lo, hi)) / 1000), va="center", ha="right", fontsize=7, color=palette.ink)89        a2.text(max(lo, hi) + 8000, k, fmt_milliers((max(lo, hi)) / 1000), va="center", ha="left", fontsize=7, color=palette.ink)90    a2.set_yticks(range(len(items))); a2.set_yticklabels([i[0] for i in items], fontsize=7)91    a2.axvline(base, color=palette.ink, lw=0.8)92    a2.set_xlim(base - 420_000, base + 420_000); a2.xaxis.set_major_formatter(plt.FuncFormatter(lambda x, p_: fmt_milliers(x / 1000)))93    a2.set_xlabel("Valeur du site brut (milliers de dollars)"); a2.set_title(f"(b) Sensibilité autour de {fmt_dollars(base)}", loc="left", fontsize=9)94    a2.grid(axis="y", visible=False); a2.invert_yaxis()95    savefig(fig, "ch25/ch25-fig-lotissement.pdf")969798def fig_residuelle(S):99    t3 = S["cas3"]; rs = S["residuelle"]100    fig, ax = plt.subplots(figsize=(LARGEUR, 3.3))101    RL = np.linspace(0.03, 0.05, 81)102    for d, col, ls in [(0.44, palette.cat[1], "--"), (0.52, palette.cat[0], "-"), (0.60, palette.cat[2], "-.")]:103        VL = [cas.residuelle_cas3({"taux_depreciation": d, "R_L": r}, t3)["V_L"] for r in RL]104        CB = cas.residuelle_cas3({"taux_depreciation": d}, t3)["C_B"]105        ax.plot(RL * 100, VL, color=col, ls=ls, lw=1.6, label=f"Dépréciation {d*100:.0f} % : bâtiment déprécié {fmt_dollars(CB)}")106    ax.axhline(0, color=palette.ink2, lw=0.6)107    ax.scatter([rs["R_L"] * 100], [rs["V_L"]], s=60, color=palette.cat[3], zorder=5, edgecolor=palette.ink, linewidths=0.5)108    ax.annotate(f"Cas de base : taux du terrain 3,75 %, 40 ans restants\nvaleur résiduelle {fmt_dollars(rs['V_L'])}", (rs["R_L"] * 100, rs["V_L"]), xytext=(4.05, 1_000_000), fontsize=8,109                arrowprops=dict(arrowstyle="-", color=palette.ink2, lw=0.6))110    ext = rs["V_cap"] - rs["C_B"]111    ax.axhline(ext, color=palette.ink2, lw=0.8, ls=":")112    ax.text(3.02, ext - 75_000, f"Extraction sur la valeur capitalisée (V − bâtiment déprécié) : {fmt_dollars(ext)}", fontsize=7.5, color=palette.ink2)113    ax.set_xlabel("Taux de rendement du terrain $R_L$ (%)"); ax.set_ylabel("Valeur résiduelle du terrain (dollars)")114    ax.yaxis.set_major_formatter(plt.FuncFormatter(fmt_dollars)); ax.set_ylim(-300_000, 1_300_000)115    ax.legend(loc="lower left", fontsize=7.5)116    savefig(fig, "ch25/ch25-fig-residuelle-sensibilite.pdf")117118119def main():120    setup()121    S = cas.synthese()122    fig_valeur_unitaire(S); fig_carte(S); fig_lotissement(S); fig_residuelle(S)123    print("Figures ch25 écrites dans", ROOT / "assets/figures/ch25")124125126if __name__ == "__main__":127    main()128
Files · dfd0c291e81581841a9798b3fcf4f3f303e56b99
Menu

Theme