# π Hedonic Housing Price Models for the United States
### A Multi-Method Comparison of Parametric, Quantile, and Machine Learning Approaches
**UQO Working Paper No. 3**
[](https://www.python.org/)
[](https://www.latex-project.org/)
[](https://xgboost.readthedocs.io/)
[](https://lightgbm.readthedocs.io/)
[](https://shap.readthedocs.io/)
[](https://www.statsmodels.org/)
[](https://duckdb.org/)
[](paper/main.pdf)
[](#-data)
[](#-data)
[](#-figures)
[-2E8B57)](paper/references.bib)
[](#-verification--integrity)
[](#-citation)
**Author : [Simon-Pierre Boucher](mailto:contact@spboucher.ai)**
DΓ©partement des sciences administratives β UniversitΓ© du QuΓ©bec en Outaouais (UQO)
π§ **contact@spboucher.ai**
---
## π Table of Contents
- [Overview](#-overview)
- [Key Findings](#-key-findings)
- [Repository Structure](#-repository-structure)
- [Data](#-data)
- [Methodology at a Glance](#-methodology-at-a-glance)
- [The Analysis Pipeline](#-the-analysis-pipeline)
- [Installation & Setup](#-installation--setup)
- [Reproducing the Results](#-reproducing-the-results)
- [Building the Paper](#-building-the-paper)
- [Figures](#-figures)
- [Results Summary](#-results-summary)
- [Verification & Integrity](#-verification--integrity)
- [Project History & Provenance](#-project-history--provenance)
- [Citation](#-citation)
- [Contact & License](#-contact--license)
---
## π Overview
This repository contains the complete research compendium β analysis code, LaTeX
source, publication-ready figures, and machine-readable result tables β for
**UQO Working Paper No. 3**, which compares three frameworks for hedonic housing
valuation on a single large dataset of **788,842 active Zillow listings** spanning
all 50 U.S. states and the District of Columbia:
| Framework | Question it answers | Headline result |
|---|---|---|
| **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 |
| **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) |
| **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) |
The paper's core methodological contribution is a systematic study of
**spatial leakage** in hedonic model evaluation: random train/test splits let
geographically proximate listings appear on both sides of the split, inflating
performance metrics. Three complementary designs quantify the damage β a 10-state
geographic holdout, a 6-stage feature-ablation cascade, and a latitude/longitude
augmentation experiment.
> β οΈ **Interpretation caveat.** All estimates are *listing-price capitalization
> gradients* β conditional associations between attributes and *asking* prices.
> They are not causal willingness-to-pay parameters and not transaction-price
> implicit prices.
---
## π Key Findings
1. **Geography does the heavy lifting in OLS.** Moving from 4 Census-region
dummies to state fixed effects to 886 ZIP3 fixed effects raises out-of-sample
$R^2$ from 0.630 β 0.678 β 0.725 β a 9.5 pp gain from spatial granularity alone.
2. **Strong, stable residual spatial autocorrelation.** Moran's $I$ on OLS
residuals averages **0.2745** (SD 0.0076) across three independent 5,000-listing
subsamples (row-standardized KNN weights, $k = 8$; all $p < 0.001$).
3. **Mean effects mask distributional heterogeneity.** The garage gradient is
**10Γ larger** at $\tau = 0.10$ than at $\tau = 0.90$ ($z = 28.9$); the pool
premium only emerges above the median; the age penalty is concentrated in
lower-priced homes.
4. **Random validation flatters machine learning.** XGBoost reaches $R^2 = 0.833$
under a random 80/20 split but only **0.425β0.547** when 10 entire states
(438,315 listings) are held out.
5. **Geographic features can *hurt* generalization.** Removing all geographic
features *improves* geographic-holdout $R^2$ from 0.425 to **0.519**, while
adding raw lat/lon coordinates boosts random $R^2$ (+3.7 pp) but *degrades*
geographic holdout. Region dummies memorize training-set price levels.
6. **SHAP rankings are model-stable but not structural.** Spearman rank
correlations of mean $|\phi|$ across XGBoost / LightGBM / Random Forest range
from **0.89 to 0.99**, with the same six features on top β yet these remain
predictive decompositions, not implicit prices.
7. **Imputation matters.** The lot-size gradient **triples** (0.018 β 0.059) when
state-median-imputed lot sizes are dropped β a warning for hedonic work on
scraped listing data.
---
## π Repository Structure
```
wp3_uqo/
βββ README.md β you are here
βββ AUDIT.md β forensic audit of the original project archive
βββ CHANGES.md β every move/refactor/fix made during restructuring
βββ requirements.txt β pinned Python dependencies (validated 2026-08-05)
β
βββ data/ β NOT in the Git repo (3.2 GB β see "Data" below)
β βββ raw/
β β βββ us_housing.duckdb # raw Zillow extract (839,313 Γ 116 + 4 aux tables)
β βββ processed/
β βββ analytical_sample.pkl # final sample, 788,842 Γ 68 engineered columns
β βββ model_data.pkl # standardized X (788,842 Γ 63), y, OLS fit, residuals
β βββ shap_data.pkl # TreeSHAP values (10,000 Γ 62) + explanation sample
β
βββ src/wp3/ # shared library code
β βββ config.py # paths (relative, WP3_ROOT-overridable), constants,
β β # feature-block definitions, holdout states, seeds
β βββ data.py # loaders + 62-regressor design-matrix builder
β βββ models.py # XGBoost training helper (dual validation schemes)
β βββ plotting.py # journal figure style + human-readable variable labels
β
βββ scripts/ # numbered pipeline entry points
β βββ 01_spatial_robustness.py # ZIP3 FE OLS Β· Moran's I Β· XGB Β± geography Β· ablation
β βββ 02_qr_imputation.py # QR stability Β· inter-quantile Wald Β· imputation Β·
β β # winsorization sensitivity
β βββ 03_make_figures.py # regenerates all 11 figures + verification stats
β βββ 04_export_tables.py # exports paper tables β results/tables/*.csv
β
βββ figures/ # all 11 paper figures (PNG, 300 dpi, unified style)
β
βββ results/
β βββ *.pkl β NOT in the Git repo (up to 437 MB)
β βββ tables/ # 13 CSVs mirroring the paper's tables (in repo)
β
βββ paper/ # LaTeX source
βββ main.tex # preamble + metadata + \input skeleton
βββ references.bib # 37 BibTeX entries (natbib + apalike)
βββ sections/ # one file per section (IMRaD + robustness)
β βββ titlepage.tex βββ introduction.tex βββ literature.tex
β βββ data.tex βββ methodology.tex βββ results.tex
β βββ robustness.tex βββ discussion.tex βββ limitations.tex
β βββ conclusion.tex
βββ appendix/appendix.tex # extra figures, full OLS table, reproducibility checklist
βββ Makefile # latexmk targets (all / clean / distclean)
βββ uq_logo.jpg
βββ main.pdf # compiled paper β 52 pages, zero warnings
```
---
## πΎ Data
### What the data are
- **Source:** Zillow active for-sale listings, 2025β2026 snapshot.
- **Raw:** 839,313 residential properties Γ 116 variables, stored in a DuckDB
database (`properties`, `price_history`, `schools`, `tax_history`, `meta_columns`).
- **Analytical sample:** 788,842 listings after five sequential filters
(valid price \$10Kβ\$10M, living area 200β20,000 sqft, 1β10 beds/baths,
valid coordinates, US states + DC only β 6.0 % attrition).
- **68 engineered columns:** log transforms, age & ageΒ², ratios, amenity dummies,
neighborhood scores, market-status flags, 6 interaction terms, and simplified
categoricals (roof / construction / foundation / region).
### Why the data are not in this Git repository
GitHub rejects files above 100 MB. The DuckDB file is **1.6 GB** and the processed
pickles reach **867 MB**, so `data/` and `results/*.pkl` are excluded via
`.gitignore`. The canonical copies live in the local research archive:
| Artifact | Size | Location |
|---|---|---|
| `us_housing.duckdb` | 1.6 GB | `data/raw/` (local) |
| `analytical_sample.pkl` | 457 MB | `data/processed/` (local) |
| `model_data.pkl` | 867 MB | `data/processed/` (local) |
| `ml_results.pkl` | 437 MB | `results/` (local) |
| `shap_data.pkl`, `qr_results.pkl`, `extended_results.pkl`, `v3_*.pkl` | < 10 MB each | `results/` (local; small ones could be added on request) |
π¬ **Data access:** available on request (subject to Zillow Terms of Service) β
**contact@spboucher.ai**.
---
## π§ͺ Methodology at a Glance
| Component | Specification |
|---|---|
| Dependent variable | $\ln(\text{listing price})$ |
| OLS | 62 regressors, standardized + unstandardized variants, **HC3** robust SE |
| Fixed effects | Census region (baseline) β state (50) β ZIP3 (886) |
| Quantile regression | statsmodels `QuantReg`, $\tau \in \{0.10,\ldots,0.90\}$, 150,000-obs subsample, 10-seed stability check |
| Inter-quantile tests | Wald $z$-tests on $\hat\beta(0.10)-\hat\beta(0.90)$ |
| XGBoost | 1,000 trees, depth 8, lr 0.05, subsample/colsample 0.8, early stopping 50 |
| LightGBM / RF / Ridge / Lasso / Elastic Net | benchmark suite |
| 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) |
| Ablation | 6 nested feature sets: structural β +lot β +amenities β +neighborhood β +market β full |
| Interpretation | TreeSHAP on 10,000 test obs; cross-model Spearman stability (XGB/LGB/RF) |
| Spatial diagnostics | Moran's $I$, row-standardized KNN ($k=8$), 3 Γ 5,000-obs subsamples, 999 permutations |
| Sensitivity | state-median-imputation drop-out, lot-size winsorization (p99.5), Bike Score cap |
---
## π The Analysis Pipeline
```
us_housing.duckdb (raw, 839,313 listings)
β
β [sample construction & feature engineering β original script lost;
β the stored analytical_sample.pkl is the canonical artifact]
βΌ
analytical_sample.pkl (788,842 Γ 68)
β
ββ [estimation β original scripts lost; stored artifacts are canonical]
β βββ model_data.pkl (standardized X, OLS fit, residuals)
β βββ qr_results.pkl (QR params at 5 quantiles)
β βββ ml_results.pkl (fitted XGB/LGBM + test predictions)
β βββ shap_data.pkl (TreeSHAP values, 10,000 Γ 62)
β βββ extended_results.pkl (unstandardized OLS, geo holdout, RF)
β βββ v3_shap_stability.pkl (XGB/LGB/RF SHAP rank correlations)
β
ββ scripts/01_spatial_robustness.py βββΊ results/v3_spatial_results.pkl
ββ scripts/02_qr_imputation.py ββββββββΊ results/v3_qr_imputation_results.pkl
ββ scripts/03_make_figures.py βββββββββΊ figures/fig1β¦fig11 (+ verification)
ββ scripts/04_export_tables.py ββββββββΊ results/tables/*.csv
β
βΌ
paper/main.tex βββΊ main.pdf (52 p.)
```
The bracketed upstream stages were produced before this restructuring by scripts
that no longer exist (see [Project History](#-project-history--provenance)); their
outputs are preserved and every downstream number is verified against them.
---
## βοΈ Installation & Setup
**Prerequisites:** Python β₯ 3.11, a TeX Live distribution (with `newtx`, `natbib`,
`booktabs`, `threeparttable`), and ~4 GB of free disk for the data artifacts.
```bash
git clone https://github.com/spboucher-ai/wp3-hedonic-housing-us.git
cd wp3-hedonic-housing-us
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
```
Then place the data artifacts (request them at **contact@spboucher.ai**) into
`data/raw/`, `data/processed/`, and `results/` as shown in
[Repository Structure](#-repository-structure).
All paths resolve relative to the repository root. To point the code at another
location, set the environment variable:
```bash
export WP3_ROOT=/path/to/artifacts
```
---
## βΆοΈ Reproducing the Results
| Step | Command | Runtime* | Needs |
|---|---|---|---|
| Spatial & robustness suite | `python scripts/01_spatial_robustness.py` | ~1β2 h (trains 16 XGBoost models) | `analytical_sample.pkl` |
| QR & sensitivity suite | `python scripts/02_qr_imputation.py` | ~1β3 h (12 quantile regressions on 150k obs) | `model_data.pkl`, DuckDB |
| **All 11 figures** | `python scripts/03_make_figures.py` | ~5 min | processed pickles |
| Single figures | `python scripts/03_make_figures.py --figs fig2 fig4` | secondsβminutes | idem |
| **CSV table exports** | `python scripts/04_export_tables.py` | ~2 min | result pickles |
\* Apple Silicon, 32 GB RAM; scripts use all cores (`n_jobs=-1`).
Every script prints **verification statistics** comparing recomputed values with
the numbers reported in the paper (see [Verification](#-verification--integrity)).
---
## π Building the Paper
```bash
cd paper
latexmk # pdflatex ΓN + bibtex, via Makefile/latexmkrc defaults
# or: make # same thing
# or: make clean / make distclean
```
- Output: `paper/main.pdf` β **52 pages**, compiles with **zero errors, zero
undefined references/citations**.
- Figures are pulled from `../figures/` via `\graphicspath`.
- Bibliography: `paper/references.bib` (37 entries), `natbib` + `apalike`.
- Metadata (title, abstract, keywords R31/C21/C45/C52, version) is centralized in
`main.tex` `\WP*` macros.
---
## πΌ Figures
All figures are regenerated from the stored artifacts by `scripts/03_make_figures.py`
in a unified publication style (serif typography matched to the paper's `newtx`
text font, muted colorblind-safe palette, no chart junk, 300 dpi).
| # | File | Content | Built from |
|---|---|---|---|
| 1 | `fig1_price_distribution.png` | Price & log-price histograms with medians | `analytical_sample.pkl` |
| 2 | `fig2_ols_diagnostics.png` | Residuals-vs-fitted, Q-Q, density, scale-location | `model_data.pkl` |
| 3 | `fig3_quantile_coefficients.png` | QR coefficient paths vs. OLS benchmark (8 panels) | `qr_results.pkl` + `model_data.pkl` |
| 4 | `fig4_model_comparison.png` | Predicted vs. actual: OLS / XGBoost / LightGBM | `ml_results.pkl` |
| 5 | `fig5_shap_summary.png` | SHAP beeswarm, top 20 features | `shap_data.pkl` |
| 6 | `fig6_shap_importance.png` | Mean \|SHAP\| bar chart, top 20 | `shap_data.pkl` |
| 7 | `fig7_geographic_prices.png` | 50,000-listing national price map (lat/lon, viridis) | `analytical_sample.pkl` |
| 8 | `fig8_regional_prices.png` | Log-price boxplots by Census region (with N) | `analytical_sample.pkl` |
| 9 | `fig9_marginal_effects.png` | Bivariate scatters + binned means (6 attributes) | `analytical_sample.pkl` |
| 10 | `fig10_shap_dependence.png` | SHAP dependence, 8 key features | `shap_data.pkl` |
| 11 | `fig11_shap_interactions.png` | SHAP for interaction terms (waterfrontΓsqft, poolΓSouth) | `shap_data.pkl` |
---
## π Results Summary
**Predictive performance (log-price scale):**
| Model | Random $R^2$ | Geo-holdout $R^2$ | RMSE (random) |
|---|---:|---:|---:|
| OLS (62 regressors) | 0.630 | < 0 | 0.491 |
| OLS + State FE | 0.678 | β | 0.459 |
| OLS + ZIP3 FE | 0.725 | β | 0.423 |
| Random Forest | 0.784 | 0.542 | 0.375 |
| LightGBM | 0.809 | β | 0.353 |
| **XGBoost** | **0.833** | **0.547** | **0.330** |
| XGBoost, no geographic features | 0.830 | **0.519** | β |
| XGBoost + lat/lon | 0.870 | 0.464 | β |
| XGBoost (ablation variant, full 62) | 0.833 | 0.425 | β |
**Ablation cascade (XGBoost):** structural 0.498 β +lot 0.546 β +amenities 0.638 β
+neighborhood **0.814 (+17.6 pp)** β +market 0.824 β full 0.833 (random $R^2$).
**Machine-readable versions** of these and all other paper tables are in
[`results/tables/`](results/tables/) (13 CSV files: OLS coefficients, QR paths,
inter-quantile Wald tests, stability CVs, Moran's I, imputation/winsorization
sensitivity, SHAP importance & cross-model correlations, ZIP3 FE, ablation).
---
## β
Verification & Integrity
The restructuring included a systematic audit reconciling the manuscript against
the stored result artifacts. Highlights:
- **Exact matches** β OLS residual diagnostics (skewness 0.18, kurtosis 5.55,
JarqueβBera 217,078), test-set $R^2$ (OLS 0.6301, XGBoost 0.8330,
LightGBM 0.8088), Moran's $I$ subsample values and mean/SD, QR coefficients at
all five quantiles, and the OLS reference line (0.312) in Figure 3.
- **End-to-end reproduction** β re-running the refactored Moran's $I$ pipeline
reproduces the stored values to 4 decimal places (0.2742 / 0.2839 / 0.2652).
- **Manuscript corrections** β six internal inconsistencies were found and fixed
(wrong KNN $k$ in the text, mislabeled inter-quantile sign convention,
winsorization description not matching the actual procedure, a duplicated table
row, mixed in/out-of-sample $R^2$ in the summary matrix). Every fix aligns the
text to the stored results and is itemized in [`CHANGES.md`](CHANGES.md) Β§4.
**No scientific result was altered.**
- **Completed placeholders** β 13 `[TODO]` table cells in the original manuscript
were filled with the exact values from the result pickles.
See [`AUDIT.md`](AUDIT.md) for the full audit and [`CHANGES.md`](CHANGES.md) for
the change log.
---
## π° Project History & Provenance
- The project was completed in **May 2026** in a flat working folder
(`immo-wp3-spb-20260519`), then restructured into this compendium on
**2026-08-05**. The original archive is preserved untouched.
- The scripts that built the analytical sample and estimated the primary models
(OLS/QR/ML/SHAP) were **lost** prior to restructuring; only two robustness
scripts survived and were refactored into `scripts/01` and `scripts/02`. The
stored pickles are the canonical record of those stages and every downstream
number is validated against them.
- Five paper figures had been deleted from the archive; they were rebuilt from the
stored data, and subsequently **all 11 figures** were regenerated in a unified
journal style.
- Three earlier monolithic drafts (`paper_hedonic_pricing{,_v2,_v3}.tex`) precede
the sectioned `wp3/` source that became `paper/`.
---
## π Citation
If you use this code, the figures, or the results, please cite:
```bibtex
@techreport{boucher2026hedonic,
author = {Boucher, Simon-Pierre},
title = {Hedonic Housing Price Models for the United States:
A Multi-Method Comparison of Parametric, Quantile,
and Machine Learning Approaches},
institution = {Universit\'e du Qu\'ebec en Outaouais,
D\'epartement des sciences administratives},
type = {Working Paper},
number = {3},
year = {2026},
month = {May}
}
```
---
## π¬ Contact & License
**Simon-Pierre Boucher**
DΓ©partement des sciences administratives
UniversitΓ© du QuΓ©bec en Outaouais β Gatineau (QuΓ©bec), Canada
π§ **contact@spboucher.ai** Β· π simon-pierre.boucher@uqo.ca
Β© 2026 Simon-Pierre Boucher. Analysis code is shared for **replication and review**
purposes. The underlying Zillow data are subject to Zillow's Terms of Service and
are **not redistributed** in this repository; the paper (`paper/main.pdf`) is a
working paper β please cite rather than redistribute.
Built with Python Β· XGBoost Β· LightGBM Β· statsmodels Β· SHAP Β· DuckDB Β· LaTeX β reproducible from stored artifacts end to end.