SPB Git

spb/wp5_uqo

Public

UQO Working Paper No. 5 — Airbnb, residential rents and housing market pressure.

Redesign README: badge-rich presentation, paper section-by-section guide, headline-result badges

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Simon-Pierre Boucher committed (Aug 5, 2026) parent 8607e01 Browse files

1 changed file +201 −228

Unified Split
modified README.md +201 −228
@@ -2,310 +2,282 @@
2 2
3 <div align="center"> 3 <div align="center">
4 4
5−# 🏠 Airbnb, Residential Rents, and Housing Market Pressure 5+# 🏠 Airbnb, Residential Rents & Housing Market Pressure
6 6
7−### A Hedonic and Spatial Econometric Analysis — UQO Working Paper No. 5 7+### *A Hedonic and Spatial Econometric Analysis*
8 8
9−[![Python](https://img.shields.io/badge/Python-3.14-3776AB?logo=python&logoColor=white)](https://www.python.org/) 9+**UQO Working Paper No. 5**
10−[![pandas](https://img.shields.io/badge/pandas-3.0.2-150458?logo=pandas&logoColor=white)](https://pandas.pydata.org/)
11−[![statsmodels](https://img.shields.io/badge/statsmodels-0.14.6-4051B5)](https://www.statsmodels.org/)
12−[![scikit--learn](https://img.shields.io/badge/scikit--learn-1.6.1-F7931E?logo=scikitlearn&logoColor=white)](https://scikit-learn.org/)
13−[![PySAL/spreg](https://img.shields.io/badge/PySAL%2Fspreg-1.9.0-8A2BE2)](https://pysal.org/)
14−[![SHAP](https://img.shields.io/badge/SHAP-0.48.0-FF4081)](https://shap.readthedocs.io/)
15−[![LaTeX](https://img.shields.io/badge/LaTeX-latexmk-008080?logo=latex&logoColor=white)](https://www.latex-project.org/)
16 10
17−[![Status](https://img.shields.io/badge/status-working%20paper-blue)]() 11+<br>
18−[![Reproducible](https://img.shields.io/badge/pipeline-fully%20reproducible-brightgreen)]()
19−[![Verified](https://img.shields.io/badge/tables-14%2F16%20byte--identical-brightgreen)]()
20−[![Paper](https://img.shields.io/badge/PDF-43%20pages%2C%200%20unresolved%20refs-success)]()
21−[![Figures](https://img.shields.io/badge/figures-16%20regenerated-informational)]()
22−[![Models](https://img.shields.io/badge/models-OLS%20%7C%20SAR%2FSEM%20%7C%20Quantile%20%7C%20ML-orange)]()
23 12
24−**Simon-Pierre Boucher** · Département des sciences administratives, Université du Québec en Outaouais (UQO) 13+[![Author](https://img.shields.io/badge/Author-Simon--Pierre%20Boucher-1f6feb?style=for-the-badge)](mailto:contact@spboucher.ai)
25−📧 [contact@spboucher.ai](mailto:contact@spboucher.ai) · 📄 [`paper/main.pdf`](paper/main.pdf) 14+[![Institution](https://img.shields.io/badge/UQO-Sciences%20administratives-00457C?style=for-the-badge)](https://uqo.ca)
15+[![Contact](https://img.shields.io/badge/✉️-contact%40spboucher.ai-EA4335?style=for-the-badge)](mailto:contact@spboucher.ai)
16+
17+<br>
18+
19+**— The paper at a glance —**
20+
21+[![PDF](https://img.shields.io/badge/📄_PDF-51_pages-success?style=flat-square)](paper/main.pdf)
22+[![References](https://img.shields.io/badge/📚_References-56_(all_DOI--verified)-blueviolet?style=flat-square)]()
23+[![Sample](https://img.shields.io/badge/🏘️_Sample-8,303_rentals_·_3,456_Airbnb-informational?style=flat-square)]()
24+[![Headline](https://img.shields.io/badge/β-%2B0.4%25_rent_per_listing_(500m)-e63946?style=flat-square)]()
25+[![Tables](https://img.shields.io/badge/📊-13_tables-teal?style=flat-square)]()
26+[![Figures](https://img.shields.io/badge/📈-16_figures-teal?style=flat-square)]()
27+[![Build](https://img.shields.io/badge/latexmk-0_unresolved_refs-brightgreen?style=flat-square)]()
28+
29+**— Methods —**
30+
31+[![OLS](https://img.shields.io/badge/Hedonic-OLS_·_HC1_·_city_FE-orange?style=flat-square)]()
32+[![Spatial](https://img.shields.io/badge/Spatial-SAR_·_SEM_·_GMM_(spreg)-8A2BE2?style=flat-square)]()
33+[![Quantile](https://img.shields.io/badge/Quantile-τ_∈_{.10….90}-c44536?style=flat-square)]()
34+[![ML](https://img.shields.io/badge/ML-LASSO_·_ENet_·_RF_·_GBM_·_SHAP-F7931E?style=flat-square)]()
35+[![Geo](https://img.shields.io/badge/Geospatial-Haversine_buffers_250m→2km-2a9d8f?style=flat-square)]()
36+
37+**— Stack —**
38+
39+[![Python](https://img.shields.io/badge/Python-3.14-3776AB?style=flat-square&logo=python&logoColor=white)](https://www.python.org/)
40+[![pandas](https://img.shields.io/badge/pandas-3.0.2-150458?style=flat-square&logo=pandas&logoColor=white)](https://pandas.pydata.org/)
41+[![statsmodels](https://img.shields.io/badge/statsmodels-0.14.6-4051B5?style=flat-square)](https://www.statsmodels.org/)
42+[![scikit-learn](https://img.shields.io/badge/scikit--learn-1.6.1-F7931E?style=flat-square&logo=scikitlearn&logoColor=white)](https://scikit-learn.org/)
43+[![PySAL](https://img.shields.io/badge/PySAL%2Fspreg-1.9.0-8A2BE2?style=flat-square)](https://pysal.org/)
44+[![SHAP](https://img.shields.io/badge/SHAP-0.48.0-FF4081?style=flat-square)](https://shap.readthedocs.io/)
45+[![LaTeX](https://img.shields.io/badge/LaTeX-latexmk-008080?style=flat-square&logo=latex&logoColor=white)](https://www.latex-project.org/)
46+
47+**— Quality —**
48+
49+[![Reproducible](https://img.shields.io/badge/pipeline-100%25_reproducible-brightgreen?style=flat-square)]()
50+[![Verified](https://img.shields.io/badge/tables-14%2F16_byte--identical_to_originals-brightgreen?style=flat-square)]()
51+[![Deterministic](https://img.shields.io/badge/seed-42_·_deterministic-lightgrey?style=flat-square)]()
52+[![Status](https://img.shields.io/badge/status-working_paper_v1.0-blue?style=flat-square)]()
26 53
27 </div> 54 </div>
28 55
29 --- 56 ---
30 57
31−## 📋 Table of Contents 58+## 🧭 Navigation
32− 59+
33−1. [Overview](#-overview) 60+| | | |
34−2. [Key Findings](#-key-findings) 61+|---|---|---|
35−3. [Repository Structure](#-repository-structure) 62+| [📜 The Paper](#-the-paper) | [🔍 Abstract](#-abstract) | [📖 Inside the Paper](#-inside-the-paper--section-by-section) |
36−4. [Data](#-data) 63+| [🏆 Headline Results](#-headline-results) | [📁 Repository Map](#-repository-map) | [💾 Data](#-data) |
37−5. [Analysis Pipeline](#-analysis-pipeline) 64+| [⚙️ Pipeline](#️-analysis-pipeline) | [📐 Models](#-econometric-models) | [🛡️ Robustness](#️-robustness-programme) |
38−6. [Econometric Models](#-econometric-models) 65+| [🚀 Reproduce](#-reproducing-everything) | [🧰 Environment](#-environment--dependencies) | [✅ Provenance](#-verification--provenance) |
39−7. [Robustness Programme](#-robustness-programme) 66+| [📖 Citation](#-citation) | [📬 Contact](#-contact) | |
40−8. [Reproducing Everything](#-reproducing-everything)
41−9. [Environment & Dependencies](#-environment--dependencies)
42−10. [Outputs Inventory](#-outputs-inventory)
43−11. [Verification & Provenance](#-verification--provenance)
44−12. [Citation](#-citation)
45−13. [Contact](#-contact)
46 67
47 --- 68 ---
48 69
49−## 🔍 Overview 70+## 📜 The Paper
50 71
51−This repository contains the **complete, reproducible research compendium** for UQO 72+<div align="center">
52−Working Paper No. 5: data pipeline, econometric analysis, machine-learning 73+
53−robustness suite, all figures/tables, and the LaTeX source of the paper. 74+> ### **Airbnb, Residential Rents, and Housing Market Pressure:**
75+> ### **A Hedonic and Spatial Econometric Analysis**
76+>
77+> **Simon-Pierre Boucher** — Université du Québec en Outaouais
78+> *UQO Working Paper No. 5 · May 2026 · 51 pages · 56 references*
79+>
80+> [![Read](https://img.shields.io/badge/▶_READ_THE_PAPER-paper%2Fmain.pdf-b31b1b?style=for-the-badge)](paper/main.pdf)
81+
82+</div>
54 83
55−The paper studies the relationship between **Airbnb short-term rental activity** 84+## 🔍 Abstract
56−and **residential rents** in Quebec, Canada, using cross-sectional microdata:
57−~5,000 Airbnb listings and 8,356 Realtor.ca rental listings, linked by an exact
58−**Haversine spatial buffer merge** (250 m / 500 m / 1 km / 2 km). The empirical
59−strategy combines hedonic OLS with city fixed effects, spatial econometrics
60−(SAR / SEM via GMM), quantile regressions, and machine-learning benchmarks
61−(LASSO, elastic net, random forest, gradient boosting, SHAP).
62 85
63−> ⚠️ **Interpretation note.** The design is cross-sectional. All estimates are 86+> This paper investigates the relationship between **Airbnb short-term rental activity** and **residential rents** in Quebec, Canada. Using cross-sectional microdata comprising ~5,000 Airbnb listings and ~8,300 rental listings, we employ a **hedonic pricing framework** augmented with **spatial econometric techniques** to quantify the conditional association between nearby Airbnb presence and monthly rents. For each rental listing we construct Airbnb exposure measures within **250 m, 500 m, 1 km, and 2 km buffers** using Haversine distances. Baseline estimates indicate that an additional Airbnb listing within 500 m is associated with a statistically significant increase in monthly rent of approximately **0.3–0.5%**, controlling for dwelling characteristics, building type, and city fixed effects. **Quantile regressions** reveal that the association is stronger at the upper tail of the rent distribution. A complementary hedonic model of Airbnb nightly prices indicates that short-term rental pricing is driven primarily by listing characteristics. Robustness checks — alternative buffer radii, subsample analyses, and machine-learning benchmarks — confirm the stability of the rent–exposure association. We discuss policy implications for housing affordability and short-term rental regulation, while cautioning that **cross-sectional associations should not be interpreted as causal effects**.
64−> *conditional correlations*, not causal effects — the paper is explicit about 87+>
65−> reverse causality, omitted amenities, and spatial sorting. 88+> **Keywords:** Airbnb · short-term rentals · housing rents · hedonic pricing · spatial econometrics · housing affordability · Quebec
89+> **JEL:** R21 · R31 · L83 · C21
66 90
67 --- 91 ---
68 92
69−## 📊 Key Findings 93+## 📖 Inside the Paper — Section by Section
94+
95+| § | Section | What it delivers | Key exhibits |
96+|:-:|---|---|---|
97+| **1** | 🎬 **Introduction** | Motivation, the P2P-platform housing channel, the Canadian evidence gap, magnitude preview vs. Berlin/LA quasi-experiments, three contributions | — |
98+| **2** | 📚 **Literature Review** | 10 thematic subsections: correlational → quasi-experimental STR evidence, Canadian & Quebec studies, P2P economics, STR pricing, hedonic theory, spatial econometrics, quantile methods, ML in housing, tourism gentrification & regulation, gap statement | 56 refs |
99+| **3** | 💾 **Data** | Two scraped microdatasets, cleaning protocol, **Haversine spatial buffer merge** (6 exposure metrics × 4 radii), summary statistics, geography | Tables 1–2 · Figures 1–3 |
100+| **4** | 📐 **Methodology** | Six models: hedonic rent (1a–1e), Airbnb pricing (2a–2c), city-level (3), SAR/SEM via Kelejian–Prucha GMM (4), quantile (5), ML benchmark (6); identification & endogeneity discussion | Equations 1–6 |
101+| **5** | 📊 **Results** | β = 0.0039*** per nearby listing; spatial dependence (ρ̂ = 0.137***, λ̂ = 0.539); quantile gradient .0037→.0047; superhost *discount*; RF test R² = 0.71 | Tables 3–9 · Figures 4–7 |
102+| **6** | 🛡️ **Robustness** | Buffer decay, alternative exposures, subsamples, trimming, **city-clustered SEs**, size control, log form, **ring decomposition**, **leave-one-city-out** | Tables 10–12 · Figures 8–10 |
103+| **7** | 💬 **Discussion** | Magnitudes benchmarked against Berlin ($6–10 ≈ 7–13 €/listing), LA & Boston; two candid disagreements with the literature; policy for CITQ/Montreal zoning; 7 limitations; future work | — |
104+| **8** | 🎯 **Conclusion** | Five findings, cross-design corroboration, no overselling | — |
105+| **A–C** | 📎 **Appendices** | Variable dictionary, city-name standardisation, sample attrition · Haversine & KNN-weights formulas, estimator details · supplementary robustness | Tables A1–A2 |
106+
107+---
108+
109+## 🏆 Headline Results
110+
111+<div align="center">
112+
113+[![F1](https://img.shields.io/badge/1-%2B0.4%25_rent_per_Airbnb_listing_within_500m-e63946?style=for-the-badge)]()
114+[![F2](https://img.shields.io/badge/2-Spatial_decay:_0.93%25_→_0.06%25_(250m_→_2km)-f4a261?style=for-the-badge)]()
115+[![F3](https://img.shields.io/badge/3-Strongest_at_the_top:_β(τ=.90)=0.0047-e9c46a?style=for-the-badge)]()
116+[![F4](https://img.shields.io/badge/4-Survives_SAR%2FSEM_(ρ̂=0.137***)-2a9d8f?style=for-the-badge)]()
117+[![F5](https://img.shields.io/badge/5-No_city--rent_premium_in_nightly_prices-264653?style=for-the-badge)]()
118+[![F6](https://img.shields.io/badge/6-Stable_in_leave--one--city--out_(incl._Montreal)-6d597a?style=for-the-badge)]()
119+
120+</div>
70 121
71 | # | Finding | Evidence | 122 | # | Finding | Evidence |
72−|---|---------|----------| 123+|:-:|---|---|
73−| 1 | Each additional Airbnb listing within **500 m** is associated with **≈ +0.4 %** monthly rent (0.3–0.5 % across specifications), conditional on dwelling characteristics, building type, and city fixed effects | Table 3, Model 1c: β = 0.0039, HC1 SE = 0.0002 | 124+| 1 | Each additional Airbnb listing within **500 m** ⇒ **≈ +0.4%** monthly rent (0.3–0.5% across specs) — ≈ **$6–10/month** at the median rent, the same order as Berlin's quasi-experimental 7–13 €/listing | Table 3 (1c) · §7.1 |
74−| 2 | The per-listing association **decays with distance**: 0.93 % (250 m) → 0.38 % (500 m) → 0.16 % (1 km) → 0.06 % (2 km) | Table 8, Figure 4 | 125+| 2 | Per-listing association **decays monotonically with distance** | Table 10 · Fig. 4 |
75−| 3 | The association is **strongest at the top of the rent distribution**: β rises from ≈ 0.0034–0.0037 (τ ≤ 0.50) to **0.0047 at τ = 0.90** | Table 7, Figure 5 | 126+| 3 | Significant at **every quantile**, rising to **0.0047 at τ = 0.90** (~25% above OLS) | Table 7 · Fig. 5 |
76−| 4 | **Spatial dependence is substantial** (SAR ρ̂ = 0.137***, SEM λ̂ = 0.539) and the Airbnb coefficient survives with mild attenuation (0.0034 SAR) | Table 6 | 127+| 4 | **Spatial dependence is substantial** and the coefficient survives GMM spatial models with mild attenuation (0.0034) | Table 6 |
77−| 5 | Airbnb **nightly prices** are driven by listing characteristics; city-level mean rent carries **no significant premium** | Table 4 | 128+| 5 | Airbnb nightly prices driven by **listing characteristics**; superhost enters with a *discount* (vs. premia in prior work — discussed, not hidden) | Table 4 · §7.1 |
78−| 6 | **ML benchmarks**: random forest test R² = 0.71, gradient boosting 0.69 vs OLS 0.51 — nonlinearities exist, but the linear hedonic remains adequate for inference | Table 9, SHAP Figures 6–7 | 129+| 6 | Robust to **city-clustered SEs** (tighter than HC1), size control, log(1+count), ring decomposition, trimming, and dropping any of the 8 largest cities | Table 12 · Fig. 9 |
79−| 7 | The estimate is stable under **city-clustered SEs**, an **interior-size control**, a **log(1+count)** functional form, **ring-exposure** decomposition, and **leave-one-city-out** exclusions (incl. Montreal) | Table 12, Figure 9 |
80−
81−Economic magnitude: at the median rent (≈ $1,950/month), one extra nearby
82−listing ≈ **$6–10/month**; a neighbourhood with 20 nearby listings ≈ 6–10 %
83−higher rent, all else equal.
84 130
85 --- 131 ---
86 132
87−## 📁 Repository Structure 133+## 📁 Repository Map
88 134
89 ``` 135 ```
90 wp5_uqo/ 136 wp5_uqo/
91−├── README.md ← you are here 137+├── 📄 README.md ← you are here
92−├── AUDIT.md ← forensic audit of the original project 138+├── 🔎 AUDIT.md ← forensic audit of the original project
93−├── CHANGES.md ← full restructuring & rewrite log 139+├── 📝 CHANGES.md ← restructuring & rewrite log
94−├── requirements.txt ← pinned dependencies (exact reproduction) 140+├── 🧪 PAPER_REVIEW.md ← pre-upgrade critical assessment
95−├── .gitignore 141+├── 📚 UPGRADE_REPORT.md ← literature expansion report (per-ref justification)
142+├── 📦 requirements.txt ← pinned dependencies
96 │ 143 │
97 ├── data/ 144 ├── data/
98−│ ├── raw/ ← airbnb.csv + rent.json (NOT distributed) 145+│ ├── raw/ ← airbnb.csv + rent.json (NOT distributed → README)
99−│ │ └── README.md ← what the raw files are & where they go 146+│ └── processed/ ← 5 verified parquet datasets (committed)
100−│ └── processed/ ← committed, verified parquet datasets
101−│ ├── airbnb_clean.parquet (3,456 × 21)
102−│ ├── rent_clean.parquet (8,303 × 12)
103−│ ├── merged_spatial.parquet (8,303 × 36)
104−│ ├── merged_neighborhood.parquet (8,303 × 19)
105−│ └── merged_analysis.parquet (8,303 × 43) ← main analysis file
106 │ 147 │
107−├── src/ ← shared modules (imported by all scripts) 148+├── src/ ← shared modules
108−│ ├── config.py ← every path & constant; output dirs auto-created 149+│ ├── config.py ← all paths & constants
109−│ ├── geo.py ← vectorised Haversine distance matrix 150+│ ├── geo.py ← vectorised Haversine matrix
110−│ ├── latex_tables.py ← significance stars + stargazer-style builder 151+│ ├── latex_tables.py ← stargazer-style table builder
111−│ └── plotting.py ← the two publication matplotlib styles 152+│ └── plotting.py ← publication matplotlib styles
112 │ 153 │
113−├── scripts/ ← numbered pipeline entry points 154+├── scripts/ ← 01 → 11 numbered pipeline
114−│ ├── 01_inspect_raw_data.py ├── 07_spatial_models.py 155+├── figures/ ← 16 publication PDF figures
115−│ ├── 02_clean_airbnb.py ├── 08_quantile_models.py 156+├── results/tables/ ← 13 LaTeX fragments + 4 CSVs
116−│ ├── 03_clean_rent.py ├── 09_ml_robustness.py 157+├── results/logs/ ← data-inspection log
117−│ ├── 04_merge_data.py ├── 10_robustness_tables_figures.py
118−│ ├── 05_descriptive_analysis.py └── 11_extended_robustness.py
119−│ └── 06_hedonic_models.py
120 │ 158 │
121−├── figures/ ← all 16 PDF figures (pipeline output) 159+└── paper/ ← LaTeX → 📄 main.pdf (51 pages)
122−├── results/ 160+ ├── main.tex · references.bib (56 entries) · Makefile
123−│ ├── tables/ ← 13 LaTeX fragments + 4 CSVs 161+ ├── sections/ (titlepage + 8 sections)
124−│ └── logs/ ← data-inspection log 162+ └── appendix/ (data · methods · robustness)
125−│
126−└── paper/ ← LaTeX source → main.pdf (43 pages)
127− ├── main.tex ← preamble + metadata + assembly
128− ├── Makefile / .latexmkrc ← `make` or `latexmk` builds the PDF
129− ├── references.bib ← 24 entries, all cited
130− ├── sections/ ← titlepage + 8 numbered sections
131− └── appendix/ ← data / methods / robustness appendices
132 ``` 163 ```
133 164
134 --- 165 ---
135 166
136 ## 💾 Data 167 ## 💾 Data
137 168
138−### Sources 169+<div align="center">
139−
140−| Dataset | Source | Raw size | Cleaned size | Key fields |
141−|---|---|---|---|---|
142−| Airbnb listings | Airbnb (scraped, Quebec) | ~5,000 | **3,456** | nightly price, lat/lon, property type, rating, reviews, superhost |
143−| Rental listings | Realtor.ca (scraped, Quebec) | 8,356 | **8,303** | monthly rent, lat/lon, bedrooms, bathrooms, building type, size |
144−
145−> 🔒 The **raw files are not distributed** (`data/raw/README.md`). The committed
146−> parquets in `data/processed/` make steps 04–10 and the paper fully
147−> reproducible without them; steps 01–03 fail with an explanatory message.
148−
149−### Cleaning highlights
150 170
151−- City names standardised across accented/unaccented variants and boroughs 171+[![Airbnb](https://img.shields.io/badge/Airbnb-3,456_cleaned_listings-FF5A5F?style=flat-square&logo=airbnb&logoColor=white)]()
152− (Montréal arrondissements → *Montreal*, Québec arrondissements → *Québec*, …). 172+[![Realtor](https://img.shields.io/badge/Realtor.ca-8,303_cleaned_rentals-006AFF?style=flat-square)]()
153−- Prices/rents winsorised at p1/p99; log transforms (`log_price`, `log_rent`). 173+[![Merge](https://img.shields.io/badge/Spatial_merge-24_exposure_variables-2a9d8f?style=flat-square)]()
154−- Ratings imputed at the median (unrated ≠ zero-quality); review counts at 0. 174+[![Coverage](https://img.shields.io/badge/Coverage-Province_of_Quebec-1d3557?style=flat-square)]()
155−- `is_entire_home` built from property types (House, Cabin/Chalet, Condo).
156 175
157−### Spatial merge (the core construction) 176+</div>
158 177
159−For every rental listing *i* and buffer radius *r* ∈ {250 m, 500 m, 1 km, 2 km}, 178+| Dataset | Source | Raw → Clean | Key fields |
160−six exposure metrics are computed against all Airbnb listings via a **chunked, 179+|---|---|---|---|
161−fully vectorised Haversine distance matrix** (`src/geo.py`, 500 rentals/chunk): 180+| 🛏️ Airbnb listings | Airbnb (scraped) | ~5,000 → **3,456** | nightly price, lat/lon, property type, rating, reviews, superhost |
181+| 🏢 Rental listings | Realtor.ca (scraped) | 8,356 → **8,303** | monthly rent, lat/lon, bedrooms, bathrooms, building type, size |
162 182
163−`airbnb_count_r` · `airbnb_density_r` (count/πr²) · `mean_airbnb_price_r` · 183+**Spatial merge** — for every rental listing × radius *r* ∈ {250 m, 500 m, 1 km, 2 km}, six metrics computed against all Airbnb listings via chunked vectorised Haversine distances: `count` · `density` · `mean_price` · `share_entire_home` · `mean_rating` · `superhost_share`, plus city-level aggregates.
164−`share_entire_home_r` · `mean_rating_r` · `superhost_share_r`
165 184
166−plus city-level aggregates (`airbnb_count_city`, …) merged by standardised name. 185+> 🔒 Raw files are **not distributed** (`data/raw/README.md`). The committed parquets make steps 04–11 and the paper fully reproducible without them.
167 186
168 --- 187 ---
169 188
170 ## ⚙️ Analysis Pipeline 189 ## ⚙️ Analysis Pipeline
171 190
172−Each script is standalone (`python3 scripts/NN_*.py`), reads/writes only through 191+| Step | Script | Produces | ⏱ |
173−`src/config.py` paths, and prints a full execution log. 192+|:-:|---|---|:-:|
174− 193+| 01 | `01_inspect_raw_data.py` | data dictionary log | ⚡ |
175−| Step | Script | Inputs | Outputs | Runtime* | 194+| 02 | `02_clean_airbnb.py` | `airbnb_clean.parquet` | ⚡ |
176−|---|---|---|---|---| 195+| 03 | `03_clean_rent.py` | `rent_clean.parquet` | ⚡ |
177−| 01 | `01_inspect_raw_data.py` | raw csv/json | `results/logs/data_inspection.txt` | s | 196+| 04 | `04_merge_data.py` | 3 merged parquets (buffer merge) | ~1 min |
178−| 02 | `02_clean_airbnb.py` | `airbnb.csv` | `airbnb_clean.parquet` | s | 197+| 05 | `05_descriptive_analysis.py` | 3 tables + 7 figures | ⚡ |
179−| 03 | `03_clean_rent.py` | `rent.json` | `rent_clean.parquet` | s | 198+| 06 | `06_hedonic_models.py` | Models 1–3 → 3 tables | ⚡ |
180−| 04 | `04_merge_data.py` | 2 clean parquets | 3 merged parquets | ~1 min | 199+| 07 | `07_spatial_models.py` | SAR/SEM + buffer robustness | ~1 min |
181−| 05 | `05_descriptive_analysis.py` | parquets | 3 tables + 7 figures | s | 200+| 08 | `08_quantile_models.py` | quantile table + coefficient path | ~1 min |
182−| 06 | `06_hedonic_models.py` | `merged_analysis` | Models 1–3 (3 tables) | s | 201+| 09 | `09_ml_robustness.py` | ML comparison + SHAP figures | ~2 min |
183−| 07 | `07_spatial_models.py` | `merged_analysis` | SAR/SEM + buffer tables, 1 figure | ~1 min | 202+| 10 | `10_robustness_tables_figures.py` | outlier/subsample tables + 3 figures | ⚡ |
184−| 08 | `08_quantile_models.py` | `merged_analysis` | quantile table + figure | ~1 min | 203+| 11 | `11_extended_robustness.py` | clustered SEs · ring · leave-one-out | ~1 min |
185−| 09 | `09_ml_robustness.py` | `merged_analysis` | ML table + 3 figures (SHAP) | ~2 min | 204+
186−| 10 | `10_robustness_tables_figures.py` | `merged_analysis` | 2 tables + 3 figures | s | 205+<sub>⚡ = under 15 s on Apple Silicon. Everything reads/writes through `src/config.py`; fully deterministic (seed 42).</sub>
187−| 11 | `11_extended_robustness.py` | `merged_analysis` | extended table + 1 figure | ~1 min |
188−
189−<sub>*Apple Silicon, Python 3.14; “s” < 15 seconds.</sub>
190 206
191 --- 207 ---
192 208
193 ## 📐 Econometric Models 209 ## 📐 Econometric Models
194 210
195−| Model | Specification | Estimator | Script | 211+| Model | Specification | Estimator |
196−|---|---|---|---| 212+|:-:|---|---|
197−| **1 (a–e)** Hedonic rent | `log_rent ~ airbnb_count_500m + bedrooms + bathrooms + building type [+ city FE]`; (1d) density, (1e) entire-home share | OLS, HC1 | 06 | 213+| **1a–1e** | `log_rent ~ airbnb_count_500m + X + building type [+ city FE]`; variants: density, entire-home share | OLS · HC1 |
198−| **2 (a–c)** Airbnb pricing | `log_price ~ mean_rent_city + listing controls [+ city FE]` | OLS, HC1 | 06 | 214+| **2a–2c** | `log_price ~ mean_rent_city + listing controls [+ city FE]` | OLS · HC1 |
199−| **3** City-level | forward `mean_log_rent ~ airbnb_count_city + W_c` and reverse (153 cities) | OLS, HC1 | 06 | 215+| **3** | city-level forward & reverse regressions (153 cities) | OLS · HC1 |
200−| **4** Spatial | SAR (`GM_Lag`) & SEM (`GM_Error`), KNN(k=5) row-standardised weights | GMM (spreg) | 07 | 216+| **4** | SAR & SEM, KNN(k=5) row-standardised weights | GMM — `GM_Lag` / `GM_Error` (Kelejian–Prucha) |
201−| **5** Quantile | τ ∈ {.10, .25, .50, .75, .90} + fine grid for the coefficient path | IRLS (statsmodels) | 08 | 217+| **5** | quantile regressions, τ ∈ {.10, .25, .50, .75, .90} + fine grid | IRLS (statsmodels) |
202−| **6** ML benchmark | OLS / LASSO / ENet / RF / GBM, 80/20 split, seed 42, SHAP on GBM | scikit-learn | 09 | 218+| **6** | OLS / LASSO / ENet / RF / GBM, 80/20 split, SHAP attribution | scikit-learn |
203 219
204 --- 220 ---
205 221
206 ## 🛡️ Robustness Programme 222 ## 🛡️ Robustness Programme
207 223
208−- **Buffer radii** 250 m → 2 km (Table 8, Figure 4) — monotone spatial decay. 224+<div align="center">
209−- **Alternative exposure**: density (≡ count at fixed r); entire-home share (null → discussed). 225+
210−- **Subsamples**: Montreal / outside-Montreal / apartments / houses (Table 11). 226+[![R1](https://img.shields.io/badge/☑-4_buffer_radii-2a9d8f?style=flat-square)]()
211−- **Outliers**: rent trimmed p5–p95; Airbnb count trimmed p1–p99 (Table 10). 227+[![R2](https://img.shields.io/badge/☑-alternative_exposures-2a9d8f?style=flat-square)]()
212−- **Extended** (Table 12, Figure 9): city-clustered SEs (tighter than HC1), 228+[![R3](https://img.shields.io/badge/☑-4_subsamples-2a9d8f?style=flat-square)]()
213− interior-size control, log(1+count), 500 m + ring 500 m–1 km decomposition, 229+[![R4](https://img.shields.io/badge/☑-outlier_trims-2a9d8f?style=flat-square)]()
214− leave-one-city-out over the 8 largest cities. 230+[![R5](https://img.shields.io/badge/☑-city--clustered_SEs-2a9d8f?style=flat-square)]()
231+[![R6](https://img.shields.io/badge/☑-size_control-2a9d8f?style=flat-square)]()
232+[![R7](https://img.shields.io/badge/☑-log(1%2Bcount)-2a9d8f?style=flat-square)]()
233+[![R8](https://img.shields.io/badge/☑-ring_decomposition-2a9d8f?style=flat-square)]()
234+[![R9](https://img.shields.io/badge/☑-leave--one--city--out-2a9d8f?style=flat-square)]()
235+
236+</div>
237+
238+The Airbnb coefficient stays **positive and significant at 1%** in every single check; the leave-one-city-out range is 0.0038–0.0040 — excluding Montreal (half the sample) barely moves it.
215 239
216 --- 240 ---
217 241
218 ## 🚀 Reproducing Everything 242 ## 🚀 Reproducing Everything
219 243
220 ```bash 244 ```bash
221−git clone https://github.com/spboucher-ai/wp5_uqo.git 245+git clone https://github.com/spboucher-ai/wp5_uqo.git && cd wp5_uqo
222−cd wp5_uqo
223 python3 -m pip install -r requirements.txt 246 python3 -m pip install -r requirements.txt
224 247
225−# (optional) steps 01–03 need data/raw/airbnb.csv + rent.json 248+# full pipeline (steps 01–03 optional: need data/raw/)
226 for s in scripts/0{4..9}_*.py scripts/1{0,1}_*.py; do python3 "$s"; done 249 for s in scripts/0{4..9}_*.py scripts/1{0,1}_*.py; do python3 "$s"; done
227 250
228−cd paper && latexmk # → paper/main.pdf 251+cd paper && latexmk # → paper/main.pdf (51 pages)
229 ``` 252 ```
230 253
231−Or step by step:
232−
233−```bash
234−python3 scripts/04_merge_data.py # spatial buffer merge
235−python3 scripts/05_descriptive_analysis.py # summary stats + descriptive figures
236−python3 scripts/06_hedonic_models.py # Models 1–3
237−python3 scripts/07_spatial_models.py # Model 4 (SAR/SEM) + buffer robustness
238−python3 scripts/08_quantile_models.py # Model 5
239−python3 scripts/09_ml_robustness.py # Model 6 + SHAP
240−python3 scripts/10_robustness_tables_figures.py
241−python3 scripts/11_extended_robustness.py # clustered SEs, ring, leave-one-out
242−cd paper && latexmk # or: make
243−```
244−
245−Everything is deterministic (fixed seed 42 for ML; no other stochastic steps).
246−
247 --- 254 ---
248 255
249 ## 🧰 Environment & Dependencies 256 ## 🧰 Environment & Dependencies
250 257
251−Developed and verified on **macOS (Apple Silicon), Python 3.14.4**, TeX Live 2025. 258+Verified on **macOS (Apple Silicon) · Python 3.14.4 · TeX Live 2025**.
252 259
253−| Package | Pinned | Used for | 260+| Package | Pinned | Role |
254−|---|---|---| 261+|---|:-:|---|
255−| pandas / pyarrow | 3.0.2 / 24.0.0 | data wrangling, parquet I/O | 262+| pandas / pyarrow | 3.0.2 / 24.0.0 | wrangling, parquet I/O |
256−| numpy / scipy | 2.4.4 / 1.17.1 | vectorised Haversine, KD-tree | 263+| numpy / scipy | 2.4.4 / 1.17.1 | Haversine, KD-tree |
257 | statsmodels | 0.14.6 | OLS (HC1/cluster), quantile regression | 264 | statsmodels | 0.14.6 | OLS (HC1/cluster), quantile regression |
258−| libpysal / spreg | 4.14.1 / 1.9.0 | KNN weights, SAR (GM_Lag), SEM (GM_Error) | 265+| libpysal / spreg | 4.14.1 / 1.9.0 | KNN weights, SAR/SEM (GMM) |
259 | scikit-learn | 1.6.1 | LASSO, ENet, RF, GBM | 266 | scikit-learn | 1.6.1 | LASSO, ENet, RF, GBM |
260−| shap | 0.48.0 | SHAP values & plots | 267+| shap | 0.48.0 | SHAP values |
261 | matplotlib | 3.10.9 | all figures | 268 | matplotlib | 3.10.9 | all figures |
262 269
263−`pip install -r requirements.txt` reproduces the exact environment.
264−
265−---
266−
267−## 📤 Outputs Inventory
268−
269−<details>
270−<summary><b>16 figures</b> (click to expand)</summary>
271−
272−`map_airbnb` · `map_rent` · `dist_airbnb_price` · `dist_rent` · `airbnb_by_city` ·
273−`rent_by_city` · `scatter_airbnb_rent` · `rent_airbnb_heatmap` ·
274−`coefficient_buffer_comparison` · `quantile_coefficients` ·
275−`ml_predicted_vs_actual` · `feature_importance` · `shap_summary` ·
276−`coefficient_robustness` · `rent_by_airbnb_bins` · `leave_one_city_out`
277−</details>
278−
279−<details>
280−<summary><b>13 LaTeX tables + 4 CSVs</b> (click to expand)</summary>
281−
282−`summary_stats_airbnb` · `summary_stats_rent` · `correlation_matrix` ·
283−`hedonic_rent_baseline` · `hedonic_airbnb_pricing` · `city_level_interaction` ·
284−`spatial_models` · `buffer_robustness` · `quantile_regression` ·
285−`ml_comparison` · `robustness_outliers` · `robustness_subsamples` ·
286−`extended_robustness` (+ CSV mirrors of the summary/correlation/ML tables)
287−
288−Tables are emitted as **booktabs fragments** — the paper wraps each `\input`
289−in its own `table` environment, so the pipeline feeds the PDF with no manual edits.
290−</details>
291−
292 --- 270 ---
293 271
294 ## ✅ Verification & Provenance 272 ## ✅ Verification & Provenance
295 273
296−This repository was rebuilt on **2026-08-05** from the original project 274+Rebuilt on **2026-08-05** from the original project (kept untouched as backup) under a strict *no-result-changes* policy:
297−(`immo-wp5-spb-20260519`, kept untouched as backup) with a strict
298−*no-result-changes* policy:
299 275
300−- The 3 merged parquets regenerated from the committed clean data are 276+- ✅ 3/3 merged parquets **value-identical** to the originals
301− **value-identical** to the originals (`pandas.testing.assert_frame_equal`). 277+- ✅ **14/16 tables byte-identical** (2 residual float-level diffs documented in [`AUDIT.md`](AUDIT.md) §8)
302−- **14 / 16 tables byte-identical**; the 2 residual differences are float-level 278+- ✅ All figures regenerated, including 5 missing from the original (which broke its LaTeX build)
303− and fully documented in [`AUDIT.md`](AUDIT.md) §8. 279+- ✅ Every text↔table inconsistency corrected *in the prose only* — itemised in [`CHANGES.md`](CHANGES.md)
304−- All figures regenerated, including 5 that were missing from the original 280+- ✅ All 32 references added in the scholarly upgrade **verified via Crossref/JMLR** (DOIs) — per-reference justification in [`UPGRADE_REPORT.md`](UPGRADE_REPORT.md)
305− project (which previously broke the LaTeX build).
306−- Every text↔table inconsistency found in the original manuscript was corrected
307− *in the prose* (never in the results) and is itemised in
308− [`CHANGES.md`](CHANGES.md) §4 — start there if you review the paper.
309 281
310 --- 282 ---
311 283
@@ -330,18 +302,19 @@ This repository was rebuilt on **2026-08-05** from the original project
330 302
331 ## 📬 Contact 303 ## 📬 Contact
332 304
305+<div align="center">
306+
333 **Simon-Pierre Boucher** 307 **Simon-Pierre Boucher**
334 Département des sciences administratives · Université du Québec en Outaouais 308 Département des sciences administratives · Université du Québec en Outaouais
335 283, boulevard Alexandre-Taché, Gatineau (Québec) J9A 1L8, Canada 309 283, boulevard Alexandre-Taché, Gatineau (Québec) J9A 1L8, Canada
336 310
337−- 📧 **contact@spboucher.ai** 311+[![Email](https://img.shields.io/badge/✉️_contact@spboucher.ai-primary-EA4335?style=for-the-badge)](mailto:contact@spboucher.ai)
338−- 🏛️ simon-pierre.boucher@uqo.ca 312+[![UQO](https://img.shields.io/badge/🏛️_simon--pierre.boucher@uqo.ca-00457C?style=for-the-badge)](mailto:simon-pierre.boucher@uqo.ca)
339 313
340−*Issues and pull requests are welcome — please do not open issues asking for the 314+<sub>Issues and PRs welcome — please do not request the raw scraped data, which cannot be redistributed.</sub>
341−raw scraped data, which cannot be redistributed.*
342 315
343−--- 316+<br><br>
317+
318+<sub>© 2026 Simon-Pierre Boucher — UQO Working Paper No. 5 · Code and manuscript for academic use.</sub>
344 319
345−<div align="center">
346−<sub>© 2026 Simon-Pierre Boucher — UQO Working Paper No. 5. Code and manuscript for academic use.</sub>
347 </div> 320 </div>
348 321
Menu

Theme