SPB Git

spb/wp11_uqo

Public

UQO Working Paper No. 11 — Half a million prices, twenty models: a systematic assessment of hedonic specifications.

This looks like a research repository (LaTeX, bibliography, figures). Preview Research Mode· enable it with research: true on the API.

History
NameLast commitUpdated
data WP11 — Half a Million Prices, Twenty Models: full research project
docs révision scientifique complète (editorial board, 2026-10-17)
figures révision scientifique complète (editorial board, 2026-10-17)
paper révision scientifique complète (editorial board, 2026-10-17)
results révision scientifique complète (editorial board, 2026-10-17)
scripts révision scientifique complète (editorial board, 2026-10-17)
src révision scientifique complète (editorial board, 2026-10-17)
.gitignore harmonisation standard UQO WP (2026-10-17)
README.md révision scientifique complète (editorial board, 2026-10-17)
requirements.txt WP11 — Half a Million Prices, Twenty Models: full research project
README.md source

# 🏇 Half a Million Prices, Twenty Models

UQO Working Paper No. 11 — A Systematic Assessment of Hedonic Specifications and Estimation Methods for the Quebec Housing Market, 2021–2026

Paper Python Data Models Reproducible Institution Author

TL;DR — A controlled horse race of 20 hedonic models on 514,212 Quebec sales (2021–2026) with assessor-grade structural attributes: 6 functional forms × a time-FE ladder × a spatial-FE ladder × 6 estimation methods, all scored on the same price-level scoreboard (one retransformation rule for every model) under two holdouts (random 80/20 and forward-in-time), with municipality-clustered bootstrap intervals and Diebold–Mariano-type tests on every comparison. Functional form is second-order (1.5 pp of MdAPE); spatial controls are first-order (~10 pp), and a ~1 km grid overfits when used alone but becomes the best linear specification once nested in municipalities; gradient boosting wins under random validation (14.4% vs 17.6% for the best linear model, −3.2 pp [−4.7, −1.9]), a gain that survives parcel- and building-grouped holdouts. Under the forward-in-time split (train < 2025, test 2025–26) every model degrades and the gap shrinks to 0.5 pp and to insignificance — but the decomposition shows this is a level (staleness) effect: re-levelled, boosting still leads by 2.9 pp. Random cross-validation hides the staleness cost; it does not reverse the ranking (the reversal reported in v1.0 was an artefact of an asymmetric retransformation, corrected in v1.1).


# 📌 Status

Item Value
Version 1.1 (October 2026) — revised 2026-10-17 by the editorial-board review (see docs/); v1.0 = August 2026
SSRN 7626698 — v1.0 uploaded 2026-10-17; v1.1 PDF to be uploaded as a revision
Journal not yet submitted
Code & data reproducible from scripts/ (see Quick start); raw parquet snapshot not redistributed

# 📖 Table of contents

  1. Status
  2. What this paper does
  3. Headline results
  4. Repository layout
  5. Quick start
  6. The pipeline, step by step
  7. Data
  8. Methodology
  9. Figures & tables inventory
  10. Limitations
  11. Citation
  12. Author & contact
  13. Notes

# 🎯 What this paper does

Fifty years after Rosen (1974), applied hedonic practice still rests on specification choices — functional form, temporal controls, spatial controls, estimation method — that are made by convention and rarely stress-tested jointly. This paper runs the debate as a controlled experiment: twenty models organised along four design axes, one attribute set, one scoreboard, two holdouts, half a million sales.

Axis Models Question
A. Functional form linear, semi-log, log-log, Box–Cox (λ̂ by profile likelihood), quadratics, cubic splines does curvature matter?
B. Time effects none / year / quarter / month FE how fine must the time dummies be?
C. Spatial controls none / municipality / ~5.5 km grid / ~1.1 km grid FE how fine can location FE get before overfitting?
D. Estimation method OLS, ridge (CV), random forest, gradient boosting (± coordinates), spatial k-NN comparables what do the machines actually buy?

Every model sees the same sales and the same attributes (floor area, lot, age, storeys, units, class, physical link). Accuracy is measured on price levels (MdAPE, MAPE, RMSE_ln, R²_ln) — log models are Duan-retransformed, Box–Cox models inverted — under a random 80/20 split and a forward-in-time split (train < 2025, test 2025–26, with carry-forward time effects). Extensions compare the constant-quality monthly indices and the implicit age / floor-area profiles implied by the competing forms, learning curves, and accuracy by market segment.

# 🏆 Headline results

All MdAPE in %, median retransformation rule for every model (v1.1 scoreboard).

Object Random 80/20 holdout Forward-in-time holdout
Functional-form range (A1–A6) 17.6 (splines) – 19.2 (quadratics) 20.4 (linear) – 23.3
Splines vs. semi-log −1.4 pp [−1.8, −1.1] −1.4 pp [−2.3, −0.8]
Data-driven Box–Cox exponent λ̂ = 0.25 (0.35 on a fine grid), −0.5 pp vs. semi-log, worse on MAPE —
Dropping spatial FE (C1 vs C2) 28.8 vs 19.2 (+9.6 pp) 29.0 vs 23.3
~5.5 km grid vs. municipality FE (C3 vs C2) 18.7 vs 19.2 — not significant (−0.45 [−1.07, +0.37]) 24.3 vs 23.3
~1.1 km grid alone (C4) 21.0 — overfits (+1.8 pp [+1.2, +2.3]) 25.3
~1.1 km grid nested in municipalities (C4′) 17.3 — best linear specification 21.6
Dropping time FE (B1 vs B3) 22.0 vs 19.2 29.3 vs 23.3
Gradient boosting (D4) vs. best linear (A6) 14.4 vs 17.6, −3.2 pp [−4.7, −1.9], DM t = −3.6 21.2 vs 21.7, −0.5 pp [−1.7, +0.6], n.s.
Random forest (D3) 14.6 21.3
Boosting without coordinates (D5) 23.2 26.2
Parcel- / building-grouped holdout, D4 vs A6 14.5 vs 17.6 / 14.6 vs 17.8 (gap unchanged) —
Staleness cost (forward − same-period random) — D4 +7.5 pp, A6 +4.7, A1 +3.3
Median log level error, forward split — D4 +0.18, A6 +0.16, A1 +0.10
Re-levelled forward MdAPE — D4 13.9 vs A6 16.8 (gap 2.9 ≈ random 3.5)
Boosting lead over splines by test quarter — 1.5 pp (2025Q1) → 0.0 (2026Q3)
Monthly indices, four linear forms agree within 2.5 index points
Boosted imputation index, July 2026 160.5 vs 168.6–171.0 for the linear forms
Boosted age profile vs. quadratic OLS same shape, 0.10–0.13 log points flatter from age 20

Practical synthesis: a semi-log with splines, quarter effects and spatial fixed effects nested from municipality down to a fine grid is within three points of the frontier under random validation and indistinguishable from it in deployment. Supply the price level from an index or a frequent refit, spend the flexibility budget on the spatial surface, and validate against the future, not a shuffle of the past.

# 🗂 Repository layout

text
wp11_uqo/
├── README.md
├── requirements.txt
├── .gitignore
├── data/
│   ├── raw/                  # transactions_700k_avec_registre_foncier.parquet — NOT in git (see data/raw/README.md)
│   └── processed/            # estimation sample (generated by step 01)
├── src/wp11/                 # reusable package
│   ├── config.py             # paths, thresholds, grid sizes, seeds
│   ├── sample.py             # sample construction & variable definitions
│   ├── models.py             # the harness: 20 models, 2 splits, scoreboard, Box–Cox profile
│   └── plotstyle.py          # journal-calibre matplotlib style
├── scripts/                  # the pipeline, in order
│   ├── 01_build_sample.py    # 745,119 → 514,212 sales
│   ├── 02_horserace.py       # 20 models × 2 splits + Box–Cox profile
│   ├── 03_extensions.py      # indices, implicit-price profiles, learning curves, segments
│   ├── 04_make_figures.py    # 9 figures
│   └── 05_make_tables.py     # 7 LaTeX tables
├── results/
│   ├── reproduced/           # every CSV the scripts emit (scoreboards, Box–Cox, index, profiles, …)
│   └── tables/               # LaTeX tables input by the paper
├── figures/                  # 9 publication figures (PNG, 300 dpi)
└── paper/                    # main.tex + sections/ + references.bib → main.pdf

# 🚀 Quick start

bash
python3 -m pip install -r requirements.txt

# place the parquet snapshot in data/raw/ (see data/raw/README.md), then:
python3 scripts/01_build_sample.py         # 745,119 → 514,212 sales
python3 scripts/02_horserace.py            # 20 models × 2 splits + Box–Cox profile
python3 scripts/03_extensions.py           # indices, profiles, learning curves, segments
python3 scripts/04_make_figures.py         # 9 figures
python3 scripts/05_make_tables.py          # 7 LaTeX tables

cd paper && make                           # compile main.pdf (latexmk)

Runtime ≈ 10 minutes on an Apple-silicon laptop (the boosting and forest fits dominate); peak RAM ≈ 8 GB.

# 🔬 The pipeline, step by step

Step Script What it does Output
01 01_build_sample.py residential use codes, roll match ≤ 50 m / score ≥ 150, price ≥ $50k, floor area 20–2,000 m², known year built, 1/99 % trims within sale year; builds grid cells data/processed/, results/reproduced/sample_counts.csv, summary_stats.csv
02 02_horserace.py fits the 20 models on each split, retransforms predictions to price levels, scores MdAPE/MAPE/RMSE_ln/R²_ln; Box–Cox profile likelihood horserace_random.csv, horserace_temporal.csv, boxcox.csv
03 03_extensions.py constant-quality monthly indices by form, implicit ln-price profiles (quadratic OLS vs boosting), learning curves, accuracy by segment index.csv, profiles.csv, learning.csv, segments.csv
04 04_make_figures.py the 10 figures figures/fig_*.png
05 05_make_tables.py the 16 LaTeX tables results/tables/*.tex
06 06_robustness.py cluster-bootstrap CIs + clustered DM tests, same-period and by-quarter decomposition, level re-levelling, leakage decomposition, parcel- and building-grouped races, nested spatial FE, fine Box–Cox grid, boosting hyper-parameter sensitivity (run after 02; ≈ 20 min) inference_*.csv, pairs.csv, same_period.csv, by_quarter.csv, relevel.csv, leakage.csv, horserace_group_*.csv, nested_fe.csv, boxcox_fine.csv, hgb_sensitivity.csv

# 🗃 Data

745,119 Quebec residential-market transactions (January 2021 – July 2026) matched at the parcel level to the municipal assessment roll in force at the sale date (median match distance 0.6 m; same snapshot as WP 10). The roll supplies the structural descriptors missing from transaction registries: floor area, lot area, year built, units, storeys, physical configuration, use code. Sample cascade (paper §3): residential use codes → high-confidence match → price ≥ $50,000 → plausible floor area → known year built → 1/99 % trims ⇒ 514,212 sales (348,253 single-family, 75,734 condominiums, 74,909 plexes, 10,349 cottages, 4,881 mobile homes) over 1,115 municipalities, 5,255 cells of ~5.5 km and 30,548 cells of ~1.1 km. Mean price $440,207 (median $397,375); 86 further dwellings of undetermined class. One fifth of sales are repeat sales of the same parcel and 30% share exact coordinates with another sale (92% of condominiums). The raw parquet (100 MB, transaction microdata) is not redistributed.

# 🔬 Methodology

  1. One attribute set, one sample for every model (src/wp11/sample.py).
  2. Axis A — six functional forms on a municipality + quarter FE backbone; Box–Cox λ by profile likelihood. Every transformed-scale prediction is inverted with the same median rule (plain inversion); Duan smearing is computed for every model and reported as a robustness scoreboard (src/wp11/models.py).
  3. Axis B / C — time-FE ladder (none → month) and spatial-FE ladder (none → ~1.1 km grid) on the quadratic semi-log backbone.
  4. Axis D — OLS, cross-validated ridge, random forest, gradient boosting with and without coordinates, spatial k-NN comparables, all on the identical attribute set.
  5. Two holdouts — random 80/20 and forward-in-time (train < 2025, test 2025–26, time effects carried forward), plus parcel- and building-grouped holdouts; metrics on price levels; municipality-clustered bootstrap intervals and clustered Diebold–Mariano tests (src/wp11/inference.py); level/skill decomposition of the forward error.
  6. Extensions — implied monthly indices, implicit-price profiles, learning curves, segment accuracy (scripts/03_extensions.py).
  7. Robustness — retransformation rule, leakage-free holdouts, nested spatial FE, fine Box–Cox grid, boosting hyper-parameters (scripts/06_robustness.py).

# 🖼 Figures & tables inventory

Figure Content
fig_forms Axis A — functional form, MdAPE on the random holdout
fig_time Axis B — time effects
fig_space Axis C — spatial controls (overfitting at ~1 km)
fig_methods Axis D — estimation methods on the identical attribute set
fig_generalization the generalization gap: random vs forward-in-time MdAPE, all 20 models
fig_index constant-quality monthly price indices implied by competing forms
fig_profiles implicit ln-price profiles: quadratic OLS vs gradient boosting
fig_learning learning curves: MdAPE against training-set size
fig_segments MdAPE by market segment (random holdout)
fig_horizon forward-in-time MdAPE by test quarter (distance from the training window)

Tables (results/tables/*.tex): summary statistics, scoreboard random holdout, scoreboard forward-in-time holdout, inference (bootstrap CIs + DM tests), targeted pairs, Box–Cox profile, generalization gap, same-period / level decomposition, by-quarter accuracy, learning curves, segments, median vs. Duan retransformation, grouped holdouts, seen/unseen leakage decomposition, nested spatial FE, boosting hyper-parameters.

# ⚠️ Limitations

  • Sales below $50k are absent from the source registry; residual non-arm's-length transfers may survive the screens.
  • 2021–2026 spans a boom, a rate correction and a recovery — level statistics are period-specific; the forward-in-time test is deliberately demanding.
  • Attributes are those of the assessment roll (no interior quality, renovations or listing text); the ML advantage measured here is the value of flexible spatial learning on that attribute set, not of richer data.
  • Tree ensembles use fixed, conventional hyper-parameters; four materially different configurations (including early stopping on a time-ordered validation set) leave the ranking unchanged (paper §7.5), but no linear model with a continuous spatial surface (kriging, bivariate splines) is estimated as a competitor.
  • The forward-in-time test covers one regime (the 2025–26 recovery); the level re-levelling uses the test-set median ratio (an oracle upper bound for what a published index would deliver).

# 📝 Citation

bibtex
@techreport{boucher2026horserace,
  author      = {Boucher, Simon-Pierre},
  title       = {Half a Million Prices, Twenty Models: A Systematic Assessment
                 of Hedonic Specifications and Estimation Methods for the
                 Quebec Housing Market, 2021--2026},
  institution = {Universit\'e du Qu\'ebec en Outaouais,
                 D\'epartement des sciences administratives},
  type        = {Working Paper},
  number      = {11},
  year        = {2026},
  month       = {October},
  note        = {Version 1.1}
}

# 👤 Author & contact

Simon-Pierre Boucher — Département des sciences administratives, Université du Québec en Outaouais (UQO), Gatineau, QC. 📧 simon-pierre.boucher@uqo.ca

# 🗒 Notes

  • Numbering: UQO Working Paper No. 11 belongs to this paper (August 2026). Two other manuscripts briefly carried the label "WP 11": Hedonic House Pricing with Language-Model and Vision Features (now WP 14) and Does the Sales Comparison Approach Actually Work? (now WP 16).
  • Cover page, metadata and layout follow Recherche-UQO/HARMONISATION.md (2026-10-17).
  • Revision 2026-10-17 (v1.1): full editorial-board review. Scientific report in docs/REVIEW-REPORT.md, audit trail (every changed number, old → new) in docs/CHANGELOG-REVISION.md; pre-revision PDF kept as docs/main_pre-revision-20261017.pdf (git tag pre-revision-20261017). Main correction: v1.0 applied Duan smearing to the linear log models but not to the tree ensembles; under a uniform rule the forward-in-time "reversal" disappears (the gap shrinks to 0.5 pp, n.s.) and the paper now decomposes the forward error into a common level (staleness) component and intact relative skill.

Clone wp11_uqo

Anonymous clone and fetch. Pushing requires the owner's personal access token.

ZIP tar.gz snapshot of main
Files · main
Menu

Theme