SPB Git
9.0 KB · 127 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        ax.text(lo, la + 0.030, sct, ha="center", fontsize=8, color=palette.ink, fontweight="semibold")56    ax.set_xlabel("Longitude (coordonnées fictives)"); ax.set_ylabel("Latitude (coordonnées fictives)")57    ax.set_aspect(111 / 78); ax.legend(loc="lower right", fontsize=7.5)58    ax.set_xlim(-75.92, -75.33); ax.set_ylim(45.34, 45.64)59    savefig(fig, "ch25/ch25-fig-carte-terrains.pdf")606162def fig_lotissement(S):63    L = S["lotissement"]; det = L["detail"]; p = L["params"]; m = S["comparaison"]["modele"]64    fig, (a1, a2) = plt.subplots(1, 2, figsize=(LARGEUR, 3.4), gridspec_kw={"width_ratios": [1.1, 1]})65    yrs = det["annee"].values66    couts = -(det["infrastructure"] + det["honoraires"] + det["taxes"] + det["frais_vente"] + det["profit"]).values67    a1.bar(yrs, det["revenus"].values, color=palette.cat[0], width=0.6, label="Produit des ventes de lots")68    a1.bar(yrs, couts, color=palette.cat[1], width=0.6, label="Coûts, frais et profit du promoteur")69    r = p["taux"]; cum = np.cumsum([f / (1 + r) ** t for t, f in zip(yrs, L["flux"])])70    a1.plot(yrs, cum, color=palette.cat[3], marker="o", lw=1.6, label="Valeur actualisée cumulée (10 %)")71    for t, v in zip(yrs, cum):72        a1.annotate(fmt_dollars(v), (t, v), xytext=(0, 6), textcoords="offset points", ha="center", fontsize=7, color=palette.ink)73    a1.axhline(0, color=palette.ink2, lw=0.6)74    a1.set_xticks(yrs); a1.set_xticklabels([f"Année {t}" for t in yrs]); a1.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, p_: fmt_milliers(x / 1000)))75    a1.set_ylabel("Milliers de dollars"); a1.set_title("(a) Flux du lotissement de 12 lots", loc="left", fontsize=9)76    a1.legend(fontsize=7, loc="upper left")77    sl = cas.sensibilite_lotissement(m); base = sl["base"]78    items = [("Absorption : 2 à 6 lots/an", sl["absorption"][2], sl["absorption"][6]),79             ("Prix des lots : −10 % / +10 %", sl["prix"][-0.10], sl["prix"][0.10]),80             ("Infrastructures : 80 000 / 50 000 $ par lot", sl["infrastructure"][80_000], sl["infrastructure"][50_000]),81             ("Profit du promoteur : 20 % / 10 %", sl["profit"][0.20], sl["profit"][0.10]),82             ("Taux d'actualisation : 15 % / 8 %", sl["taux"][0.15], sl["taux"][0.08])]83    items.sort(key=lambda x: abs(x[2] - x[1]), reverse=True)84    for k, (lab, lo, hi) in enumerate(items):85        a2.barh(k, lo - base, left=base, color=palette.cat[1], height=0.55)86        a2.barh(k, hi - base, left=base, color=palette.cat[0], height=0.55)87        a2.text(min(lo, hi) - 8000, k, fmt_milliers((min(lo, hi)) / 1000), va="center", ha="right", fontsize=7, color=palette.ink)88        a2.text(max(lo, hi) + 8000, k, fmt_milliers((max(lo, hi)) / 1000), va="center", ha="left", fontsize=7, color=palette.ink)89    a2.set_yticks(range(len(items))); a2.set_yticklabels([i[0] for i in items], fontsize=7.5)90    a2.axvline(base, color=palette.ink, lw=0.8)91    a2.set_xlim(base - 420_000, base + 420_000); a2.xaxis.set_major_formatter(plt.FuncFormatter(lambda x, p_: fmt_milliers(x / 1000)))92    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)93    a2.grid(axis="y", visible=False); a2.invert_yaxis()94    savefig(fig, "ch25/ch25-fig-lotissement.pdf")959697def fig_residuelle(S):98    t3 = S["cas3"]; rs = S["residuelle"]99    fig, ax = plt.subplots(figsize=(LARGEUR, 3.3))100    RL = np.linspace(0.03, 0.05, 81)101    for d, col, ls in [(0.44, palette.cat[1], "--"), (0.52, palette.cat[0], "-"), (0.60, palette.cat[2], "-.")]:102        VL = [cas.residuelle_cas3({"taux_depreciation": d, "R_L": r}, t3)["V_L"] for r in RL]103        CB = cas.residuelle_cas3({"taux_depreciation": d}, t3)["C_B"]104        ax.plot(RL * 100, VL, color=col, ls=ls, lw=1.6, label=f"Dépréciation {d*100:.0f} % — $C_B$ = {fmt_dollars(CB)}")105    ax.axhline(0, color=palette.ink2, lw=0.6)106    ax.scatter([rs["R_L"] * 100], [rs["V_L"]], s=60, color=palette.cat[3], zorder=5, edgecolor=palette.ink, linewidths=0.5)107    ax.annotate(f"Cas de base : $R_L$ = 3,75 %, n = 40 ans\n$V_L$ = {fmt_dollars(rs['V_L'])}", (rs["R_L"] * 100, rs["V_L"]), xytext=(4.2, 900_000), fontsize=8,108                arrowprops=dict(arrowstyle="-", color=palette.ink2, lw=0.6))109    ext = rs["V_cap"] - rs["C_B"]110    ax.axhline(ext, color=palette.ink2, lw=0.8, ls=":")111    ax.text(3.02, ext + 25_000, f"Par extraction sur la capitalisation directe : $V$ − $C_B$ = {fmt_dollars(ext)}", fontsize=7.5, color=palette.ink2)112    ax.set_xlabel("Taux de rendement du terrain $R_L$ (%)"); ax.set_ylabel("Valeur résiduelle du terrain $V_L$ ($)")113    ax.yaxis.set_major_formatter(plt.FuncFormatter(fmt_dollars)); ax.set_ylim(-300_000, 1_300_000)114    ax.legend(loc="upper right", fontsize=7.5)115    savefig(fig, "ch25/ch25-fig-residuelle-sensibilite.pdf")116117118def main():119    setup()120    S = cas.synthese()121    fig_valeur_unitaire(S); fig_carte(S); fig_lotissement(S); fig_residuelle(S)122    print("Figures ch25 écrites dans", ROOT / "assets/figures/ch25")123124125if __name__ == "__main__":126    main()127
Files · a58cc978dd49eee20fac414bfb0e69af32f1a29e
Menu

Theme