Redesign README: badge-rich presentation, paper section-by-section guide, headline-result badges
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Showing 1 changed file with +201 and −228
modified
README.md
+201 −228
@@ -2,310 +2,282 @@ | ||
| 2 | 2 | |
| 3 | 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 | −[](https://www.python.org/) | |
| 10 | −[](https://pandas.pydata.org/) | |
| 11 | −[](https://www.statsmodels.org/) | |
| 12 | −[](https://scikit-learn.org/) | |
| 13 | −[](https://pysal.org/) | |
| 14 | −[](https://shap.readthedocs.io/) | |
| 15 | −[](https://www.latex-project.org/) | |
| 9 | +**UQO Working Paper No. 5** | |
| 16 | 10 | |
| 17 | −[]() | |
| 18 | −[]() | |
| 19 | −[]() | |
| 20 | −[]() | |
| 21 | −[]() | |
| 22 | −[]() | |
| 11 | +<br> | |
| 23 | 12 | |
| 24 | −**Simon-Pierre Boucher** · Département des sciences administratives, Université du Québec en Outaouais (UQO) | |
| 25 | −📧 [contact@spboucher.ai](mailto:contact@spboucher.ai) · 📄 [`paper/main.pdf`](paper/main.pdf) | |
| 13 | +[](mailto:contact@spboucher.ai) | |
| 14 | +[](https://uqo.ca) | |
| 15 | +[](mailto:contact@spboucher.ai) | |
| 16 | + | |
| 17 | +<br> | |
| 18 | + | |
| 19 | +**— The paper at a glance —** | |
| 20 | + | |
| 21 | +[](paper/main.pdf) | |
| 22 | +[-blueviolet?style=flat-square)]() | |
| 23 | +[]() | |
| 24 | +[-e63946?style=flat-square)]() | |
| 25 | +[]() | |
| 26 | +[]() | |
| 27 | +[]() | |
| 28 | + | |
| 29 | +**— Methods —** | |
| 30 | + | |
| 31 | +[]() | |
| 32 | +[-8A2BE2?style=flat-square)]() | |
| 33 | +[]() | |
| 34 | +[]() | |
| 35 | +[]() | |
| 36 | + | |
| 37 | +**— Stack —** | |
| 38 | + | |
| 39 | +[](https://www.python.org/) | |
| 40 | +[](https://pandas.pydata.org/) | |
| 41 | +[](https://www.statsmodels.org/) | |
| 42 | +[](https://scikit-learn.org/) | |
| 43 | +[](https://pysal.org/) | |
| 44 | +[](https://shap.readthedocs.io/) | |
| 45 | +[](https://www.latex-project.org/) | |
| 46 | + | |
| 47 | +**— Quality —** | |
| 48 | + | |
| 49 | +[]() | |
| 50 | +[]() | |
| 51 | +[]() | |
| 52 | +[]() | |
| 26 | 53 | |
| 27 | 54 | </div> |
| 28 | 55 | |
| 29 | 56 | --- |
| 30 | 57 | |
| 31 | −## 📋 Table of Contents | |
| 32 | − | |
| 33 | −1. [Overview](#-overview) | |
| 34 | −2. [Key Findings](#-key-findings) | |
| 35 | −3. [Repository Structure](#-repository-structure) | |
| 36 | −4. [Data](#-data) | |
| 37 | −5. [Analysis Pipeline](#-analysis-pipeline) | |
| 38 | −6. [Econometric Models](#-econometric-models) | |
| 39 | −7. [Robustness Programme](#-robustness-programme) | |
| 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) | |
| 58 | +## 🧭 Navigation | |
| 59 | + | |
| 60 | +| | | | | |
| 61 | +|---|---|---| | |
| 62 | +| [📜 The Paper](#-the-paper) | [🔍 Abstract](#-abstract) | [📖 Inside the Paper](#-inside-the-paper--section-by-section) | | |
| 63 | +| [🏆 Headline Results](#-headline-results) | [📁 Repository Map](#-repository-map) | [💾 Data](#-data) | | |
| 64 | +| [⚙️ Pipeline](#️-analysis-pipeline) | [📐 Models](#-econometric-models) | [🛡️ Robustness](#️-robustness-programme) | | |
| 65 | +| [🚀 Reproduce](#-reproducing-everything) | [🧰 Environment](#-environment--dependencies) | [✅ Provenance](#-verification--provenance) | | |
| 66 | +| [📖 Citation](#-citation) | [📬 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 | |
| 52 | −Working Paper No. 5: data pipeline, econometric analysis, machine-learning | |
| 53 | −robustness suite, all figures/tables, and the LaTeX source of the paper. | |
| 72 | +<div align="center"> | |
| 73 | + | |
| 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 | +> [](paper/main.pdf) | |
| 81 | + | |
| 82 | +</div> | |
| 54 | 83 | |
| 55 | −The paper studies the relationship between **Airbnb short-term rental activity** | |
| 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). | |
| 84 | +## 🔍 Abstract | |
| 62 | 85 | |
| 63 | −> ⚠️ **Interpretation note.** The design is cross-sectional. All estimates are | |
| 64 | −> *conditional correlations*, not causal effects — the paper is explicit about | |
| 65 | −> reverse causality, omitted amenities, and spatial sorting. | |
| 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**. | |
| 87 | +> | |
| 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 | +[]() | |
| 114 | +[-f4a261?style=for-the-badge)]() | |
| 115 | +[=0.0047-e9c46a?style=for-the-badge)]() | |
| 116 | +[-2a9d8f?style=for-the-badge)]() | |
| 117 | +[]() | |
| 118 | +[-6d597a?style=for-the-badge)]() | |
| 119 | + | |
| 120 | +</div> | |
| 70 | 121 | |
| 71 | 122 | | # | Finding | Evidence | |
| 72 | −|---|---------|----------| | |
| 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 | | |
| 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 | | |
| 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 | | |
| 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 | | |
| 77 | −| 5 | Airbnb **nightly prices** are driven by listing characteristics; city-level mean rent carries **no significant premium** | Table 4 | | |
| 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 | | |
| 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. | |
| 123 | +|:-:|---|---| | |
| 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 | | |
| 125 | +| 2 | Per-listing association **decays monotonically with distance** | Table 10 · Fig. 4 | | |
| 126 | +| 3 | Significant at **every quantile**, rising to **0.0047 at τ = 0.90** (~25% above OLS) | Table 7 · Fig. 5 | | |
| 127 | +| 4 | **Spatial dependence is substantial** and the coefficient survives GMM spatial models with mild attenuation (0.0034) | Table 6 | | |
| 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 | | |
| 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 | | |
| 84 | 130 | |
| 85 | 131 | --- |
| 86 | 132 | |
| 87 | −## 📁 Repository Structure | |
| 133 | +## 📁 Repository Map | |
| 88 | 134 | |
| 89 | 135 | ``` |
| 90 | 136 | wp5_uqo/ |
| 91 | −├── README.md ← you are here | |
| 92 | −├── AUDIT.md ← forensic audit of the original project | |
| 93 | −├── CHANGES.md ← full restructuring & rewrite log | |
| 94 | −├── requirements.txt ← pinned dependencies (exact reproduction) | |
| 95 | −├── .gitignore | |
| 137 | +├── 📄 README.md ← you are here | |
| 138 | +├── 🔎 AUDIT.md ← forensic audit of the original project | |
| 139 | +├── 📝 CHANGES.md ← restructuring & rewrite log | |
| 140 | +├── 🧪 PAPER_REVIEW.md ← pre-upgrade critical assessment | |
| 141 | +├── 📚 UPGRADE_REPORT.md ← literature expansion report (per-ref justification) | |
| 142 | +├── 📦 requirements.txt ← pinned dependencies | |
| 96 | 143 | │ |
| 97 | 144 | ├── data/ |
| 98 | −│ ├── raw/ ← airbnb.csv + rent.json (NOT distributed) | |
| 99 | −│ │ └── README.md ← what the raw files are & where they go | |
| 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 | |
| 145 | +│ ├── raw/ ← airbnb.csv + rent.json (NOT distributed → README) | |
| 146 | +│ └── processed/ ← 5 verified parquet datasets (committed) | |
| 106 | 147 | │ |
| 107 | −├── src/ ← shared modules (imported by all scripts) | |
| 108 | −│ ├── config.py ← every path & constant; output dirs auto-created | |
| 109 | −│ ├── geo.py ← vectorised Haversine distance matrix | |
| 110 | −│ ├── latex_tables.py ← significance stars + stargazer-style builder | |
| 111 | −│ └── plotting.py ← the two publication matplotlib styles | |
| 148 | +├── src/ ← shared modules | |
| 149 | +│ ├── config.py ← all paths & constants | |
| 150 | +│ ├── geo.py ← vectorised Haversine matrix | |
| 151 | +│ ├── latex_tables.py ← stargazer-style table builder | |
| 152 | +│ └── plotting.py ← publication matplotlib styles | |
| 112 | 153 | │ |
| 113 | −├── scripts/ ← numbered pipeline entry points | |
| 114 | −│ ├── 01_inspect_raw_data.py ├── 07_spatial_models.py | |
| 115 | −│ ├── 02_clean_airbnb.py ├── 08_quantile_models.py | |
| 116 | −│ ├── 03_clean_rent.py ├── 09_ml_robustness.py | |
| 117 | −│ ├── 04_merge_data.py ├── 10_robustness_tables_figures.py | |
| 118 | −│ ├── 05_descriptive_analysis.py └── 11_extended_robustness.py | |
| 119 | −│ └── 06_hedonic_models.py | |
| 154 | +├── scripts/ ← 01 → 11 numbered pipeline | |
| 155 | +├── figures/ ← 16 publication PDF figures | |
| 156 | +├── results/tables/ ← 13 LaTeX fragments + 4 CSVs | |
| 157 | +├── results/logs/ ← data-inspection log | |
| 120 | 158 | │ |
| 121 | −├── figures/ ← all 16 PDF figures (pipeline output) | |
| 122 | −├── results/ | |
| 123 | −│ ├── tables/ ← 13 LaTeX fragments + 4 CSVs | |
| 124 | −│ └── logs/ ← data-inspection log | |
| 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 | |
| 159 | +└── paper/ ← LaTeX → 📄 main.pdf (51 pages) | |
| 160 | + ├── main.tex · references.bib (56 entries) · Makefile | |
| 161 | + ├── sections/ (titlepage + 8 sections) | |
| 162 | + └── appendix/ (data · methods · robustness) | |
| 132 | 163 | ``` |
| 133 | 164 | |
| 134 | 165 | --- |
| 135 | 166 | |
| 136 | 167 | ## 💾 Data |
| 137 | 168 | |
| 138 | −### Sources | |
| 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 | |
| 169 | +<div align="center"> | |
| 150 | 170 | |
| 151 | −- City names standardised across accented/unaccented variants and boroughs | |
| 152 | − (Montréal arrondissements → *Montreal*, Québec arrondissements → *Québec*, …). | |
| 153 | −- Prices/rents winsorised at p1/p99; log transforms (`log_price`, `log_rent`). | |
| 154 | −- Ratings imputed at the median (unrated ≠ zero-quality); review counts at 0. | |
| 155 | −- `is_entire_home` built from property types (House, Cabin/Chalet, Condo). | |
| 171 | +[]() | |
| 172 | +[]() | |
| 173 | +[]() | |
| 174 | +[]() | |
| 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}, | |
| 160 | −six exposure metrics are computed against all Airbnb listings via a **chunked, | |
| 161 | −fully vectorised Haversine distance matrix** (`src/geo.py`, 500 rentals/chunk): | |
| 178 | +| Dataset | Source | Raw → Clean | Key fields | | |
| 179 | +|---|---|---|---| | |
| 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` · | |
| 164 | −`share_entire_home_r` · `mean_rating_r` · `superhost_share_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. | |
| 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 | 189 | ## ⚙️ Analysis Pipeline |
| 171 | 190 | |
| 172 | −Each script is standalone (`python3 scripts/NN_*.py`), reads/writes only through | |
| 173 | −`src/config.py` paths, and prints a full execution log. | |
| 174 | − | |
| 175 | −| Step | Script | Inputs | Outputs | Runtime* | | |
| 176 | −|---|---|---|---|---| | |
| 177 | −| 01 | `01_inspect_raw_data.py` | raw csv/json | `results/logs/data_inspection.txt` | s | | |
| 178 | −| 02 | `02_clean_airbnb.py` | `airbnb.csv` | `airbnb_clean.parquet` | s | | |
| 179 | −| 03 | `03_clean_rent.py` | `rent.json` | `rent_clean.parquet` | s | | |
| 180 | −| 04 | `04_merge_data.py` | 2 clean parquets | 3 merged parquets | ~1 min | | |
| 181 | −| 05 | `05_descriptive_analysis.py` | parquets | 3 tables + 7 figures | s | | |
| 182 | −| 06 | `06_hedonic_models.py` | `merged_analysis` | Models 1–3 (3 tables) | s | | |
| 183 | −| 07 | `07_spatial_models.py` | `merged_analysis` | SAR/SEM + buffer tables, 1 figure | ~1 min | | |
| 184 | −| 08 | `08_quantile_models.py` | `merged_analysis` | quantile table + figure | ~1 min | | |
| 185 | −| 09 | `09_ml_robustness.py` | `merged_analysis` | ML table + 3 figures (SHAP) | ~2 min | | |
| 186 | −| 10 | `10_robustness_tables_figures.py` | `merged_analysis` | 2 tables + 3 figures | s | | |
| 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> | |
| 191 | +| Step | Script | Produces | ⏱ | | |
| 192 | +|:-:|---|---|:-:| | |
| 193 | +| 01 | `01_inspect_raw_data.py` | data dictionary log | ⚡ | | |
| 194 | +| 02 | `02_clean_airbnb.py` | `airbnb_clean.parquet` | ⚡ | | |
| 195 | +| 03 | `03_clean_rent.py` | `rent_clean.parquet` | ⚡ | | |
| 196 | +| 04 | `04_merge_data.py` | 3 merged parquets (buffer merge) | ~1 min | | |
| 197 | +| 05 | `05_descriptive_analysis.py` | 3 tables + 7 figures | ⚡ | | |
| 198 | +| 06 | `06_hedonic_models.py` | Models 1–3 → 3 tables | ⚡ | | |
| 199 | +| 07 | `07_spatial_models.py` | SAR/SEM + buffer robustness | ~1 min | | |
| 200 | +| 08 | `08_quantile_models.py` | quantile table + coefficient path | ~1 min | | |
| 201 | +| 09 | `09_ml_robustness.py` | ML comparison + SHAP figures | ~2 min | | |
| 202 | +| 10 | `10_robustness_tables_figures.py` | outlier/subsample tables + 3 figures | ⚡ | | |
| 203 | +| 11 | `11_extended_robustness.py` | clustered SEs · ring · leave-one-out | ~1 min | | |
| 204 | + | |
| 205 | +<sub>⚡ = under 15 s on Apple Silicon. Everything reads/writes through `src/config.py`; fully deterministic (seed 42).</sub> | |
| 190 | 206 | |
| 191 | 207 | --- |
| 192 | 208 | |
| 193 | 209 | ## 📐 Econometric Models |
| 194 | 210 | |
| 195 | −| Model | Specification | Estimator | Script | | |
| 196 | −|---|---|---|---| | |
| 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 | | |
| 198 | −| **2 (a–c)** Airbnb pricing | `log_price ~ mean_rent_city + listing controls [+ city FE]` | OLS, HC1 | 06 | | |
| 199 | −| **3** City-level | forward `mean_log_rent ~ airbnb_count_city + W_c` and reverse (153 cities) | OLS, HC1 | 06 | | |
| 200 | −| **4** Spatial | SAR (`GM_Lag`) & SEM (`GM_Error`), KNN(k=5) row-standardised weights | GMM (spreg) | 07 | | |
| 201 | −| **5** Quantile | τ ∈ {.10, .25, .50, .75, .90} + fine grid for the coefficient path | IRLS (statsmodels) | 08 | | |
| 202 | −| **6** ML benchmark | OLS / LASSO / ENet / RF / GBM, 80/20 split, seed 42, SHAP on GBM | scikit-learn | 09 | | |
| 211 | +| Model | Specification | Estimator | | |
| 212 | +|:-:|---|---| | |
| 213 | +| **1a–1e** | `log_rent ~ airbnb_count_500m + X + building type [+ city FE]`; variants: density, entire-home share | OLS · HC1 | | |
| 214 | +| **2a–2c** | `log_price ~ mean_rent_city + listing controls [+ city FE]` | OLS · HC1 | | |
| 215 | +| **3** | city-level forward & reverse regressions (153 cities) | OLS · HC1 | | |
| 216 | +| **4** | SAR & SEM, KNN(k=5) row-standardised weights | GMM — `GM_Lag` / `GM_Error` (Kelejian–Prucha) | | |
| 217 | +| **5** | quantile regressions, τ ∈ {.10, .25, .50, .75, .90} + fine grid | IRLS (statsmodels) | | |
| 218 | +| **6** | OLS / LASSO / ENet / RF / GBM, 80/20 split, SHAP attribution | scikit-learn | | |
| 203 | 219 | |
| 204 | 220 | --- |
| 205 | 221 | |
| 206 | 222 | ## 🛡️ Robustness Programme |
| 207 | 223 | |
| 208 | −- **Buffer radii** 250 m → 2 km (Table 8, Figure 4) — monotone spatial decay. | |
| 209 | −- **Alternative exposure**: density (≡ count at fixed r); entire-home share (null → discussed). | |
| 210 | −- **Subsamples**: Montreal / outside-Montreal / apartments / houses (Table 11). | |
| 211 | −- **Outliers**: rent trimmed p5–p95; Airbnb count trimmed p1–p99 (Table 10). | |
| 212 | −- **Extended** (Table 12, Figure 9): city-clustered SEs (tighter than HC1), | |
| 213 | − interior-size control, log(1+count), 500 m + ring 500 m–1 km decomposition, | |
| 214 | − leave-one-city-out over the 8 largest cities. | |
| 224 | +<div align="center"> | |
| 225 | + | |
| 226 | +[]() | |
| 227 | +[]() | |
| 228 | +[]() | |
| 229 | +[]() | |
| 230 | +[]() | |
| 231 | +[]() | |
| 232 | +[-2a9d8f?style=flat-square)]() | |
| 233 | +[]() | |
| 234 | +[]() | |
| 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 | 242 | ## 🚀 Reproducing Everything |
| 219 | 243 | |
| 220 | 244 | ```bash |
| 221 | −git clone https://github.com/spboucher-ai/wp5_uqo.git | |
| 222 | −cd wp5_uqo | |
| 245 | +git clone https://github.com/spboucher-ai/wp5_uqo.git && cd wp5_uqo | |
| 223 | 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 | 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 | 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 | | |
| 254 | −|---|---|---| | |
| 255 | −| pandas / pyarrow | 3.0.2 / 24.0.0 | data wrangling, parquet I/O | | |
| 256 | −| numpy / scipy | 2.4.4 / 1.17.1 | vectorised Haversine, KD-tree | | |
| 260 | +| Package | Pinned | Role | | |
| 261 | +|---|:-:|---| | |
| 262 | +| pandas / pyarrow | 3.0.2 / 24.0.0 | wrangling, parquet I/O | | |
| 263 | +| numpy / scipy | 2.4.4 / 1.17.1 | Haversine, KD-tree | | |
| 257 | 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 | 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 | 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 | 272 | ## ✅ Verification & Provenance |
| 295 | 273 | |
| 296 | −This repository was rebuilt on **2026-08-05** from the original project | |
| 297 | −(`immo-wp5-spb-20260519`, kept untouched as backup) with a strict | |
| 298 | −*no-result-changes* policy: | |
| 274 | +Rebuilt on **2026-08-05** from the original project (kept untouched as backup) under a strict *no-result-changes* policy: | |
| 299 | 275 | |
| 300 | −- The 3 merged parquets regenerated from the committed clean data are | |
| 301 | − **value-identical** to the originals (`pandas.testing.assert_frame_equal`). | |
| 302 | −- **14 / 16 tables byte-identical**; the 2 residual differences are float-level | |
| 303 | − and fully documented in [`AUDIT.md`](AUDIT.md) §8. | |
| 304 | −- All figures regenerated, including 5 that were missing from the original | |
| 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. | |
| 276 | +- ✅ 3/3 merged parquets **value-identical** to the originals | |
| 277 | +- ✅ **14/16 tables byte-identical** (2 residual float-level diffs documented in [`AUDIT.md`](AUDIT.md) §8) | |
| 278 | +- ✅ All figures regenerated, including 5 missing from the original (which broke its LaTeX build) | |
| 279 | +- ✅ Every text↔table inconsistency corrected *in the prose only* — itemised in [`CHANGES.md`](CHANGES.md) | |
| 280 | +- ✅ All 32 references added in the scholarly upgrade **verified via Crossref/JMLR** (DOIs) — per-reference justification in [`UPGRADE_REPORT.md`](UPGRADE_REPORT.md) | |
| 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 | 303 | ## 📬 Contact |
| 332 | 304 | |
| 305 | +<div align="center"> | |
| 306 | + | |
| 333 | 307 | **Simon-Pierre Boucher** |
| 334 | 308 | Département des sciences administratives · Université du Québec en Outaouais |
| 335 | 309 | 283, boulevard Alexandre-Taché, Gatineau (Québec) J9A 1L8, Canada |
| 336 | 310 | |
| 337 | −- 📧 **contact@spboucher.ai** | |
| 338 | −- 🏛️ simon-pierre.boucher@uqo.ca | |
| 311 | +[](mailto:contact@spboucher.ai) | |
| 312 | +[](mailto:simon-pierre.boucher@uqo.ca) | |
| 339 | 313 | |
| 340 | −*Issues and pull requests are welcome — please do not open issues asking for the | |
| 341 | −raw scraped data, which cannot be redistributed.* | |
| 314 | +<sub>Issues and PRs welcome — please do not request the raw scraped data, which cannot be redistributed.</sub> | |
| 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 | 320 | </div> |
| 348 | 321 | |