SPB Git

spb/wp11_uqo

Public

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

harmonisation standard UQO WP (2026-10-17)

Page titre canonique, hyperref pdftitle/pdfauthor, mots-clés normalisés,
.gitignore du gabarit, README : Status + Notes, courriel uqo.ca.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Simon-Pierre Boucher committed (Oct 17, 2026) parent 16670be Browse files

7 changed files +213 −45

modified .gitignore +14 −1
@@ -1,4 +1,4 @@
1 −# Raw & processed data — transaction microdata are NOT redistributed
1 +# Raw data snapshot — 100 MB parquet (transaction microdata), not redistributed (see data/raw/README.md)
2 2 data/raw/*.parquet
3 3 data/raw/*.csv
4 4 data/processed/*.parquet
@@ -6,16 +6,29 @@ data/processed/*.parquet
6 6 # Python
7 7 __pycache__/
8 8 *.pyc
9 +.pytest_cache/
10 +.venv/
11 +*.egg-info/
9 12
10 13 # LaTeX build artifacts (paper/main.pdf IS committed)
11 14 paper/*.aux
12 15 paper/*.log
13 16 paper/*.out
17 +paper/*.toc
18 +paper/*.lof
19 +paper/*.lot
14 20 paper/*.fls
15 21 paper/*.fdb_latexmk
16 22 paper/*.bbl
17 23 paper/*.blg
24 +paper/*.spl
18 25 paper/*.synctex.gz
26 +paper/*.run.xml
27 +paper/*.bcf
28 +
29 +# Logs
30 +logs/
31 +*.log
19 32
20 33 # macOS
21 34 .DS_Store
modified README.md +177 −31
@@ -1,4 +1,4 @@
1 −<!-- Author: Simon-Pierre Boucher — contact@spboucher.ai -->
1 +<!-- Author: Simon-Pierre Boucher — simon-pierre.boucher@uqo.ca -->
2 2 # 🏇 Half a Million Prices, Twenty Models
3 3
4 4 **UQO Working Paper No. 11** — *A Systematic Assessment of Hedonic Specifications
@@ -6,28 +6,59 @@ and Estimation Methods for the Quebec Housing Market, 2021–2026*
6 6
7 7 [![Paper](https://img.shields.io/badge/paper-PDF%20(23%20p.)-b31b1b?logo=latex&logoColor=white)](paper/main.pdf)
8 8 [![Python](https://img.shields.io/badge/python-3.11%2B-3776AB?logo=python&logoColor=white)](requirements.txt)
9 −[![Data](https://img.shields.io/badge/data-514%2C212%20sales%20·%20roll--matched-4051b5)](data/raw/README.md)
9 +[![Data](https://img.shields.io/badge/data-514%2C212%20sales%20·%20roll--matched%20parquet-4051b5)](data/raw/README.md)
10 10 [![Models](https://img.shields.io/badge/models-20%20·%204%20design%20axes-4051b5)](src/wp11/models.py)
11 11 [![Reproducible](https://img.shields.io/badge/reproducible-end--to--end%20pipeline-2e7d32)](scripts/)
12 12 [![Institution](https://img.shields.io/badge/UQO-D%C3%A9pt.%20des%20sciences%20administratives-16365c)](https://uqo.ca)
13 −[![Author](https://img.shields.io/badge/author-Simon--Pierre%20Boucher-16365c)](mailto:contact@spboucher.ai)
14 −[![Contact](https://img.shields.io/badge/contact-contact%40spboucher.ai-a02020?logo=maildotru&logoColor=white)](mailto:contact@spboucher.ai)
13 +[![Author](https://img.shields.io/badge/author-Simon--Pierre%20Boucher-16365c)](mailto:simon-pierre.boucher@uqo.ca)
15 14
16 15 > **TL;DR** — A controlled horse race of **20 hedonic models** on 514,212 Quebec sales
17 −> with assessor-grade structural attributes: 6 functional forms × time-FE ladder ×
18 −> spatial-FE ladder × 6 estimation methods, all scored on the **same price-level
19 −> scoreboard** (Duan smearing / Box–Cox inversion) under **two holdouts** (random 80/20
20 −> and forward-in-time). Functional form is second-order (±2 pp of MdAPE); spatial
21 −> controls are first-order (~10 pp) but overfit at ~1 km granularity; **gradient
22 −> boosting wins under random validation** (MdAPE ≈ 14.4% vs 16.5% for the best linear
16 +> (2021–2026) with assessor-grade structural attributes: 6 functional forms × a time-FE
17 +> ladder × a spatial-FE ladder × 6 estimation methods, all scored on the **same
18 +> price-level scoreboard** (Duan smearing / Box–Cox inversion) under **two holdouts**
19 +> (random 80/20 and forward-in-time). Functional form is second-order (±2 pp of MdAPE);
20 +> spatial controls are first-order (~10 pp) but overfit at ~1 km granularity; **gradient
21 +> boosting wins under random validation** (MdAPE 14.4% vs 16.5% for the best linear
23 22 > model) — but under the forward-in-time split *every* model degrades and the ML
24 23 > advantage **reverses** (splines 19.1% vs boosting 21.2%, identical R²_ln): random
25 −> cross-validation is the wrong experiment for valuation models. Implied price indices
26 −> and implicit-price profiles agree across forms far more than the accuracy gap suggests.
24 +> cross-validation is the wrong experiment for valuation models.
27 25
28 26 ---
29 27
30 −## 🎯 The four design axes
28 +## 📌 Status
29 +
30 +| Item | Value |
31 +|---|---|
32 +| Version | 1.0 (August 2026) |
33 +| SSRN | [7626698](https://ssrn.com/abstract=7626698) — submitted 2026-10-17 (preliminary upload) |
34 +| Journal | not yet submitted |
35 +| Code & data | reproducible from `scripts/` (see Quick start); raw parquet snapshot not redistributed |
36 +
37 +## 📖 Table of contents
38 +
39 +0. [Status](#-status)
40 +1. [What this paper does](#-what-this-paper-does)
41 +2. [Headline results](#-headline-results)
42 +3. [Repository layout](#-repository-layout)
43 +4. [Quick start](#-quick-start)
44 +5. [The pipeline, step by step](#-the-pipeline-step-by-step)
45 +6. [Data](#-data)
46 +7. [Methodology](#-methodology)
47 +8. [Figures & tables inventory](#-figures--tables-inventory)
48 +9. [Limitations](#-limitations)
49 +10. [Citation](#-citation)
50 +11. [Author & contact](#-author--contact)
51 +12. [Notes](#-notes)
52 +
53 +---
54 +
55 +## 🎯 What this paper does
56 +
57 +Fifty years after Rosen (1974), applied hedonic practice still rests on specification
58 +choices — functional form, temporal controls, spatial controls, estimation method — that
59 +are made by convention and rarely stress-tested jointly. This paper runs the debate as a
60 +**controlled experiment**: twenty models organised along four design axes, one attribute
61 +set, one scoreboard, two holdouts, half a million sales.
31 62
32 63 | Axis | Models | Question |
33 64 |---|---|---|
@@ -36,40 +67,147 @@ and Estimation Methods for the Quebec Housing Market, 2021–2026*
36 67 | **C. Spatial controls** | none / municipality / ~5.5 km grid / ~1.1 km grid FE | how fine can location FE get before overfitting? |
37 68 | **D. Estimation method** | OLS, ridge (CV), random forest, gradient boosting (± coordinates), spatial k-NN comparables | what do the machines actually buy? |
38 69
39 −Every model: same sales, same attributes (floor area, lot, age, storeys, units,
40 −class, physical link), metrics on price levels (MdAPE, MAPE, RMSE_ln, R²_ln),
41 −municipality-clustered design, and **two splits** — random 80/20 and
42 −train < 2025 / test 2025–26 with carry-forward time effects.
70 +Every model sees the same sales and the same attributes (floor area, lot, age, storeys,
71 +units, class, physical link). Accuracy is measured on **price levels** (MdAPE, MAPE,
72 +RMSE_ln, R²_ln) — log models are Duan-retransformed, Box–Cox models inverted — under a
73 +random 80/20 split and a **forward-in-time** split (train < 2025, test 2025–26, with
74 +carry-forward time effects). Extensions compare the constant-quality monthly indices and
75 +the implicit age / floor-area profiles implied by the competing forms, learning curves,
76 +and accuracy by market segment.
77 +
78 +## 🏆 Headline results
79 +
80 +| Object | Random 80/20 holdout | Forward-in-time holdout |
81 +|---|---|---|
82 +| Functional-form range (A1–A6), MdAPE | 16.5 % (splines) – 18.6 % (Box–Cox) | 19.1 % – 21.0 % |
83 +| Data-driven Box–Cox exponent | λ̂ = 0.25, buys nothing out of sample | — |
84 +| Dropping spatial FE (C1 vs C2) | 27.9 % vs 18.1 % (+10 pp) | 26.5 % vs 20.5 % |
85 +| ~1.1 km grid FE (C4) | 19.4 % — **overfits** vs 17.5 % at ~5.5 km | 22.1 % |
86 +| Dropping time FE (B1 vs B3) | 21.1 % vs 18.1 % | 25.4 % vs 20.5 % |
87 +| Gradient boosting (D4) | **14.4 %** (−13 % vs best linear 16.5 %) | 21.2 % |
88 +| Random forest (D3) | 14.6 % | 21.3 % |
89 +| Boosting without coordinates (D5) | 23.2 % | 26.2 % |
90 +| Semi-log + splines (A6) | 16.5 % | **19.1 %** — beats boosting, same R²_ln (0.52) |
91 +| Implied monthly indices across forms | agree within a few index points | |
92 +| Boosted implicit age / area profiles | track quadratic OLS closely | |
93 +
94 +Practical synthesis: a semi-log with splines, quarter effects and spatial FE matched to
95 +market thickness is within two points of the frontier in deployment conditions —
96 +transparent, auditable, fast. Validate against the future, not a shuffle of the past.
43 97
44 98 ## 🗂 Repository layout
45 99
46 100 ```
47 101 wp11_uqo/
48 −├── data/raw/ # snapshot (NOT in git — see data/raw/README.md)
49 −├── src/wp11/ # config, sample, models (the harness), plotstyle
50 −├── scripts/
102 +├── README.md
103 +├── requirements.txt
104 +├── .gitignore
105 +├── data/
106 +│ ├── raw/ # transactions_700k_avec_registre_foncier.parquet — NOT in git (see data/raw/README.md)
107 +│ └── processed/ # estimation sample (generated by step 01)
108 +├── src/wp11/ # reusable package
109 +│ ├── config.py # paths, thresholds, grid sizes, seeds
110 +│ ├── sample.py # sample construction & variable definitions
111 +│ ├── models.py # the harness: 20 models, 2 splits, scoreboard, Box–Cox profile
112 +│ └── plotstyle.py # journal-calibre matplotlib style
113 +├── scripts/ # the pipeline, in order
51 114 │ ├── 01_build_sample.py # 745,119 → 514,212 sales
52 115 │ ├── 02_horserace.py # 20 models × 2 splits + Box–Cox profile
53 116 │ ├── 03_extensions.py # indices, implicit-price profiles, learning curves, segments
54 −│ ├── 04_make_figures.py # 9 journal-calibre figures
117 +│ ├── 04_make_figures.py # 9 figures
55 118 │ └── 05_make_tables.py # 7 LaTeX tables
56 −├── results/{reproduced,tables}/
57 −├── figures/
119 +├── results/
120 +│ ├── reproduced/ # every CSV the scripts emit (scoreboards, Box–Cox, index, profiles, …)
121 +│ └── tables/ # LaTeX tables input by the paper
122 +├── figures/ # 9 publication figures (PNG, 300 dpi)
58 123 └── paper/ # main.tex + sections/ + references.bib → main.pdf
59 124 ```
60 125
61 126 ## 🚀 Quick start
62 127
63 128 ```bash
64 −cd wp11_uqo
65 129 python3 -m pip install -r requirements.txt
66 −# place the parquet snapshot in data/raw/ (not distributed), then:
67 −for s in scripts/0*.py; do python3 "$s"; done
68 −cd paper && make
130 +
131 +# place the parquet snapshot in data/raw/ (see data/raw/README.md), then:
132 +python3 scripts/01_build_sample.py # 745,119 → 514,212 sales
133 +python3 scripts/02_horserace.py # 20 models × 2 splits + Box–Cox profile
134 +python3 scripts/03_extensions.py # indices, profiles, learning curves, segments
135 +python3 scripts/04_make_figures.py # 9 figures
136 +python3 scripts/05_make_tables.py # 7 LaTeX tables
137 +
138 +cd paper && make # compile main.pdf (latexmk)
69 139 ```
70 140
71 −Runtime ≈ 10 minutes on an Apple-silicon laptop (the boosting and forest fits
72 −dominate); peak RAM ≈ 8 GB.
141 +Runtime ≈ 10 minutes on an Apple-silicon laptop (the boosting and forest fits dominate);
142 +peak RAM ≈ 8 GB.
143 +
144 +## 🔬 The pipeline, step by step
145 +
146 +| Step | Script | What it does | Output |
147 +|---|---|---|---|
148 +| 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` |
149 +| 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` |
150 +| 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` |
151 +| 04 | `04_make_figures.py` | the 9 figures | `figures/fig_*.png` |
152 +| 05 | `05_make_tables.py` | the 7 LaTeX tables | `results/tables/*.tex` |
153 +
154 +## 🗃 Data
155 +
156 +745,119 Quebec residential-market transactions (January 2021 – July 2026) matched at the
157 +**parcel level** to the municipal assessment roll in force at the sale date (median match
158 +distance 0.6 m; same snapshot as WP 10). The roll supplies the structural descriptors
159 +missing from transaction registries: floor area, lot area, year built, units, storeys,
160 +physical configuration, use code. Sample cascade (paper §3): residential use codes →
161 +high-confidence match → price ≥ $50,000 → plausible floor area → known year built →
162 +1/99 % trims ⇒ **514,212 sales** (348,253 single-family, 75,734 condominiums, 74,909
163 +plexes, 10,349 cottages, 4,881 mobile homes) over 1,115 municipalities, 5,255 cells of
164 +~5.5 km and 30,548 cells of ~1.1 km. Mean price $462,000 (median $399,000). The raw
165 +parquet (100 MB, transaction microdata) is **not redistributed**.
166 +
167 +## 🔬 Methodology
168 +
169 +1. **One attribute set, one sample** for every model (`src/wp11/sample.py`).
170 +2. **Axis A** — six functional forms on a municipality + quarter FE backbone; Box–Cox λ by
171 + profile likelihood; log models retransformed with Duan smearing, Box–Cox inverted
172 + (`src/wp11/models.py`).
173 +3. **Axis B / C** — time-FE ladder (none → month) and spatial-FE ladder (none → ~1.1 km
174 + grid) on the quadratic semi-log backbone.
175 +4. **Axis D** — OLS, cross-validated ridge, random forest, gradient boosting with and
176 + without coordinates, spatial k-NN comparables, all on the identical attribute set.
177 +5. **Two holdouts** — random 80/20 and forward-in-time (train < 2025, test 2025–26, time
178 + effects carried forward); metrics on price levels, design clustered by municipality.
179 +6. **Extensions** — implied monthly indices, implicit-price profiles, learning curves,
180 + segment accuracy (`scripts/03_extensions.py`).
181 +
182 +## 🖼 Figures & tables inventory
183 +
184 +| Figure | Content |
185 +|---|---|
186 +| `fig_forms` | Axis A — functional form, MdAPE on the random holdout |
187 +| `fig_time` | Axis B — time effects |
188 +| `fig_space` | Axis C — spatial controls (overfitting at ~1 km) |
189 +| `fig_methods` | Axis D — estimation methods on the identical attribute set |
190 +| `fig_generalization` | the generalization gap: random vs forward-in-time MdAPE, all 20 models |
191 +| `fig_index` | constant-quality monthly price indices implied by competing forms |
192 +| `fig_profiles` | implicit ln-price profiles: quadratic OLS vs gradient boosting |
193 +| `fig_learning` | learning curves: MdAPE against training-set size |
194 +| `fig_segments` | MdAPE by market segment (random holdout) |
195 +
196 +Tables (`results/tables/*.tex`): summary statistics, scoreboard random holdout,
197 +scoreboard forward-in-time holdout, Box–Cox profile, generalization gap, learning
198 +curves, segments.
199 +
200 +## ⚠️ Limitations
201 +
202 +- Sales below $50k are absent from the source registry; residual non-arm's-length
203 + transfers may survive the screens.
204 +- 2021–2026 spans a boom, a rate correction and a recovery — level statistics are
205 + period-specific; the forward-in-time test is deliberately demanding.
206 +- Attributes are those of the assessment roll (no interior quality, renovations or
207 + listing text); the ML advantage measured here is the value of flexible *spatial*
208 + learning on that attribute set, not of richer data.
209 +- Tree ensembles are tuned lightly (fixed hyper-parameters); the ranking is robust to
210 + the choices tried but not exhaustively optimised.
73 211
74 212 ## 📝 Citation
75 213
@@ -78,7 +216,7 @@ dominate); peak RAM ≈ 8 GB.
78 216 author = {Boucher, Simon-Pierre},
79 217 title = {Half a Million Prices, Twenty Models: A Systematic Assessment
80 218 of Hedonic Specifications and Estimation Methods for the
81 − Quebec Housing Market},
219 + Quebec Housing Market, 2021--2026},
82 220 institution = {Universit\'e du Qu\'ebec en Outaouais,
83 221 D\'epartement des sciences administratives},
84 222 type = {Working Paper},
@@ -92,4 +230,12 @@ dominate); peak RAM ≈ 8 GB.
92 230
93 231 **Simon-Pierre Boucher** — Département des sciences administratives,
94 232 Université du Québec en Outaouais (UQO), Gatineau, QC.
95 −📧 [contact@spboucher.ai](mailto:contact@spboucher.ai)
233 +📧 [simon-pierre.boucher@uqo.ca](mailto:simon-pierre.boucher@uqo.ca)
234 +
235 +## 🗒 Notes
236 +
237 +- Numbering: UQO Working Paper No. 11 belongs to this paper (August 2026). Two other
238 + manuscripts briefly carried the label "WP 11": *Hedonic House Pricing with
239 + Language-Model and Vision Features* (now **WP 14**) and *Does the Sales Comparison
240 + Approach Actually Work?* (now **WP 16**).
241 +- Cover page, metadata and layout follow `Recherche-UQO/HARMONISATION.md` (2026-10-17).
modified paper/.latexmkrc +0 −1
@@ -1,4 +1,3 @@
1 −# Author: Simon-Pierre Boucher — contact@spboucher.ai
2 1 $pdf_mode = 1;
3 2 $pdflatex = 'pdflatex -interaction=nonstopmode -halt-on-error -synctex=1 %O %S';
4 3 $bibtex_use = 2;
modified paper/Makefile +4 −2
@@ -1,9 +1,11 @@
1 −# Author: Simon-Pierre Boucher — contact@spboucher.ai
1 +.PHONY: all clean distclean
2 +
2 3 all:
3 − latexmk -pdf main.tex
4 + latexmk
4 5
5 6 clean:
6 7 latexmk -c
7 8
8 9 distclean:
9 10 latexmk -C
11 + rm -f *.synctex.gz *.run.xml *.bbl *.bcf
modified paper/main.pdf +0 −0

Binary file not shown.

modified paper/main.tex +3 −2
@@ -57,8 +57,7 @@
57 57 Estimation Methods for the Quebec Housing Market, 2021--2026}
58 58 \newcommand{\WPdate}{August 2026}
59 59 \newcommand{\WPversion}{1.0}
60 −\newcommand{\WPkeywords}{Hedonic pricing, Functional form, House price
61 − indexes, Machine learning, Automated valuation, Out-of-sample prediction}
60 +\newcommand{\WPkeywords}{hedonic pricing, functional form, house price indexes, machine learning, automated valuation, out-of-sample prediction}
62 61 \newcommand{\WPjel}{R31, C21, C52, C53}
63 62
64 63 \newcommand{\WPauthor}{Simon-Pierre Boucher}
@@ -98,6 +97,8 @@ index points, and the boosted model's implicit age and floor-area
98 97 profiles track the quadratic OLS closely --- interpretation and
99 98 prediction disagree less than the random-validation gap suggests.}
100 99
100 +\hypersetup{pdftitle={Half a Million Prices, Twenty Models}, pdfauthor={Simon-Pierre Boucher}}
101 +
101 102 \begin{document}
102 103
103 104 \input{sections/titlepage}
modified paper/sections/titlepage.tex +15 −8
@@ -1,17 +1,17 @@
1 −% Author: Simon-Pierre Boucher — contact@spboucher.ai
2 1 % ============================================================================
3 −% Title and abstract pages
2 +% Title Page
4 3 % ============================================================================
5 4 \thispagestyle{empty}
6 5
7 6 \begin{center}
8 7
9 −\includegraphics[width=4cm]{uq_logo.jpg}
8 +% --- Logo ---
9 +\includegraphics[width=3.5cm]{uq_logo.jpg}
10 10
11 11 \vspace{0.6cm}
12 12
13 −{\footnotesize\textsc{Universit\'e du Qu\'ebec en Outaouais}}\\[0.15cm]
14 −{\footnotesize\textsc{D\'epartement des sciences administratives}}
13 +{\footnotesize\textsc{Université du Québec en Outaouais}}\\[0.15cm]
14 +{\footnotesize\textsc{Département des sciences administratives}}
15 15
16 16 \vspace{0.8cm}
17 17
@@ -19,13 +19,17 @@
19 19
20 20 \vspace{1.2cm}
21 21
22 +% --- Title ---
22 23 {\LARGE\bfseries \WPtitle\par}
23 24
24 −\vspace{0.4cm}
25 −{\large\itshape \WPsubtitle\par}
25 +\ifx\WPsubtitle\empty\else
26 + \vspace{0.3cm}
27 + {\large\itshape \WPsubtitle\par}
28 +\fi
26 29
27 30 \vspace{1.2cm}
28 31
32 +% --- Author ---
29 33 {\large \WPauthor}\\[0.3cm]
30 34 {\normalsize \WPaffiliation}\\[0.15cm]
31 35 {\normalsize \href{mailto:\WPemail}{\WPemail}}\\[0.15cm]
@@ -33,6 +37,7 @@
33 37
34 38 \vspace{0.8cm}
35 39
40 +% --- Date & Version ---
36 41 {\normalsize \WPdate}\\[0.1cm]
37 42 {\small Version~\WPversion}
38 43
@@ -42,7 +47,9 @@
42 47
43 48 \newpage
44 49
45 −% ---------------------------------------------------------------- abstract
50 +% ============================================================================
51 +% Abstract Page
52 +% ============================================================================
46 53 \thispagestyle{empty}
47 54
48 55 \vspace*{1cm}
49 56
Menu

Theme