SPB Git
8.9 KB · 128 lines python
1#!/usr/bin/env python32"""Figures du chapitre 1 (DONNÉES SYNTHÉTIQUES du cas 1, graine 20261031 ; simulations étiquetées).3Sorties (assets/figures/ch01/) :4  ch01-fig-prix-valeur.pdf        nuage prix observé vs valeur estimée (modèle hédonique de référence), comparables du ch. 16 en évidence5  ch01-fig-residus.pdf            distribution des écarts prix/valeur (résidus) et loi normale ajustée6  ch01-fig-recherche.pdf          simulation du modèle de recherche : offres, prix acceptés, délais de vente7  ch01-fig-rosen.pdf              schéma de l'équilibre hédonique de Rosen : enchères, offres et enveloppe des prix8  ch01-fig-sensibilite-sigma.pdf  sensibilité : bornes de l'intervalle à 95 % et probabilité d'un écart > 10 % selon sigma9Exécuter depuis la racine : python3 analysis/python/figures/ch01_figures.py"""10import sys, pathlib, math11ROOT = pathlib.Path(__file__).resolve().parents[3]12sys.path.insert(0, str(ROOT / "analysis/python")); sys.path.insert(0, str(ROOT / "analysis/python/cases"))13import numpy as np14import matplotlib.pyplot as plt15from scipy.stats import norm16from traite.style import setup, palette, savefig, LARGEUR, fmt_dollars, fmt_pct17import ch01_valeur_prix as c11819def fig_prix_valeur(t):20    m = t["modele"]; v = t["ventes"]21    pred = np.exp(m["X"].values @ m["beta"]); obs = v["prix_vente"].values22    comps = t["comparables"]23    fig, ax = plt.subplots(figsize=(LARGEUR, 3.9))24    ax.scatter(pred, obs, s=9, color=palette.cat[0], alpha=0.45, linewidths=0, label="600 ventes synthétiques")25    lo, hi = 150_000, 1_000_00026    ax.plot([lo, hi], [lo, hi], color=palette.ink2, lw=1.0, ls="--", label="Prix = valeur estimée")27    ax.plot([lo, hi], [lo * 1.1, hi * 1.1], color=palette.grid, lw=0.8, ls=":"); ax.plot([lo, hi], [lo * 0.9, hi * 0.9], color=palette.grid, lw=0.8, ls=":")28    ax.text(hi / 1.1 * 0.97, hi * 0.985, "+10 %", ha="right", va="top", fontsize=7.5, color=palette.ink2)29    ax.text(hi * 0.985, hi * 0.9 * 0.985, "−10 %", ha="right", va="top", fontsize=7.5, color=palette.ink2)30    ax.scatter(comps["valeur_estimee"], comps["prix_vente"], s=34, facecolors="none", edgecolors=palette.cat[1], linewidths=1.3, label="Comparables du chapitre 16", zorder=5)31    for _, r in comps.iterrows():32        ax.annotate(r["id_vente"], (r["valeur_estimee"], r["prix_vente"]), xytext=(5, -9), textcoords="offset points", fontsize=7, color=palette.ink)33    p = t["pred"]34    ax.axvline(p["mediane"], color=palette.cat[2], lw=0.9, ls="-.")35    ax.text(p["mediane"] * 1.01, lo * 1.15, "valeur estimée du sujet\n" + fmt_dollars(p["mediane"]), fontsize=7.5, color=palette.ink, ha="left")36    ax.set_xlim(lo, hi); ax.set_ylim(lo, hi)37    ax.xaxis.set_major_formatter(plt.FuncFormatter(fmt_dollars)); ax.yaxis.set_major_formatter(plt.FuncFormatter(fmt_dollars))38    ax.set_xlabel("Valeur estimée $\\hat V = \\exp(\\mathbf{x}'\\hat{\\beta})$ (médiane conditionnelle)"); ax.set_ylabel("Prix de vente observé $P$")39    ax.legend(loc="upper left", fontsize=7.5)40    return savefig(fig, "ch01/ch01-fig-prix-valeur.pdf")4142def fig_residus(t):43    res = t["modele"]["residus"]; sigma = t["modele"]["sigma"]44    ecarts = (np.exp(res) - 1) * 10045    fig, ax = plt.subplots(figsize=(LARGEUR, 3.2))46    bins = np.arange(-30, 32, 2.5)47    ax.hist(ecarts, bins=bins, density=True, color=palette.cat[0], alpha=0.75, edgecolor=palette.surface, linewidth=0.5, label="Écarts observés (600 ventes)")48    x = np.linspace(-30, 30, 400)49    # densité de (exp(e) − 1)·100 avec e ~ N(0, σ²)50    dens = norm.pdf(np.log(1 + x / 100), 0, sigma) / (1 + x / 100) / 10051    ax.plot(x, dens, color=palette.cat[1], lw=1.6, label=f"Loi log-normale ajustée ($\\sigma$ = {sigma:.3f})")52    for q in (-10, 10):53        ax.axvline(q, color=palette.ink2, lw=0.8, ls="--")54    d = t["dispersion"]55    ax.text(-29, ax.get_ylim()[1] * 0.80, f"{d['part_dans_pm10pct']*100:.1f} % des ventes\nà ±10 % de la\nvaleur estimée", ha="left", fontsize=8, color=palette.ink)56    ax.set_xlabel("Écart entre le prix observé et la valeur estimée, $P/\\hat V - 1$ (%)"); ax.set_ylabel("Densité")57    ax.legend(loc="upper right", fontsize=7.5)58    return savefig(fig, "ch01/ch01-fig-residus.pdf")5960def fig_recherche(t):61    s = t["recherche"]; centre = t["pred"]["mediane"]; P = c1.PARAMS_RECHERCHE62    prix_rel = (s["prix"] / centre - 1) * 100; r_rel = (s["prix_reserve"] / centre - 1) * 10063    fig, (a1, a2) = plt.subplots(1, 2, figsize=(LARGEUR, 3.0), gridspec_kw={"width_ratios": [1.5, 1]})64    x = np.linspace(-25, 25, 500)65    a1.plot(x, norm.pdf(np.log(1 + x / 100), 0, P["sigma_o"]) / (1 + x / 100) / 100, color=palette.cat[0], lw=1.5, label="Offres reçues (toutes)")66    a1.hist(prix_rel, bins=np.arange(-5, 25, 1.0), density=True, color=palette.cat[1], alpha=0.7, edgecolor=palette.surface, linewidth=0.4, label="Prix acceptés (simulation)")67    a1.axvline(r_rel, color=palette.ink2, lw=0.9, ls="--")68    a1.text(r_rel + 7.5, a1.get_ylim()[1] * 0.62, f"prix de réserve\n+{r_rel:.1f} %", fontsize=7.5, color=palette.ink)69    a1.set_xlabel("Écart au centre des enchères $m$ (%)"); a1.set_ylabel("Densité"); a1.legend(loc="upper left", fontsize=7)70    delais = s["delai"]; mx = int(min(delais.max(), 24))71    vals = [np.mean(delais == k) for k in range(1, mx + 1)]72    a2.bar(range(1, mx + 1), vals, color=palette.cat[0], width=0.8)73    a2.axvline(s["delai_moyen"], color=palette.ink2, lw=0.9, ls="--")74    a2.text(s["delai_moyen"] + 0.4, max(vals) * 0.9, f"moyenne\n{s['delai_moyen']:.1f} mois", fontsize=7.5, color=palette.ink)75    a2.set_xlabel("Délai de vente (mois)"); a2.set_ylabel("Fréquence"); a2.set_xlim(0.3, mx + 0.7)76    fig.tight_layout(w_pad=1.5)77    return savefig(fig, "ch01/ch01-fig-recherche.pdf")7879def fig_rosen():80    """Schéma : fonctions d'enchère θ(z; u) concaves de deux acheteurs, fonctions d'offre φ(z; π) convexes de deux vendeurs,81    enveloppe p(z) tangente aux deux familles. Formes fonctionnelles déclarées (purement illustratives)."""82    z = np.linspace(0.5, 4.5, 400)83    p = 100 + 60 * np.log(z)                      # enveloppe (fonction de prix hédonique)84    dp = 60 / z85    fig, ax = plt.subplots(figsize=(LARGEUR, 3.4))86    ax.plot(z, p, color=palette.ink, lw=2.0, label="Fonction de prix $p(z)$ (enveloppe)")87    for i, z0 in enumerate((1.3, 3.2)):88        p0, s0 = 100 + 60 * math.log(z0), 60 / z089        theta = p0 + s0 * (z - z0) - 18 * (z - z0) ** 2          # enchère d'un acheteur : concave, tangente en z090        phi = p0 + s0 * (z - z0) + 18 * (z - z0) ** 2            # offre d'un vendeur : convexe, tangente en z091        mask = np.abs(z - z0) < 0.992        ax.plot(z[mask], theta[mask], color=palette.cat[0], lw=1.3, ls="--", label="Enchères des acheteurs $\\theta(z; u)$" if i == 0 else None)93        ax.plot(z[mask], phi[mask], color=palette.cat[1], lw=1.3, ls=":", label="Offres des vendeurs $\\phi(z; \\pi)$" if i == 0 else None)94        ax.scatter([z0], [p0], color=palette.ink, s=22, zorder=5)95        ax.annotate(f"appariement {i+1}", (z0, p0), xytext=(8, -14), textcoords="offset points", fontsize=7.5, color=palette.ink)96    ax.set_xlabel("Quantité de l'attribut $z$ (p. ex. superficie)"); ax.set_ylabel("Prix")97    ax.set_yticks([]); ax.set_xticks([])98    ax.text(4.45, 100 + 60 * math.log(4.45) - 6, "pente = prix implicite $\\partial p/\\partial z$", ha="right", fontsize=7.5, color=palette.ink2)99    ax.legend(loc="lower right", fontsize=7.5)100    return savefig(fig, "ch01/ch01-fig-rosen.pdf")101102def fig_sensibilite(t):103    sig = np.linspace(0.02, 0.20, 100)104    fig, (a1, a2) = plt.subplots(1, 2, figsize=(LARGEUR, 3.0))105    a1.plot(sig, (np.exp(1.96 * sig) - 1) * 100, color=palette.cat[0], lw=1.6, label="Borne haute (+)")106    a1.plot(sig, (np.exp(-1.96 * sig) - 1) * 100, color=palette.cat[1], lw=1.6, label="Borne basse (−)")107    a1.fill_between(sig, (np.exp(-1.96 * sig) - 1) * 100, (np.exp(1.96 * sig) - 1) * 100, color=palette.cat[0], alpha=0.08)108    s0 = t["modele"]["sigma"]109    a1.axvline(s0, color=palette.ink2, lw=0.8, ls="--"); a1.text(s0 + 0.004, -35, f"cas 1 : $\\sigma$ = {s0:.3f}", fontsize=7.5, color=palette.ink)110    a1.set_xlabel("Dispersion $\\sigma$ (écart-type de $\\ln P$ à $\\mathbf{x}$ donné)"); a1.set_ylabel("Bornes de l'intervalle à 95 % (% de la valeur)")111    a1.legend(loc="upper left", fontsize=7.5)112    for d, col, lab in ((0.05, palette.cat[2], "> 5 %"), (0.10, palette.cat[0], "> 10 %"), (0.20, palette.cat[1], "> 20 %")):113        a2.plot(sig, [c1.proba_ecart(s, d) * 100 for s in sig], color=col, lw=1.6, label=f"écart {lab}")114    a2.axvline(s0, color=palette.ink2, lw=0.8, ls="--")115    a2.set_xlabel("Dispersion $\\sigma$"); a2.set_ylabel("Probabilité qu'un prix s'écarte de la valeur (%)")116    a2.legend(loc="upper left", fontsize=7.5)117    fig.tight_layout(w_pad=1.5)118    return savefig(fig, "ch01/ch01-fig-sensibilite-sigma.pdf")119120def main():121    setup()122    t = c1.tout()123    for f in (fig_prix_valeur(t), fig_residus(t), fig_recherche(t), fig_rosen(), fig_sensibilite(t)):124        print("OK", f)125126if __name__ == "__main__":127    main()128
Files · 2fa255cfbe38460cb046bb32c81cf4f586081ba2
Menu

Theme