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%
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[](https://www.python.org/)10[](https://www.latex-project.org/)11[](https://xgboost.readthedocs.io/)12[](https://lightgbm.readthedocs.io/)13[](https://shap.readthedocs.io/)14[](https://www.statsmodels.org/)15[](https://duckdb.org/)1617[](paper/main.pdf)18[](#-data)19[](#-data)20[](#-figures)21[-2E8B57)](paper/references.bib)22[](#-verification--integrity)23[](#-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)124│125├── 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 sample132│133├── 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 labels139│140├── 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/*.csv146│147├── figures/ # all 11 paper figures (PNG, 300 dpi, unified style)148│149├── results/150│ ├── *.pkl ⚠ NOT in the Git repo (up to 437 MB)151│ └── tables/ # 13 CSVs mirroring the paper's tables (in repo)152│153└── 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)225 │226 │ [sample construction & feature engineering — original script lost;227 │ the stored analytical_sample.pkl is the canonical artifact]228 ▼229analytical_sample.pkl (788,842 × 68)230 │231 ├─ [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)238 │239 ├─ 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/*.csv243 │244 ▼245 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