# π Decoding Real Estate Descriptions
### Semantic Embeddings and Hedonic Pricing of Residential Properties in Quebec
**UQO Working Paper No. 2**
[](mailto:contact@spboucher.ai)
[](mailto:contact@spboucher.ai)
[](https://uqo.ca)
[](https://www.python.org/)
[](https://www.latex-project.org/)
[](https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2)
[](https://www.statsmodels.org/)
[](https://scikit-learn.org/)
[](#-reproducing-everything)
[](#-data)
[](#-key-results)
[](paper/main.pdf)
[-00695C?style=flat-square&logo=googlescholar&logoColor=white)](paper/references.bib)
[](#-citation)
---
> **TL;DR** β Listing descriptions written by real estate agents contain price-relevant information that classic hedonic variables miss. This project embeds the free-text `PublicRemarks` of **17,087 Quebec single-family house listings** with a sentence transformer, projects each embedding onto **20 researcher-defined semantic reference descriptions** (*Luxury*, *Needs Renovation*, *Waterfront*, *Motivated Seller*, β¦) via cosine similarity, and adds those 20 interpretable scores to a log-price hedonic OLS model. Adjusted RΒ² rises from **0.452 to 0.511** (joint *F* = 99.53, *p* < 0.001), and each semantic dimension carries a named, signed, economically meaningful implicit price.
---
## π Table of Contents
- [Overview](#-overview)
- [Key Results](#-key-results)
- [Methodology](#-methodology)
- [Repository Structure](#-repository-structure)
- [The Pipeline, Script by Script](#-the-pipeline-script-by-script)
- [Data](#-data)
- [Reproducing Everything](#-reproducing-everything)
- [Building the Paper](#-building-the-paper)
- [Verification & Provenance](#-verification--provenance)
- [Figures Gallery](#-figures-gallery)
- [Requirements](#-requirements)
- [Citation](#-citation)
- [Author & Contact](#-author--contact)
---
## π Overview
Hedonic pricing models decompose a property's price into the implicit prices of its characteristics β bedrooms, bathrooms, lot size. But the *narrative* of a listing ("cuisine gastronomique, comptoirs de quartzβ¦" vs. "besoin de rΓ©novation, vendu tel quelβ¦") carries quality information that no structured field captures.
The catch, historically, is a **depth-vs-interpretability trade-off**:
| Approach | Semantic depth | Economic interpretability |
|---|---|---|
| Keyword counts / bag-of-words | β shallow | β
high |
| Sentiment scores | β one dimension | β
high |
| LDA topics | π‘ moderate | β unstable |
| Raw BERT/transformer embeddings | β
deep | β opaque (384β768 anonymous dims) |
| **Reference-based cosine projection (this paper)** | β
**deep** | β
**high** |
**The idea:** instead of feeding 384 anonymous embedding dimensions into a regression, define 20 short *reference descriptions* β synthetic French paragraphs, each embodying one qualitative housing dimension β embed them with the same model, and use the **cosine similarity between each listing and each reference** as 20 named regressors. Every coefficient then reads directly as "the implicit price of sounding more *luxury* / more *fixer-upper* / more *waterfront*", per standard deviation.
This is conceptually analogous to factor-mimicking portfolios in asset pricing, and to concept-bottleneck models in interpretable ML: the deep representation is channeled through human-named concepts before inference.
---
## π Key Results
### Model comparison (n = 17,087)
| Model | Specification | RΒ² | Adj. RΒ² | AIC | BIC | k |
|:---:|---|---:|---:|---:|---:|---:|
| A | Structural only | 0.4525 | 0.4523 | 26,165 | 26,219 | 6 |
| B | A + description length | 0.4647 | 0.4645 | 25,779 | 25,841 | 7 |
| C | A + 20 semantic similarities | 0.4962 | 0.4954 | 24,783 | 24,992 | 26 |
| **D** | **Full (B + C)** | **0.5122** | **0.5115** | **24,232** | 24,448 | 27 |
| E | Parsimonious (16 significant dims) | 0.5118 | 0.5111 | 24,239 | **24,425** | 23 |
Joint significance of the 21 text variables (D vs. A): ***F* = 99.53, *p* < 0.001**. Variance decomposition of Model D: structural 88.3 %, text length 2.4 %, semantics 9.3 %.
### Implicit semantic price gradients (Model D, per +1 SD, HC3 errors)
| π Positive | Impact | π Negative | Impact |
|---|---:|---|---:|
| Modern/Contemporary | **+16.4 %*** | Family-Friendly | **β11.6 %*** |
| Luxury | **+14.2 %*** | New Construction | **β10.5 %*** |
| Land & Nature | **+13.0 %*** | Quiet & Peaceful | β9.5 %*** |
| Entry-Level | +10.4 %*** | Bright & Spacious | β8.9 %*** |
| Waterfront | +4.7 %*** | Motivated Seller | **β8.5 %*** |
| Panoramic View | +3.0 %** | Needs Renovation | **β7.9 %*** |
*** p < 0.001, ** p < 0.01. Negative coefficients on Family-Friendly / New Construction largely proxy suburban location (see paper Β§6). Impact = (e^Ξ² β 1) Γ 100 %.
### Heterogeneity & robustness (highlights)
- **Quantile regressions** (Ο = 0.25 / 0.50 / 0.75): the Luxury premium rises monotonically from **9.7 %** at the 25th percentile to **17.3 %** at the 75th; urgency and condition discounts attenuate at the top of the market.
- **Bootstrap** (1,000 replications, seed 42): SEs within 9 % of HC3 (15/20 within 5 %).
- **Outlier trimming** (1st/99th price percentiles): no sign reversal, all 16 significant dimensions stay significant.
- **Multicollinearity is real and disclosed**: similarity dimensions correlate at 0.66β0.97 (mean VIF 17.2). Block-level inference is unaffected; individual coefficients are partial associations within a correlated block.
- **PCA benchmark**: 20 principal components of the raw embeddings fit *better* (ΞRΒ² +0.070 vs. +0.044) but are economically unreadable β the paper quantifies the interpretability-vs-fit trade-off explicitly.
---
## π§ͺ Methodology
```
βββββββββββββββ ββββββββββββββββ ββββββββββββββββ βββββββββββββββ ββββββββββββββββ
β Property β β Sentence β β Cosine β β Hedonic β β Implicit β
β Listings ββββΆβ Embeddings ββββΆβ Similarity ββββΆβ OLS Model ββββΆβ Price β
β (n=17,087) β β (384-d) β β (20 refs) β β (HC3 s.e.) β β Estimates β
βββββββββββββββ ββββββββββββββββ ββββββββββββββββ βββββββββββββββ ββββββββββββββββ
PublicRemarks all-MiniLM-L6-v2 S = E Β· Rα΅ log(P) = Ξ± + Ξ²'X + Ξ΄β + Ξ³'s + Ξ΅
```
1. **Extraction** β houses only, price > 0, description β₯ 20 characters; minimal preprocessing (the transformer handles raw text).
2. **Embedding** β `all-MiniLM-L6-v2` (22.7M params, 384-d, L2-normalized), batch 256; the whole corpus encodes in seconds on Apple Silicon.
3. **Projection** β 20 French reference descriptions (146β257 chars each, full verbatim text in paper Appendix A), designed for *semantic saturation*, *dimensional specificity*, *linguistic consistency*. One matrix product yields the 17,087 Γ 20 similarity matrix.
4. **Estimation** β nested OLS models AβE on log price, all covariates standardized, HC3 robust errors; joint F-tests; quantile regressions; bootstrap; winsorization; VIF; Lasso/Elastic-Net selection; PCA benchmark.
The 20 dimensions span six domains: **Quality & Standing** (Luxury, Entry-Level) Β· **Condition** (Renovated, Needs Renovation) Β· **Physical** (Bright & Spacious, Garage & Parking, Finished Basement, Pool & Landscaping) Β· **Location & Setting** (Land & Nature, Panoramic View, Premium Location, Quiet & Peaceful, Waterfront) Β· **Style & Character** (Modern/Contemporary, Heritage/Character, New Construction, Energy Efficient, Family-Friendly) Β· **Market Signals** (Income/Investment, Motivated Seller).
---
## π Repository Structure
```
wp2_uqo/
βββ README.md β you are here
βββ AUDIT.md β forensic audit of the original (pre-restructuring) project
βββ CHANGES.md β everything that was moved / refactored / corrected, with diffs
βββ PAPER_REVIEW.md β critical pre-upgrade assessment of the paper's scholarship
βββ UPGRADE_REPORT.md β literature expansion: refs added/replaced, claims flagged
βββ requirements.txt β pinned dependencies (exact versions used for verification)
β
βββ data/ β NOT tracked by git (see data/README.md)
β βββ raw/louka.db β SQLite, 268 MB, 46,479 listings (5 categories)
β βββ processed/ β parquet sample, embeddings cache, similarity matrix,
β βββ legacy/ β analysis CSV + pre-refactor artifacts kept for comparison
β
βββ src/ β analysis package (imported by scripts/)
β βββ config.py β repo-relative paths, model name, seed, structural vars
β βββ references.py β the 20 French reference descriptions + English labels
β βββ data.py β SQLite β DataFrame extraction (n = 17,087)
β βββ embeddings.py β encoding, caching, cosine-similarity features
β βββ models.py β standardized-design OLS AβE, HC3
β
βββ scripts/ β numbered pipeline entry points (run in order)
β βββ 01_prepare_data.py β louka.db β data/processed/houses.parquet
β βββ 02_similarities.py β embeddings (cached) + 20 similarity features β CSV
β βββ 03_models.py β models AβE β results/*.csv
β βββ 04_robustness.py β VIF Β· BP Β· quantile Β· winsor Β· bootstrap Β· Lasso/ENet
β βββ 05_figures.py β figures fig1βfig11 (PDF + PNG)
β βββ 06_tables.py β LaTeX tables generated from results (no hand transcription)
β
βββ figures/ β all 11 paper figures, regenerated from the pipeline
βββ results/ β model & robustness outputs (CSV, versioned)
β
βββ paper/ β LaTeX source (UQO working-paper style)
βββ main.tex β metadata + section inputs (53-page PDF)
βββ preamble.tex β packages, layout, custom macros
βββ main.pdf β compiled paper (committed for convenience)
βββ Makefile β latexmk build
βββ references.bib β 86 entries, every one verified (OpenAlex + DOI)
βββ sections/ β titlepage Β· intro Β· literature Β· methodology Β· data Β·
β β results Β· robustness Β· discussion Β· conclusion Β· appendix
βββ tables/ β 7 machine-generated + 3 hand-maintained booktabs tables
```
---
## βοΈ The Pipeline, Script by Script
| # | Script | Input | Output | Runtime* |
|---|---|---|---|---|
| 1 | `01_prepare_data.py` | `data/raw/louka.db` | `houses.parquet` (17,087 Γ 14) | ~15 s |
| 2 | `02_similarities.py` | parquet + refs | embeddings `.npy` (cached), `sim_matrix.npy`, analysis CSV | ~5 s cached / ~3 min fresh |
| 3 | `03_models.py` | analysis CSV | `model_comparison.csv`, coefficient tables, descriptive stats | ~5 s |
| 4 | `04_robustness.py` | analysis CSV | `robustness_results.csv` (163 rows: VIF, BP, QR, winsor, bootstrap, Lasso) | ~4 min (bootstrap) |
| 5 | `05_figures.py` | analysis CSV | `figures/fig1β¦fig11.{pdf,png}` | ~30 s |
| 6 | `06_tables.py` | `results/*.csv` | `paper/tables/*.tex` (7 tables) | ~2 s |
*Apple M-series laptop. All stochastic steps are seeded (`seed = 42`): bootstrap resampling, CV folds, scatter subsampling.
**Design principles applied during the refactor** (see `CHANGES.md` for the full story):
- π« **No hardcoded paths** β everything resolves from the repo root (`src/config.py`); the DB location can be overridden with `WP2_DB_PATH`.
- 𧬠**Single source of truth** β the extraction logic and the 20 reference texts, previously copy-pasted across five scripts (in two languages), now live in exactly one place each.
- π **Deterministic & cached** β embeddings are deterministic for a given model version and cached; `--force` re-encodes from scratch.
- π **Tables are compiled artifacts** β every number in the paper's statistical tables is written by `06_tables.py` from the results CSVs. The original project transcribed numbers by hand, which introduced errors that this restructuring caught and fixed (documented, with oldβnew values, in `CHANGES.md` Β§4).
---
## πΎ Data
The raw data is a **SQLite database (`louka.db`, 268 MB)** of Quebec residential listings collected from Realtor.ca / Centris: **46,479 listings** across five categories β houses (17,089), condos (8,586), land (8,506), rentals (8,240), plexes (4,058). Each row carries structured fields (price, bedrooms, bathrooms) plus a full JSON blob including the agent-written `PublicRemarks`.
**The analysis sample**: single-family houses with a positive price and a description of β₯ 20 characters β **n = 17,087**. Median listing price $589,900 (mean $807,259, max $25M). Descriptions average 510 characters and are truncated at ~700 characters in the export (disclosed in the paper).
> β οΈ **`data/` is not tracked by git**: the raw database exceeds GitHub's 100 MB file limit, contains a leftover application `users` table, and the listing texts are proprietary platform content. `data/README.md` explains how to place a copy of `louka.db` to re-run the pipeline from scratch. All *derived, aggregate* outputs (results CSVs, figures, tables, paper) **are** versioned, so every number in the paper is inspectable without the raw data.
---
## π Reproducing Everything
```bash
git clone https://github.com/spboucher-ai/wp2_uqo.git
cd wp2_uqo
python3 -m pip install -r requirements.txt
# place louka.db in data/raw/ (see data/README.md), then:
python3 scripts/01_prepare_data.py # extract the house sample
python3 scripts/02_similarities.py # embed + project (add --force to re-encode)
python3 scripts/03_models.py # hedonic models AβE
python3 scripts/04_robustness.py # full robustness battery (~4 min)
python3 scripts/05_figures.py # all 11 figures
python3 scripts/06_tables.py # LaTeX tables from results
cd paper && latexmk -pdf main.tex # 53-page PDF, 0 errors
```
Expected checkpoints along the way:
- Step 1 prints `17,087 houses with a valid price and description`.
- Step 3 prints the model table with `RΒ² = 0.4525 / 0.4647 / 0.4962 / 0.5122 / 0.5118` and `F = 99.53`.
- Step 4 prints `Winsorized R2=0.4867` and the Lasso selection (4/20 at the CV penalty).
---
## π Building the Paper
The paper (`paper/`) follows the UQO working-paper format: title page with logo, abstract page with keywords + JEL codes, IMRaD body (Introduction Β· Literature Β· Methodology Β· Data Β· Results Β· Robustness Β· Discussion Β· Conclusion), `natbib`/`apalike` bibliography, and two appendices (verbatim reference texts; parsimonious-model estimates).
```bash
cd paper
make # or: latexmk -pdf main.tex
```
Figures are pulled from `../figures/` via `\graphicspath`, so the paper always reflects the latest pipeline run. The build is clean: **0 errors, 0 undefined references, 0 missing citations** (59/59 bib keys resolve).
---
## β
Verification & Provenance
This repository is a restructuring of an earlier research folder. Before anything was rewritten, the pipeline was validated against the original artifacts:
| Check | Result |
|---|---|
| Analysis dataset (17,087 Γ 30) | β
identical, column by column |
| Similarity matrix (17,087 Γ 20) | β
bit-identical |
| Models AβE fit statistics | β
identical to the paper's Table 4 |
| Model D coefficients (27 params) | β
identical to the paper's Table 5 |
| Figures fig1, fig4βfig11 | β
regenerated, byte-size within Β±2 B |
| Figures fig2, fig3 | π§ were *missing* from the original repo (placeholder boxes in the old PDF) β regenerated |
| `robustness_results.csv` | π§ lost in the original β regenerated (seeded) |
| Descriptive/robustness numbers in the old LaTeX | β οΈ several did not match the code's own outputs β corrected and now machine-generated; full oldβnew list in [`CHANGES.md`](CHANGES.md) Β§4 |
`AUDIT.md` documents the original project's layout, dead files, and defects; `CHANGES.md` documents every move, refactor, and numeric correction.
---
## πΌοΈ Figures Gallery
| # | Figure | What it shows |
|---|---|---|
| 1 | `fig1_model_comparison` | RΒ² / adj. RΒ² across models AβE |
| 2 | `fig2_coefficient_plot` | Forest plot of the 20 semantic implicit prices (Model D) |
| 3 | `fig3_similarity_distributions` | Box plots of the 20 cosine-similarity features |
| 4 | `fig4_quintile_heatmap` | Mean similarity by price quintile Γ dimension |
| 5 | `fig5_correlation_matrix` | Inter-dimension Pearson correlations (0.66β0.97) |
| 6 | `fig6_scatter_plots` | Four illustrative similarityβprice scatters |
| 7 | `fig7_methodology` | Pipeline diagram |
| 8 | `fig8_r2_decomposition` | Stacked decomposition of Model D's RΒ² |
| 9 | `fig9_price_distribution` | Price and log-price histograms |
| 10 | `fig10_structural_coefficients` | Structural coefficients with 95 % CIs |
| 11 | `fig11_residual_diagnostics` | Residuals vs. fitted + normal Q-Q |
All figures exist as publication PDF (vector) and PNG (300 dpi).
---
## π¦ Requirements
| Package | Version | Used for |
|---|---|---|
| Python | 3.14 | β |
| numpy | 2.4.4 | linear algebra |
| pandas | 3.0.2 | data wrangling |
| pyarrow | 24.0.0 | parquet I/O |
| scipy | 1.17.1 | tests, distributions |
| scikit-learn | 1.6.1 | PCA, Lasso/ElasticNet CV, scaling |
| statsmodels | 0.14.6 | OLS/HC3, QuantReg, BP test, VIF |
| matplotlib | 3.10.9 | figures |
| sentence-transformers | 5.5.0 | embeddings |
| torch | 2.12.0 | transformer backend (MPS on Apple Silicon) |
LaTeX: TeX Live 2026 with `latexmk` (newtx, booktabs, threeparttable, natbib, hyperrefβ¦).
---
## π Citation
```bibtex
@techreport{boucher2026decoding,
author = {Boucher, Simon-Pierre},
title = {Decoding Real Estate Descriptions: Semantic Embeddings and
Hedonic Pricing of Residential Properties in Quebec},
institution = {Universit\'e du Qu\'ebec en Outaouais,
D\'epartement des sciences administratives},
type = {Working Paper},
number = {2},
year = {2026},
month = {May}
}
```
---
## π€ Author & Contact
**Simon-Pierre Boucher**
DΓ©partement des sciences administratives
UniversitΓ© du QuΓ©bec en Outaouais (UQO)
283, boulevard Alexandre-TachΓ©, Gatineau (QuΓ©bec) J9A 1L8, Canada
π§ **contact@spboucher.ai**
*Comments and suggestions are welcome. All errors are my own.*
---
Β© 2026 Simon-Pierre Boucher. Paper, code, and figures β all rights reserved.