SPB Git
6.4 KB · 105 lines python
1#!/usr/bin/env python32"""Chapitre 17 — figures : cascade d'ajustements (G0404), dispersion des paires, prix bruts vs ajustés, tornade de sensibilité.3Usage (racine du dépôt) : PYTHONPATH=analysis/python python3 analysis/python/figures/ch17_figures.py"""4from __future__ import annotations5import sys, pathlib6import numpy as np7import matplotlib.pyplot as plt8from matplotlib.ticker import FuncFormatter9sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1]))10sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1] / "cases"))11from traite.style import setup, palette, savefig, LARGEUR, fmt_dollars12import ch17_ajustements as c171314def fig_cascade(r):15    c = r["cascade_G0404"]16    fig, ax = plt.subplots(figsize=(LARGEUR, 3.7))17    n = len(c); x = np.arange(n)18    bases = c["base_apres"].values; adj = c["ajustement"].values19    for i in range(n):20        if i == 0 or i == n - 1:21            ax.bar(x[i], bases[i], color=palette.cat[0] if i == 0 else palette.brand, width=0.7)22            ax.text(x[i], bases[i] + 900, fmt_dollars(bases[i]), ha="center", va="bottom", fontsize=7.5)23        elif adj[i] == 0:24            ax.plot([x[i] - 0.35, x[i] + 0.35], [bases[i], bases[i]], color=palette.ink2, lw=1.2)25            ax.text(x[i], bases[i] + 900, "0", ha="center", va="bottom", fontsize=7, color=palette.ink2)26        else:27            bottom = min(bases[i - 1], bases[i])28            couleur = palette.cat[2] if adj[i] > 0 else palette.cat[1]29            ax.bar(x[i], abs(adj[i]), bottom=bottom, color=couleur, width=0.7)30            lab = ("+" if adj[i] > 0 else "\u2212") + f"{abs(adj[i]):,.0f}".replace(",", " ")31            if adj[i] > 0:32                ax.text(x[i], bases[i] + 900, lab, ha="center", va="bottom", fontsize=7.5)33            else:34                ax.text(x[i], bases[i] - 900, lab, ha="center", va="top", fontsize=7.5)35        if 0 < i < n:36            ax.plot([x[i - 1] + 0.35, x[i] - 0.35], [bases[i - 1], bases[i - 1]], color=palette.ink2, lw=0.6)37    ax.set_ylim(495000, 558000); ax.set_xticks(x)38    ax.set_xticklabels(c["etape"], rotation=35, ha="right", fontsize=7.5)39    ax.yaxis.set_major_formatter(FuncFormatter(fmt_dollars)); ax.set_ylabel("Base après l'étape")40    savefig(fig, "ch17/ch17-fig-cascade-g0404.pdf")4142def fig_paires(r):43    fig, axes = plt.subplots(1, 2, figsize=(LARGEUR, 3.3), sharey=True)44    for ax, attr, titre in zip(axes, ("garage", "sous_sol"), ("Garage (une place de plus)", "Sous-sol fini")):45        p = r[f"paires_{attr}"].sort_values("diff_temps").reset_index(drop=True)46        rs = r[f"resume_{attr}"]; im = r[f"implicite_{attr}"]47        y = p["diff_temps"].values; x = np.arange(len(y))48        ax.axhline(0, color=palette.ink2, lw=0.6)49        ax.axhspan(rs["moyenne"] - 1.96 * rs["erreur_type"], rs["moyenne"] + 1.96 * rs["erreur_type"], color=palette.cat[0], alpha=0.12, lw=0)50        ax.axhline(rs["moyenne"], color=palette.cat[0], lw=1.4, label="moyenne des paires (± 1,96 erreur-type)")51        ax.axhline(rs["mediane"], color=palette.cat[0], lw=1.0, ls=":", label="médiane des paires")52        ax.axhline(im["au_prix_median_paires"], color=palette.cat[1], lw=1.4, ls="--", label="prix implicite hédonique")53        ax.scatter(x, y, s=22, color=palette.ink, zorder=3)54        ax.set_title(f"{titre} — n = {rs['n']} paires", fontsize=9)55        ax.set_xticks([]); ax.set_xlabel("Paires classées par écart croissant")56        ax.yaxis.set_major_formatter(FuncFormatter(fmt_dollars))57        ax.set_ylim(-160000, 160000)58    axes[0].set_ylabel("Écart de prix « avec » − « sans » (date alignée)")59    axes[0].legend(loc="upper left", fontsize=7)60    savefig(fig, "ch17/ch17-fig-paires.pdf")6162def fig_bruts_ajustes(r):63    b = r["base"]; a = b["adj"]; rec = r["reconciliation"]64    fig, ax = plt.subplots(figsize=(LARGEUR, 3.2))65    y = np.arange(len(a))[::-1]66    for yi, (_, row) in zip(y, a.iterrows()):67        ax.plot([row["prix_vente"], row["prix_ajuste"]], [yi, yi], color=palette.grid, lw=2.2, zorder=1)68    ax.scatter(a["prix_vente"], y, s=42, facecolor="white", edgecolor=palette.cat[0], linewidth=1.6, zorder=3, label="prix de vente")69    ax.scatter(a["prix_ajuste"], y, s=42, color=palette.cat[0], zorder=4, label="prix ajusté (grille hédonique)")70    w = rec["poids_inverse_brut"]71    for yi, (_, row), wi in zip(y, a.iterrows(), w):72        ax.text(max(row["prix_vente"], row["prix_ajuste"]) + 4000, yi, f"brut {row['ajust_brut_pct']*100:.0f} % · poids {wi:.2f}".replace(".", ","), va="center", fontsize=7.2, color=palette.ink2)73    vp = rec["inverse_brut"]["valeur_ponderee"]; pred = b["prediction_hedonique"]74    ax.axvline(vp, color=palette.brand, lw=1.3, ls="-")75    ax.text(vp, len(a) - 0.45, "valeur pondérée\n" + fmt_dollars(vp), ha="center", va="bottom", fontsize=7.3, color=palette.brand)76    ax.axvline(pred, color=palette.cat[1], lw=1.3, ls="--")77    ax.text(pred, len(a) - 0.45, "prédiction hédonique\n" + fmt_dollars(pred), ha="center", va="bottom", fontsize=7.3, color=palette.cat[1])78    ax.set_yticks(y); ax.set_yticklabels(a["id_vente"]); ax.set_ylim(-0.7, len(a) + 0.6)79    ax.set_xlim(490000, 720000); ax.xaxis.set_major_formatter(FuncFormatter(fmt_dollars))80    ax.legend(loc="upper center", bbox_to_anchor=(0.5, -0.14), ncol=2, fontsize=7.3)81    savefig(fig, "ch17/ch17-fig-bruts-ajustes.pdf")8283def fig_sensibilite(r):84    s = r["sensibilite"]; ref = float(s.iloc[0]["bas"])85    s = s.iloc[1:].copy(); s = s.sort_values("etendue")86    fig, ax = plt.subplots(figsize=(LARGEUR, 3.6))87    y = np.arange(len(s))88    ax.barh(y, s["haut"] - s["bas"], left=s["bas"], color=palette.cat[0], alpha=0.85, height=0.62)89    ax.axvline(ref, color=palette.ink, lw=1.0)90    for yi, (_, row) in zip(y, s.iterrows()):91        ax.text(row["bas"] - 1500, yi, fmt_dollars(row["bas"]), ha="right", va="center", fontsize=6.8, color=palette.ink2)92        ax.text(row["haut"] + 1500, yi, fmt_dollars(row["haut"]), ha="left", va="center", fontsize=6.8, color=palette.ink2)93    ax.set_yticks(y); ax.set_yticklabels(s["scenario"], fontsize=7.4)94    ax.set_xlim(530000, 615000); ax.xaxis.set_major_formatter(FuncFormatter(fmt_dollars))95    ax.set_xlabel("Valeur pondérée (poids inverse de l'ajustement brut) ; trait vertical : référence " + fmt_dollars(ref))96    savefig(fig, "ch17/ch17-fig-sensibilite.pdf")9798def main():99    setup(); r = c17.tout()100    fig_cascade(r); fig_paires(r); fig_bruts_ajustes(r); fig_sensibilite(r)101    print("figures écrites dans assets/figures/ch17/")102103if __name__ == "__main__":104    main()105
Files · bd29a9d04d8eeed63095d8e5215d7c874a35ce53
Menu

Theme