SPB Git

spb/wp3_uqo Public

UQO Working Paper No. 3 — Hedonic housing price models for the US: parametric, quantile, and machine-learning approaches.

TeX 77.8% Python 22.1%
3.8 KB · 113 lines python
Raw Blame History
1# Author: Simon-Pierre Boucher — contact@spboucher.ai2#3"""Publication style shared by all paper figures.45Design goals (housing/urban economics journal standard):6  - serif typography consistent with the paper's newtx text font;7  - muted, colorblind-safe palette (no default-Stata / default-matplotlib look);8  - no chart junk: top/right spines removed, light dotted y-grid only;9  - human-readable variable labels instead of raw column names;10  - panel titles "(a) ..." left-aligned, no redundant figure-level titles11    (the LaTeX caption carries the title).12"""1314import matplotlib.pyplot as plt1516# Muted palette17PRIMARY = "#3B6B9B"      # deep steel blue (marks, bars, boxes)18PRIMARY_LIGHT = "#7FA5C6"  # scatter clouds19ACCENT = "#A63E38"       # muted brick red (reference lines, binned means)20NEUTRAL = "#6E6E6E"      # gray for zero lines / secondary elements21CMAP_SEQ = "Blues"       # densities (hexbin)22CMAP_DIV = "coolwarm"    # SHAP dependence coloring23DPI = 3002425# Human-readable labels for model variables (used in axis labels and SHAP plots)26LABELS = {27    "ln_sqft": "Log(Living Area)",28    "ln_lot": "Log(Lot Size)",29    "ln_hoa": "Log(HOA Fee)",30    "ln_price": "Log(Price)",31    "bedrooms": "Bedrooms",32    "bathrooms": "Bathrooms",33    "age": "Property Age",34    "age_sq": "Property Age$^2$",35    "stories": "Stories",36    "bath_per_bed": "Baths per Bedroom",37    "sqft_per_bed": "Sqft per Bedroom",38    "parking_spaces": "Parking Spaces",39    "luxury_score": "Luxury Score",40    "walk_score": "Walk Score",41    "bike_score": "Bike Score",42    "transit_score": "Transit Score",43    "school_count": "School Count",44    "avg_school_rating": "Avg. School Rating",45    "nearest_school_distance": "Nearest School Dist.",46    "property_tax_rate": "Property Tax Rate",47    "has_pool": "Pool",48    "has_spa": "Spa",49    "has_basement": "Basement",50    "has_fireplace": "Fireplace",51    "has_garage": "Garage",52    "on_waterfront": "Waterfront",53    "has_central_air": "Central Air",54    "has_forced_air": "Forced Air",55    "has_hardwood": "Hardwood Floors",56    "is_condo": "Condo",57    "has_hoa": "HOA",58    "tag_new_construction": "New Construction",59    "tag_foreclosure": "Foreclosure",60    "region_Northeast": "Region: Northeast",61    "region_South": "Region: South",62    "region_West": "Region: West",63    "region_Midwest": "Region: Midwest",64    "sqft_x_age": "Sqft $\\times$ Age",65    "pool_x_south": "Pool $\\times$ South",66    "waterfront_x_sqft": "Waterfront $\\times$ Log(sqft)",67    "basement_x_north": "Basement $\\times$ North",68    "condo_x_walkscore": "Condo $\\times$ Walk Score",69    "age_x_luxury": "Age $\\times$ Luxury",70}717273def label(var: str) -> str:74    """Human-readable label for a model variable (falls back to the raw name)."""75    return LABELS.get(var, var)767778def apply_paper_style() -> None:79    """Set the rcParams used for every figure in the paper."""80    plt.rcParams.update({81        "font.family": "serif",82        "mathtext.fontset": "dejavuserif",83        "font.size": 13,84        "axes.titlesize": 14,85        "axes.labelsize": 13.5,86        "legend.fontsize": 12,87        "xtick.labelsize": 12,88        "ytick.labelsize": 12,89        # Clean journal look90        "axes.spines.top": False,91        "axes.spines.right": False,92        "axes.linewidth": 0.9,93        "axes.edgecolor": "#333333",94        "xtick.direction": "out",95        "ytick.direction": "out",96        "axes.grid": True,97        "axes.grid.axis": "y",98        "grid.linestyle": ":",99        "grid.linewidth": 0.7,100        "grid.alpha": 0.45,101        "grid.color": "#999999",102        "legend.frameon": False,103        "figure.dpi": 100,104        "savefig.dpi": DPI,105        "savefig.bbox": "tight",106        "savefig.facecolor": "white",107    })108109110def panel_title(ax, text: str) -> None:111    """Left-aligned panel title, e.g. '(a) OLS'."""112    ax.set_title(text, loc="left", fontweight="regular", pad=8)113