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%
21.5 KB · 443 lines markdown
Rendered Raw Blame History
1<div align="center">23# 🏠 Hedonic Housing Price Models for the United States45### A Multi-Method Comparison of Parametric, Quantile, and Machine Learning Approaches67**UQO Working Paper No. 3**89[![Python](https://img.shields.io/badge/Python-3.11%2B-3776AB?logo=python&logoColor=white)](https://www.python.org/)10[![LaTeX](https://img.shields.io/badge/LaTeX-pdflatex%20%2B%20BibTeX-008080?logo=latex&logoColor=white)](https://www.latex-project.org/)11[![XGBoost](https://img.shields.io/badge/XGBoost-3.2.0-EB5E28)](https://xgboost.readthedocs.io/)12[![LightGBM](https://img.shields.io/badge/LightGBM-4.6.0-9ACD32)](https://lightgbm.readthedocs.io/)13[![SHAP](https://img.shields.io/badge/SHAP-0.48.0-blueviolet)](https://shap.readthedocs.io/)14[![statsmodels](https://img.shields.io/badge/statsmodels-0.14.6-4051B5)](https://www.statsmodels.org/)15[![DuckDB](https://img.shields.io/badge/DuckDB-1.5.2-FFF000?logo=duckdb&logoColor=black)](https://duckdb.org/)1617[![Paper](https://img.shields.io/badge/Paper-60%20pages%20PDF-B31B1B?logo=adobeacrobatreader&logoColor=white)](paper/main.pdf)18[![Sample](https://img.shields.io/badge/Sample-788%2C842%20listings-2B5C8A)](#-data)19[![Coverage](https://img.shields.io/badge/Coverage-50%20states%20%2B%20DC-2B5C8A)](#-data)20[![Figures](https://img.shields.io/badge/Figures-11%20publication--ready-A63E38)](#-figures)21[![References](https://img.shields.io/badge/References-70%20verified%20(OpenAlex%2FDOI)-2E8B57)](paper/references.bib)22[![Verified](https://img.shields.io/badge/Results-verified%20against%20stored%20artifacts-2E8B57)](#-verification--integrity)23[![Status](https://img.shields.io/badge/Status-Working%20Paper%20v1.1-orange)](#-citation)2425**Author : [Simon-Pierre Boucher](mailto:contact@spboucher.ai)**26Département des sciences administratives — Université du Québec en Outaouais (UQO)27📧 **contact@spboucher.ai**2829</div>3031---3233## 📑 Table of Contents3435- [Overview](#-overview)36- [Key Findings](#-key-findings)37- [Repository Structure](#-repository-structure)38- [Data](#-data)39- [Methodology at a Glance](#-methodology-at-a-glance)40- [The Analysis Pipeline](#-the-analysis-pipeline)41- [Installation & Setup](#-installation--setup)42- [Reproducing the Results](#-reproducing-the-results)43- [Building the Paper](#-building-the-paper)44- [Figures](#-figures)45- [Results Summary](#-results-summary)46- [Verification & Integrity](#-verification--integrity)47- [Project History & Provenance](#-project-history--provenance)48- [Citation](#-citation)49- [Contact & License](#-contact--license)5051---5253## 🔎 Overview5455This repository contains the complete research compendium — analysis code, LaTeX56source, publication-ready figures, and machine-readable result tables — for57**UQO Working Paper No. 3**, which compares three frameworks for hedonic housing58valuation on a single large dataset of **788,842 active Zillow listings** spanning59all 50 U.S. states and the District of Columbia:6061| Framework | Question it answers | Headline result |62|---|---|---|63| **Semi-log OLS** (62 regressors, HC3 SE) | What is the *average* conditional association between an attribute and log listing price? | $R^2 = 0.634$; price-to-area elasticity 0.63 |64| **Quantile regression** ($\tau \in \{0.10, 0.25, 0.50, 0.75, 0.90\}$) | How do attribute gradients *vary across the price distribution*? | Coefficient equality rejected for **11 of 13** variables (inter-quantile Wald tests) |65| **Gradient boosting + SHAP** (XGBoost, LightGBM) | How well can prices be *predicted*, and which features drive predictions? | $R^2 = 0.833$ (random split) vs. **0.425–0.547** (geographic holdout) |6667The paper's core methodological contribution is a systematic study of68**spatial leakage** in hedonic model evaluation: random train/test splits let69geographically proximate listings appear on both sides of the split, inflating70performance metrics. Three complementary designs quantify the damage — a 10-state71geographic holdout, a 6-stage feature-ablation cascade, and a latitude/longitude72augmentation experiment.7374> ⚠️ **Interpretation caveat.** All estimates are *listing-price capitalization75> gradients* — conditional associations between attributes and *asking* prices.76> They are not causal willingness-to-pay parameters and not transaction-price77> implicit prices.7879---8081## 🏆 Key Findings82831. **Geography does the heavy lifting in OLS.** Moving from 4 Census-region84   dummies to state fixed effects to 886 ZIP3 fixed effects raises out-of-sample85   $R^2$ from 0.630 → 0.678 → 0.725 — a 9.5 pp gain from spatial granularity alone.86872. **Strong, stable residual spatial autocorrelation.** Moran's $I$ on OLS88   residuals averages **0.2745** (SD 0.0076) across three independent 5,000-listing89   subsamples (row-standardized KNN weights, $k = 8$; all $p < 0.001$).90913. **Mean effects mask distributional heterogeneity.** The garage gradient is92   **10× larger** at $\tau = 0.10$ than at $\tau = 0.90$ ($z = 28.9$); the pool93   premium only emerges above the median; the age penalty is concentrated in94   lower-priced homes.95964. **Random validation flatters machine learning.** XGBoost reaches $R^2 = 0.833$97   under a random 80/20 split but only **0.425–0.547** when 10 entire states98   (438,315 listings) are held out.991005. **Geographic features can *hurt* generalization.** Removing all geographic101   features *improves* geographic-holdout $R^2$ from 0.425 to **0.519**, while102   adding raw lat/lon coordinates boosts random $R^2$ (+3.7 pp) but *degrades*103   geographic holdout. Region dummies memorize training-set price levels.1041056. **SHAP rankings are model-stable but not structural.** Spearman rank106   correlations of mean $|\phi|$ across XGBoost / LightGBM / Random Forest range107   from **0.89 to 0.99**, with the same six features on top — yet these remain108   predictive decompositions, not implicit prices.1091107. **Imputation matters.** The lot-size gradient **triples** (0.018 → 0.059) when111   state-median-imputed lot sizes are dropped — a warning for hedonic work on112   scraped listing data.113114---115116## 🗂 Repository Structure117118```119wp3_uqo/120├── README.md                      ← you are here121├── AUDIT.md                       ← forensic audit of the original project archive122├── CHANGES.md                     ← every move/refactor/fix made during restructuring123├── requirements.txt               ← pinned Python dependencies (validated 2026-08-05)124125├── data/                          ⚠ NOT in the Git repo (3.2 GB — see "Data" below)126│   ├── raw/127│   │   └── us_housing.duckdb      # raw Zillow extract (839,313 × 116 + 4 aux tables)128│   └── processed/129│       ├── analytical_sample.pkl  # final sample, 788,842 × 68 engineered columns130│       ├── model_data.pkl         # standardized X (788,842 × 63), y, OLS fit, residuals131│       └── shap_data.pkl          # TreeSHAP values (10,000 × 62) + explanation sample132133├── src/wp3/                       # shared library code134│   ├── config.py                  # paths (relative, WP3_ROOT-overridable), constants,135│   │                              #   feature-block definitions, holdout states, seeds136│   ├── data.py                    # loaders + 62-regressor design-matrix builder137│   ├── models.py                  # XGBoost training helper (dual validation schemes)138│   └── plotting.py                # journal figure style + human-readable variable labels139140├── scripts/                       # numbered pipeline entry points141│   ├── 01_spatial_robustness.py   # ZIP3 FE OLS · Moran's I ·  XGB ± geography · ablation142│   ├── 02_qr_imputation.py        # QR stability · inter-quantile Wald · imputation ·143│   │                              #   winsorization sensitivity144│   ├── 03_make_figures.py         # regenerates all 11 figures + verification stats145│   └── 04_export_tables.py        # exports paper tables → results/tables/*.csv146147├── figures/                       # all 11 paper figures (PNG, 300 dpi, unified style)148149├── results/150│   ├── *.pkl                      ⚠ NOT in the Git repo (up to 437 MB)151│   └── tables/                    # 13 CSVs mirroring the paper's tables (in repo)152153└── paper/                         # LaTeX source154    ├── main.tex                   # preamble + metadata + \input skeleton155    ├── references.bib             # 37 BibTeX entries (natbib + apalike)156    ├── sections/                  # one file per section (IMRaD + robustness)157    │   ├── titlepage.tex          ├── introduction.tex   ├── literature.tex158    │   ├── data.tex               ├── methodology.tex    ├── results.tex159    │   ├── robustness.tex         ├── discussion.tex     ├── limitations.tex160    │   └── conclusion.tex161    ├── appendix/appendix.tex      # extra figures, full OLS table, reproducibility checklist162    ├── Makefile                   # latexmk targets (all / clean / distclean)163    ├── uq_logo.jpg164    └── main.pdf                   # compiled paper — 52 pages, zero warnings165```166167---168169## 💾 Data170171### What the data are172173- **Source:** Zillow active for-sale listings, 2025–2026 snapshot.174- **Raw:** 839,313 residential properties × 116 variables, stored in a DuckDB175  database (`properties`, `price_history`, `schools`, `tax_history`, `meta_columns`).176- **Analytical sample:** 788,842 listings after five sequential filters177  (valid price \$10K–\$10M, living area 200–20,000 sqft, 1–10 beds/baths,178  valid coordinates, US states + DC only — 6.0 % attrition).179- **68 engineered columns:** log transforms, age & age², ratios, amenity dummies,180  neighborhood scores, market-status flags, 6 interaction terms, and simplified181  categoricals (roof / construction / foundation / region).182183### Why the data are not in this Git repository184185GitHub rejects files above 100 MB. The DuckDB file is **1.6 GB** and the processed186pickles reach **867 MB**, so `data/` and `results/*.pkl` are excluded via187`.gitignore`. The canonical copies live in the local research archive:188189| Artifact | Size | Location |190|---|---|---|191| `us_housing.duckdb` | 1.6 GB | `data/raw/` (local) |192| `analytical_sample.pkl` | 457 MB | `data/processed/` (local) |193| `model_data.pkl` | 867 MB | `data/processed/` (local) |194| `ml_results.pkl` | 437 MB | `results/` (local) |195| `shap_data.pkl`, `qr_results.pkl`, `extended_results.pkl`, `v3_*.pkl` | < 10 MB each | `results/` (local; small ones could be added on request) |196197📬 **Data access:** available on request (subject to Zillow Terms of Service) —198**contact@spboucher.ai**.199200---201202## 🧪 Methodology at a Glance203204| Component | Specification |205|---|---|206| Dependent variable | $\ln(\text{listing price})$ |207| OLS | 62 regressors, standardized + unstandardized variants, **HC3** robust SE |208| Fixed effects | Census region (baseline) → state (50) → ZIP3 (886) |209| Quantile regression | statsmodels `QuantReg`, $\tau \in \{0.10,\ldots,0.90\}$, 150,000-obs subsample, 10-seed stability check |210| Inter-quantile tests | Wald $z$-tests on $\hat\beta(0.10)-\hat\beta(0.90)$ |211| XGBoost | 1,000 trees, depth 8, lr 0.05, subsample/colsample 0.8, early stopping 50 |212| LightGBM / RF / Ridge / Lasso / Elastic Net | benchmark suite |213| Validation | random 80/20 (seed 42) **and** 10-state geographic holdout (CA NY TX FL OH CO NC WA IL GA — 438,315 test obs) |214| Ablation | 6 nested feature sets: structural → +lot → +amenities → +neighborhood → +market → full |215| Interpretation | TreeSHAP on 10,000 test obs; cross-model Spearman stability (XGB/LGB/RF) |216| Spatial diagnostics | Moran's $I$, row-standardized KNN ($k=8$), 3 × 5,000-obs subsamples, 999 permutations |217| Sensitivity | state-median-imputation drop-out, lot-size winsorization (p99.5), Bike Score cap |218219---220221## 🔁 The Analysis Pipeline222223```224us_housing.duckdb  (raw, 839,313 listings)225226        │  [sample construction & feature engineering — original script lost;227        │   the stored analytical_sample.pkl is the canonical artifact]228229analytical_sample.pkl  (788,842 × 68)230231        ├─ [estimation — original scripts lost; stored artifacts are canonical]232        │      ├── model_data.pkl        (standardized X, OLS fit, residuals)233        │      ├── qr_results.pkl        (QR params at 5 quantiles)234        │      ├── ml_results.pkl        (fitted XGB/LGBM + test predictions)235        │      ├── shap_data.pkl         (TreeSHAP values, 10,000 × 62)236        │      ├── extended_results.pkl  (unstandardized OLS, geo holdout, RF)237        │      └── v3_shap_stability.pkl (XGB/LGB/RF SHAP rank correlations)238239        ├─ scripts/01_spatial_robustness.py ──► results/v3_spatial_results.pkl240        ├─ scripts/02_qr_imputation.py ───────► results/v3_qr_imputation_results.pkl241        ├─ scripts/03_make_figures.py ────────► figures/fig1…fig11 (+ verification)242        └─ scripts/04_export_tables.py ───────► results/tables/*.csv243244245                                          paper/main.tex ──► main.pdf (52 p.)246```247248The bracketed upstream stages were produced before this restructuring by scripts249that no longer exist (see [Project History](#-project-history--provenance)); their250outputs are preserved and every downstream number is verified against them.251252---253254## ⚙️ Installation & Setup255256**Prerequisites:** Python ≥ 3.11, a TeX Live distribution (with `newtx`, `natbib`,257`booktabs`, `threeparttable`), and ~4 GB of free disk for the data artifacts.258259```bash260git clone https://github.com/spboucher-ai/wp3-hedonic-housing-us.git261cd wp3-hedonic-housing-us262263python3 -m venv .venv264source .venv/bin/activate265pip install -r requirements.txt266```267268Then place the data artifacts (request them at **contact@spboucher.ai**) into269`data/raw/`, `data/processed/`, and `results/` as shown in270[Repository Structure](#-repository-structure).271272All paths resolve relative to the repository root. To point the code at another273location, set the environment variable:274275```bash276export WP3_ROOT=/path/to/artifacts277```278279---280281## ▶️ Reproducing the Results282283| Step | Command | Runtime* | Needs |284|---|---|---|---|285| Spatial & robustness suite | `python scripts/01_spatial_robustness.py` | ~1–2 h (trains 16 XGBoost models) | `analytical_sample.pkl` |286| QR & sensitivity suite | `python scripts/02_qr_imputation.py` | ~1–3 h (12 quantile regressions on 150k obs) | `model_data.pkl`, DuckDB |287| **All 11 figures** | `python scripts/03_make_figures.py` | ~5 min | processed pickles |288| Single figures | `python scripts/03_make_figures.py --figs fig2 fig4` | seconds–minutes | idem |289| **CSV table exports** | `python scripts/04_export_tables.py` | ~2 min | result pickles |290291\* Apple Silicon, 32 GB RAM; scripts use all cores (`n_jobs=-1`).292293Every script prints **verification statistics** comparing recomputed values with294the numbers reported in the paper (see [Verification](#-verification--integrity)).295296---297298## 📄 Building the Paper299300```bash301cd paper302latexmk          # pdflatex ×N + bibtex, via Makefile/latexmkrc defaults303# or: make       # same thing304# or: make clean / make distclean305```306307- Output: `paper/main.pdf`**52 pages**, compiles with **zero errors, zero308  undefined references/citations**.309- Figures are pulled from `../figures/` via `\graphicspath`.310- Bibliography: `paper/references.bib` (37 entries), `natbib` + `apalike`.311- Metadata (title, abstract, keywords R31/C21/C45/C52, version) is centralized in312  `main.tex` `\WP*` macros.313314---315316## 🖼 Figures317318All figures are regenerated from the stored artifacts by `scripts/03_make_figures.py`319in a unified publication style (serif typography matched to the paper's `newtx`320text font, muted colorblind-safe palette, no chart junk, 300 dpi).321322| # | File | Content | Built from |323|---|---|---|---|324| 1 | `fig1_price_distribution.png` | Price & log-price histograms with medians | `analytical_sample.pkl` |325| 2 | `fig2_ols_diagnostics.png` | Residuals-vs-fitted, Q-Q, density, scale-location | `model_data.pkl` |326| 3 | `fig3_quantile_coefficients.png` | QR coefficient paths vs. OLS benchmark (8 panels) | `qr_results.pkl` + `model_data.pkl` |327| 4 | `fig4_model_comparison.png` | Predicted vs. actual: OLS / XGBoost / LightGBM | `ml_results.pkl` |328| 5 | `fig5_shap_summary.png` | SHAP beeswarm, top 20 features | `shap_data.pkl` |329| 6 | `fig6_shap_importance.png` | Mean \|SHAP\| bar chart, top 20 | `shap_data.pkl` |330| 7 | `fig7_geographic_prices.png` | 50,000-listing national price map (lat/lon, viridis) | `analytical_sample.pkl` |331| 8 | `fig8_regional_prices.png` | Log-price boxplots by Census region (with N) | `analytical_sample.pkl` |332| 9 | `fig9_marginal_effects.png` | Bivariate scatters + binned means (6 attributes) | `analytical_sample.pkl` |333| 10 | `fig10_shap_dependence.png` | SHAP dependence, 8 key features | `shap_data.pkl` |334| 11 | `fig11_shap_interactions.png` | SHAP for interaction terms (waterfront×sqft, pool×South) | `shap_data.pkl` |335336---337338## 📊 Results Summary339340**Predictive performance (log-price scale):**341342| Model | Random $R^2$ | Geo-holdout $R^2$ | RMSE (random) |343|---|---:|---:|---:|344| OLS (62 regressors) | 0.630 | < 0 | 0.491 |345| OLS + State FE | 0.678 | — | 0.459 |346| OLS + ZIP3 FE | 0.725 | — | 0.423 |347| Random Forest | 0.784 | 0.542 | 0.375 |348| LightGBM | 0.809 | — | 0.353 |349| **XGBoost** | **0.833** | **0.547** | **0.330** |350| XGBoost, no geographic features | 0.830 | **0.519** | — |351| XGBoost + lat/lon | 0.870 | 0.464 | — |352| XGBoost (ablation variant, full 62) | 0.833 | 0.425 | — |353354**Ablation cascade (XGBoost):** structural 0.498 → +lot 0.546 → +amenities 0.638 →355+neighborhood **0.814 (+17.6 pp)** → +market 0.824 → full 0.833 (random $R^2$).356357**Machine-readable versions** of these and all other paper tables are in358[`results/tables/`](results/tables/) (13 CSV files: OLS coefficients, QR paths,359inter-quantile Wald tests, stability CVs, Moran's I, imputation/winsorization360sensitivity, SHAP importance & cross-model correlations, ZIP3 FE, ablation).361362---363364## ✅ Verification & Integrity365366The restructuring included a systematic audit reconciling the manuscript against367the stored result artifacts. Highlights:368369- **Exact matches** — OLS residual diagnostics (skewness 0.18, kurtosis 5.55,370  Jarque–Bera 217,078), test-set $R^2$ (OLS 0.6301, XGBoost 0.8330,371  LightGBM 0.8088), Moran's $I$ subsample values and mean/SD, QR coefficients at372  all five quantiles, and the OLS reference line (0.312) in Figure 3.373- **End-to-end reproduction** — re-running the refactored Moran's $I$ pipeline374  reproduces the stored values to 4 decimal places (0.2742 / 0.2839 / 0.2652).375- **Manuscript corrections** — six internal inconsistencies were found and fixed376  (wrong KNN $k$ in the text, mislabeled inter-quantile sign convention,377  winsorization description not matching the actual procedure, a duplicated table378  row, mixed in/out-of-sample $R^2$ in the summary matrix). Every fix aligns the379  text to the stored results and is itemized in [`CHANGES.md`](CHANGES.md) §4.380  **No scientific result was altered.**381- **Completed placeholders** — 13 `[TODO]` table cells in the original manuscript382  were filled with the exact values from the result pickles.383384See [`AUDIT.md`](AUDIT.md) for the full audit and [`CHANGES.md`](CHANGES.md) for385the change log.386387---388389## 🕰 Project History & Provenance390391- The project was completed in **May 2026** in a flat working folder392  (`immo-wp3-spb-20260519`), then restructured into this compendium on393  **2026-08-05**. The original archive is preserved untouched.394- The scripts that built the analytical sample and estimated the primary models395  (OLS/QR/ML/SHAP) were **lost** prior to restructuring; only two robustness396  scripts survived and were refactored into `scripts/01` and `scripts/02`. The397  stored pickles are the canonical record of those stages and every downstream398  number is validated against them.399- Five paper figures had been deleted from the archive; they were rebuilt from the400  stored data, and subsequently **all 11 figures** were regenerated in a unified401  journal style.402- Three earlier monolithic drafts (`paper_hedonic_pricing{,_v2,_v3}.tex`) precede403  the sectioned `wp3/` source that became `paper/`.404405---406407## 📖 Citation408409If you use this code, the figures, or the results, please cite:410411```bibtex412@techreport{boucher2026hedonic,413  author      = {Boucher, Simon-Pierre},414  title       = {Hedonic Housing Price Models for the United States:415                 A Multi-Method Comparison of Parametric, Quantile,416                 and Machine Learning Approaches},417  institution = {Universit\'e du Qu\'ebec en Outaouais,418                 D\'epartement des sciences administratives},419  type        = {Working Paper},420  number      = {3},421  year        = {2026},422  month       = {May}423}424```425426---427428## 📬 Contact & License429430**Simon-Pierre Boucher**431Département des sciences administratives432Université du Québec en Outaouais — Gatineau (Québec), Canada433📧 **contact@spboucher.ai** · 🎓 simon-pierre.boucher@uqo.ca434435© 2026 Simon-Pierre Boucher. Analysis code is shared for **replication and review**436purposes. The underlying Zillow data are subject to Zillow's Terms of Service and437are **not redistributed** in this repository; the paper (`paper/main.pdf`) is a438working paper — please cite rather than redistribute.439440<div align="center">441<sub>Built with Python · XGBoost · LightGBM · statsmodels · SHAP · DuckDB · LaTeX — reproducible from stored artifacts end to end.</sub>442</div>443