🏠 Hedonic House Pricing with Language-Model and Vision Features
UQO Working Paper No. 14 — Evidence from Quebec Real-Estate Listings
TL;DR — Much of a home's value is recorded only in the free text and the photographs of its listing. We use a large language model to turn each description into structured specifications and a vision–language model to turn the façade photo into objective visual attributes, both under strict JSON schemas. On 19,123 Quebec house listings the two AI layers lift the R² of an already rich log-linear hedonic model from 0.717 to 0.755; the vision layer is the largest out-of-sample gain of any feature block for every estimator (OLS, gradient boosting, neural nets); the best ensemble reaches out-of-sample R² 0.779 with a 14.0% median absolute error. Implicit prices are economically sensible (+2.3% per point of curb appeal, +3–4% for brick or stone) and survive municipality-clustered inference.
📌 Status
| Item | Value |
|---|---|
| Version | 2.0 (July 2026) — revised draft after an internal review round (scripts/13_revision_extras.py) |
| SSRN | not yet submitted |
| Journal | Elsevier-format highlights prepared (docs/highlights_elsevier.tex); target to be decided |
| Code & data | reproducible from scripts/ run from the repository root; raw crawls and LLM caches not committed |
🎯 What this paper does
Hedonic models are bounded by the characteristics an analyst can measure. We treat general-purpose AI models as a feature-engineering technology: each listing description is parsed by an LLM into a fixed schema of property specifications (renovations, finishes, systems, extras), and each façade photograph is parsed by a vision–language model into objective attributes (cladding, storeys, garage, curb appeal). The outputs are ordinary econometric variables and enter a log-linear hedonic regression with city fixed effects:
ln P_i = α_city(i) + X_i'β + S_i'γ (LLM text layer) + V_i'δ (vision layer) + ε_iWe then measure, under identical 5-fold splits, how much each layer adds for OLS, histogram gradient boosting, a residual MLP with city/type embeddings, and a stacked blend.
🏆 Headline results
| Object | Estimate |
|---|---|
| Base hedonic OLS (structural + location), in-sample R² | 0.717 |
| + LLM text layer + vision layer | 0.755 |
| Out-of-sample R², OLS: base → +text → +text+vision (5-fold) | 0.52 → 0.62 → … (see paper/tables/ablation.tex) |
| Out-of-sample R², GBM: base → +text | 0.69 → 0.73 |
| Best ensemble (robust NN + GBM blend) | R²_OOS = 0.779, median absolute error 14.0% |
| Curb-appeal score (1–5) | +2.3% per point |
| Brick / stone façade | +3–4% |
| Inference | HC3 and municipality-clustered standard errors agree |
🗂 Repository layout
hedonic-llm-vision-features/
├── data/
│ ├── raw/ # two Apify crawls (Realtor.ca, DuProprio), 2026-07-03 — not committed
│ └── processed/ # LLM/vision caches, merged datasets, OOS predictions — not committed
├── scripts/ # run from the repository root, in order
│ ├── 00a_test_openai_extraction.py / 00b_test_vision_claude.py # API smoke tests
│ ├── 01_extract_specs_llm.py # description → JSON specs (parallel, resumable)
│ ├── 02_facade_vision.py # façade photo → visual attributes
│ ├── 03_merge_llm_features.py
│ ├── 04_hedonic_ols_base.py / 05_hedonic_ols_v4_text.py
│ ├── 06_hedonic_ml_cv.py / 07_torch_nn.py / 08_torch_nn_robust.py
│ ├── 09_v5_ml_text_vision.py / 10_final_blend.py
│ ├── 11_extras_v5_robustness.py / 12_ablation_extras.py / 13_revision_extras.py
│ └── 14_figures_v5.py / 15_make_tables.py / 16_make_tables_v5.py / 17_gen_regression_table.py
├── results/ # hedonic summaries (txt), ablation_extras.json, revision_extras.json
├── docs/ # METHODOLOGIE.md + methodologie.tex/pdf (French data & method note), Elsevier highlights
└── paper/ # main.tex + sections/ + tables/ + figures/ + references.bib → main.pdf🚀 Quick start
python3 -m pip install -r requirements.txt
cp .env.example .env # OPENAI_API_KEY=…, ANTHROPIC_API_KEY=… (only steps 00–02 call the APIs)
# place the two Apify crawls in data/raw/ and build data/processed/maisons_avec_prix.json (docs/METHODOLOGIE.md §2–4), then:
WORKERS=24 python3 scripts/01_extract_specs_llm.py
python3 scripts/02_facade_vision.py
python3 scripts/03_merge_llm_features.py
python3 scripts/05_hedonic_ols_v4_text.py
python3 scripts/08_torch_nn_robust.py && python3 scripts/09_v5_ml_text_vision.py && python3 scripts/10_final_blend.py
python3 scripts/11_extras_v5_robustness.py && python3 scripts/12_ablation_extras.py && python3 scripts/13_revision_extras.py
python3 scripts/14_figures_v5.py && python3 scripts/16_make_tables_v5.py && python3 scripts/17_gen_regression_table.py
cd paper && make # latexmk → main.pdfLLM extraction ≈ 600 tokens in / 110 out per listing (24 threads); vision ≈ 2,470 tokens in per image (16 threads); both are resumable. Econometrics and ML ≈ 20 minutes on an Apple-silicon laptop (PyTorch on MPS).
🗃 Data
27,302 Realtor.ca and 7,693 DuProprio listings crawled on 2026-07-03; houses (single-family and townhouse) with a valid
price retained, outliers removed by the rules in docs/METHODOLOGIE.md §3, giving 19,123 observations. Structural
variables (living area, lot, rooms, year built, type, city), text-derived specifications and façade attributes. See
data/raw/README.md and data/processed/README.md.
🔬 Methodology
- AI feature layers with strict JSON schemas and retry/backoff —
scripts/01_…,scripts/02_…. - Log-linear hedonic OLS with city fixed effects, HC3 and municipality-clustered SEs —
scripts/05_…,scripts/13_…. - Ablation by feature block × estimator under identical folds (OLS, GBM, NN) —
scripts/09_…,scripts/12_…. - Stacked blend of the robust NN and HistGradientBoosting —
scripts/10_final_blend.py. - Robustness by segment, extreme city effects, calibration and importance plots —
scripts/11_…,scripts/14_….
🖼 Figures & tables inventory
| Figure | Content |
|---|---|
fig_ablation |
out-of-sample gain by feature block and estimator |
fig_calibration |
predicted vs actual, blend |
fig_importance |
feature importance |
fig_premia |
location premia |
fig_validation |
validation of AI-extracted attributes |
nn_diagram (TikZ) |
architecture of the robust NN |
Tables (paper/tables/*.tex): summary stats, regression (A/B panels), models, ablation, robustness, AI summary.
⚠️ Limitations
- Asking prices from listings, single cross-section (July 2026).
- Realtor.ca listings lack coordinates; location enters through city fixed effects and postal codes.
- LLM/vision outputs are model-dependent; schemas and prompts are versioned in the scripts.
- API costs make the extraction layers the expensive part of replication.
📝 Citation
@techreport{boucher2026llmvisionhedonic,
author = {Boucher, Simon-Pierre},
title = {Hedonic House Pricing with Language-Model and Vision Features: Evidence from Quebec Real-Estate Listings},
institution = {Universit\'e du Qu\'ebec en Outaouais, D\'epartement des sciences administratives},
type = {Working Paper},
number = {14},
year = {2026},
month = {July}
}👤 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: this paper was first issued as "Working Paper No. 11" (July 2026); number 11 belongs to Half a Million
Prices, Twenty Models. Renumbered 14 on 2026-10-17; cover date fixed to July 2026 (was
\today). - Harmonised 2026-10-17: flat scripts moved to
scripts/(numbered), data todata/, logs removed, summaries toresults/, methodology note todocs/; file paths inside the scripts now point todata/…andresults/…(run them from the repository root). Bibliography switched fromplainnattoaer. - The script
13_revision_extras.pywas written for a Journal of Housing Economics revision round (clustered SEs, in-sample ablation, extreme city effects).