WP10 — The Assessment Gap in Quebec: full research project
End-to-end pipeline (sample, IAAO diagnostics, Cheng FE / Clapp IV / quantile / heterogeneity / horizontal / tax shift), 10 journal-calibre figures, 10 LaTeX tables, 29-page paper with literature review. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Showing 65 changed files with +3,864 and −0
added
.gitignore
+20 −0
@@ -0,0 +1,20 @@ | ||
| 1 | +# Raw data snapshot — 100 MB parquet, not redistributed | |
| 2 | +data/raw/*.parquet | |
| 3 | +data/processed/*.parquet | |
| 4 | + | |
| 5 | +# Python | |
| 6 | +__pycache__/ | |
| 7 | +*.pyc | |
| 8 | + | |
| 9 | +# LaTeX build artifacts (paper/main.pdf IS committed) | |
| 10 | +paper/*.aux | |
| 11 | +paper/*.log | |
| 12 | +paper/*.out | |
| 13 | +paper/*.fls | |
| 14 | +paper/*.fdb_latexmk | |
| 15 | +paper/*.bbl | |
| 16 | +paper/*.blg | |
| 17 | +paper/*.synctex.gz | |
| 18 | + | |
| 19 | +# macOS | |
| 20 | +.DS_Store | |
added
README.md
+200 −0
@@ -0,0 +1,200 @@ | ||
| 1 | +<!-- Author: Simon-Pierre Boucher — contact@spboucher.ai --> | |
| 2 | +# ⚖️ The Assessment Gap in Quebec | |
| 3 | + | |
| 4 | +**UQO Working Paper No. 10** — *Vertical and Horizontal Inequity in Municipal | |
| 5 | +Property Valuation: Evidence from 522,769 Sales Matched to the Assessment Roll* | |
| 6 | + | |
| 7 | +[-b31b1b?logo=latex&logoColor=white)](paper/main.pdf) | |
| 8 | +[](requirements.txt) | |
| 9 | +[](data/raw/README.md) | |
| 10 | +[-4051b5)](src/wp10/models.py) | |
| 11 | +[](scripts/) | |
| 12 | +[](https://uqo.ca) | |
| 13 | +[](mailto:contact@spboucher.ai) | |
| 14 | +[](mailto:contact@spboucher.ai) | |
| 15 | + | |
| 16 | +> **TL;DR** — Matching 522,769 Quebec residential sales (2021–2026) to the triennial | |
| 17 | +> assessment rolls that tax them, this paper delivers the **first province-wide audit | |
| 18 | +> of property-assessment equity in Canada**. Within the same municipality × roll × | |
| 19 | +> year, the elasticity of the assessment ratio w.r.t. price is **−0.34** (Cheng FE) | |
| 20 | +> and still **−0.08** under Clapp's measurement-error-robust IV. **99% of | |
| 21 | +> municipalities** fail the IAAO uniformity standard, **95%** fail its vertical-equity | |
| 22 | +> standard — Montréal is the lone progressive large market. The median dwelling in the | |
| 23 | +> bottom local price decile pays **~65% more property tax** than uniform assessment | |
| 24 | +> would imply; the top decile pays ~5% less. | |
| 25 | + | |
| 26 | +--- | |
| 27 | + | |
| 28 | +## 📖 Table of contents | |
| 29 | + | |
| 30 | +1. [What this paper does](#-what-this-paper-does) | |
| 31 | +2. [Headline results](#-headline-results) | |
| 32 | +3. [Repository layout](#-repository-layout) | |
| 33 | +4. [Quick start](#-quick-start) | |
| 34 | +5. [The pipeline, step by step](#-the-pipeline-step-by-step) | |
| 35 | +6. [Data](#-data) | |
| 36 | +7. [Methodology](#-methodology) | |
| 37 | +8. [Figures & tables inventory](#-figures--tables-inventory) | |
| 38 | +9. [Limitations](#-limitations) | |
| 39 | +10. [Citation](#-citation) | |
| 40 | +11. [Author & contact](#-author--contact) | |
| 41 | + | |
| 42 | +--- | |
| 43 | + | |
| 44 | +## 🎯 What this paper does | |
| 45 | + | |
| 46 | +Quebec taxes every dwelling in proportion to its **assessed value**, redrawn on a | |
| 47 | +triennial roll whose values must, by statute (art. 46 LFM), reflect market conditions | |
| 48 | +at a single reference date. If assessments are *regressive* — cheap homes overvalued | |
| 49 | +relative to expensive ones — the effective tax rate falls with wealth, silently. | |
| 50 | +This project measures that gap at province scale: | |
| 51 | + | |
| 52 | +``` | |
| 53 | +ln AV_i = α_c(i) + β · ln SP_i + ε_i (c = municipality × roll × sale-year) | |
| 54 | +``` | |
| 55 | + | |
| 56 | +- **γ = β − 1** is the elasticity of the assessment ratio w.r.t. price | |
| 57 | + (γ < 0 ⇒ regressive), identified **within** 2,884 market-timing cells; | |
| 58 | +- the **Clapp (1990) rank IV** purges the attenuation bias that pushes naïve | |
| 59 | + ratio studies toward spurious regressivity; | |
| 60 | +- the full **IAAO ratio-study battery** (median ratio, COD, PRD, PRB) with | |
| 61 | + bootstrap CIs is computed for every municipality with ≥ 100 usable sales; | |
| 62 | +- quantile profiles, subgroup elasticities, horizontal-dispersion regressions and | |
| 63 | + an exact **intra-municipal tax-shift** calculation complete the audit. | |
| 64 | + | |
| 65 | +## 🏆 Headline results | |
| 66 | + | |
| 67 | +| Object | Estimate | | |
| 68 | +|---|---| | |
| 69 | +| Cheng elasticity, pooled OLS | β = 0.812 (0.029) | | |
| 70 | +| Cheng elasticity, cell FE | **β = 0.656 (0.029)** → γ = −0.344 | | |
| 71 | +| Clapp rank-IV (error-robust) | **β = 0.917 (0.021)** → γ = −0.083 | | |
| 72 | +| Quantile profile β(τ), τ = .10 → .90 | 0.87 → **0.49** (failure concentrated at the top) | | |
| 73 | +| Province-wide PRB (2021 / 2024) | −0.104 / −0.002 (staleness mechanism) | | |
| 74 | +| Municipalities with PRB < 0 | **99%** (95% below IAAO band) | | |
| 75 | +| Median municipality COD | 26 (IAAO ceiling: 15) | | |
| 76 | +| Excess tax, bottom ↔ top local decile | **+65% ↔ −5%** (median) | | |
| 77 | +| The exception | Montréal: PRB **+0.07**, progressive in all 6 years | | |
| 78 | + | |
| 79 | +Sub-group anatomy: condos γ = −0.12 vs single-family −0.45 / plex −0.49; | |
| 80 | +land share > 0.4 → −0.50; age > 60 y → −0.43; large markets (>10k sales) −0.27. | |
| 81 | + | |
| 82 | +## 🗂 Repository layout | |
| 83 | + | |
| 84 | +``` | |
| 85 | +wp10_uqo/ | |
| 86 | +├── data/ | |
| 87 | +│ ├── raw/ # matched sales–roll snapshot (see data/raw/README.md) | |
| 88 | +│ └── processed/ # analysis.parquet (built by step 01) | |
| 89 | +├── src/wp10/ # reusable package | |
| 90 | +│ ├── config.py # paths, thresholds, constants | |
| 91 | +│ ├── sample.py # sample construction & variable definitions | |
| 92 | +│ ├── iaao.py # COD / PRD / PRB + bootstrap CIs | |
| 93 | +│ ├── models.py # Cheng FE, Clapp IV, quantiles, horizontal, tax shift | |
| 94 | +│ └── plotstyle.py # journal-calibre matplotlib style (validated palette) | |
| 95 | +├── scripts/ # the pipeline, in order | |
| 96 | +│ ├── 01_build_sample.py | |
| 97 | +│ ├── 02_iaao_stats.py | |
| 98 | +│ ├── 03_estimate_regressions.py | |
| 99 | +│ ├── 04_make_figures.py | |
| 100 | +│ └── 05_make_tables.py | |
| 101 | +├── results/ | |
| 102 | +│ ├── reproduced/ # every CSV the scripts emit | |
| 103 | +│ └── tables/ # LaTeX tables input by the paper | |
| 104 | +├── figures/ # 10 publication figures (PNG, 300 dpi) | |
| 105 | +└── paper/ # main.tex + sections/ + references.bib → main.pdf | |
| 106 | +``` | |
| 107 | + | |
| 108 | +## 🚀 Quick start | |
| 109 | + | |
| 110 | +```bash | |
| 111 | +cd wp10_uqo | |
| 112 | +python3 -m pip install -r requirements.txt | |
| 113 | + | |
| 114 | +# place the parquet snapshot in data/raw/ (see data/raw/README.md), then: | |
| 115 | +python3 scripts/01_build_sample.py # 745,119 → 522,769 sales | |
| 116 | +python3 scripts/02_iaao_stats.py # IAAO diagnostics + bootstrap | |
| 117 | +python3 scripts/03_estimate_regressions.py # FE / IV / quantile / heterogeneity / tax shift | |
| 118 | +python3 scripts/04_make_figures.py # 10 figures | |
| 119 | +python3 scripts/05_make_tables.py # 10 LaTeX tables | |
| 120 | + | |
| 121 | +cd paper && make # compile main.pdf (latexmk) | |
| 122 | +``` | |
| 123 | + | |
| 124 | +Total runtime ≈ 5 minutes on an Apple-silicon laptop; peak RAM ≈ 6 GB. | |
| 125 | + | |
| 126 | +## 🗃 Data | |
| 127 | + | |
| 128 | +745,119 residential-market transactions (Jan 2021 – Jul 2026) matched at the | |
| 129 | +**parcel level** to the assessment roll in force at the sale date (median match | |
| 130 | +distance 0.6 m; every retained sale reproduces the roll value exactly). From the | |
| 131 | +roll: total/land/building assessed values, lot & floor areas, year built, unit | |
| 132 | +count, CUBF use code, roll vintage and the statutory market-condition date. | |
| 133 | +Sample cascade (details in the paper, §4): residential CUBF → match quality → | |
| 134 | +valid AV/SP → 1/99% ratio trim within roll vintage → cells ≥ 20 sales | |
| 135 | +⇒ **522,769 sales, 625 municipalities, 2,884 cells**. | |
| 136 | + | |
| 137 | +## 🔬 Methodology | |
| 138 | + | |
| 139 | +1. **IAAO diagnostics** (Standard on Ratio Studies, 2013) within | |
| 140 | + municipality × sale-year blocks, aggregated by median — COD, PRD, PRB with | |
| 141 | + percentile-bootstrap CIs (`src/wp10/iaao.py`). | |
| 142 | +2. **Cheng (1974) log-log regression** with 2,884 absorbed fixed effects | |
| 143 | + (linearmodels `AbsorbingLS`), SEs clustered on 625 municipalities. | |
| 144 | +3. **Clapp (1990) rank IV** — Z ∈ {−1,0,+1} from within-cell rank agreement of | |
| 145 | + ln AV and ln SP, 2SLS on demeaned data (`IV2SLS`) — the conservative bound. | |
| 146 | +4. **Quantile regressions** on within-cell demeaned data (τ = .10….90). | |
| 147 | +5. **Heterogeneity**: separate FE estimates by class, age, land share, roll lag, | |
| 148 | + municipality size, sale year. | |
| 149 | +6. **Horizontal inequity**: |ln r − cell median| regressed on characteristics. | |
| 150 | +7. **Tax shift**: r / cell-median − 1 by within-cell price decile — exact % | |
| 151 | + over/under-payment under Quebec's exemption-free ad valorem rule. | |
| 152 | + | |
| 153 | +## 🖼 Figures & tables inventory | |
| 154 | + | |
| 155 | +| Figure | Content | | |
| 156 | +|---|---| | |
| 157 | +| `fig_ratio_dist` | ratio distribution + by roll lag (Panels A/B) | | |
| 158 | +| `fig_time` | median ratio by month × roll vintage (staleness drift) | | |
| 159 | +| `fig_binscatter` | within-cell ln ratio vs ln price, 20 bins — the core fact | | |
| 160 | +| `fig_quantile` | β(τ) profile with FE/IV benchmarks | | |
| 161 | +| `fig_heterogeneity` | forest plot of subgroup γ | | |
| 162 | +| `fig_prb_muni`, `fig_cod` | municipal PRB / COD distributions vs IAAO bands | | |
| 163 | +| `fig_map` | province map of municipal PRB (Montréal exception) | | |
| 164 | +| `fig_taxshift` | excess tax burden by local price decile | | |
| 165 | +| `fig_robustness` | γ_FE vs γ_IV across 10 sample variants | | |
| 166 | + | |
| 167 | +Tables (`results/tables/*.tex`): summary stats, IAAO province-wide, ten largest | |
| 168 | +markets, vertical regressions, quantiles, heterogeneity, horizontal, tax shift, | |
| 169 | +robustness, Paglin–Fogarty. | |
| 170 | + | |
| 171 | +## ⚠️ Limitations | |
| 172 | + | |
| 173 | +- Sales < $50k absent from the source registry; residual non-arm's-length | |
| 174 | + transfers may survive screens (bottom-decile shift = upper bound). | |
| 175 | +- 2021–2026 is an unusually turbulent market — levels statistics are | |
| 176 | + period-specific (the within-market elasticity is not). | |
| 177 | +- No appeals, renovations, or owner demographics observed. | |
| 178 | +- Montréal's progressivity may partly reflect borough-level composition. | |
| 179 | + | |
| 180 | +## 📝 Citation | |
| 181 | + | |
| 182 | +```bibtex | |
| 183 | +@techreport{boucher2026assessmentgap, | |
| 184 | + author = {Boucher, Simon-Pierre}, | |
| 185 | + title = {The Assessment Gap in Quebec: Vertical and Horizontal Inequity | |
| 186 | + in Municipal Property Valuation}, | |
| 187 | + institution = {Universit\'e du Qu\'ebec en Outaouais, | |
| 188 | + D\'epartement des sciences administratives}, | |
| 189 | + type = {Working Paper}, | |
| 190 | + number = {10}, | |
| 191 | + year = {2026}, | |
| 192 | + month = {August} | |
| 193 | +} | |
| 194 | +``` | |
| 195 | + | |
| 196 | +## 👤 Author & contact | |
| 197 | + | |
| 198 | +**Simon-Pierre Boucher** — Département des sciences administratives, | |
| 199 | +Université du Québec en Outaouais (UQO), Gatineau, QC. | |
| 200 | +📧 [contact@spboucher.ai](mailto:contact@spboucher.ai) | |
added
data/raw/README.md
+18 −0
@@ -0,0 +1,18 @@ | ||
| 1 | +<!-- Author: Simon-Pierre Boucher — contact@spboucher.ai --> | |
| 2 | +# Raw data | |
| 3 | + | |
| 4 | +`transactions_700k_avec_registre_foncier.parquet` — 745,119 Quebec | |
| 5 | +residential-market transactions (January 2021 – July 2026) matched at the | |
| 6 | +parcel level to the municipal assessment roll (rôle d'évaluation foncière) | |
| 7 | +in force at the sale date. | |
| 8 | + | |
| 9 | +| Group | Key columns | | |
| 10 | +|---|---| | |
| 11 | +| Transaction | `id`, `date`, `amount`, `street`, `zipCode`, `city`, `lat`, `lng`, `propertyType`, `ownerType`, `tx_year` | | |
| 12 | +| Roll identity | `role_id_provinc`, `role_matricule`, `role_code_mun`, `role_municipalite`, `role_anrole` (roll vintage), `role_date_cond_marche` (statutory market-condition reference date) | | |
| 13 | +| Roll values | `role_valeur_immeuble` (total AV), `role_valeur_terrain` (land), `role_valeur_batiment` (building), `role_valeur_role_anterieur`, `previousValue`, `totalArValue` | | |
| 14 | +| Roll structure | `role_cubf` (+ `role_cubf_libelle`), `role_superficie_terrain_m2`, `role_aire_etages_m2`, `role_annee_construction`, `role_nb_logements`, `role_nb_etages`, `role_lien_physique`, `role_genre_construction`, `role_unite_voisinage` | | |
| 15 | +| Match quality | `match_dist_m` (median 0.6 m), `match_score` (max 220), `match_valeur_exacte` | | |
| 16 | + | |
| 17 | +The file is **not redistributed** with the repository (100 MB; see | |
| 18 | +`.gitignore`). Place it in this directory before running the pipeline. | |
added
figures/fig_binscatter.png
+0 −0
Binary file not shown.
added
figures/fig_cod.png
+0 −0
Binary file not shown.
added
figures/fig_heterogeneity.png
+0 −0
Binary file not shown.
added
figures/fig_map.png
+0 −0
Binary file not shown.
added
figures/fig_prb_muni.png
+0 −0
Binary file not shown.
added
figures/fig_quantile.png
+0 −0
Binary file not shown.
added
figures/fig_ratio_dist.png
+0 −0
Binary file not shown.
added
figures/fig_robustness.png
+0 −0
Binary file not shown.
added
figures/fig_taxshift.png
+0 −0
Binary file not shown.
added
figures/fig_time.png
+0 −0
Binary file not shown.
added
paper/.latexmkrc
+6 −0
@@ -0,0 +1,6 @@ | ||
| 1 | +# Author: Simon-Pierre Boucher — contact@spboucher.ai | |
| 2 | +$pdf_mode = 1; | |
| 3 | +$pdflatex = 'pdflatex -interaction=nonstopmode -halt-on-error -synctex=1 %O %S'; | |
| 4 | +$bibtex_use = 2; | |
| 5 | +$clean_ext = 'synctex.gz run.xml bbl bcf fdb_latexmk fls log aux out toc lof lot blg'; | |
| 6 | +@default_files = ('main.tex'); | |
added
paper/Makefile
+9 −0
@@ -0,0 +1,9 @@ | ||
| 1 | +# Author: Simon-Pierre Boucher — contact@spboucher.ai | |
| 2 | +all: | |
| 3 | + latexmk -pdf main.tex | |
| 4 | + | |
| 5 | +clean: | |
| 6 | + latexmk -c | |
| 7 | + | |
| 8 | +distclean: | |
| 9 | + latexmk -C | |
added
paper/main.pdf
+0 −0
Binary file not shown.
added
paper/main.tex
+124 −0
@@ -0,0 +1,124 @@ | ||
| 1 | +% Author: Simon-Pierre Boucher — contact@spboucher.ai | |
| 2 | +% ============================================================================ | |
| 3 | +% UQO Working Paper No. 10 | |
| 4 | +% The Assessment Gap in Quebec: Vertical and Horizontal Inequity in | |
| 5 | +% Municipal Property Valuation | |
| 6 | +% | |
| 7 | +% Build: latexmk -pdf main.tex (or `make` in this directory) | |
| 8 | +% Figures are read from ../figures/, tables from ../results/tables/. | |
| 9 | +% ============================================================================ | |
| 10 | +\documentclass[12pt,letterpaper]{article} | |
| 11 | + | |
| 12 | +% ---------------------------------------------------------------- encoding | |
| 13 | +\usepackage[utf8]{inputenc} | |
| 14 | +\usepackage[T1]{fontenc} | |
| 15 | +\usepackage[english]{babel} | |
| 16 | + | |
| 17 | +% ---------------------------------------------------------------- layout | |
| 18 | +\usepackage[letterpaper,margin=1in]{geometry} | |
| 19 | +\usepackage{setspace} | |
| 20 | +\onehalfspacing | |
| 21 | + | |
| 22 | +% ---------------------------------------------------------------- typography | |
| 23 | +\usepackage{mathptmx} | |
| 24 | +\usepackage{microtype} | |
| 25 | + | |
| 26 | +% ---------------------------------------------------------------- math | |
| 27 | +\usepackage{amsmath,amssymb,amsthm} | |
| 28 | + | |
| 29 | +% ---------------------------------------------------------------- tables | |
| 30 | +\usepackage{booktabs} | |
| 31 | +\usepackage{threeparttable} | |
| 32 | +\usepackage{makecell} | |
| 33 | + | |
| 34 | +% ---------------------------------------------------------------- figures | |
| 35 | +\usepackage{graphicx} | |
| 36 | +\usepackage{subcaption} | |
| 37 | +\graphicspath{{../figures/}{./}} | |
| 38 | + | |
| 39 | +% ---------------------------------------------------------------- captions | |
| 40 | +\usepackage[font=small,labelfont=bf,labelsep=period,justification=justified,singlelinecheck=false]{caption} | |
| 41 | + | |
| 42 | +% ---------------------------------------------------------------- colours & links | |
| 43 | +\usepackage[dvipsnames]{xcolor} | |
| 44 | +\usepackage[colorlinks=true,linkcolor=NavyBlue,citecolor=NavyBlue,urlcolor=NavyBlue,breaklinks=true]{hyperref} | |
| 45 | + | |
| 46 | +% ---------------------------------------------------------------- bibliography | |
| 47 | +\usepackage[authoryear,round,semicolon]{natbib} | |
| 48 | +\bibliographystyle{aer} | |
| 49 | + | |
| 50 | +% ---------------------------------------------------------------- headings & lists | |
| 51 | +\usepackage{titlesec} | |
| 52 | +\titleformat{\section}{\large\bfseries}{\thesection.}{0.5em}{} | |
| 53 | +\titleformat{\subsection}{\normalsize\bfseries}{\thesubsection.}{0.5em}{} | |
| 54 | +\titleformat{\subsubsection}{\normalsize\itshape}{\thesubsubsection.}{0.5em}{} | |
| 55 | +\usepackage{fancyhdr} | |
| 56 | +\pagestyle{fancy}\fancyhf{} | |
| 57 | +\renewcommand{\headrulewidth}{0pt} | |
| 58 | +\fancyfoot[C]{\thepage} | |
| 59 | +\usepackage{enumitem} | |
| 60 | +\setlist{nosep,leftmargin=*} | |
| 61 | + | |
| 62 | +% ============================================================================ | |
| 63 | +% METADATA | |
| 64 | +% ============================================================================ | |
| 65 | +\newcommand{\WPnumber}{10} | |
| 66 | +\newcommand{\WPtitle}{The Assessment Gap in Quebec} | |
| 67 | +\newcommand{\WPsubtitle}{Vertical and Horizontal Inequity in Municipal Property | |
| 68 | + Valuation: Evidence from 522{,}769 Sales Matched to the Assessment Roll} | |
| 69 | +\newcommand{\WPdate}{August 2026} | |
| 70 | +\newcommand{\WPversion}{1.0} | |
| 71 | +\newcommand{\WPkeywords}{Property tax, Assessment, Vertical equity, | |
| 72 | + Regressivity, Ratio study, Quebec} | |
| 73 | +\newcommand{\WPjel}{H71, H22, R51, R31} | |
| 74 | + | |
| 75 | +\newcommand{\WPauthor}{Simon-Pierre Boucher} | |
| 76 | +\newcommand{\WPaffiliation}{D\'epartement des sciences administratives\\ | |
| 77 | + Universit\'e du Qu\'ebec en Outaouais} | |
| 78 | +\newcommand{\WPemail}{simon-pierre.boucher@uqo.ca} | |
| 79 | +\newcommand{\WPaddress}{Gatineau -- Pavillon Alexandre-Tach\'e\\ | |
| 80 | + 283, boulevard Alexandre-Tach\'e\\ Gatineau, Qu\'ebec, Canada J9A 1L8} | |
| 81 | + | |
| 82 | +\newcommand{\WPabstract}{% | |
| 83 | +Municipal property taxes are levied on assessed values, so any systematic | |
| 84 | +relationship between assessment accuracy and market value silently | |
| 85 | +redistributes the tax burden. Matching 522{,}769 residential transactions | |
| 86 | +from 2021--2026 to the triennial assessment rolls of 625 Quebec | |
| 87 | +municipalities at the parcel level, we provide the first province-wide | |
| 88 | +evaluation of vertical and horizontal equity in Canadian property assessment. | |
| 89 | +Assessment ratios decline steeply with price: within | |
| 90 | +municipality~$\times$~roll~$\times$~sale-year markets, the elasticity of the | |
| 91 | +assessment ratio with respect to the sale price is $-0.34$, and a rank-based | |
| 92 | +instrumental-variable estimator in the spirit of \mbox{Clapp (1990)}---which | |
| 93 | +purges the attenuation bias that pushes least-squares tests toward spurious | |
| 94 | +regressivity---still yields $-0.08$ ($t\approx4$). IAAO ratio-study | |
| 95 | +diagnostics place 95\% of municipalities below the acceptable price-related | |
| 96 | +bias band and 99\% above the COD uniformity ceiling; Montr\'eal is the only | |
| 97 | +large market whose assessments are progressive. Regressivity is strongest for | |
| 98 | +single-family homes, older buildings, properties with a high assessed land | |
| 99 | +share, and smaller municipalities, and it doubles in years when market prices | |
| 100 | +decouple from the rolls' reference dates, pointing to infrequent revaluation | |
| 101 | +as a first-order mechanism. The implied intra-municipal tax shift is large: | |
| 102 | +the median dwelling in the bottom within-market price decile pays roughly | |
| 103 | +65\% more property tax than it would under uniform assessment, while the top | |
| 104 | +decile pays about 5\% less.} | |
| 105 | + | |
| 106 | +\begin{document} | |
| 107 | + | |
| 108 | +\input{sections/titlepage} | |
| 109 | + | |
| 110 | +\setcounter{page}{1} | |
| 111 | +\input{sections/introduction} | |
| 112 | +\input{sections/literature} | |
| 113 | +\input{sections/institutional} | |
| 114 | +\input{sections/data} | |
| 115 | +\input{sections/methodology} | |
| 116 | +\input{sections/results} | |
| 117 | +\input{sections/robustness} | |
| 118 | +\input{sections/discussion} | |
| 119 | +\input{sections/conclusion} | |
| 120 | + | |
| 121 | +\newpage | |
| 122 | +\bibliography{references} | |
| 123 | + | |
| 124 | +\end{document} | |
added
paper/references.bib
+402 −0
@@ -0,0 +1,402 @@ | ||
| 1 | +@comment{ Author: Simon-Pierre Boucher (contact at spboucher.ai) -- Bibliography for UQO Working Paper No 10 } | |
| 2 | + | |
| 3 | +@article{paglin1972equity, | |
| 4 | + author = {Paglin, Morton and Fogarty, Michael}, | |
| 5 | + title = {Equity and the Property Tax: A New Conceptual Focus}, | |
| 6 | + journal = {National Tax Journal}, | |
| 7 | + year = {1972}, | |
| 8 | + volume = {25}, | |
| 9 | + number = {4}, | |
| 10 | + pages = {557--565} | |
| 11 | +} | |
| 12 | + | |
| 13 | +@article{cheng1974property, | |
| 14 | + author = {Cheng, Pao Lun}, | |
| 15 | + title = {Property Taxation, Assessment Performance, and Its Measurement}, | |
| 16 | + journal = {Public Finance}, | |
| 17 | + year = {1974}, | |
| 18 | + volume = {29}, | |
| 19 | + number = {3}, | |
| 20 | + pages = {268--284} | |
| 21 | +} | |
| 22 | + | |
| 23 | +@article{kochin1982vertical, | |
| 24 | + author = {Kochin, Levis A. and Parks, Richard W.}, | |
| 25 | + title = {Vertical Equity in Real Estate Assessment: A Fair Appraisal}, | |
| 26 | + journal = {Economic Inquiry}, | |
| 27 | + year = {1982}, | |
| 28 | + volume = {20}, | |
| 29 | + number = {4}, | |
| 30 | + pages = {511--532} | |
| 31 | +} | |
| 32 | + | |
| 33 | +@article{bell1984administrative, | |
| 34 | + author = {Bell, Earl J.}, | |
| 35 | + title = {Administrative Inequity and Property Assessment: The Case for the Traditional Approach}, | |
| 36 | + journal = {Property Tax Journal}, | |
| 37 | + year = {1984}, | |
| 38 | + volume = {3}, | |
| 39 | + number = {2}, | |
| 40 | + pages = {123--131} | |
| 41 | +} | |
| 42 | + | |
| 43 | +@article{kennedy1984unfair, | |
| 44 | + author = {Kennedy, Peter}, | |
| 45 | + title = {On an Unfair Appraisal of Vertical Equity in Real Estate Assessment}, | |
| 46 | + journal = {Economic Inquiry}, | |
| 47 | + year = {1984}, | |
| 48 | + volume = {22}, | |
| 49 | + number = {2}, | |
| 50 | + pages = {287--290} | |
| 51 | +} | |
| 52 | + | |
| 53 | +@article{sunderman1990testing, | |
| 54 | + author = {Sunderman, Mark A. and Birch, John W. and Cannaday, Roger E. and Hamilton, Thomas W.}, | |
| 55 | + title = {Testing for Vertical Inequity in Property Tax Systems}, | |
| 56 | + journal = {Journal of Real Estate Research}, | |
| 57 | + year = {1990}, | |
| 58 | + volume = {5}, | |
| 59 | + number = {3}, | |
| 60 | + pages = {319--334} | |
| 61 | +} | |
| 62 | + | |
| 63 | +@article{clapp1990new, | |
| 64 | + author = {Clapp, John M.}, | |
| 65 | + title = {A New Test for Equitable Real Estate Tax Assessment}, | |
| 66 | + journal = {Journal of Real Estate Finance and Economics}, | |
| 67 | + year = {1990}, | |
| 68 | + volume = {3}, | |
| 69 | + number = {3}, | |
| 70 | + pages = {233--249} | |
| 71 | +} | |
| 72 | + | |
| 73 | +@article{sirmans1995vertical, | |
| 74 | + author = {Sirmans, G. Stacy and Diskin, Barry A. and Friday, H. Swint}, | |
| 75 | + title = {Vertical Inequity in the Taxation of Real Property}, | |
| 76 | + journal = {National Tax Journal}, | |
| 77 | + year = {1995}, | |
| 78 | + volume = {48}, | |
| 79 | + number = {1}, | |
| 80 | + pages = {71--84} | |
| 81 | +} | |
| 82 | + | |
| 83 | +@article{allen2002measuring, | |
| 84 | + author = {Allen, Marcus T. and Dare, William H.}, | |
| 85 | + title = {Identifying Determinants of Horizontal Property Tax Inequity: Evidence from Florida}, | |
| 86 | + journal = {Journal of Real Estate Research}, | |
| 87 | + year = {2002}, | |
| 88 | + volume = {24}, | |
| 89 | + number = {2}, | |
| 90 | + pages = {153--164} | |
| 91 | +} | |
| 92 | + | |
| 93 | +@article{sirmans2008vertical, | |
| 94 | + author = {Sirmans, G. Stacy and Gatzlaff, Dean H. and Macpherson, David A.}, | |
| 95 | + title = {Horizontal and Vertical Inequity in Real Property Taxation}, | |
| 96 | + journal = {Journal of Real Estate Literature}, | |
| 97 | + year = {2008}, | |
| 98 | + volume = {16}, | |
| 99 | + number = {2}, | |
| 100 | + pages = {167--180} | |
| 101 | +} | |
| 102 | + | |
| 103 | +@article{mcmillen2008thin, | |
| 104 | + author = {McMillen, Daniel P. and Weber, Rachel N.}, | |
| 105 | + title = {Thin Markets and Property Tax Inequities: A Multinomial Logit Approach}, | |
| 106 | + journal = {National Tax Journal}, | |
| 107 | + year = {2008}, | |
| 108 | + volume = {61}, | |
| 109 | + number = {4}, | |
| 110 | + pages = {653--671} | |
| 111 | +} | |
| 112 | + | |
| 113 | +@article{mcmillen2011assessment, | |
| 114 | + author = {McMillen, Daniel P.}, | |
| 115 | + title = {Assessment Regressivity: A Tale of Two {Illinois} Counties}, | |
| 116 | + journal = {Land Lines}, | |
| 117 | + year = {2011}, | |
| 118 | + volume = {23}, | |
| 119 | + number = {1}, | |
| 120 | + pages = {9--15} | |
| 121 | +} | |
| 122 | + | |
| 123 | +@article{hodge2017assessment, | |
| 124 | + author = {Hodge, Timothy R. and McMillen, Daniel P. and Sands, Gary and Skidmore, Mark}, | |
| 125 | + title = {Assessment Inequity in a Declining Housing Market: The Case of {Detroit}}, | |
| 126 | + journal = {Real Estate Economics}, | |
| 127 | + year = {2017}, | |
| 128 | + volume = {45}, | |
| 129 | + number = {2}, | |
| 130 | + pages = {237--258} | |
| 131 | +} | |
| 132 | + | |
| 133 | +@article{mcmillen2020assessment, | |
| 134 | + author = {McMillen, Daniel and Singh, Ruchi}, | |
| 135 | + title = {Assessment Regressivity and Property Taxation}, | |
| 136 | + journal = {Journal of Real Estate Finance and Economics}, | |
| 137 | + year = {2020}, | |
| 138 | + volume = {60}, | |
| 139 | + number = {1}, | |
| 140 | + pages = {155--169} | |
| 141 | +} | |
| 142 | + | |
| 143 | +@article{avenancio2022assessment, | |
| 144 | + author = {Avenancio-Le{\'o}n, Carlos F. and Howard, Troup}, | |
| 145 | + title = {The Assessment Gap: Racial Inequalities in Property Taxation}, | |
| 146 | + journal = {Quarterly Journal of Economics}, | |
| 147 | + year = {2022}, | |
| 148 | + volume = {137}, | |
| 149 | + number = {3}, | |
| 150 | + pages = {1383--1434} | |
| 151 | +} | |
| 152 | + | |
| 153 | +@techreport{berry2021reassessing, | |
| 154 | + author = {Berry, Christopher R.}, | |
| 155 | + title = {Reassessing the Property Tax}, | |
| 156 | + institution = {University of Chicago, Harris School of Public Policy}, | |
| 157 | + type = {Working Paper}, | |
| 158 | + year = {2021} | |
| 159 | +} | |
| 160 | + | |
| 161 | +@techreport{amornsiripanitch2022residential, | |
| 162 | + author = {Amornsiripanitch, Natee}, | |
| 163 | + title = {Why Are Residential Property Tax Rates Regressive?}, | |
| 164 | + institution = {Federal Reserve Bank of Philadelphia}, | |
| 165 | + type = {Working Paper}, | |
| 166 | + number = {22-02}, | |
| 167 | + year = {2022} | |
| 168 | +} | |
| 169 | + | |
| 170 | +@article{mcmillen2022allocative, | |
| 171 | + author = {McMillen, Daniel and Singh, Ruchi}, | |
| 172 | + title = {Measures of Vertical Inequality in Assessments}, | |
| 173 | + journal = {Journal of Real Estate Finance and Economics}, | |
| 174 | + year = {2022}, | |
| 175 | + volume = {online first}, | |
| 176 | + pages = {1--29} | |
| 177 | +} | |
| 178 | + | |
| 179 | +@book{iaao2013standard, | |
| 180 | + author = {{International Association of Assessing Officers}}, | |
| 181 | + title = {Standard on Ratio Studies}, | |
| 182 | + publisher = {IAAO}, | |
| 183 | + address = {Kansas City, MO}, | |
| 184 | + year = {2013} | |
| 185 | +} | |
| 186 | + | |
| 187 | +@book{eckert1990property, | |
| 188 | + author = {Eckert, Joseph K. and Gloudemans, Robert J. and Almy, Richard R.}, | |
| 189 | + title = {Property Appraisal and Assessment Administration}, | |
| 190 | + publisher = {International Association of Assessing Officers}, | |
| 191 | + address = {Chicago}, | |
| 192 | + year = {1990} | |
| 193 | +} | |
| 194 | + | |
| 195 | +@book{gloudemans2011fundamentals, | |
| 196 | + author = {Gloudemans, Robert and Almy, Richard}, | |
| 197 | + title = {Fundamentals of Mass Appraisal}, | |
| 198 | + publisher = {International Association of Assessing Officers}, | |
| 199 | + address = {Kansas City, MO}, | |
| 200 | + year = {2011} | |
| 201 | +} | |
| 202 | + | |
| 203 | +@article{oates1969effects, | |
| 204 | + author = {Oates, Wallace E.}, | |
| 205 | + title = {The Effects of Property Taxes and Local Public Spending on Property Values: An Empirical Study of Tax Capitalization and the {Tiebout} Hypothesis}, | |
| 206 | + journal = {Journal of Political Economy}, | |
| 207 | + year = {1969}, | |
| 208 | + volume = {77}, | |
| 209 | + number = {6}, | |
| 210 | + pages = {957--971} | |
| 211 | +} | |
| 212 | + | |
| 213 | +@article{oates2016local, | |
| 214 | + author = {Oates, Wallace E. and Fischel, William A.}, | |
| 215 | + title = {Are Local Property Taxes Regressive, Progressive, or What?}, | |
| 216 | + journal = {National Tax Journal}, | |
| 217 | + year = {2016}, | |
| 218 | + volume = {69}, | |
| 219 | + number = {2}, | |
| 220 | + pages = {415--434} | |
| 221 | +} | |
| 222 | + | |
| 223 | +@article{rosen1974hedonic, | |
| 224 | + author = {Rosen, Sherwin}, | |
| 225 | + title = {Hedonic Prices and Implicit Markets: Product Differentiation in Pure Competition}, | |
| 226 | + journal = {Journal of Political Economy}, | |
| 227 | + year = {1974}, | |
| 228 | + volume = {82}, | |
| 229 | + number = {1}, | |
| 230 | + pages = {34--55} | |
| 231 | +} | |
| 232 | + | |
| 233 | +@incollection{malpezzi2003hedonic, | |
| 234 | + author = {Malpezzi, Stephen}, | |
| 235 | + title = {Hedonic Pricing Models: A Selective and Applied Review}, | |
| 236 | + booktitle = {Housing Economics and Public Policy}, | |
| 237 | + editor = {O'Sullivan, Tony and Gibb, Kenneth}, | |
| 238 | + publisher = {Blackwell}, | |
| 239 | + address = {Oxford}, | |
| 240 | + year = {2003}, | |
| 241 | + pages = {67--89} | |
| 242 | +} | |
| 243 | + | |
| 244 | +@article{carbonnier2024property, | |
| 245 | + author = {Carbonnier, Cl{\'e}ment}, | |
| 246 | + title = {Property Tax Regressivity, the Case of {Qu{\'e}bec}}, | |
| 247 | + journal = {Public Finance Review}, | |
| 248 | + year = {2024}, | |
| 249 | + volume = {52}, | |
| 250 | + number = {2}, | |
| 251 | + pages = {155--181} | |
| 252 | +} | |
| 253 | + | |
| 254 | +@techreport{slack2014political, | |
| 255 | + author = {Slack, Enid and Bird, Richard M.}, | |
| 256 | + title = {The Political Economy of Property Tax Reform}, | |
| 257 | + institution = {OECD}, | |
| 258 | + type = {OECD Working Papers on Fiscal Federalism}, | |
| 259 | + number = {18}, | |
| 260 | + year = {2014} | |
| 261 | +} | |
| 262 | + | |
| 263 | +@book{youngman2016good, | |
| 264 | + author = {Youngman, Joan}, | |
| 265 | + title = {A Good Tax: Legal and Policy Issues for the Property Tax in the {United States}}, | |
| 266 | + publisher = {Lincoln Institute of Land Policy}, | |
| 267 | + address = {Cambridge, MA}, | |
| 268 | + year = {2016} | |
| 269 | +} | |
| 270 | + | |
| 271 | +@techreport{found2017property, | |
| 272 | + author = {Found, Adam and Tomlinson, Peter}, | |
| 273 | + title = {Business Tax Burdens in {Canada's} Major Cities: The 2017 Report Card}, | |
| 274 | + institution = {C.D. Howe Institute}, | |
| 275 | + type = {E-Brief}, | |
| 276 | + year = {2017} | |
| 277 | +} | |
| 278 | + | |
| 279 | +@techreport{mpac2022vertical, | |
| 280 | + author = {{Municipal Property Assessment Corporation}}, | |
| 281 | + title = {Vertical Equity Review of Residential Assessed Values}, | |
| 282 | + institution = {MPAC}, | |
| 283 | + address = {Toronto}, | |
| 284 | + type = {Technical Report}, | |
| 285 | + year = {2022} | |
| 286 | +} | |
| 287 | + | |
| 288 | +@article{goolsby1997assessment, | |
| 289 | + author = {Goolsby, William C.}, | |
| 290 | + title = {Assessment Error in the Valuation of Owner-Occupied Housing}, | |
| 291 | + journal = {Journal of Real Estate Research}, | |
| 292 | + year = {1997}, | |
| 293 | + volume = {13}, | |
| 294 | + number = {1}, | |
| 295 | + pages = {33--45} | |
| 296 | +} | |
| 297 | + | |
| 298 | +@article{quintos2020prb, | |
| 299 | + author = {Quintos, Carmela}, | |
| 300 | + title = {A Gradient Boosting Approach to Understanding Vertical and Horizontal Inequity in Property Assessments}, | |
| 301 | + journal = {Journal of Property Tax Assessment \& Administration}, | |
| 302 | + year = {2020}, | |
| 303 | + volume = {17}, | |
| 304 | + number = {2}, | |
| 305 | + pages = {1--28} | |
| 306 | +} | |
| 307 | + | |
| 308 | +@article{ross2012assessor, | |
| 309 | + author = {Ross, Justin M.}, | |
| 310 | + title = {Interjurisdictional Determinants of Property Assessment Regressivity}, | |
| 311 | + journal = {Land Economics}, | |
| 312 | + year = {2012}, | |
| 313 | + volume = {88}, | |
| 314 | + number = {1}, | |
| 315 | + pages = {28--42} | |
| 316 | +} | |
| 317 | + | |
| 318 | +@article{plummer2014evidence, | |
| 319 | + author = {Plummer, Elizabeth}, | |
| 320 | + title = {The Effects of Property Tax Protests on the Assessment Uniformity of Residential Properties}, | |
| 321 | + journal = {Real Estate Economics}, | |
| 322 | + year = {2014}, | |
| 323 | + volume = {42}, | |
| 324 | + number = {4}, | |
| 325 | + pages = {900--937} | |
| 326 | +} | |
| 327 | + | |
| 328 | +@article{clapp1990methods, | |
| 329 | + author = {Clapp, John M.}, | |
| 330 | + title = {A Methodology for Constructing Vacant Land Price Indices}, | |
| 331 | + journal = {Real Estate Economics}, | |
| 332 | + year = {1990}, | |
| 333 | + volume = {18}, | |
| 334 | + number = {3}, | |
| 335 | + pages = {274--293} | |
| 336 | +} | |
| 337 | + | |
| 338 | +@misc{lfm2026, | |
| 339 | + author = {{Gouvernement du Qu\'ebec}}, | |
| 340 | + title = {Loi sur la fiscalit\'e municipale, {RLRQ}, c.\ {F-2.1}}, | |
| 341 | + howpublished = {L\'egisQu\'ebec}, | |
| 342 | + year = {2026}, | |
| 343 | + note = {Articles 42--46 (triennial rolls and market-condition reference date)} | |
| 344 | +} | |
| 345 | + | |
| 346 | +@misc{mamh2024manuel, | |
| 347 | + author = {{Minist\`ere des Affaires municipales et de l'Habitation}}, | |
| 348 | + title = {Manuel d'\'evaluation fonci\`ere du {Qu\'ebec}}, | |
| 349 | + howpublished = {Gouvernement du Qu\'ebec}, | |
| 350 | + address = {Qu\'ebec}, | |
| 351 | + year = {2024} | |
| 352 | +} | |
| 353 | + | |
| 354 | +@article{desrosiers2000hedonic, | |
| 355 | + author = {Des Rosiers, Fran{\c c}ois and Th{\'e}riault, Marius and Villeneuve, Paul-Y.}, | |
| 356 | + title = {Sorting Out Access and Neighbourhood Factors in Hedonic Price Modelling}, | |
| 357 | + journal = {Journal of Property Investment \& Finance}, | |
| 358 | + year = {2000}, | |
| 359 | + volume = {18}, | |
| 360 | + number = {3}, | |
| 361 | + pages = {291--315} | |
| 362 | +} | |
| 363 | + | |
| 364 | +@article{bostic2007land, | |
| 365 | + author = {Bostic, Raphael W. and Longhofer, Stanley D. and Redfearn, Christian L.}, | |
| 366 | + title = {Land Leverage: Decomposing Home Price Dynamics}, | |
| 367 | + journal = {Real Estate Economics}, | |
| 368 | + year = {2007}, | |
| 369 | + volume = {35}, | |
| 370 | + number = {2}, | |
| 371 | + pages = {183--208} | |
| 372 | +} | |
| 373 | + | |
| 374 | +@article{ihlanfeldt2023appraisal, | |
| 375 | + author = {Ihlanfeldt, Keith and Rodgers, Luke P.}, | |
| 376 | + title = {Homestead Exemptions, Heterogeneous Assessment, and Property Tax Progressivity}, | |
| 377 | + journal = {National Tax Journal}, | |
| 378 | + year = {2022}, | |
| 379 | + volume = {75}, | |
| 380 | + number = {1}, | |
| 381 | + pages = {7--31} | |
| 382 | +} | |
| 383 | + | |
| 384 | +@article{baar1981property, | |
| 385 | + author = {Baar, Kenneth K.}, | |
| 386 | + title = {Property Tax Assessment Discrimination Against Low-Income Neighborhoods}, | |
| 387 | + journal = {The Urban Lawyer}, | |
| 388 | + year = {1981}, | |
| 389 | + volume = {13}, | |
| 390 | + number = {3}, | |
| 391 | + pages = {333--406} | |
| 392 | +} | |
| 393 | + | |
| 394 | +@article{engle1975deassessment, | |
| 395 | + author = {Engle, Robert F.}, | |
| 396 | + title = {De Facto Discrimination in Residential Assessments: {Boston}}, | |
| 397 | + journal = {National Tax Journal}, | |
| 398 | + year = {1975}, | |
| 399 | + volume = {28}, | |
| 400 | + number = {4}, | |
| 401 | + pages = {445--451} | |
| 402 | +} | |
added
paper/sections/conclusion.tex
+37 −0
@@ -0,0 +1,37 @@ | ||
| 1 | +% Author: Simon-Pierre Boucher — contact@spboucher.ai | |
| 2 | +% ============================================================================ | |
| 3 | +\section{Conclusion} | |
| 4 | +\label{sec:concl} | |
| 5 | + | |
| 6 | +Matching more than half a million residential sales to the assessment | |
| 7 | +rolls that tax them, this paper delivers the first province-wide audit of | |
| 8 | +assessment equity in Canada. The verdict is unambiguous. Quebec's | |
| 9 | +property-tax base is systematically regressive: within the same | |
| 10 | +municipality, roll and year, the assessment ratio falls by 34\% per | |
| 11 | +log-point of price in the descriptive fixed-effects estimate, and by 8\% | |
| 12 | +under the most conservative measurement-error correction available. | |
| 13 | +Ninety-five percent of municipalities fail the IAAO vertical-equity | |
| 14 | +standard, ninety-nine percent fail its uniformity standard, and the median | |
| 15 | +dwelling in the bottom local price decile pays roughly two thirds more | |
| 16 | +property tax than uniform assessment would imply, while the top decile | |
| 17 | +pays less. The inequity concentrates where mass appraisal is hardest --- | |
| 18 | +old houses, plexes, land-heavy and luxury properties, thin markets --- | |
| 19 | +doubles when triennial rolls fall behind a booming market, and disappears | |
| 20 | +almost nowhere except Montr\'eal. | |
| 21 | + | |
| 22 | +These findings reframe the presumption that centralized, professionally | |
| 23 | +regulated assessment systems are immune to the pathologies documented in | |
| 24 | +the fragmented U.S. system. Quebec has uniform statutes, certified | |
| 25 | +evaluators and statutory market-vintage synchronization --- and produces | |
| 26 | +regressivity of U.S. magnitude anyway. The binding constraint is not | |
| 27 | +governance but valuation technology and revaluation frequency, which is | |
| 28 | +in fact encouraging: cycle length, pooled assessment services and | |
| 29 | +vertical audit standards are policy variables, and inexpensive ones | |
| 30 | +relative to the redistribution at stake. | |
| 31 | + | |
| 32 | +The matched sale--roll architecture built here supports a research agenda | |
| 33 | +beyond this audit: linking assessment gaps to neighbourhood | |
| 34 | +socio-demographics, exploiting the discrete arrival of new rolls to study | |
| 35 | +price anchoring, and using the land--building decomposition to measure | |
| 36 | +land-value dynamics at the parcel level. We pursue these in companion | |
| 37 | +work. | |
added
paper/sections/data.tex
+101 −0
@@ -0,0 +1,101 @@ | ||
| 1 | +% Author: Simon-Pierre Boucher — contact@spboucher.ai | |
| 2 | +% ============================================================================ | |
| 3 | +\section{Data and sample construction} | |
| 4 | +\label{sec:data} | |
| 5 | + | |
| 6 | +\subsection{Sources and matching} | |
| 7 | + | |
| 8 | +We start from a compiled registry of 745{,}119 residential-market | |
| 9 | +transactions recorded in Quebec between January 2021 and July 2026, each | |
| 10 | +carrying the sale price, sale date, civic address, geographic coordinates, | |
| 11 | +and a listing-derived property type. Each transaction has been matched at | |
| 12 | +the parcel level to the municipal assessment roll in force on the sale date. | |
| 13 | +The match keys on geographic proximity between the transaction's coordinates | |
| 14 | +and the roll unit's coordinates and is validated against the roll's recorded | |
| 15 | +value; a composite score (maximum 220) summarizes the quality of the | |
| 16 | +address, distance and value agreement. Matches are extremely tight: the | |
| 17 | +median distance between the transaction and the matched roll unit is | |
| 18 | +0.6~metres, and every observation retained in the estimation sample | |
| 19 | +reproduces the roll's assessed value exactly. | |
| 20 | + | |
| 21 | +From the roll we observe the taxable value of the property | |
| 22 | +($AV$, \texttt{valeur immeuble}), its land and building components, lot | |
| 23 | +area, total floor area, year of construction, number of dwelling units, the | |
| 24 | +standardized use code (CUBF), the roll vintage, and the statutory | |
| 25 | +market-condition reference date discussed in Section~\ref{sec:inst}. | |
| 26 | + | |
| 27 | +\subsection{Sample restrictions} | |
| 28 | + | |
| 29 | +Table~\ref{tab:sumstats} describes the estimation sample; the selection | |
| 30 | +cascade is as follows. We keep sales of residential use codes --- | |
| 31 | +dwellings (CUBF 1000), cottages (1100), mobile homes (1211) and other | |
| 32 | +residential (1990) --- which removes vacant land, commercial property and | |
| 33 | +construction-in-progress (672{,}276 sales remain). We require a | |
| 34 | +high-confidence roll match (distance $\le 50$~m and score $\ge 150$; | |
| 35 | +568{,}711 sales), a positive assessed value and a price of at least | |
| 36 | +\$50{,}000 --- the floor of the source registry, which also screens out | |
| 37 | +most non-arm's-length transfers. Assessment ratios $r_i = AV_i/SP_i$ are | |
| 38 | +trimmed at the 1st and 99th percentiles \emph{within each roll vintage}, so | |
| 39 | +that the mechanical drift of ratio levels across vintages is not trimmed | |
| 40 | +asymmetrically (557{,}325 sales). Finally, since all estimates compare | |
| 41 | +sales within a municipality $\times$ roll $\times$ sale-year block | |
| 42 | +(``cell''), we require at least 20 sales per cell. The estimation sample | |
| 43 | +contains \textbf{522{,}769 sales} in 625 municipalities and 2{,}884 cells: | |
| 44 | +345{,}053 single-family homes, 81{,}693 plexes (2--5 units), 81{,}461 | |
| 45 | +condominiums, 9{,}287 cottages and 4{,}701 mobile homes. | |
| 46 | + | |
| 47 | +\begin{table}[t] | |
| 48 | +\centering | |
| 49 | +\begin{threeparttable} | |
| 50 | +\caption{Summary statistics, estimation sample} | |
| 51 | +\label{tab:sumstats} | |
| 52 | +\small | |
| 53 | +\input{../results/tables/summary_stats} | |
| 54 | +\begin{tablenotes}[flushleft]\footnotesize | |
| 55 | +\item \textit{Notes:} 522{,}769 residential sales, January 2021 -- July | |
| 56 | +2026, matched at the parcel level to the assessment roll in force at the | |
| 57 | +sale date. The assessment ratio is assessed value divided by sale price. | |
| 58 | +Roll lag is the number of months between the roll's statutory | |
| 59 | +market-condition reference date (July 1) and the sale date. The assessed | |
| 60 | +land share is the roll's land value divided by total assessed value. | |
| 61 | +\end{tablenotes} | |
| 62 | +\end{threeparttable} | |
| 63 | +\end{table} | |
| 64 | + | |
| 65 | +\subsection{The raw pattern} | |
| 66 | + | |
| 67 | +The median assessment ratio is 0.78: the typical dwelling sells for about | |
| 68 | +28\% more than its rolled value, the expected imprint of a rising market on | |
| 69 | +back-dated rolls. Figure~\ref{fig:ratiodist} shows the distribution and its | |
| 70 | +decomposition by roll lag: sales occurring within two years of the | |
| 71 | +reference date centre near 0.87, while sales more than four years out | |
| 72 | +centre near 0.64 --- the mechanical staleness gradient that our fixed | |
| 73 | +effects absorb. Figure~\ref{fig:time} traces the same mechanics in | |
| 74 | +calendar time: each roll vintage enters near parity with the market it was | |
| 75 | +referenced on, then drifts down as prices rise, and the 2022--2023 rate | |
| 76 | +shock is visible as a flattening of the drift. Everything that follows | |
| 77 | +nets out this timing structure and asks a sharper question: \emph{within} a | |
| 78 | +given municipality, roll, and year, do cheap and expensive homes face the | |
| 79 | +same ratio? | |
| 80 | + | |
| 81 | +\begin{figure}[t] | |
| 82 | +\centering | |
| 83 | +\includegraphics[width=\textwidth]{fig_ratio_dist.png} | |
| 84 | +\caption{Assessment ratios. Panel A: distribution of $AV/SP$ across the | |
| 85 | +estimation sample; the median is 0.78 and the dashed line marks parity. | |
| 86 | +Panel B: kernel of the same distribution split by the number of months | |
| 87 | +between the roll's market-condition reference date and the sale; older | |
| 88 | +rolls sit systematically further below parity.} | |
| 89 | +\label{fig:ratiodist} | |
| 90 | +\end{figure} | |
| 91 | + | |
| 92 | +\begin{figure}[t] | |
| 93 | +\centering | |
| 94 | +\includegraphics[width=\textwidth]{fig_time.png} | |
| 95 | +\caption{Median assessment ratio by sale month and roll vintage. Each line | |
| 96 | +follows sales assessed under one triennial vintage (labelled by entry | |
| 97 | +year); monthly medians with fewer than 100 sales are suppressed. Vintages | |
| 98 | +enter near their reference-date market level and drift down as prices | |
| 99 | +rise; the flattening after 2022 reflects the interest-rate correction.} | |
| 100 | +\label{fig:time} | |
| 101 | +\end{figure} | |
added
paper/sections/discussion.tex
+81 −0
@@ -0,0 +1,81 @@ | ||
| 1 | +% Author: Simon-Pierre Boucher — contact@spboucher.ai | |
| 2 | +% ============================================================================ | |
| 3 | +\section{Discussion} | |
| 4 | +\label{sec:disc} | |
| 5 | + | |
| 6 | +\subsection{Mechanisms} | |
| 7 | + | |
| 8 | +The evidence assembles into a coherent mechanism story with three layers. | |
| 9 | + | |
| 10 | +\paragraph{1. Hedonic difficulty, not assessor idiosyncrasy.} The | |
| 11 | +heterogeneity profile tracks the difficulty of the valuation problem with | |
| 12 | +almost embarrassing fidelity: condominiums (homogeneous, comparable-rich) | |
| 13 | +are nearly equitable; old houses, plexes, cottages and high-land-share | |
| 14 | +properties (heterogeneous, comparable-poor, unanchored by construction | |
| 15 | +costs) are severely regressive. This is the pattern predicted if assessors | |
| 16 | +run reasonable hedonic models whose unexplained component is largest --- | |
| 17 | +and most value-correlated --- where the housing bundle is hardest to price | |
| 18 | +\citep{amornsiripanitch2022residential, gloudemans2011fundamentals, | |
| 19 | +bostic2007land}. Regression toward the mean in any imperfect valuation | |
| 20 | +model over-values the cheap tail and under-values the expensive tail; | |
| 21 | +our quantile profile, with proportionality holding at the bottom and | |
| 22 | +collapsing at the top ($\beta(0.90) = 0.49$), shows Quebec's failure is | |
| 23 | +concentrated where comparables are thinnest. | |
| 24 | + | |
| 25 | +\paragraph{2. Staleness amplifies the gap.} The doubling of the | |
| 26 | +province-wide PRB in 2021--2022 shows that triennial rolls interact | |
| 27 | +perversely with fast markets: when prices move 30--40\% between reference | |
| 28 | +dates, the cross-sectional dispersion of appreciation --- which is itself | |
| 29 | +correlated with price segment --- loads directly into measured inequity. | |
| 30 | +Jurisdictions cannot control housing cycles, but they control revaluation | |
| 31 | +frequency; annual-cycle systems mechanically cap this channel | |
| 32 | +\citep{berry2021reassessing, ross2012assessor}. | |
| 33 | + | |
| 34 | +\paragraph{3. Scale helps: the Montr\'eal exception.} Montr\'eal --- | |
| 35 | +the largest sales sample, a dedicated in-house assessment service, dense | |
| 36 | +comparable markets in every segment --- is the only large jurisdiction | |
| 37 | +with progressive assessments, in all six years. Mid-sized and small | |
| 38 | +municipalities, many relying on contracted regional evaluators with | |
| 39 | +limited modelling capacity, are uniformly regressive. The thin-market | |
| 40 | +mechanism of \citet{mcmillen2008thin} operates within a single provincial | |
| 41 | +rulebook: identical statutes, divergent outcomes, sorted by market depth | |
| 42 | +and assessment resources. We note one caveat: Montr\'eal's measured | |
| 43 | +progressivity may partly reflect within-city composition (its cell is the | |
| 44 | +whole city, pooling boroughs), and borough-level analysis is a natural | |
| 45 | +extension. | |
| 46 | + | |
| 47 | +\subsection{Policy implications} | |
| 48 | + | |
| 49 | +Three levers follow directly from the diagnosis, in increasing order of | |
| 50 | +ambition. First, \textbf{shorten the cycle}: moving from triennial to | |
| 51 | +annual (or indexed) rolls would eliminate the staleness component that | |
| 52 | +dominated 2021--2022; several U.S. states and British Columbia already | |
| 53 | +operate annual cycles. Second, \textbf{pool assessment capacity}: the | |
| 54 | +regressivity gradient by municipality size argues for regional or | |
| 55 | +provincial mass-appraisal services --- scale is the cheapest known | |
| 56 | +technology for assessment quality. Third, \textbf{audit vertically, not | |
| 57 | +just horizontally}: Quebec's oversight regime monitors median ratios and | |
| 58 | +CODs but sets no explicit PRB requirement; adding the IAAO vertical | |
| 59 | +standard to the ministry's audit criteria, with published | |
| 60 | +municipality-level statistics like those in this paper, would make the | |
| 61 | +equity dimension visible and contestable. Because low-priced-segment | |
| 62 | +owners appeal least \citep{avenancio2022assessment, plummer2014evidence}, | |
| 63 | +supply-side correction --- better models --- will do more than | |
| 64 | +demand-side remedies. | |
| 65 | + | |
| 66 | +\subsection{Limitations} | |
| 67 | + | |
| 68 | +Four limitations bound the interpretation. First, sale prices below | |
| 69 | +\$50{,}000 are absent from the source registry and some residual | |
| 70 | +non-arm's-length transfers may survive our screens; the robustness | |
| 71 | +battery (dropping sub-\$100k sales, tightening trims) shows the | |
| 72 | +conclusions survive, but the bottom-decile tax-shift magnitude should be | |
| 73 | +read as an upper bound on that decile. Second, our data cover 2021--2026, | |
| 74 | +an unusually turbulent market; the within-market elasticity is stable | |
| 75 | +across the window, but levels statistics from calmer periods would differ. | |
| 76 | +Third, we observe no appeals, renovations between assessment and sale, or | |
| 77 | +conditions of sale; some of what we call horizontal noise is genuine | |
| 78 | +unobserved quality change. Fourth, without owner demographics we cannot | |
| 79 | +speak to the distributional incidence across income or racial groups as | |
| 80 | +\citet{avenancio2022assessment} do --- linking these assessments to census | |
| 81 | +tract characteristics is the obvious next step. | |
added
paper/sections/institutional.tex
+63 −0
@@ -0,0 +1,63 @@ | ||
| 1 | +% Author: Simon-Pierre Boucher — contact@spboucher.ai | |
| 2 | +% ============================================================================ | |
| 3 | +\section{Institutional setting: property assessment in Quebec} | |
| 4 | +\label{sec:inst} | |
| 5 | + | |
| 6 | +Quebec's municipal fiscal regime is governed by the \textit{Loi sur la | |
| 7 | +fiscalit\'e municipale} (LFM) and operationalized by the province's | |
| 8 | +assessment manual, the \textit{Manuel d'\'evaluation fonci\`ere du | |
| 9 | +Qu\'ebec} \citep{lfm2026, mamh2024manuel}. Four features matter for the | |
| 10 | +measurement design of this paper. | |
| 11 | + | |
| 12 | +\paragraph{Triennial rolls.} Every municipality's assessment roll is | |
| 13 | +redrawn on a fixed three-year cycle by a municipal body or a contracted | |
| 14 | +private firm under the responsibility of a chartered evaluator. The roll | |
| 15 | +lists, for each assessment unit, the taxable value of the property and its | |
| 16 | +decomposition into land and building components, along with the structural | |
| 17 | +descriptors used in valuation (lot area, floor area, year of construction, | |
| 18 | +number of dwelling units, physical configuration, and a standardized use | |
| 19 | +code, the CUBF). | |
| 20 | + | |
| 21 | +\paragraph{A statutory market-condition date.} Article 46 of the LFM | |
| 22 | +requires that the values entered on a roll reflect the state of the market | |
| 23 | +\emph{eighteen months before} the roll takes effect: a roll entering force | |
| 24 | +on January~1 of year $t$ must value every property as of July~1 of year | |
| 25 | +$t-2$. This single reference date is printed on the roll itself and is | |
| 26 | +common to every property in the municipality. Two implications follow. | |
| 27 | +First, in a rising market the \emph{level} of assessment ratios drifts | |
| 28 | +mechanically below one as sales occur further from the reference date --- | |
| 29 | +between 18 and 54 months elapse between the reference date and a sale, 36 | |
| 30 | +months at the median in our sample. This drift is a feature of the system, | |
| 31 | +not an inequity, and our empirical design absorbs it entirely with | |
| 32 | +municipality~$\times$~roll~$\times$~sale-year fixed effects. Second, and | |
| 33 | +crucially, \emph{within} such a block every property shares the same market | |
| 34 | +vintage, so cross-sectional patterns in ratios cannot be attributed to | |
| 35 | +timing. | |
| 36 | + | |
| 37 | +\paragraph{Uniform taxation of the rolled value.} The municipal levy on a | |
| 38 | +residential property is the rolled taxable value multiplied by the | |
| 39 | +municipal rate (plus school taxes levied on the same base). There are no | |
| 40 | +assessment-growth caps, homestead exemptions, or acquisition-value rules of | |
| 41 | +the Californian or Floridian type: relative taxable values within a | |
| 42 | +municipality equal relative assessed values. This makes the mapping from | |
| 43 | +assessment error to tax-burden redistribution exact --- a property assessed | |
| 44 | +10\% above the jurisdiction's median ratio pays 10\% more tax than uniform | |
| 45 | +assessment would imply --- and motivates the tax-shift calculation of | |
| 46 | +Section~\ref{sec:results}. | |
| 47 | + | |
| 48 | +\paragraph{Oversight and appeal.} The Ministry of Municipal Affairs (MAMH) | |
| 49 | +prescribes methods and audits rolls; owners may request an administrative | |
| 50 | +review and appeal to the Tribunal administratif du Qu\'ebec. Appeal rates | |
| 51 | +for residential property are low. Unlike several U.S. jurisdictions | |
| 52 | +studied in the literature \citep{plummer2014evidence, | |
| 53 | +avenancio2022assessment}, Quebec's appeal system plays a minor quantitative | |
| 54 | +role for the housing stock at large, which makes the valuation model | |
| 55 | +itself --- rather than post-assessment litigation --- the natural locus of | |
| 56 | +any inequity we measure. | |
| 57 | + | |
| 58 | +Taken together, these institutions imply that Quebec should be a | |
| 59 | +\emph{best-case} environment for assessment equity: uniform provincial | |
| 60 | +methodology, professional certification, statutory synchronization of | |
| 61 | +market vintage, and a tax that consumes the rolled value without | |
| 62 | +exemption-driven distortions. The magnitude of the inequity we document | |
| 63 | +below should be read against this backdrop. | |
added
paper/sections/introduction.tex
+127 −0
@@ -0,0 +1,127 @@ | ||
| 1 | +% Author: Simon-Pierre Boucher — contact@spboucher.ai | |
| 2 | +% ============================================================================ | |
| 3 | +\section{Introduction} | |
| 4 | +\label{sec:intro} | |
| 5 | + | |
| 6 | +The property tax is the fiscal backbone of Canadian local government: it | |
| 7 | +finances roughly two thirds of municipal budgets and is, by statute, an | |
| 8 | +\emph{ad valorem} tax --- every dwelling in a municipality is meant to be | |
| 9 | +taxed in strict proportion to its market value. That proportionality rests | |
| 10 | +entirely on the quality of the assessment roll. If assessors systematically | |
| 11 | +overvalue inexpensive homes relative to expensive ones, the legal tax rate | |
| 12 | +becomes a fiction: the effective tax rate falls with wealth, and the | |
| 13 | +resulting redistribution --- from modest neighbourhoods toward affluent ones | |
| 14 | +--- occurs silently, without any legislative decision, embedded in a | |
| 15 | +technical document that few taxpayers ever contest. A rapidly growing | |
| 16 | +literature documents exactly this pattern in the United States, where | |
| 17 | +assessment regressivity has been measured at national scale | |
| 18 | +\citep{berry2021reassessing, avenancio2022assessment, | |
| 19 | +amornsiripanitch2022residential}. Whether the same pathology afflicts | |
| 20 | +Canadian assessment systems --- centralized, professionally supervised, and | |
| 21 | +widely presumed to be of high quality --- is essentially unknown. | |
| 22 | + | |
| 23 | +This paper provides the first province-wide answer for Quebec. We assemble | |
| 24 | +522{,}769 residential transactions recorded between January 2021 and July | |
| 25 | +2026 and match each sale, at the parcel level, to the triennial municipal | |
| 26 | +assessment roll in force on the day of the sale. The match is performed on | |
| 27 | +geographic coordinates and validated against the roll's recorded value; the | |
| 28 | +median match distance is under one metre. The resulting file couples every | |
| 29 | +sale price with the exact taxable value that generated the owner's property | |
| 30 | +tax bill, together with the roll's decomposition of that value into land and | |
| 31 | +building components, structural descriptors (lot area, floor area, year | |
| 32 | +built, number of units), and the statutory market-condition reference date | |
| 33 | +of the roll. The data span 625 municipalities --- from Montr\'eal | |
| 34 | +(79{,}581 usable sales) to towns with barely a hundred --- and a housing | |
| 35 | +cycle of unusual amplitude: the post-pandemic boom, the 2022--2023 interest | |
| 36 | +rate shock, and the subsequent recovery. | |
| 37 | + | |
| 38 | +Quebec's institutional design makes it an unusually clean laboratory for | |
| 39 | +equity measurement. Every municipality's roll is redone on a fixed | |
| 40 | +three-year cycle, and by law (art.~46, \textit{Loi sur la fiscalit\'e | |
| 41 | +municipale}) the values entered on a roll must reflect market conditions | |
| 42 | +eighteen months before the roll takes effect --- a single, known reference | |
| 43 | +date shared by every property in the municipality | |
| 44 | +\citep{lfm2026, mamh2024manuel}. Within a municipality $\times$ roll | |
| 45 | +$\times$ sale-year block, therefore, every assessment embodies the same | |
| 46 | +market vintage, and any systematic relationship between assessment ratios | |
| 47 | +and prices inside such a block is inequity, not timing. Our empirical design | |
| 48 | +exploits exactly this structure: all regression estimates absorb 2{,}884 | |
| 49 | +municipality~$\times$~roll~$\times$~year fixed effects, so that vertical | |
| 50 | +inequity is identified purely from comparisons between cheap and expensive | |
| 51 | +dwellings facing the same assessor, the same roll, and the same market | |
| 52 | +moment. | |
| 53 | + | |
| 54 | +Three findings emerge. First, assessment regressivity in Quebec is | |
| 55 | +pervasive, large, and statistically unambiguous. The elasticity of the | |
| 56 | +assessment ratio with respect to the sale price --- zero under proportional | |
| 57 | +assessment --- is $-0.34$ (s.e.\ $0.03$) in the fixed-effects log-log | |
| 58 | +regression. Because sale prices measure market value with noise, part of | |
| 59 | +that estimate reflects mechanical attenuation \citep{kochin1982vertical, | |
| 60 | +clapp1990new}; a rank-based instrumental-variables estimator in the spirit | |
| 61 | +of \citet{clapp1990new} that purges this bias still yields $-0.08$ | |
| 62 | +(s.e.\ $0.02$). Under the IAAO ratio-study standards used by assessment | |
| 63 | +authorities across North America \citep{iaao2013standard}, 95\% of Quebec | |
| 64 | +municipalities fall outside the acceptable band for price-related bias and | |
| 65 | +99\% exceed the uniformity (COD) ceiling. Strikingly, Montr\'eal --- the | |
| 66 | +province's largest and best-resourced assessment jurisdiction --- is the | |
| 67 | +\emph{only} major market whose assessments are progressive. | |
| 68 | + | |
| 69 | +Second, the inequity has a clear anatomy. Regressivity is roughly four times | |
| 70 | +stronger for single-family homes and plexes than for condominiums, whose | |
| 71 | +quasi-homogeneous units are easy to mass-appraise; it rises steeply with | |
| 72 | +building age and with the assessed land share of the property, consistent | |
| 73 | +with the difficulty of valuing land and depreciated structures | |
| 74 | +\citep{bostic2007land, gloudemans2011fundamentals}; and it is worse in small | |
| 75 | +municipalities, echoing the thin-market mechanism of \citet{mcmillen2008thin}. | |
| 76 | +The quantile profile is equally telling: the log-log slope | |
| 77 | +falls from $0.87$ at the first decile of the conditional value distribution | |
| 78 | +to $0.49$ at the ninth, so the failure of proportionality is concentrated at | |
| 79 | +the top --- expensive homes are not merely under-assessed, they are | |
| 80 | +under-assessed at an accelerating rate. | |
| 81 | + | |
| 82 | +Third, the stakes are material. Within a taxing jurisdiction the levy is | |
| 83 | +proportional to assessed value, so a property assessed above the | |
| 84 | +jurisdiction median ratio pays exactly that percentage more tax than uniform | |
| 85 | +assessment would imply. The median dwelling in the bottom within-market | |
| 86 | +price decile pays roughly 65\% more property tax than under uniform | |
| 87 | +assessment; the median dwelling in the top decile pays about 5\% less. Put | |
| 88 | +differently, the effective tax schedule that Quebec's assessment machinery | |
| 89 | +delivers is regressive enough to undo, within each municipality, a | |
| 90 | +substantial share of whatever progressivity the rest of the fiscal system | |
| 91 | +achieves --- a finding that complements \citet{carbonnier2024property}, who | |
| 92 | +documents the regressivity of Quebec's property tax relative to income using | |
| 93 | +survey data, but who could not observe the assessment channel isolated here. | |
| 94 | + | |
| 95 | +We also show \emph{when} the gap opens. Assessment ratios drift mechanically | |
| 96 | +between reference dates, and the province-wide price-related bias doubles in | |
| 97 | +2021--2022, precisely when the post-pandemic boom pulled market prices away | |
| 98 | +from rolls anchored in 2018--2020 conditions --- direct evidence that | |
| 99 | +infrequent revaluation is a first-order driver of measured inequity, as | |
| 100 | +conjectured in the U.S. literature \citep{berry2021reassessing, | |
| 101 | +ross2012assessor}. Because the diagnosis singles out stale rolls, thin | |
| 102 | +markets, and hard-to-value property types --- rather than assessor | |
| 103 | +discretion alone --- it maps directly into policy: shorter revaluation | |
| 104 | +cycles, pooled assessment services for small municipalities, and targeted | |
| 105 | +review of high-land-share and older properties. | |
| 106 | + | |
| 107 | +Beyond the Canadian evidence gap, the paper makes two methodological | |
| 108 | +contributions to the ratio-study literature. It is, to our knowledge, the | |
| 109 | +first large-scale equity study to exploit a statutory single-date reference | |
| 110 | +regime to separate timing drift from genuine vertical inequity by design | |
| 111 | +rather than by econometric correction; and it implements the full modern | |
| 112 | +test battery --- IAAO diagnostics with bootstrap inference | |
| 113 | +\citep{iaao2013standard}, the classical regression tests | |
| 114 | +\citep{paglin1972equity, cheng1974property}, measurement-error-robust IV | |
| 115 | +\citep{clapp1990new}, and quantile profiles \citep{mcmillen2020assessment} | |
| 116 | +--- on more than half a million sales, allowing precise subgroup estimates | |
| 117 | +that smaller samples cannot support. | |
| 118 | + | |
| 119 | +The remainder of the paper proceeds as follows. Section~\ref{sec:lit} | |
| 120 | +positions the paper in the ratio-study and property-tax-equity literatures. | |
| 121 | +Section~\ref{sec:inst} describes Quebec's assessment institutions. | |
| 122 | +Section~\ref{sec:data} presents the data and the matched sample. | |
| 123 | +Section~\ref{sec:method} lays out the diagnostics and econometric | |
| 124 | +specifications. Section~\ref{sec:results} reports the results, | |
| 125 | +Section~\ref{sec:robust} the robustness battery, Section~\ref{sec:disc} | |
| 126 | +discusses mechanisms and policy implications, and Section~\ref{sec:concl} | |
| 127 | +concludes. | |
added
paper/sections/literature.tex
+100 −0
@@ -0,0 +1,100 @@ | ||
| 1 | +% Author: Simon-Pierre Boucher — contact@spboucher.ai | |
| 2 | +% ============================================================================ | |
| 3 | +\section{Related literature} | |
| 4 | +\label{sec:lit} | |
| 5 | + | |
| 6 | +\subsection{Measuring vertical inequity: from ratio studies to modern tests} | |
| 7 | + | |
| 8 | +The empirical study of assessment equity is nearly as old as the modern | |
| 9 | +property tax. Its workhorse is the \emph{sales ratio study}: for each sale | |
| 10 | +$i$, compute the ratio $r_i = AV_i/SP_i$ of assessed value to sale price and | |
| 11 | +ask whether $r_i$ is uniform across the value distribution. The | |
| 12 | +practitioner's canon --- codified in the IAAO \textit{Standard on Ratio | |
| 13 | +Studies} \citep{iaao2013standard} and the mass-appraisal manuals of | |
| 14 | +\citet{eckert1990property} and \citet{gloudemans2011fundamentals} --- | |
| 15 | +summarizes a jurisdiction with the median ratio, the coefficient of | |
| 16 | +dispersion (COD) for horizontal uniformity, and the price-related | |
| 17 | +differential (PRD) or, since 2013, the coefficient of price-related bias | |
| 18 | +(PRB) for vertical equity. These diagnostics discipline assessment practice | |
| 19 | +across North America, and we report all of them, with bootstrap confidence | |
| 20 | +intervals, for every Quebec municipality with sufficient sales. | |
| 21 | + | |
| 22 | +The academic literature grew out of dissatisfaction with those summary | |
| 23 | +measures. \citet{paglin1972equity} proposed regressing assessed value on | |
| 24 | +sale price in levels and reading regressivity off a positive intercept; | |
| 25 | +\citet{cheng1974property} moved the test to logarithms, where the slope | |
| 26 | +$\beta$ has the clean interpretation of an elasticity and $\beta<1$ signals | |
| 27 | +regressivity. Subsequent work refined the specification menu --- | |
| 28 | +\citet{bell1984administrative} and \citet{sunderman1990testing} allowed | |
| 29 | +splines and structural breaks, and \citet{sirmans1995vertical} surveyed the | |
| 30 | +proliferating test battery. A decisive critique came from | |
| 31 | +\citet{kochin1982vertical} and \citet{kennedy1984unfair}: because the sale | |
| 32 | +price is itself a noisy measure of market value, the log-log slope is | |
| 33 | +attenuated toward zero even under perfectly proportional assessment --- | |
| 34 | +ratio studies are biased \emph{toward} finding regressivity. | |
| 35 | +\citet{clapp1990new} answered with a rank-based instrumental-variables | |
| 36 | +estimator whose coarse instrument is nearly orthogonal to transitory price | |
| 37 | +noise; \citet{goolsby1997assessment} and \citet{mcmillen2020assessment} | |
| 38 | +document how severely the correction can matter, and | |
| 39 | +\citet{quintos2020prb} extends the toolkit with machine-learning | |
| 40 | +diagnostics. Our design follows this lesson to the letter: we report the | |
| 41 | +na\"ive estimates, the Clapp IV, and quantile profiles | |
| 42 | +\citep{mcmillen2020assessment} side by side, and treat the IV as the lower | |
| 43 | +bound on true regressivity. | |
| 44 | + | |
| 45 | +\subsection{Evidence: pervasive regressivity, mostly documented in the U.S.} | |
| 46 | + | |
| 47 | +Substantively, the modern consensus is that assessment regressivity is the | |
| 48 | +rule rather than the exception. \citet{berry2021reassessing} computes ratio | |
| 49 | +statistics for essentially every U.S. county and finds that the | |
| 50 | +lowest-decile home within a jurisdiction faces an effective tax rate roughly | |
| 51 | +double that of the top decile; \citet{avenancio2022assessment} show in the | |
| 52 | +\textit{QJE} that the same machinery generates a 10--13\% assessment gap | |
| 53 | +against Black and Hispanic homeowners, driven partly by unpriced | |
| 54 | +neighbourhood attributes and partly by differential appeals; | |
| 55 | +\citet{amornsiripanitch2022residential} attributes much of the gradient to | |
| 56 | +assessors' inability to capture unobserved quality that scales with price. | |
| 57 | +Earlier strands documented the same pattern city by city --- | |
| 58 | +\citet{engle1975deassessment} in Boston, \citet{baar1981property} across | |
| 59 | +low-income neighbourhoods, \citet{mcmillen2011assessment} and | |
| 60 | +\citet{mcmillen2008thin} in Illinois, where thin markets and infrequent | |
| 61 | +reassessment emerge as structural drivers, \citet{hodge2017assessment} in | |
| 62 | +collapsing Detroit, and \citet{plummer2014evidence} and | |
| 63 | +\citet{ihlanfeldt2023appraisal} on the role of appeals and exemptions. | |
| 64 | +\citet{ross2012assessor} shows regressivity responds to the institutional | |
| 65 | +incentives of assessors themselves. The mechanism menu that emerges --- | |
| 66 | +stale valuations, hard-to-value heterogeneous properties, thin markets, | |
| 67 | +asymmetric appeals --- guides our heterogeneity analysis directly. | |
| 68 | + | |
| 69 | +\subsection{Canada: a conspicuous gap} | |
| 70 | + | |
| 71 | +For Canada the cupboard is nearly bare. Ontario's assessment corporation | |
| 72 | +publishes an internal vertical-equity review of its own values | |
| 73 | +\citep{mpac2022vertical}, and \citet{found2017property} compare tax | |
| 74 | +\emph{rates} across cities, but neither measures sale-level equity. | |
| 75 | +For Quebec, \citet{carbonnier2024property} establishes with survey microdata | |
| 76 | +that property tax payments are steeply regressive relative to | |
| 77 | +\emph{income}; the assessment channel --- whether the tax base itself is | |
| 78 | +mismeasured against \emph{market value} --- has never been examined at | |
| 79 | +scale, for Quebec or for any Canadian province. This is the gap the present | |
| 80 | +paper fills. The Canadian case is of independent interest precisely because | |
| 81 | +its institutions differ from the fragmented U.S. county system on the | |
| 82 | +dimensions the U.S. literature blames: Quebec assessment is governed by a | |
| 83 | +uniform provincial statute and manual \citep{lfm2026, mamh2024manuel}, | |
| 84 | +performed by certified professional evaluators, and synchronized to a | |
| 85 | +statutory market-condition date. Finding large regressivity \emph{despite} | |
| 86 | +this apparatus sharpens the interpretation considerably: the problem is not | |
| 87 | +lax governance but the intrinsic difficulty of mass appraisal, compounded by | |
| 88 | +triennial staleness. | |
| 89 | + | |
| 90 | +\subsection{Hedonic foundations} | |
| 91 | + | |
| 92 | +Finally, the paper connects to the hedonic tradition. Mass appraisal is | |
| 93 | +applied hedonics \citep{rosen1974hedonic, malpezzi2003hedonic}: the | |
| 94 | +assessor's model prices a bundle of structural and locational attributes, | |
| 95 | +and equity failures are hedonic specification failures --- unpriced | |
| 96 | +land-share gradients \citep{bostic2007land}, depreciation curvature, or | |
| 97 | +neighbourhood effects that Quebec practitioners have long modelled in the | |
| 98 | +Quebec City market \citep{desrosiers2000hedonic}. Reading our subgroup | |
| 99 | +estimates through this lens turns an audit into a diagnosis: the assessment | |
| 100 | +gap is largest exactly where the hedonic problem is hardest. | |
added
paper/sections/methodology.tex
+108 −0
@@ -0,0 +1,108 @@ | ||
| 1 | +% Author: Simon-Pierre Boucher — contact@spboucher.ai | |
| 2 | +% ============================================================================ | |
| 3 | +\section{Methodology} | |
| 4 | +\label{sec:method} | |
| 5 | + | |
| 6 | +Let $AV_i$ denote the assessed value of property $i$ on the roll in force | |
| 7 | +at its sale, $SP_i$ its sale price, and $r_i = AV_i/SP_i$ the assessment | |
| 8 | +ratio. Under perfectly proportional (vertically equitable) assessment, | |
| 9 | +$\mathbb{E}[r_i \mid SP_i]$ is constant. All tests below ask, in different | |
| 10 | +ways, whether $r_i$ instead declines with value. | |
| 11 | + | |
| 12 | +\subsection{IAAO ratio-study diagnostics} | |
| 13 | + | |
| 14 | +For any group of sales we report the four statistics of the IAAO | |
| 15 | +\textit{Standard on Ratio Studies} \citep{iaao2013standard}. The | |
| 16 | +\emph{median ratio} measures the assessment level. The \emph{coefficient of | |
| 17 | +dispersion}, | |
| 18 | +$\mathrm{COD} = 100 \cdot \operatorname{mean}\!\left(|r_i - | |
| 19 | +\tilde r|\right) / \tilde r$ with $\tilde r$ the median ratio, measures | |
| 20 | +horizontal uniformity; the standard deems residential CODs above 15 | |
| 21 | +unacceptable. The \emph{price-related differential}, | |
| 22 | +$\mathrm{PRD} = \bar r \,/\, (\sum_i AV_i / \sum_i SP_i)$, compares the | |
| 23 | +unweighted and value-weighted mean ratios; PRD~$>1.03$ indicates | |
| 24 | +regressivity. Because the PRD is sensitive to outliers, the standard's | |
| 25 | +preferred vertical measure is the \emph{coefficient of price-related bias} | |
| 26 | +(PRB), the slope $b$ in | |
| 27 | +\begin{equation} | |
| 28 | +\frac{r_i - \tilde r}{\tilde r} | |
| 29 | + \;=\; a + b \cdot \log_2\!\Big(\tfrac{1}{2}SP_i + | |
| 30 | + \tfrac{1}{2}\,AV_i/\tilde r\Big) + u_i , | |
| 31 | +\label{eq:prb} | |
| 32 | +\end{equation} | |
| 33 | +which measures the proportional change in the ratio per doubling of value; | |
| 34 | +the acceptable band is $[-0.05, 0.05]$. We compute percentile-bootstrap | |
| 35 | +confidence intervals for all four statistics. Because ratio levels drift | |
| 36 | +with roll staleness, municipality-level diagnostics are computed within | |
| 37 | +municipality~$\times$~sale-year blocks --- inside which a single roll is in | |
| 38 | +force --- and aggregated across years by the median. | |
| 39 | + | |
| 40 | +\subsection{Regression tests with market-timing fixed effects} | |
| 41 | + | |
| 42 | +Our workhorse is the log-log specification of \citet{cheng1974property}, | |
| 43 | +\begin{equation} | |
| 44 | +\ln AV_i \;=\; \alpha_{c(i)} + \beta \, \ln SP_i + \varepsilon_i , | |
| 45 | +\label{eq:cheng} | |
| 46 | +\end{equation} | |
| 47 | +where $c(i)$ indexes the municipality~$\times$~roll~$\times$~sale-year cell | |
| 48 | +of sale $i$ and the $\alpha_c$ are 2{,}884 absorbed fixed effects. | |
| 49 | +Proportionality implies $\beta = 1$; we report | |
| 50 | +$\gamma \equiv \beta - 1$, the elasticity of the assessment \emph{ratio} | |
| 51 | +with respect to price ($\gamma<0$: regressive). The fixed effects guarantee | |
| 52 | +that $\gamma$ is identified only from comparisons of dwellings assessed by | |
| 53 | +the same authority, on the same roll vintage, and sold in the same year --- | |
| 54 | +purging the staleness drift, municipal composition, and aggregate market | |
| 55 | +movements in one stroke. For completeness we also report the pooled | |
| 56 | +regression without fixed effects and the levels test of | |
| 57 | +\citet{paglin1972equity}. Inference is clustered at the municipality level | |
| 58 | +throughout (625 clusters). | |
| 59 | + | |
| 60 | +\subsection{Measurement error and the Clapp instrument} | |
| 61 | + | |
| 62 | +Sale prices measure market value with idiosyncratic noise --- bilateral | |
| 63 | +bargaining, unobserved conditions of sale --- so OLS on | |
| 64 | +equation~\eqref{eq:cheng} suffers attenuation bias: $\hat\beta < 1$ even | |
| 65 | +under proportional assessment \citep{kochin1982vertical, kennedy1984unfair}. | |
| 66 | +Following \citet{clapp1990new}, we instrument $\ln SP_i$ with a coarse rank | |
| 67 | +variable $Z_i \in \{-1, 0, +1\}$ that flags sales in the bottom or top | |
| 68 | +third of \emph{both} the within-cell $\ln AV$ and $\ln SP$ distributions. | |
| 69 | +Because $Z_i$ retains only ordinal information agreed on by both measures of | |
| 70 | +value, it is (nearly) orthogonal to the transitory component of either, and | |
| 71 | +the resulting two-stage least-squares estimate of $\beta$ is consistent | |
| 72 | +under classical measurement error. The demeaned-within-cell implementation | |
| 73 | +preserves the fixed-effects structure. Throughout the paper we treat | |
| 74 | +$\gamma_{\text{IV}}$ as a conservative lower bound on regressivity and | |
| 75 | +$\gamma_{\text{FE}}$ as the descriptive upper bound; the truth lies between. | |
| 76 | + | |
| 77 | +\subsection{Quantile profile and heterogeneity} | |
| 78 | + | |
| 79 | +Averaging can hide where proportionality fails. We estimate | |
| 80 | +equation~\eqref{eq:cheng} by quantile regression on within-cell demeaned | |
| 81 | +variables at $\tau \in \{0.10, 0.25, 0.50, 0.75, 0.90\}$ | |
| 82 | +\citep{mcmillen2020assessment}, tracing $\beta(\tau)$ across the | |
| 83 | +conditional distribution of assessed values. We then re-estimate the | |
| 84 | +fixed-effects model on subsamples --- property class, building-age bands, | |
| 85 | +assessed-land-share bands, roll-lag bands, municipality size, and sale year | |
| 86 | +--- keeping only cells that retain at least 20 sales, to locate the | |
| 87 | +inequity where the mass-appraisal problem is hardest. | |
| 88 | + | |
| 89 | +\subsection{Horizontal inequity} | |
| 90 | + | |
| 91 | +Vertical tests concern the \emph{mean} of $r_i$ given value; horizontal | |
| 92 | +equity concerns its \emph{dispersion} among comparable properties | |
| 93 | +\citep{allen2002measuring, sirmans2008vertical}. Beyond the COD, we ask | |
| 94 | +who receives noisy assessments: we regress the absolute deviation of a | |
| 95 | +sale's log ratio from its cell median, | |
| 96 | +$|\ln r_i - \operatorname{med}_{c(i)} \ln r|$, on property characteristics | |
| 97 | +(age, land share, property-class indicators) with cell fixed effects. | |
| 98 | + | |
| 99 | +\subsection{The implied tax shift} | |
| 100 | + | |
| 101 | +Because Quebec taxes the rolled value without exemptions or caps | |
| 102 | +(Section~\ref{sec:inst}), a property whose ratio exceeds its jurisdiction's | |
| 103 | +median by $x\%$ pays exactly $x\%$ more tax than uniform assessment would | |
| 104 | +imply. For each sale we compute the relative assessment error | |
| 105 | +$e_i = r_i / \operatorname{med}_{c(i)}(r) - 1$ and average it by | |
| 106 | +within-cell sale-price decile. This translates the econometrics into the | |
| 107 | +policy-relevant object: the percentage over- or under-payment of property | |
| 108 | +tax by position in the local price distribution. | |
added
paper/sections/results.tex
+298 −0
@@ -0,0 +1,298 @@ | ||
| 1 | +% Author: Simon-Pierre Boucher — contact@spboucher.ai | |
| 2 | +% ============================================================================ | |
| 3 | +\section{Results} | |
| 4 | +\label{sec:results} | |
| 5 | + | |
| 6 | +\subsection{Province-wide diagnostics} | |
| 7 | + | |
| 8 | +Table~\ref{tab:iaao} reports the IAAO statistics for the full sample and by | |
| 9 | +sale year. Three facts stand out. First, uniformity is poor everywhere: the | |
| 10 | +province-wide COD is 28.5, and even within municipality-year blocks the | |
| 11 | +median municipality posts a COD of 26 --- nearly twice the IAAO ceiling of | |
| 12 | +15 for residential property; 99\% of municipalities exceed it | |
| 13 | +(Figure~\ref{fig:cod}). Second, every vertical measure points the same way: | |
| 14 | +the PRD is 1.072 (acceptable range 0.98--1.03) and the PRB is $-0.029$ with | |
| 15 | +a bootstrap confidence interval far from zero --- ratios fall by about 3\% | |
| 16 | +of the median with every doubling of value. Third, the time pattern is | |
| 17 | +diagnostic: the PRB collapses to $-0.104$ and $-0.117$ in 2021 and 2022 --- | |
| 18 | +when the boom pulled prices away from rolls referenced on 2018--2020 | |
| 19 | +markets --- and shrinks to $-0.002$ in 2024 before widening again. Vertical | |
| 20 | +inequity is not a fixed institutional constant; it breathes with the gap | |
| 21 | +between the market and the roll's vintage. | |
| 22 | + | |
| 23 | +\begin{table}[t] | |
| 24 | +\centering | |
| 25 | +\begin{threeparttable} | |
| 26 | +\caption{IAAO ratio-study diagnostics, province-wide} | |
| 27 | +\label{tab:iaao} | |
| 28 | +\small | |
| 29 | +\input{../results/tables/iaao} | |
| 30 | +\begin{tablenotes}[flushleft]\footnotesize | |
| 31 | +\item \textit{Notes:} Median assessment ratio, coefficient of dispersion | |
| 32 | +(COD), price-related differential (PRD) and coefficient of price-related | |
| 33 | +bias (PRB), computed on the estimation sample; 95\% percentile-bootstrap | |
| 34 | +confidence intervals (200 replications) in brackets. IAAO acceptable | |
| 35 | +ranges: COD $\le 15$ (single-family residential), PRD $\in [0.98, 1.03]$, | |
| 36 | +PRB $\in [-0.05, 0.05]$. | |
| 37 | +\end{tablenotes} | |
| 38 | +\end{threeparttable} | |
| 39 | +\end{table} | |
| 40 | + | |
| 41 | +\begin{figure}[t] | |
| 42 | +\centering | |
| 43 | +\includegraphics[width=\textwidth]{fig_cod.png} | |
| 44 | +\caption{Horizontal uniformity. Panel A: distribution of municipality-level | |
| 45 | +CODs (median across sale-year blocks); the IAAO ceiling for residential | |
| 46 | +property is 15. Panel B: COD against the number of sales in the | |
| 47 | +municipality; dispersion is worst in the thinnest markets.} | |
| 48 | +\label{fig:cod} | |
| 49 | +\end{figure} | |
| 50 | + | |
| 51 | +Table~\ref{tab:cities} disaggregates the ten largest markets. | |
| 52 | +Regressivity is significant in nine of them --- Sherbrooke ($-0.39$), | |
| 53 | +Trois-Rivi\`eres ($-0.32$) and Terrebonne ($-0.29$) are the worst --- in | |
| 54 | +every single sale year. The exception is Montr\'eal, whose PRB of $+0.07$ | |
| 55 | +makes it the lone \emph{progressive} large jurisdiction in the province, in | |
| 56 | +all six years. We return to this contrast in Section~\ref{sec:disc}. | |
| 57 | +Figure~\ref{fig:map} maps the municipal estimates: regressive (red) | |
| 58 | +municipalities carpet the province, with the Montr\'eal agglomeration a | |
| 59 | +solitary blue island, and Figure~\ref{fig:prbmuni} shows the distribution: | |
| 60 | +99\% of the 261 municipalities with at least 100 usable sales have a | |
| 61 | +negative PRB, and 95\% fall below the IAAO band. | |
| 62 | + | |
| 63 | +\begin{table}[t] | |
| 64 | +\centering | |
| 65 | +\begin{threeparttable} | |
| 66 | +\caption{Assessment equity in the ten largest markets} | |
| 67 | +\label{tab:cities} | |
| 68 | +\small | |
| 69 | +\input{../results/tables/iaao_cities} | |
| 70 | +\begin{tablenotes}[flushleft]\footnotesize | |
| 71 | +\item \textit{Notes:} Statistics computed within municipality | |
| 72 | +$\times$ sale-year blocks (a single roll in force per block) and | |
| 73 | +aggregated across years by the median; $n$ is the total number of sales. | |
| 74 | +The last column is the share of the six sale years in which the | |
| 75 | +municipality's PRB is negative. | |
| 76 | +\end{tablenotes} | |
| 77 | +\end{threeparttable} | |
| 78 | +\end{table} | |
| 79 | + | |
| 80 | +\begin{figure}[t] | |
| 81 | +\centering | |
| 82 | +\includegraphics[width=0.72\textwidth]{fig_prb_muni.png} | |
| 83 | +\caption{Distribution of municipality-level PRB across the 261 | |
| 84 | +municipalities with at least 100 usable sales. The shaded band is the IAAO | |
| 85 | +acceptable range $[-0.05, 0.05]$.} | |
| 86 | +\label{fig:prbmuni} | |
| 87 | +\end{figure} | |
| 88 | + | |
| 89 | +\begin{figure}[t] | |
| 90 | +\centering | |
| 91 | +\includegraphics[width=\textwidth]{fig_map.png} | |
| 92 | +\caption{The geography of vertical inequity. Municipality-level PRB | |
| 93 | +(median across sale-year blocks); bubble area is proportional to the | |
| 94 | +square root of the number of sales. Montr\'eal is the only large | |
| 95 | +progressive jurisdiction.} | |
| 96 | +\label{fig:map} | |
| 97 | +\end{figure} | |
| 98 | + | |
| 99 | +\subsection{Regression estimates} | |
| 100 | + | |
| 101 | +Table~\ref{tab:vertical} presents the econometric core. | |
| 102 | +Figure~\ref{fig:binscatter} shows the raw material: within | |
| 103 | +municipality~$\times$~roll~$\times$~year cells, mean log assessment ratios | |
| 104 | +fall monotonically --- and remarkably linearly through the central 90\% of | |
| 105 | +the distribution --- as log prices rise. The pooled \citet{cheng1974property} | |
| 106 | +elasticity is $\beta = 0.812$ (s.e.\ 0.029); absorbing the 2{,}884 cell | |
| 107 | +fixed effects \emph{steepens} it to $\beta = 0.656$, i.e.\ | |
| 108 | +$\gamma_{\text{FE}} = -0.344$: within the same market and vintage, a home | |
| 109 | +priced 10\% above its neighbours is assessed only about 6.6\% higher, so | |
| 110 | +its ratio is 3.4\% lower. The levels test of \citet{paglin1972equity} | |
| 111 | +agrees: the intercept is \$31{,}729 (s.e.\ \$5{,}378) --- inexpensive | |
| 112 | +properties carry a fixed assessment premium. | |
| 113 | + | |
| 114 | +\begin{table}[t] | |
| 115 | +\centering | |
| 116 | +\begin{threeparttable} | |
| 117 | +\caption{Vertical-inequity regressions} | |
| 118 | +\label{tab:vertical} | |
| 119 | +\small | |
| 120 | +\input{../results/tables/vertical} | |
| 121 | +\begin{tablenotes}[flushleft]\footnotesize | |
| 122 | +\item \textit{Notes:} Dependent variable: $\ln AV$. Column (1): pooled | |
| 123 | +OLS. Column (2): absorbing least squares with 2{,}884 municipality | |
| 124 | +$\times$ roll $\times$ sale-year fixed effects. Column (3): within-cell | |
| 125 | +2SLS using the \citet{clapp1990new} rank instrument | |
| 126 | +$Z \in \{-1,0,+1\}$. Standard errors clustered by municipality (625 | |
| 127 | +clusters) in parentheses. Stars test $H_0\!: \beta = 1$ (proportional | |
| 128 | +assessment); *** $p<0.01$. | |
| 129 | +\end{tablenotes} | |
| 130 | +\end{threeparttable} | |
| 131 | +\end{table} | |
| 132 | + | |
| 133 | +\begin{figure}[t] | |
| 134 | +\centering | |
| 135 | +\includegraphics[width=0.72\textwidth]{fig_binscatter.png} | |
| 136 | +\caption{Vertical inequity within markets. Mean of the within-cell demeaned | |
| 137 | +log assessment ratio by vigintile of the within-cell demeaned log sale | |
| 138 | +price; whiskers are 95\% confidence intervals (they are smaller than the | |
| 139 | +markers). The fitted line is the sale-weighted least-squares slope.} | |
| 140 | +\label{fig:binscatter} | |
| 141 | +\end{figure} | |
| 142 | + | |
| 143 | +How much of this is measurement-error artefact? The \citet{clapp1990new} | |
| 144 | +instrument delivers $\beta_{\text{IV}} = 0.917$ (s.e.\ 0.021): | |
| 145 | +attenuation indeed accounts for the majority of the na\"ive gap, exactly | |
| 146 | +as the critique of \citet{kochin1982vertical} predicts --- but the | |
| 147 | +corrected elasticity remains four standard errors below one. Genuine | |
| 148 | +regressivity of roughly $-0.08$ survives the harshest correction in the | |
| 149 | +literature. An 8\% ratio penalty per log-point of value is economically | |
| 150 | +large: across the interquartile price range within a typical market | |
| 151 | +(roughly 0.8 log points), it generates a 6--7\% differential in effective | |
| 152 | +tax rates from vertical inequity alone, before adding the much larger | |
| 153 | +dispersion documented below. | |
| 154 | + | |
| 155 | +\subsection{Where proportionality fails: the quantile profile} | |
| 156 | + | |
| 157 | +Figure~\ref{fig:quantile} plots $\beta(\tau)$. The profile is strikingly | |
| 158 | +asymmetric: $\beta$ is 0.87 at $\tau=0.10$ and 0.86 at $\tau=0.25$, then | |
| 159 | +falls to 0.70 at $\tau=0.75$ and 0.49 at $\tau=0.90$ | |
| 160 | +(Table~\ref{tab:quantile} in the robustness section reports the | |
| 161 | +coefficients). Assessment tracks the market tolerably well through the | |
| 162 | +middle of the distribution and loses contact at the top: the most expensive | |
| 163 | +homes within each market are under-assessed at an accelerating rate. This | |
| 164 | +is the signature of hedonic truncation --- mass-appraisal models built on | |
| 165 | +mid-market comparables extrapolate poorly into thin luxury segments | |
| 166 | +\citep{amornsiripanitch2022residential, gloudemans2011fundamentals}. | |
| 167 | + | |
| 168 | +\begin{figure}[t] | |
| 169 | +\centering | |
| 170 | +\includegraphics[width=0.72\textwidth]{fig_quantile.png} | |
| 171 | +\caption{Quantile profile of the Cheng elasticity. Quantile regressions of | |
| 172 | +within-cell demeaned $\ln AV$ on demeaned $\ln SP$; the shaded band is the | |
| 173 | +pointwise 95\% confidence interval. Reference lines mark proportionality | |
| 174 | +($\beta=1$) and the FE and IV point estimates.} | |
| 175 | +\label{fig:quantile} | |
| 176 | +\end{figure} | |
| 177 | + | |
| 178 | +\subsection{The anatomy of regressivity} | |
| 179 | + | |
| 180 | +Figure~\ref{fig:het} and Table~\ref{tab:het} dissect | |
| 181 | +$\gamma_{\text{FE}}$ by subsample, and the pattern reads like a map of | |
| 182 | +mass-appraisal difficulty: | |
| 183 | + | |
| 184 | +\begin{itemize} | |
| 185 | +\item \textbf{Property class.} Condominiums --- homogeneous units with | |
| 186 | +abundant comparables --- show $\gamma = -0.12$, single-family homes | |
| 187 | +$-0.45$ and plexes $-0.49$. The assessor's problem, not the assessor's | |
| 188 | +jurisdiction, drives the gradient. | |
| 189 | +\item \textbf{Building age.} Regressivity deepens from $-0.30$ (under 20 | |
| 190 | +years) to $-0.43$ (over 60): depreciation and renovation heterogeneity are | |
| 191 | +hard to observe from the roll. | |
| 192 | +\item \textbf{Land share.} The starkest gradient: $-0.26$ where land is | |
| 193 | +under 20\% of assessed value versus $-0.50$ where it exceeds 40\%. | |
| 194 | +Land is the component without construction-cost anchoring | |
| 195 | +\citep{bostic2007land}, and properties whose value is mostly land are | |
| 196 | +assessed worst. | |
| 197 | +\item \textbf{Municipality size.} Large markets ($>$10k sales) do | |
| 198 | +better ($-0.27$) than mid-sized ones ($-0.41$), consistent with | |
| 199 | +\citet{mcmillen2008thin}. | |
| 200 | +\item \textbf{Stability.} Estimates are remarkably stable across sale | |
| 201 | +years ($-0.32$ to $-0.38$) and roll lags --- unlike the \emph{level} | |
| 202 | +statistics, the within-market elasticity is a structural feature of the | |
| 203 | +valuation technology, not of the cycle. | |
| 204 | +\end{itemize} | |
| 205 | + | |
| 206 | +\begin{figure}[p] | |
| 207 | +\centering | |
| 208 | +\includegraphics[width=0.85\textwidth]{fig_heterogeneity.png} | |
| 209 | +\caption{Heterogeneity of the within-market elasticity | |
| 210 | +$\gamma$. Each point is a separate fixed-effects estimate of | |
| 211 | +equation~\eqref{eq:cheng} on the indicated subsample; whiskers are 95\% | |
| 212 | +confidence intervals with municipality-clustered standard errors.} | |
| 213 | +\label{fig:het} | |
| 214 | +\end{figure} | |
| 215 | + | |
| 216 | +\begin{table}[p] | |
| 217 | +\centering | |
| 218 | +\begin{threeparttable} | |
| 219 | +\caption{Heterogeneity of vertical inequity} | |
| 220 | +\label{tab:het} | |
| 221 | +\small | |
| 222 | +\input{../results/tables/heterogeneity} | |
| 223 | +\begin{tablenotes}[flushleft]\footnotesize | |
| 224 | +\item \textit{Notes:} Cheng fixed-effects estimates of | |
| 225 | +$\gamma = \beta - 1$ by subsample; cells with fewer than 20 sales after | |
| 226 | +masking are dropped. Municipality-clustered standard errors in | |
| 227 | +parentheses. *, **, *** denote significance at 10\%, 5\%, 1\%. | |
| 228 | +\end{tablenotes} | |
| 229 | +\end{threeparttable} | |
| 230 | +\end{table} | |
| 231 | + | |
| 232 | +\subsection{Horizontal inequity} | |
| 233 | + | |
| 234 | +Table~\ref{tab:horizontal} reports the dispersion regression. The mean | |
| 235 | +absolute deviation of a sale's log ratio from its cell median is 0.188 --- | |
| 236 | +the typical Quebec dwelling is assessed 19\% away from its market's norm, | |
| 237 | +an enormous horizontal lottery consistent with the COD evidence. The | |
| 238 | +deviation rises by 0.75~log points per decade of building age and by | |
| 239 | +5.2~points for cottages, and falls by 5.5~points for condominiums. | |
| 240 | +Old, idiosyncratic, land-heavy properties receive not only biased but | |
| 241 | +\emph{noisy} valuations. | |
| 242 | + | |
| 243 | +\begin{table}[t] | |
| 244 | +\centering | |
| 245 | +\begin{threeparttable} | |
| 246 | +\caption{Horizontal inequity: who receives noisy assessments?} | |
| 247 | +\label{tab:horizontal} | |
| 248 | +\small | |
| 249 | +\input{../results/tables/horizontal} | |
| 250 | +\begin{tablenotes}[flushleft]\footnotesize | |
| 251 | +\item \textit{Notes:} Dependent variable: | |
| 252 | +$|\ln r_i - \operatorname{med}_{c(i)} \ln r|$. Absorbing least squares | |
| 253 | +with cell fixed effects; municipality-clustered standard errors. | |
| 254 | +Omitted class: single-family. *, **, *** denote significance at 10\%, | |
| 255 | +5\%, 1\%. | |
| 256 | +\end{tablenotes} | |
| 257 | +\end{threeparttable} | |
| 258 | +\end{table} | |
| 259 | + | |
| 260 | +\subsection{The implied tax shift} | |
| 261 | + | |
| 262 | +Figure~\ref{fig:taxshift} and Table~\ref{tab:taxshift} translate the | |
| 263 | +estimates into tax dollars. Within the median market, the median dwelling | |
| 264 | +in the bottom price decile is assessed 65\% above the local norm --- and | |
| 265 | +therefore pays 65\% more property tax than uniform assessment would imply. | |
| 266 | +The overpayment falls to $+7\%$ in the second decile and crosses zero at | |
| 267 | +the median; the top decile underpays by about 5\%. The redistribution is | |
| 268 | +strongly convex: the burden of assessment error is overwhelmingly | |
| 269 | +concentrated on the cheapest tenth of the housing stock --- precisely the | |
| 270 | +segment where owners have the least capacity to appeal and the highest | |
| 271 | +housing-cost burdens. The magnitudes rival those documented for the U.S. | |
| 272 | +by \citet{berry2021reassessing}, in a system with none of the U.S.'s | |
| 273 | +institutional fragmentation. | |
| 274 | + | |
| 275 | +\begin{figure}[t] | |
| 276 | +\centering | |
| 277 | +\includegraphics[width=0.85\textwidth]{fig_taxshift.png} | |
| 278 | +\caption{The implied property-tax shift. Median relative assessment error | |
| 279 | +$e_i = r_i/\operatorname{med}_{c(i)}(r) - 1$ by within-market sale-price | |
| 280 | +decile. Because the levy is proportional to the rolled value, $e_i$ equals | |
| 281 | +the percentage over- or under-payment relative to uniform assessment.} | |
| 282 | +\label{fig:taxshift} | |
| 283 | +\end{figure} | |
| 284 | + | |
| 285 | +\begin{table}[t] | |
| 286 | +\centering | |
| 287 | +\begin{threeparttable} | |
| 288 | +\caption{Excess tax burden by within-market price decile} | |
| 289 | +\label{tab:taxshift} | |
| 290 | +\small | |
| 291 | +\input{../results/tables/taxshift} | |
| 292 | +\begin{tablenotes}[flushleft]\footnotesize | |
| 293 | +\item \textit{Notes:} Relative assessment error by within-cell sale-price | |
| 294 | +decile. The mean is sensitive to extreme low-price sales; the median is | |
| 295 | +the preferred summary. Standard errors of the mean in parentheses. | |
| 296 | +\end{tablenotes} | |
| 297 | +\end{threeparttable} | |
| 298 | +\end{table} | |
added
paper/sections/robustness.tex
+86 −0
@@ -0,0 +1,86 @@ | ||
| 1 | +% Author: Simon-Pierre Boucher — contact@spboucher.ai | |
| 2 | +% ============================================================================ | |
| 3 | +\section{Robustness} | |
| 4 | +\label{sec:robust} | |
| 5 | + | |
| 6 | +Table~\ref{tab:robust} and Figure~\ref{fig:robust} re-estimate the two | |
| 7 | +headline elasticities --- $\gamma_{\text{FE}}$ and the Clapp | |
| 8 | +$\gamma_{\text{IV}}$ --- across ten sample and measurement variants; | |
| 9 | +Table~\ref{tab:quantile} reports the quantile coefficients underlying | |
| 10 | +Figure~\ref{fig:quantile}. | |
| 11 | + | |
| 12 | +\begin{table}[t] | |
| 13 | +\centering | |
| 14 | +\begin{threeparttable} | |
| 15 | +\caption{Robustness of the vertical-inequity elasticities} | |
| 16 | +\label{tab:robust} | |
| 17 | +\small | |
| 18 | +\input{../results/tables/robustness} | |
| 19 | +\begin{tablenotes}[flushleft]\footnotesize | |
| 20 | +\item \textit{Notes:} Each row re-estimates the Cheng fixed-effects and | |
| 21 | +Clapp IV regressions on the indicated variant; cells with fewer than 20 | |
| 22 | +sales after restriction are dropped. Municipality-clustered standard | |
| 23 | +errors in parentheses. *, **, *** denote significance at 10\%, 5\%, 1\% | |
| 24 | +against $\gamma = 0$. | |
| 25 | +\end{tablenotes} | |
| 26 | +\end{threeparttable} | |
| 27 | +\end{table} | |
| 28 | + | |
| 29 | +\begin{figure}[t] | |
| 30 | +\centering | |
| 31 | +\includegraphics[width=0.85\textwidth]{fig_robustness.png} | |
| 32 | +\caption{Stability of $\gamma$ across sample variants. Filled circles: | |
| 33 | +fixed-effects estimates; open squares: Clapp IV. Whiskers are 95\% | |
| 34 | +confidence intervals, municipality-clustered.} | |
| 35 | +\label{fig:robust} | |
| 36 | +\end{figure} | |
| 37 | + | |
| 38 | +\begin{table}[t] | |
| 39 | +\centering | |
| 40 | +\begin{threeparttable} | |
| 41 | +\caption{Quantile-regression coefficients} | |
| 42 | +\label{tab:quantile} | |
| 43 | +\small | |
| 44 | +\input{../results/tables/quantile} | |
| 45 | +\begin{tablenotes}[flushleft]\footnotesize | |
| 46 | +\item \textit{Notes:} Quantile regressions of within-cell demeaned | |
| 47 | +$\ln AV$ on demeaned $\ln SP$, estimated on a seeded 250{,}000-sale | |
| 48 | +subsample. Stars test $H_0\!:\beta(\tau)=1$. | |
| 49 | +\end{tablenotes} | |
| 50 | +\end{threeparttable} | |
| 51 | +\end{table} | |
| 52 | + | |
| 53 | +\paragraph{Match quality.} Requiring the maximum matcher score of 220 | |
| 54 | +(210{,}820 sales) or a match distance under 10~metres changes | |
| 55 | +$\gamma_{\text{FE}}$ to $-0.41$ and $-0.36$ respectively, and | |
| 56 | +$\gamma_{\text{IV}}$ to $-0.10$ and $-0.09$: if anything, the cleanest | |
| 57 | +matches show \emph{more} regressivity, ruling out mismatch noise as the | |
| 58 | +source. | |
| 59 | + | |
| 60 | +\paragraph{Extreme sales.} Dropping all sales under \$100{,}000 --- the | |
| 61 | +segment most likely to harbour residual non-arm's-length transfers --- | |
| 62 | +attenuates the FE estimate modestly ($-0.30$) and the IV to $-0.05$, both | |
| 63 | +still overwhelmingly significant. Tightening the ratio trim to the 5th--95th | |
| 64 | +percentiles, which mechanically compresses dispersion, cuts | |
| 65 | +$\gamma_{\text{FE}}$ to $-0.15$ and $\gamma_{\text{IV}}$ to $-0.04$; the | |
| 66 | +qualitative conclusion is unchanged, and the ordering | |
| 67 | +$|\gamma_{\text{IV}}| < |\gamma_{\text{FE}}|$ is preserved in every | |
| 68 | +variant, as the measurement-error logic requires. | |
| 69 | + | |
| 70 | +\paragraph{Composition.} Single-family homes alone give | |
| 71 | +$\gamma_{\text{FE}} = -0.45$; condominiums alone $-0.12$ (with an IV | |
| 72 | +estimate indistinguishable from zero), confirming that the aggregate | |
| 73 | +result is not an artefact of pooling heterogeneous property classes --- | |
| 74 | +each class is regressive or neutral on its own, none is progressive. | |
| 75 | + | |
| 76 | +\paragraph{Time and market depth.} Splitting the window into 2021--2023 | |
| 77 | +and 2024--2026 yields $-0.35$ and $-0.34$ (FE); restricting to | |
| 78 | +municipalities with at least 300 sales, or to cells with at least 50, | |
| 79 | +moves the estimates by less than 0.005. The phenomenon is stable across | |
| 80 | +the largest housing-cycle swing in recent Canadian history. | |
| 81 | + | |
| 82 | +\paragraph{Inference.} With 625 municipality clusters, the | |
| 83 | +cluster-robust $t$-statistics on $\gamma_{\text{FE}}$ exceed 11 in every | |
| 84 | +variant; the IV first stage is enormous (the rank instrument correlates | |
| 85 | +with within-cell log price at $F$ far above conventional thresholds), so | |
| 86 | +weak-instrument concerns do not arise. | |
added
paper/sections/titlepage.tex
+70 −0
@@ -0,0 +1,70 @@ | ||
| 1 | +% Author: Simon-Pierre Boucher — contact@spboucher.ai | |
| 2 | +% ============================================================================ | |
| 3 | +% Title and abstract pages | |
| 4 | +% ============================================================================ | |
| 5 | +\thispagestyle{empty} | |
| 6 | + | |
| 7 | +\begin{center} | |
| 8 | + | |
| 9 | +\includegraphics[width=4cm]{uq_logo.jpg} | |
| 10 | + | |
| 11 | +\vspace{0.6cm} | |
| 12 | + | |
| 13 | +{\footnotesize\textsc{Universit\'e du Qu\'ebec en Outaouais}}\\[0.15cm] | |
| 14 | +{\footnotesize\textsc{D\'epartement des sciences administratives}} | |
| 15 | + | |
| 16 | +\vspace{0.8cm} | |
| 17 | + | |
| 18 | +{\footnotesize\textsc{Working Paper No.~\WPnumber}} | |
| 19 | + | |
| 20 | +\vspace{1.2cm} | |
| 21 | + | |
| 22 | +{\LARGE\bfseries \WPtitle\par} | |
| 23 | + | |
| 24 | +\vspace{0.4cm} | |
| 25 | +{\large\itshape \WPsubtitle\par} | |
| 26 | + | |
| 27 | +\vspace{1.2cm} | |
| 28 | + | |
| 29 | +{\large \WPauthor}\\[0.3cm] | |
| 30 | +{\normalsize \WPaffiliation}\\[0.15cm] | |
| 31 | +{\normalsize \href{mailto:\WPemail}{\WPemail}}\\[0.15cm] | |
| 32 | +{\small \WPaddress} | |
| 33 | + | |
| 34 | +\vspace{0.8cm} | |
| 35 | + | |
| 36 | +{\normalsize \WPdate}\\[0.1cm] | |
| 37 | +{\small Version~\WPversion} | |
| 38 | + | |
| 39 | +\end{center} | |
| 40 | + | |
| 41 | +\vfill | |
| 42 | + | |
| 43 | +\newpage | |
| 44 | + | |
| 45 | +% ---------------------------------------------------------------- abstract | |
| 46 | +\thispagestyle{empty} | |
| 47 | + | |
| 48 | +\vspace*{1cm} | |
| 49 | + | |
| 50 | +\noindent\rule{\textwidth}{0.4pt} | |
| 51 | +\vspace{0.3cm} | |
| 52 | + | |
| 53 | +\noindent\textbf{Abstract} | |
| 54 | + | |
| 55 | +\vspace{0.15cm} | |
| 56 | + | |
| 57 | +\noindent\WPabstract | |
| 58 | + | |
| 59 | +\vspace{0.4cm} | |
| 60 | + | |
| 61 | +\noindent\textbf{Keywords:} \WPkeywords | |
| 62 | + | |
| 63 | +\vspace{0.15cm} | |
| 64 | + | |
| 65 | +\noindent\textbf{JEL Classification:} \WPjel | |
| 66 | + | |
| 67 | +\vspace{0.3cm} | |
| 68 | +\noindent\rule{\textwidth}{0.4pt} | |
| 69 | + | |
| 70 | +\newpage | |
added
paper/uq_logo.jpg
+0 −0
Binary file not shown.
added
requirements.txt
+8 −0
@@ -0,0 +1,8 @@ | ||
| 1 | +# Author: Simon-Pierre Boucher — contact@spboucher.ai | |
| 2 | +# Python >= 3.11. Versions pinned to the environment used for the analysis. | |
| 3 | +numpy==2.4.4 | |
| 4 | +pandas==3.0.2 | |
| 5 | +pyarrow>=16.0 | |
| 6 | +statsmodels==0.14.6 | |
| 7 | +linearmodels==7.0 | |
| 8 | +matplotlib==3.10.9 | |
added
results/reproduced/binscatter.csv
+21 −0
@@ -0,0 +1,21 @@ | ||
| 1 | +bin,x,y,se,n | |
| 2 | +0,-1.1299388141576843,0.7401279154992733,0.003196992348982775,26139 | |
| 3 | +1,-0.6850118092956418,0.2574425293076139,0.0025583543636468675,26138 | |
| 4 | +2,-0.5073601868074681,0.10436219388765414,0.0020528587799829565,26153 | |
| 5 | +3,-0.39073743914550024,0.047860490092793556,0.001736111026990563,26124 | |
| 6 | +4,-0.30065659167407555,0.016570011132268175,0.0015548062368614822,26139 | |
| 7 | +5,-0.22425213523591567,-0.002513336220497968,0.0014291012118165137,26156 | |
| 8 | +6,-0.15838142150231835,-0.020241310853057217,0.0013378404347182862,26124 | |
| 9 | +7,-0.09992669391808244,-0.03963672880796096,0.0012165744895958942,26148 | |
| 10 | +8,-0.04710053458833093,-0.053894099650792227,0.001160689913664137,26200 | |
| 11 | +9,0.002122479477476558,-0.06362384340772381,0.0011248344303339988,26066 | |
| 12 | +10,0.049307323000753994,-0.07312651316133213,0.0010811167525577004,26137 | |
| 13 | +11,0.09626979736571095,-0.08352363160551252,0.001065184297317105,26142 | |
| 14 | +12,0.14419727346672664,-0.08918200392095645,0.0010663579136722928,26136 | |
| 15 | +13,0.19538855584502757,-0.09595623188857955,0.0010518993779298034,26136 | |
| 16 | +14,0.2517919431123114,-0.09939639689803642,0.0010652961471458247,26160 | |
| 17 | +15,0.31579361306351245,-0.10044775245396857,0.0010607621841663896,26122 | |
| 18 | +16,0.3909496467065449,-0.1001064460067215,0.0010745180988978066,26136 | |
| 19 | +17,0.48726172323242867,-0.10229496211595199,0.0010849459604205768,26136 | |
| 20 | +18,0.6279334261827585,-0.10814069396200333,0.0010932289509538405,26138 | |
| 21 | +19,0.9827359705440116,-0.13437611742305566,0.0011936815957342471,26139 | |
added
results/reproduced/heterogeneity.csv
+23 −0
@@ -0,0 +1,23 @@ | ||
| 1 | +group,gamma,se,n | |
| 2 | +Single-family,-0.4537825487653664,0.017669914962190263,337855 | |
| 3 | +Condominium,-0.12464520941899382,0.01518233161943941,77976 | |
| 4 | +Plex (2–5 units),-0.48721753704516557,0.054393819641049175,69745 | |
| 5 | +Cottage,-0.28835281011784275,0.026012993411055806,2114 | |
| 6 | +Age < 20 y,-0.29681001316776945,0.04138996753677022,117898 | |
| 7 | +Age 20–60 y,-0.3838945768600194,0.03428322120554528,227857 | |
| 8 | +Age > 60 y,-0.4348917536559145,0.027878597780328678,108049 | |
| 9 | +Land share < 0.2,-0.26159665555927447,0.04182768275315659,119718 | |
| 10 | +Land share 0.2–0.4,-0.37212541838852264,0.03209301087096023,236923 | |
| 11 | +Land share > 0.4,-0.5032586691495551,0.030343186599237057,125714 | |
| 12 | +Roll lag < 24 m,-0.33521036537225046,0.027518136511668685,86001 | |
| 13 | +Roll lag 24–48 m,-0.3383010361647635,0.03253068680640818,327031 | |
| 14 | +Roll lag > 48 m,-0.36120558524361845,0.026496805098668315,91367 | |
| 15 | +Muni < 1k sales,-0.37853163229754105,0.007225289628016334,125139 | |
| 16 | +Muni 1k–10k sales,-0.40556881854947346,0.013683713072741294,185111 | |
| 17 | +Muni > 10k sales,-0.26678422681660763,0.04292003298652767,212519 | |
| 18 | +Sales 2021,-0.31582561216354454,0.02741379492805952,115221 | |
| 19 | +Sales 2022,-0.37986236880270274,0.03017363968613998,90627 | |
| 20 | +Sales 2023,-0.365252633545463,0.03376287524528814,74110 | |
| 21 | +Sales 2024,-0.33272608196080145,0.03235286456948262,89204 | |
| 22 | +Sales 2025,-0.33508012882403526,0.026198875018876387,102026 | |
| 23 | +Sales 2026,-0.3494522195906299,0.029905254010043902,51581 | |
added
results/reproduced/horizontal.csv
+6 −0
@@ -0,0 +1,6 @@ | ||
| 1 | +,coef,se,tstat | |
| 2 | +age_dec,0.007468884136129831,0.0012336871606707124,6.0541151551494305 | |
| 3 | +land_share,0.0030967138766707955,0.02979602772694477,0.10393042673505146 | |
| 4 | +is_condo,-0.055121433754366414,0.0073939373971805885,-7.4549500209976065 | |
| 5 | +is_plex,0.02472595832635656,0.015081161360430577,1.6395261436053368 | |
| 6 | +is_cottage,0.0520317625932596,0.007306467785143703,7.121329228201921 | |
added
results/reproduced/horizontal_meta.csv
+4 −0
@@ -0,0 +1,4 @@ | ||
| 1 | +,0 | |
| 2 | +n,495092.0 | |
| 3 | +r2,0.04549344925077059 | |
| 4 | +mean_dep,0.18776662312587053 | |
added
results/reproduced/iaao_cities.csv
+11 −0
@@ -0,0 +1,11 @@ | ||
| 1 | +muni,n,n_years,median_ratio,cod,prd,prb,share_years_prb_neg | |
| 2 | +Gatineau,21270,6,0.7984077215662176,16.931554716735143,1.0495027784666382,-0.13658868222945214,1.0 | |
| 3 | +Laval,23569,6,0.7292941176470589,20.99004120848114,1.07591200087497,-0.1677198430090882,1.0 | |
| 4 | +Longueuil,15343,6,0.6993231963838167,22.936569556574902,1.073902247304333,-0.12759857852649836,1.0 | |
| 5 | +Lévis,10622,6,0.8274403990009007,22.318404178238602,1.0892991972930153,-0.23689185802043072,1.0 | |
| 6 | +Montréal,79581,6,0.8922306974762114,24.895070443807846,1.0571629439855559,0.06966324444056746,0.0 | |
| 7 | +Québec,39927,6,0.8088761174968071,22.021089492553433,1.0711624215910516,-0.04798363355653862,1.0 | |
| 8 | +Saguenay,10794,6,0.7872989417989418,19.073245445985677,1.0638779683601711,-0.18854120739412214,1.0 | |
| 9 | +Sherbrooke,11413,6,0.7435681818181819,30.520246620851758,1.1372039979494142,-0.3872307527051138,1.0 | |
| 10 | +Terrebonne,8061,6,0.7949895317396622,23.75303561732275,1.0961290692775711,-0.28961986412143703,1.0 | |
| 11 | +Trois-Rivières,9070,6,0.7291707983633229,29.363374404233365,1.1225282017869498,-0.3166532132665827,1.0 | |
added
results/reproduced/iaao_muni.csv
+262 −0
@@ -0,0 +1,262 @@ | ||
| 1 | +muni,n,n_years,median_ratio,cod,prd,prb,share_years_prb_neg,lat,lng,name | |
| 2 | +01023,539,5,0.6673333333333333,50.91474528551908,1.2248771217060996,-0.34468825127789776,1.0,47.373297415,-61.892867091,Les Îles-de-la-Madeleine | |
| 3 | +02028,193,3,0.7335620300751879,24.924431206109645,1.0698721233673953,-0.18103197862957446,1.0,48.348539266,-64.682155177,Chandler | |
| 4 | +03005,631,6,0.8679785511938487,32.09747197510151,1.1090280708711022,-0.18068585618165026,1.0,48.837329995,-64.464568935,Gaspé | |
| 5 | +04037,400,5,0.6966666666666667,24.657677166061557,1.044320194185855,-0.029292963930198,0.8,49.125795114,-66.48063584,Sainte-Anne-des-Monts | |
| 6 | +07047,154,2,0.8250403510712454,18.715867104102017,1.0429833315137729,-0.05918136199537266,1.0,48.4661869825,-67.4294386585,Amqui | |
| 7 | +08053,924,6,0.7849542717864635,22.961756490553256,1.0676455781752932,-0.12514845360658441,1.0,48.844493941500005,-67.523459035,Matane | |
| 8 | +09077,416,5,0.7596562499999999,21.291435789055335,1.0548283361050281,-0.12982997825324094,1.0,48.587353906,-68.191464128,Mont-Joli | |
| 9 | +10043,2952,6,0.7817020537241743,32.027913818017566,1.1328015504123914,-0.3029817279665089,1.0,48.436574925,-68.5190762055,Rimouski | |
| 10 | +12015,119,2,0.772315153471027,15.183800703538104,1.0416125937656937,-0.058507880001827606,1.0,47.773246705999995,-69.4710944155,Saint-Antonin | |
| 11 | +12072,1128,6,0.7966340986130649,14.969808769491877,1.0265360685354072,-0.06693134453658334,1.0,47.8276978835,-69.5320477905,Rivière-du-Loup | |
| 12 | +13073,143,2,0.7872480720728201,19.48365920891809,1.0294657084213812,-0.02696053619823202,0.5,47.679574698,-68.879280856,Témiscouata-sur-le-Lac | |
| 13 | +15013,626,6,0.7636936026936028,23.977174989511976,1.084659312941719,-0.13630189728350917,0.6666666666666666,47.653839902499996,-70.154518171,La Malbaie | |
| 14 | +16013,238,3,0.8511296296296296,32.56224604586135,1.0957467754441717,-0.1674954492469384,1.0,47.4433681525,-70.51061298799999,Baie-Saint-Paul | |
| 15 | +18050,669,6,0.815526698145736,19.519300312098004,1.048588309170369,-0.0816733383768248,1.0,46.977625381,-70.557680462,Montmagny | |
| 16 | +19062,226,4,0.8106281573263143,27.439661292313254,1.1094424684068356,-0.4294672773533285,0.75,46.63032661,-70.97501990949999,Saint-Anselme | |
| 17 | +19068,301,4,0.8489275767899632,26.548869666269344,1.107642649780156,-0.29915439960145335,1.0,46.690995181,-71.07396918399999,Saint-Henri | |
| 18 | +19105,101,2,0.847860831323592,23.395844030649442,1.1136408119218577,-0.2104739818721205,1.0,46.830723019000004,-71.00647754799999,Beaumont | |
| 19 | +21010,483,5,0.776255707762557,25.54827289959927,1.0746565664467242,-0.007935727616828097,0.6,47.088081377500004,-70.88400634300001,Saint-Ferréol-les-Neiges | |
| 20 | +21025,414,5,0.7632876712328767,19.1893209073912,1.0676453235538235,-0.042170544608549236,0.8,47.0531188095,-70.8950832745,Beaupré | |
| 21 | +21035,283,4,0.8476034069736491,22.82023013038416,1.08235603365272,-0.23215142029214486,1.0,46.976472478000005,-71.0231973965,Château-Richer | |
| 22 | +21045,709,6,0.8284982561670373,17.98839469618786,1.067921620148053,-0.08884275574512873,1.0,46.901759032,-71.143656871,Boischatel | |
| 23 | +22005,860,6,0.7824572474617508,25.34594286142555,1.1054442494360623,-0.2780885837540912,1.0,46.8442399275,-71.6147577,Sainte-Catherine-de-la-Jacques-Cartier | |
| 24 | +22010,111,2,0.7879374902272098,22.278392800147888,1.0942267327131154,-0.14510322400523507,1.0,46.880712103,-71.6133648635,Fossambault-sur-le-Lac | |
| 25 | +22020,561,6,0.7876398038915275,27.994845862418558,1.13734252635859,-0.4505269529039112,1.0,46.883512871,-71.532064949,Shannon | |
| 26 | +22035,931,6,0.7901038482590206,24.17844235537976,1.0956374310640464,-0.19103461724047766,1.0,47.008459672,-71.375813659,Stoneham-et-Tewkesbury | |
| 27 | +22040,718,6,0.7630942346287174,28.260839719893976,1.1238613719535708,-0.20672851511595283,1.0,46.9485322015,-71.300988286,Lac-Beauport | |
| 28 | +22045,904,6,0.7989815294450779,19.48589386774982,1.0674612417675986,-0.2483627463824416,1.0,46.996448249,-71.2022620465,Sainte-Brigitte-de-Laval | |
| 29 | +23027,39927,6,0.8088761174968071,22.021089492553433,1.0711624215910516,-0.04798363355653862,1.0,46.845432242,-71.284341623,Québec | |
| 30 | +23057,1256,6,0.7958226723756803,20.83415657438064,1.0839279319598174,-0.32141718627637206,1.0,46.8058055155,-71.3587655525,L'Ancienne-Lorette | |
| 31 | +23072,1494,6,0.7916239054702947,18.91738691312515,1.0844010250630294,-0.1522959320199167,1.0,46.741946145,-71.392724151,Saint-Augustin-de-Desmaures | |
| 32 | +25213,10622,6,0.8274403990009007,22.318404178238602,1.0892991972930153,-0.23689185802043072,1.0,46.734125024,-71.21804311950001,Lévis | |
| 33 | +26030,817,6,0.8015482334259627,24.138578405030643,1.0968411674968217,-0.2225915996902068,1.0,46.446288901,-71.019009516,Sainte-Marie | |
| 34 | +26070,433,5,0.8462264150943396,33.3912910416355,1.118002113482546,-0.3059912138982086,1.0,46.587031214,-71.222876646,Saint-Lambert-de-Lauzon | |
| 35 | +27028,239,4,0.8086742424242425,29.11413633851084,1.123900641050577,-0.35346427459067,1.0,46.2083331485,-70.7789057075,Beauceville | |
| 36 | +27043,221,4,0.7670741607157514,26.12427786850234,1.1054264617213942,-0.1822426203555372,1.0,46.307911006,-70.8754787755,Saint-Joseph-de-Beauce | |
| 37 | +28020,118,2,0.7477937325513451,36.432113509221125,1.1462049821936644,-0.21638507574156152,1.0,46.212335225000004,-70.483038276,Saint-Prosper | |
| 38 | +28053,264,4,0.7347851427131247,17.35436494841649,1.0454657317728653,-0.03620096106684201,1.0,46.397102893500005,-70.5098076245,Lac-Etchemin | |
| 39 | +29073,2196,6,0.8083449413489736,25.755695026295424,1.0770891060813792,-0.10798779415583887,0.8333333333333334,46.1141325115,-70.66347340850001,Saint-Georges | |
| 40 | +30030,367,5,0.7629411764705882,23.40617277896169,1.0725742458683925,-0.056711080582011186,0.8,45.580061028,-70.88744873600001,Lac-Mégantic | |
| 41 | +31084,2083,6,0.7679633740288568,23.200578245790716,1.0632822378115123,-0.07864610355357327,1.0,46.100656922,-71.298757211,Thetford Mines | |
| 42 | +32033,381,5,0.7161185770750988,25.36432789859908,1.0724563014146264,-0.22461012647627657,1.0,46.168170629,-71.880109919,Princeville | |
| 43 | +32043,133,2,0.7078918774437928,21.547516555339485,1.0467423122026374,-0.05363017912735923,1.0,46.220329115,-71.77564931,Plessisville | |
| 44 | +33035,118,2,0.813110300081103,24.25198091954615,1.1075171862309936,-0.4105833065487351,1.0,46.510338575,-71.363735332,Saint-Gilles | |
| 45 | +33045,365,5,0.7725760286225403,24.777323647820566,1.093959945902275,-0.27699719905934855,1.0,46.558726373,-71.439719591,Saint-Agapit | |
| 46 | +33090,801,6,0.7834754939542901,21.722636341184472,1.079801411095684,-0.18195529622856327,1.0,46.610267401,-71.504652032,Saint-Apollinaire | |
| 47 | +34007,275,4,0.8409123376623376,23.602702704062967,1.100676310204747,-0.2278918738548532,1.0,46.7100307405,-71.585762454,Neuville | |
| 48 | +34017,870,6,0.7965175280806989,23.617933054488034,1.0936190199222806,-0.3870384304969796,1.0,46.7532657545,-71.6892814165,Pont-Rouge | |
| 49 | +34025,625,6,0.8213156424581005,22.5154051048244,1.0804485119666485,-0.1853439391006324,1.0,46.674770253,-71.722938844,Donnacona | |
| 50 | +34030,127,2,0.8109785171936221,26.431353685573384,1.1088532854538589,-0.2728652908822181,1.0,46.6753289075,-71.76630738899999,Cap-Santé | |
| 51 | +34048,200,3,0.8914040114613181,22.4419217687073,1.0777283157443607,-0.16351424193035438,1.0,46.707453458,-71.887872363,Portneuf | |
| 52 | +34128,1058,6,0.7943707552421599,33.892653129910514,1.1411084126189521,-0.16653534972200065,1.0,46.887098,-71.817427818,Saint-Raymond | |
| 53 | +36033,4203,6,0.7286444444444444,25.881872445103877,1.0841432224933971,-0.11015824114165505,1.0,46.573502159,-72.735323152,Shawinigan | |
| 54 | +37067,9070,6,0.7291707983633229,29.363374404233365,1.1225282017869498,-0.3166532132665827,1.0,46.3598810855,-72.573001212,Trois-Rivières | |
| 55 | +37235,379,6,0.698388888888889,34.58912731014522,1.1494863386313716,-0.19023003788389053,1.0,46.473336473,-72.681230831,Notre-Dame-du-Mont-Carmel | |
| 56 | +38010,953,6,0.7378636526665802,29.34676894055913,1.1041531520581835,-0.11951993425484558,1.0,46.331892837,-72.436542647,Bécancour | |
| 57 | +39062,3397,6,0.7725079365079366,21.167131268805907,1.0590575297473819,-0.11969422015713542,1.0,46.056750684,-71.955100058,Victoriaville | |
| 58 | +40043,645,6,0.7140458839406207,25.050406547460213,1.081643635149071,-0.22814897980200044,1.0,45.774533753,-71.932177531,Val-des-Sources | |
| 59 | +40047,236,4,0.6974422000860849,33.23499525532314,1.1015789067458372,-0.11661627086349244,1.0,45.785007603500006,-72.01360457550001,Danville | |
| 60 | +41038,185,3,0.6448275862068965,38.606331978071005,1.1660730742063186,-0.3968719817353335,1.0,45.3844633695,-71.6295519155,Cookshire-Eaton | |
| 61 | +41060,196,3,0.8599224137931034,35.53421652858063,1.1691374253389413,-0.4351925732778529,1.0,45.485394948,-71.6580871,East Angus | |
| 62 | +41098,195,3,0.6753623188405797,31.53311390998279,1.1320286579703374,-0.17149384178633773,1.0,45.69994358,-71.445326867,Weedon | |
| 63 | +42025,362,5,0.7549885680965192,35.203169940963946,1.1763525360553093,-0.24823982708549983,1.0,45.450530426,-72.090137863,Saint-Denis-de-Brompton | |
| 64 | +42088,339,5,0.7247393919144671,25.079939475478415,1.088690267817179,-0.2944486676543964,1.0,45.572104515,-72.000885705,Windsor | |
| 65 | +42098,120,2,0.7598296152643589,28.60356720524078,1.0901799648126935,-0.18915439032617962,1.0,45.661565373,-72.13979631699999,Richmond | |
| 66 | +43027,11413,6,0.7435681818181819,30.520246620851758,1.1372039979494142,-0.3872307527051138,1.0,45.392633295,-71.930715774,Sherbrooke | |
| 67 | +44037,630,6,0.7636818947432714,39.103320928672524,1.174166223587914,-0.5171929791954604,1.0,45.13080419,-71.80705083449999,Coaticook | |
| 68 | +45030,135,2,0.7746368795305014,24.602025504275613,1.0656614832850893,-0.07064281825699523,1.0,45.0788365575,-72.3409277395,Potton | |
| 69 | +45072,2576,6,0.6996702637889688,28.74585155682162,1.1231887982499333,-0.16961190181816477,1.0,45.269992867,-72.151298396,Magog | |
| 70 | +45085,176,3,0.8217012726054923,29.60658953091504,1.1136327841600513,-0.05055215467062311,0.6666666666666666,45.254333104,-72.261598675,Austin | |
| 71 | +45093,193,3,0.5888311688311688,37.122686831974555,1.160332247800896,-0.2906102901008709,1.0,45.313381840999995,-72.300395944,Eastman | |
| 72 | +45115,679,6,0.6063801904310379,29.630617868711955,1.1389419969522057,-0.12787910511482986,1.0,45.316923081,-72.167720393,Orford | |
| 73 | +46058,440,6,0.6716763687600644,35.0399114099244,1.1644503220221871,-0.3021844843769021,1.0,45.105737911,-72.6047236695,Sutton | |
| 74 | +46075,507,5,0.6399901315789474,27.55162268404918,1.1477159758154432,-0.1968557522728025,1.0,45.238537717,-72.533189645,Lac-Brome | |
| 75 | +46078,988,6,0.7235671402106036,25.82041311803009,1.1175358334345749,-0.20549848425065131,1.0,45.3095708915,-72.6607766245,Bromont | |
| 76 | +46080,996,6,0.7869125951584968,26.198853324684954,1.1147771543579075,-0.3610004408751044,1.0,45.207637269,-72.74552933449999,Cowansville | |
| 77 | +46112,860,6,0.7050468809797168,31.438957313985355,1.1301858043558206,-0.3655193836711927,1.0,45.2805323405,-72.97625546500001,Farnham | |
| 78 | +47017,4901,6,0.7294086689011172,29.52259191115126,1.1118951327046869,-0.31021184964998916,1.0,45.400759665,-72.731849819,Granby | |
| 79 | +47025,431,5,0.7389595410628019,30.393345512426347,1.1426260017062118,-0.6243582754165377,0.8,45.340766011,-72.523795612,Waterloo | |
| 80 | +47035,551,6,0.6814398233345602,38.47509613172598,1.2034380183091464,-0.36554592129527963,0.8333333333333334,45.363765456,-72.617788786,Shefford | |
| 81 | +47047,362,5,0.7858909678137878,42.382459905281635,1.2008030800169927,-0.3865934883575038,1.0,45.472375239,-72.66004383949999,Roxton Pond | |
| 82 | +48028,559,6,0.7543902258274068,28.018807118374035,1.1091013787799113,-0.21701741650534076,1.0,45.647339981,-72.567065213,Acton Vale | |
| 83 | +49048,181,3,0.7025,30.409774665224678,1.1497028833165948,-0.2823453496491139,0.6666666666666666,45.839364424,-72.56928357550001,Saint-Germain-de-Grantham | |
| 84 | +49058,5438,6,0.780937265257802,28.156173910893166,1.0985192856127424,-0.15805926106005436,1.0,45.8636461665,-72.488506331,Drummondville | |
| 85 | +49070,240,4,0.7331632399064072,28.26332419308384,1.0959426599044875,-0.17573180014727086,0.75,45.9286980065,-72.42569711600001,Saint-Cyrille-de-Wendover | |
| 86 | +50072,536,6,0.7499419147961521,23.653935663973613,1.072662630300501,-0.11256109410431805,0.8333333333333334,46.234372841500004,-72.606647624,Nicolet | |
| 87 | +51015,428,5,0.7371850087593252,36.04550143376593,1.1358027356965468,-0.22142935485853846,1.0,46.254489091,-72.945032821,Louiseville | |
| 88 | +51065,343,5,0.7208805031446541,34.97788003353477,1.1447463757537515,-0.1445638307342712,0.8,46.4667905365,-73.147176442,Saint-Alexis-des-Monts | |
| 89 | +51085,246,4,0.7110915228807202,36.60035597094894,1.1535195526106898,-0.27891311631728755,1.0,46.499025471,-72.826011591,Saint-Boniface | |
| 90 | +51090,176,3,0.6595238095238095,46.32063307271163,1.2066301454514747,-0.2955329419528453,1.0,46.438600727,-72.7760082695,Saint-Étienne-des-Grès | |
| 91 | +52007,1351,6,0.7811588002873564,18.604861636030577,1.0581659181587162,-0.25745548213469116,1.0,45.883310054,-73.289400883,Lavaltrie | |
| 92 | +52017,454,6,0.7424664099602105,27.458078813029132,1.106305536658132,-0.23829756434408023,1.0,45.9622651545,-73.2234576355,Lanoraie | |
| 93 | +52035,200,3,0.7861176470588235,22.655549017167036,1.091352261310293,-0.25046959637460336,1.0,46.083254857,-73.180119373,Berthierville | |
| 94 | +52080,119,2,0.6955672547533602,28.708856460143494,1.0711018490504467,-0.0813118223932662,1.0,46.295695435,-73.38626004,Saint-Gabriel | |
| 95 | +52085,177,3,0.727027027027027,27.88324615835368,1.0925763371789967,-0.14670307401976548,1.0,46.299322264,-73.400681704,Saint-Gabriel-de-Brandon | |
| 96 | +52095,259,4,0.8312473389237706,30.540380247851083,1.1357915288335463,-0.14562631914040758,0.75,46.3822765345,-73.372126772,Mandeville | |
| 97 | +53040,125,2,0.7212600732600732,20.859605403839247,1.0397414336249162,0.0195953827442501,0.5,45.89315123,-73.159513486,Saint-Roch-de-Richelieu | |
| 98 | +53052,2900,6,0.7163174603174602,22.015700026909695,1.069732650385139,-0.15586730118239517,1.0,46.025134898999994,-73.120790133,Sorel-Tracy | |
| 99 | +54008,509,6,0.7409696934629468,36.175329889110756,1.1485863416499411,-0.31318822759192966,1.0,45.506691818,-72.89942077,Saint-Pie | |
| 100 | +54048,3188,6,0.7215852526947326,29.983735016934077,1.120637745311388,-0.4548109525949386,1.0,45.624822898000005,-72.94903571949999,Saint-Hyacinthe | |
| 101 | +55023,436,6,0.7155175586947871,35.10632381575468,1.1429899815511533,-0.5190053063839385,1.0,45.4122608645,-73.0083647985,Saint-Césaire | |
| 102 | +55048,988,6,0.6665826990500734,27.68549680670662,1.115740440522321,-0.3310324780296,1.0,45.431773133,-73.15662568,Marieville | |
| 103 | +55057,210,3,0.7058823529411765,39.22349855731835,1.1791480122856313,-0.5927454024538161,1.0,45.437899329,-73.245698844,Richelieu | |
| 104 | +55065,250,4,0.7067612524461839,32.49284470613207,1.132452062518392,-0.3582422420168257,1.0,45.4843284675,-73.2594419175,Saint-Mathias-sur-Richelieu | |
| 105 | +56005,182,3,0.6952126233702937,25.981522684659765,1.0532080535284765,-0.004549080929276983,0.6666666666666666,45.075641823,-73.152962375,Venise-en-Québec | |
| 106 | +56023,100,2,0.6628845451458486,32.81492586289752,1.1049183792145993,-0.159649344387989,1.0,45.078495171,-73.3724631485,Lacolle | |
| 107 | +56083,6860,6,0.6857290061587995,26.58305679732141,1.1033053033841163,-0.27318761992146323,1.0,45.3173844665,-73.26839254449999,Saint-Jean-sur-Richelieu | |
| 108 | +57005,2466,6,0.7958329542147105,27.67591821449598,1.1284734909133656,-0.30965970135070986,1.0,45.4386584325,-73.29915464550001,Chambly | |
| 109 | +57010,1010,6,0.6948232586712788,26.000694608760995,1.1148493785861135,-0.2039855201907288,1.0,45.458347274,-73.33319411650001,Carignan | |
| 110 | +57020,1203,6,0.662749373433584,28.651959329107765,1.1198338888029058,-0.36229490745193826,1.0,45.524009139,-73.289582455,Saint-Basile-le-Grand | |
| 111 | +57025,348,5,0.665,26.685732093244475,1.1124067946453324,-0.6607363263019816,1.0,45.54996469,-73.23327609750001,McMasterville | |
| 112 | +57030,744,6,0.6713241217798596,22.627379704392297,1.09090788751882,-0.3634820536670958,1.0,45.539227893,-73.2053586795,Otterburn Park | |
| 113 | +57035,1502,6,0.7662368421052632,23.948325462191995,1.0928237725547036,-0.15912137452024816,1.0,45.5703179335,-73.18333605250001,Mont-Saint-Hilaire | |
| 114 | +57040,2016,6,0.765013455787344,27.043523508117854,1.11590548883787,-0.5387275185042903,1.0,45.5766494225,-73.21203484899999,Beloeil | |
| 115 | +58007,4943,6,0.7298304235121789,18.157822443347143,1.0517755883825637,-0.07511871550042507,1.0,45.456964004,-73.460176368,Brossard | |
| 116 | +58012,1479,6,0.764075235109718,19.23169014322753,1.0562801725449358,-0.015803048035999905,0.8333333333333334,45.49607166,-73.502201617,Saint-Lambert | |
| 117 | +58033,2818,6,0.7214520247728389,28.282212165129522,1.1241439694492887,-0.30088808757487173,1.0,45.5948568775,-73.43997271149999,Boucherville | |
| 118 | +58037,1868,6,0.7155095152983274,23.403401825301806,1.0981337097937294,-0.2023309864341323,1.0,45.526278876999996,-73.343779198,Saint-Bruno-de-Montarville | |
| 119 | +58227,15343,6,0.6993231963838167,22.936569556574902,1.073902247304333,-0.12759857852649836,1.0,45.511886497,-73.455769263,Longueuil | |
| 120 | +59010,2186,6,0.6892368654082559,27.101368542651706,1.1305064886095173,-0.4208004484202777,1.0,45.58665754,-73.34013155,Sainte-Julie | |
| 121 | +59015,969,5,0.6941578947368421,27.55841878174844,1.1062269197523142,-0.2654275934202815,1.0,45.641254737,-73.301101941,Saint-Amable | |
| 122 | +59020,1445,6,0.699660685296846,24.833384416142934,1.1064585476041848,-0.3238212478103721,1.0,45.680047018,-73.42792562,Varennes | |
| 123 | +59025,316,5,0.714789419619928,37.91237662771402,1.1764206667175272,-0.6104876127849174,1.0,45.7716982595,-73.359030094,Verchères | |
| 124 | +59035,797,5,0.6441,20.7123316545552,1.0731133733528522,-0.20195773491992464,1.0,45.85806846,-73.228782724,Contrecoeur | |
| 125 | +60005,358,5,0.7413559322033898,16.885494403406323,1.0479257422809722,-0.19259103274095157,0.8,45.726849001,-73.489765653,Charlemagne | |
| 126 | +60013,6713,6,0.6672872479706728,21.0598366386061,1.074717623319002,-0.14803103612872476,1.0,45.763412589,-73.452409152,Repentigny | |
| 127 | +60028,1816,6,0.6641461896639159,26.47252266986331,1.098006926018187,-0.37232052536630766,1.0,45.847046872,-73.42668152600001,L'Assomption | |
| 128 | +60037,725,6,0.8081229328680933,26.03219665844953,1.1007082495328808,-0.4117621700398755,1.0,45.850995196,-73.493655426,L'Épiphanie | |
| 129 | +61005,560,6,0.7581785130423317,25.33968913013652,1.0920848292142917,-0.2066034333520754,1.0,45.993637825,-73.44000416099999,Saint-Paul | |
| 130 | +61013,171,3,0.6333333333333333,34.26526659043471,1.1392978456036058,-0.3231291053094608,1.0,45.966378601,-73.476801969,Crabtree | |
| 131 | +61025,1239,6,0.7232373306724722,21.78002201339599,1.0773392447696346,-0.21467207149062822,1.0,46.019966328,-73.442065366,Joliette | |
| 132 | +61030,714,6,0.753252407704655,19.795712908840187,1.0674690275569825,-0.2114305898722327,0.8333333333333334,46.0511858175,-73.43218235,Notre-Dame-des-Prairies | |
| 133 | +61035,718,6,0.7409494773519164,20.1414097732401,1.046712724191733,-0.12750349507855074,1.0,46.048532085,-73.46625851499999,Saint-Charles-Borromée | |
| 134 | +61040,116,2,0.8286152882205513,33.12170191273214,1.1315083813730664,-0.3073266401447655,1.0,46.0796626785,-73.5277993025,Saint-Ambroise-de-Kildare | |
| 135 | +61050,119,2,0.6857176850171405,27.477878878609587,1.074415485857116,-0.17338003774365124,1.0,46.135610829,-73.52854585,Sainte-Mélanie | |
| 136 | +62007,548,6,0.7623781687762731,25.150478859407567,1.0750411207032586,-0.14234524882818494,0.8333333333333334,46.1652538545,-73.438296302,Saint-Félix-de-Valois | |
| 137 | +62015,491,5,0.7808888888888889,30.881862719342106,1.113263005777294,-0.155376216739658,0.8,46.254256657,-73.536897228,Saint-Jean-de-Matha | |
| 138 | +62020,112,2,0.7460389512337418,30.265828679987003,1.0932190159392952,-0.07945162393825822,1.0,46.193276397,-73.61716932,Sainte-Béatrix | |
| 139 | +62037,1364,6,0.7565606494746896,27.04102518019009,1.1077998636864796,-0.16603887711981763,1.0,46.048327736999994,-73.725588486,Rawdon | |
| 140 | +62047,916,6,0.6686273865669214,34.70941173536347,1.1448167828128435,-0.19856673586099222,1.0,46.109469743000005,-73.895015133,Chertsey | |
| 141 | +62060,794,6,0.7427972147972148,33.904336974441236,1.1834926479685908,-0.21682129664326572,1.0,46.318582148000004,-74.213703653,Saint-Donat | |
| 142 | +62065,420,5,0.6465903582009157,34.457159167787715,1.1454066195583492,-0.20381444347154196,1.0,46.2776111765,-73.7758384495,Saint-Côme | |
| 143 | +62070,108,2,0.6529564054139125,36.33391748449871,1.1417382804528984,-0.11266234441125024,0.5,46.321132874499995,-73.639100665,Sainte-Émélie-de-l'Énergie | |
| 144 | +62075,386,5,0.7098152254249814,29.668639557181635,1.1299303238462528,-0.2023320820761078,1.0,46.328845049,-73.518669413,Saint-Damien | |
| 145 | +63013,263,4,0.7297487480112332,30.749630320275678,1.135104477252584,-0.6872060215266782,1.0,45.950303911,-73.56989595,Saint-Jacques | |
| 146 | +63035,259,4,0.7150263554216867,25.025814834852063,1.115795536117923,-0.4967946545942795,1.0,45.857606134,-73.596808363,Saint-Roch-de-l'Achigan | |
| 147 | +63048,2657,6,0.6787482807072664,32.399731567075136,1.1426349702029932,-0.4409546828425126,1.0,45.829154267,-73.756059204,Saint-Lin--Laurentides | |
| 148 | +63055,990,6,0.6991879004344631,34.772906014843656,1.1179805996735728,-0.12841835399662452,1.0,45.9496960915,-73.8518049635,Saint-Calixte | |
| 149 | +63060,1376,6,0.6786177255124288,29.932403168703473,1.1084628215703884,-0.18243715273619399,1.0,45.9692136175,-73.721131893,Sainte-Julienne | |
| 150 | +64008,8061,6,0.7949895317396622,23.75303561732275,1.0961290692775711,-0.28961986412143703,1.0,45.712940538,-73.677957948,Terrebonne | |
| 151 | +64015,4084,6,0.6733830040040366,24.907510224848203,1.0966100878654021,-0.2790068811456091,1.0,45.742800969,-73.620258493,Mascouche | |
| 152 | +65005,23569,6,0.7292941176470589,20.99004120848114,1.07591200087497,-0.1677198430090882,1.0,45.573336747,-73.753870945,Laval | |
| 153 | +66007,122,2,0.7185924821990395,24.77281784551642,1.1040070044998445,-0.33961014858943006,1.0,45.631109163,-73.502026454,Montréal-Est | |
| 154 | +66023,79581,6,0.8922306974762114,24.895070443807846,1.0571629439855559,0.06966324444056746,0.0,45.52859923,-73.587846245,Montréal | |
| 155 | +66032,1001,6,0.9577432898937377,19.517732793938418,1.0650199449333386,-0.06690574065852856,1.0,45.484173923,-73.60049964,Westmount | |
| 156 | +66058,1337,6,0.9308821671736927,13.371841924509315,1.0295750134873236,-0.03141725569656499,1.0,45.474598268,-73.662658253,Côte-Saint-Luc | |
| 157 | +66062,119,2,0.828157191637994,15.169213801325093,1.0390621459475182,-0.08018877234711512,1.0,45.48090571,-73.646078423,Hampstead | |
| 158 | +66072,919,6,0.9712154346775798,18.437910632001554,1.0586410740869279,-0.03509377230935708,0.8333333333333334,45.515282794,-73.646702467,Mont-Royal | |
| 159 | +66087,960,6,0.9231119095396276,17.57375988436484,1.0422460191530596,-0.021726145155715075,0.6666666666666666,45.44579454,-73.7450680235,Dorval | |
| 160 | +66097,1595,6,0.9147700349809617,16.01327842926532,1.0495921971184083,-0.13170475816618712,1.0,45.452228282,-73.811135622,Pointe-Claire | |
| 161 | +66102,912,6,0.9157315185059659,15.222291389905148,1.03620196824576,-0.0937722845882564,1.0,45.44809616249999,-73.863656111,Kirkland | |
| 162 | +66107,1099,6,0.9318441349917743,17.594256192232862,1.0390910992766764,-0.06141225217710209,0.8333333333333334,45.43256399,-73.866690441,Beaconsfield | |
| 163 | +66142,1997,6,0.9090923582911031,17.722469897676014,1.0475652915979667,-0.11857595425804798,1.0,45.488650831,-73.825879925,Dollard-des-Ormeaux | |
| 164 | +67010,712,6,0.684262379422226,25.33975426489651,1.101002924179236,-0.448853524468586,1.0,45.3677048265,-73.480672417,Saint-Philippe | |
| 165 | +67015,1622,6,0.8212459879530496,22.693926604495797,1.1042615346863807,-0.2621491447773391,1.0,45.4090403705,-73.4802975005,La Prairie | |
| 166 | +67020,1736,6,0.680335891313185,22.419102830071658,1.076173224217308,-0.12050021573808359,1.0,45.383528165,-73.513485181,Candiac | |
| 167 | +67025,522,6,0.7268149330713496,25.35998209068471,1.0964745751820337,-0.539674897123865,1.0,45.37836885,-73.5442486825,Delson | |
| 168 | +67030,1110,6,0.7798735661126965,22.248866183009056,1.096618639052464,-0.6889060659985939,1.0,45.3966073255,-73.56369906500001,Sainte-Catherine | |
| 169 | +67035,1820,6,0.8063260280719035,19.269590412382986,1.080966359291669,-0.5027860383185769,1.0,45.377947198,-73.580077421,Saint-Constant | |
| 170 | +67045,1127,6,0.6681714781145649,24.917527317624618,1.1097996485766655,-0.48606177990172883,1.0,45.314420404,-73.741056666,Mercier | |
| 171 | +67050,3054,6,0.6863984355752648,23.963031662323864,1.0937004161347055,-0.4718981001477939,1.0,45.359037404,-73.736001287,Châteauguay | |
| 172 | +68030,251,4,0.7097808837589821,30.663358396736708,1.1155094173546272,-0.5579209556372569,1.0,45.188883749,-73.401096402,Napierville | |
| 173 | +68055,589,6,0.6838214033382906,26.415322729836625,1.078747858436056,-0.15287365173213993,0.8333333333333334,45.262373942,-73.616414923,Saint-Rémi | |
| 174 | +69037,106,2,0.6881955099377458,37.92110737415667,1.1730260422804393,-0.4670013713958576,1.0,45.119982944,-73.992491204,Ormstown | |
| 175 | +69070,193,3,0.6907325174825175,29.694453762884823,1.1121747313343102,-0.11765626471433004,1.0,45.116286833000004,-74.38926475950001,Saint-Anicet | |
| 176 | +70012,309,5,0.7320459236326109,30.384012277045837,1.1280580748468252,-0.3575072591273072,1.0,45.2573235345,-73.79560172449999,Sainte-Martine | |
| 177 | +70022,1075,6,0.7597574024047884,29.870884614022984,1.1144127568575457,-0.4358589181857655,1.0,45.312101516,-73.873625864,Beauharnois | |
| 178 | +70052,3472,6,0.7403786669685437,31.742876750423854,1.129265554756235,-0.39542016635342087,1.0,45.2621602895,-74.12986425700001,Salaberry-de-Valleyfield | |
| 179 | +71025,924,6,0.7541040226866018,34.38138604039217,1.1456583338332207,-0.4523567408963357,1.0,45.246927821,-74.246740692,Saint-Zotique | |
| 180 | +71033,509,6,0.6692114261414426,25.803009197034513,1.080165640307659,-0.3618141624281369,0.8333333333333334,45.269131059,-74.226509361,Les Coteaux | |
| 181 | +71040,471,6,0.6972763087740437,35.31534275886618,1.1650689893927204,-0.43853385299879444,1.0,45.296572945,-74.175489643,Coteau-du-Lac | |
| 182 | +71050,524,6,0.811130446717522,24.906489020547767,1.0853439175884678,-0.15171924992358465,0.8333333333333334,45.3276066585,-74.0569555055,Les Cèdres | |
| 183 | +71060,718,6,0.6973422430553382,24.315276991934034,1.0998484312304608,-0.23499118611460362,0.8333333333333334,45.3895503655,-73.952990984,L'Île-Perrot | |
| 184 | +71065,723,6,0.691639751552795,21.134183691410264,1.0900101947935883,-0.2552472905767234,1.0,45.38125767,-73.923685459,Notre-Dame-de-l'Île-Perrot | |
| 185 | +71070,958,6,0.6918983367809608,22.63951634518149,1.0818047696747568,-0.3296866908207592,1.0,45.378101038,-73.9810040255,Pincourt | |
| 186 | +71083,2616,6,0.6987913699204021,20.423287853352477,1.0679629167870859,-0.201423129509599,1.0,45.391017535,-74.03721731499999,Vaudreuil-Dorion | |
| 187 | +71100,440,5,0.657512202010254,21.139092496444366,1.0567610887847834,-0.06951265776759467,0.8,45.454409926,-74.148156067,Hudson | |
| 188 | +71105,1501,6,0.6584680311149621,25.484719956369524,1.103918642345454,-0.3099676452402449,1.0,45.40426853,-74.147789979,Saint-Lazare | |
| 189 | +71133,611,6,0.6784057357430919,24.228624394914526,1.0924866258655075,-0.1834992400459184,1.0,45.478344449,-74.301244119,Rigaud | |
| 190 | +72005,2980,6,0.6602678170113345,27.26128613086194,1.1090164884002613,-0.39811780722269663,1.0,45.573503094,-73.9003547245,Saint-Eustache | |
| 191 | +72010,1093,6,0.6784331943504811,29.31978303844349,1.131091676981932,-0.49115172208957975,1.0,45.543286433,-73.903958594,Deux-Montagnes | |
| 192 | +72015,1568,6,0.6834799296337758,27.718407825988223,1.1143667823617305,-0.26218667134366386,1.0,45.53085263,-73.940682486,Sainte-Marthe-sur-le-Lac | |
| 193 | +72020,589,6,0.742374040707374,29.13344857812509,1.117375821640409,-0.21563592347582894,1.0,45.497248842,-73.97750307,Pointe-Calumet | |
| 194 | +72025,379,5,0.7158108589230086,36.251999901980206,1.1408136801835462,-0.18925690542877868,1.0,45.5231855765,-73.9802705435,Saint-Joseph-du-Lac | |
| 195 | +72032,196,3,0.7348350197615494,29.765900135180456,1.1095989251399265,-0.4030715202592383,1.0,45.47335282,-74.081503438,Oka | |
| 196 | +73005,1667,6,0.7697670591362542,25.07291047173648,1.1057264849673165,-0.3921155583951153,1.0,45.61848801,-73.841808155,Boisbriand | |
| 197 | +73010,1766,6,0.7849619311211817,21.364045543447695,1.078015806539247,-0.16574586723421708,1.0,45.6438193675,-73.83684939700001,Sainte-Thérèse | |
| 198 | +73015,4302,6,0.6642971636742129,23.040896754254227,1.0974052681194044,-0.18976729044074891,1.0,45.674802691,-73.8721633665,Blainville | |
| 199 | +73020,947,6,0.7715263047359024,23.556732862555528,1.104648618295947,-0.29614415506310915,1.0,45.643460388,-73.794777382,Rosemère | |
| 200 | +73025,749,6,0.6429262075370085,23.614168966233816,1.0945989327222034,-0.29535573454640485,1.0,45.670854774,-73.784177923,Lorraine | |
| 201 | +73030,827,6,0.6672291272123052,21.611011856728346,1.0847893348004582,-0.24500182019431868,1.0,45.673537822,-73.764234148,Bois-des-Filion | |
| 202 | +73035,1134,6,0.659490376984127,29.599130473734494,1.1208319661428983,-0.5384539363937264,1.0,45.7659122305,-73.81296885399999,Sainte-Anne-des-Plaines | |
| 203 | +74005,4618,6,0.7749613542187931,25.169913073493312,1.107774565693448,-0.40041493495052627,1.0,45.673421485999995,-73.98505922449999,Mirabel | |
| 204 | +75005,1621,6,0.6562752525252525,30.650915931084207,1.133435577808017,-0.28971919531263324,1.0,45.746477502,-74.112937545,Saint-Colomban | |
| 205 | +75017,6616,6,0.7517893023113612,23.091842242510324,1.0834015969188,-0.24785057684142733,1.0,45.789913221,-74.0082415655,Saint-Jérôme | |
| 206 | +75028,1832,6,0.673219696969697,34.519633166289346,1.1589579575042572,-0.4521593731818938,1.0,45.832600118,-73.9533649835,Sainte-Sophie | |
| 207 | +75040,1292,6,0.6564135708058492,28.883399359732728,1.1250150841218194,-0.2923979410997817,1.0,45.860940030500004,-74.0554606795,Prévost | |
| 208 | +75045,1478,6,0.7645713506986852,30.123502040027475,1.126414204841707,-0.1552297611761352,1.0,45.89739243,-73.98773667149999,Saint-Hippolyte | |
| 209 | +76020,1178,6,0.7033883848863581,25.657210202235234,1.0943487239078933,-0.18994258509577283,1.0,45.651124736,-74.3435378865,Lachute | |
| 210 | +76025,240,4,0.6969195176954546,32.67202626752153,1.1293551629367897,-0.17129339765566512,1.0,45.7657740385,-74.2481922565,Gore | |
| 211 | +76043,681,6,0.7906597930839002,33.62984625478003,1.1317947395194912,-0.3000186607193363,1.0,45.677822306,-74.406445365,Brownsburg-Chatham | |
| 212 | +76052,112,2,0.6842583441149015,28.727733669937138,1.0979075842302088,-0.10610838611500388,1.0,45.649283199,-74.647290242,Grenville-sur-la-Rouge | |
| 213 | +77012,577,6,0.767416063397733,31.70987258356473,1.1178739679916119,-0.2180822273799095,0.8333333333333334,46.048795423,-74.076073038,Sainte-Marguerite-du-Lac-Masson | |
| 214 | +77022,1813,6,0.7736937921287237,26.104447343065164,1.0829474852619216,-0.15219298535856018,1.0,45.953708666,-74.139983317,Sainte-Adèle | |
| 215 | +77030,630,6,0.674526627218935,23.9838858690541,1.0680071983146502,-0.06951841047914054,1.0,45.892511213,-74.1345699675,Piedmont | |
| 216 | +77035,421,5,0.6726760563380282,32.707153156198636,1.1487149068078237,-0.20639664687018483,0.8,45.859360428,-74.119898636,Sainte-Anne-des-Lacs | |
| 217 | +77043,1679,6,0.6642491108705737,23.70281702578965,1.0734527565770051,-0.1685823132373225,1.0,45.885168363,-74.173904996,Saint-Sauveur | |
| 218 | +77050,615,6,0.6425609382325694,32.995697452241316,1.111440447284858,-0.20400064585818517,1.0,45.90287985,-74.238219198,Morin-Heights | |
| 219 | +77060,176,3,0.6742105263157895,48.283982620796436,1.2368582258892007,-0.25484431235044575,1.0,45.8555580205,-74.467661531,Wentworth-Nord | |
| 220 | +77065,786,6,0.7760721311475409,31.111832805489417,1.1296188471049702,-0.08404023050555207,1.0,45.962066259,-74.345879549,Saint-Adolphe-d'Howard | |
| 221 | +78005,260,4,0.788936974789916,29.460609073697377,1.105329878424848,-0.19843515170141063,1.0,46.002425485,-74.1954476,Val-Morin | |
| 222 | +78010,564,6,0.7682692371448463,30.02851642067145,1.1025223014890941,-0.12295037894100631,1.0,46.027993698,-74.209720426,Val-David | |
| 223 | +78032,1015,6,0.6855593767141084,25.791843718209538,1.0749411280220096,-0.11449565917287258,1.0,46.046276118,-74.281797912,Sainte-Agathe-des-Monts | |
| 224 | +78047,413,5,0.7135,33.7908170202975,1.1300176688632169,-0.21575811886767085,0.8,46.114378271,-74.47546422100001,Mont-Blanc | |
| 225 | +78095,171,3,0.8208829530699313,28.132199595941593,1.126098556497308,-0.13958716637973806,1.0,46.16770375,-74.470425115,Lac-Supérieur | |
| 226 | +78102,1089,6,0.7311117614269789,30.794479048645194,1.1204382252400635,-0.12836311075483878,1.0,46.132329575,-74.593895639,Mont-Tremblant | |
| 227 | +78120,196,3,0.762378947368421,42.255755825822746,1.2141461619843812,-0.2992448689549128,1.0,46.275642615,-74.734005549,Labelle | |
| 228 | +79005,317,5,0.7562162162162163,30.136734693961536,1.1003274393901796,-0.06869114861633299,1.0,46.100329843500006,-75.6227275185,Notre-Dame-du-Laus | |
| 229 | +79030,127,2,0.6468191065144411,33.48178439024826,1.093039579050811,-0.10737375244102947,1.0,46.398916586,-75.02818566100001,Nominingue | |
| 230 | +79037,295,4,0.6846636546184739,35.025329593330426,1.1400383441487119,-0.1695172539305697,1.0,46.444012066,-74.891305735,Rivière-Rouge | |
| 231 | +79088,1068,6,0.7740341365461847,24.302939834888655,1.069661991560468,-0.1465788154360099,1.0,46.552159219,-75.5027332425,Mont-Laurier | |
| 232 | +79097,104,2,0.7422861778744132,26.572593593059842,1.1068266452312072,-0.09448456343410147,1.0,46.701718909,-75.451640396,Ferme-Neuve | |
| 233 | +80027,143,2,0.6577200331373031,34.64365343512506,1.1540631770509941,-0.2563220112686172,1.0,45.7288633915,-75.056881545,Saint-André-Avellin | |
| 234 | +80050,104,1,0.7304779411764706,20.060720680119704,1.0551305741351824,-0.22257162986162263,1.0,45.602108631,-75.243203534,Thurso | |
| 235 | +81017,21270,6,0.7984077215662176,16.931554716735143,1.0495027784666382,-0.13658868222945214,1.0,45.478547612499995,-75.713496176,Gatineau | |
| 236 | +82005,368,5,0.7394618996027447,15.758329630938155,1.022595285772932,-0.011565422335264312,0.6,45.607380499,-75.448113435,L'Ange-Gardien | |
| 237 | +82015,1321,6,0.7915990277134624,20.436939014421327,1.0558473384677736,-0.10941019083278125,1.0,45.633673901,-75.645517707,Val-des-Monts | |
| 238 | +82020,877,6,0.6638305562902231,19.66144762913082,1.068141505374912,-0.15680720575821785,1.0,45.554269142,-75.760626051,Cantley | |
| 239 | +82025,533,5,0.7365128205128205,20.109427070252394,1.0711324084906955,-0.13228455957137367,0.6,45.507582942,-75.80418832,Chelsea | |
| 240 | +82030,278,4,0.7453869047619048,24.653691366080988,1.0795465837628575,-0.10148238374645359,1.0,45.5065481265,-76.0379189075,Pontiac | |
| 241 | +82035,715,6,0.6650178866263071,29.44183235849443,1.1144147254778574,-0.16212541696934157,1.0,45.656126335,-75.99370325,La Pêche | |
| 242 | +83032,184,3,0.7973958333333333,34.92667040526475,1.1703174468795343,-0.07965466822893477,1.0,46.092894060000006,-76.047773002,Gracefield | |
| 243 | +83065,218,3,0.7012959191727839,28.805661670739624,1.1015631923144458,-0.26803741432214934,1.0,46.3787703725,-75.974956507,Maniwaki | |
| 244 | +86042,2724,6,0.8366583424300523,15.06147804754267,1.0368678256129944,-0.08939705321208859,1.0,48.234782821500005,-79.02640637,Rouyn-Noranda | |
| 245 | +87090,438,5,0.7851063829787234,20.116819736961197,1.045655945310056,-0.09997817214443672,1.0,48.796851013,-79.203499652,La Sarre | |
| 246 | +88057,218,2,0.811819632749137,18.0851627067117,1.0643777382946966,-0.2559327944639028,1.0,48.5760654345,-78.113034247,Amos | |
| 247 | +89008,2324,6,0.8126685967539155,28.21645553056692,1.1161070235181083,-0.32663299095099524,1.0,48.102099980999995,-77.810123723,Val-d'Or | |
| 248 | +89015,116,2,0.8094558101472995,20.431714403736038,1.072786429093131,-0.23917163934784105,1.0,48.140422102,-78.122193154,Malartic | |
| 249 | +90012,824,6,0.749237588389274,25.46135544821528,1.0756222255829053,-0.1487413121568984,1.0,47.437275442499995,-72.779832883,La Tuque | |
| 250 | +91025,664,6,0.8137683672076632,19.119137295400005,1.0635366819847192,-0.14286063685850484,0.8333333333333334,48.5130966975,-72.229779914,Roberval | |
| 251 | +91042,749,6,0.8105882352941176,24.026771569803017,1.0853518553934969,-0.14987364945513987,1.0,48.654435423,-72.443420482,Saint-Félicien | |
| 252 | +92022,1068,6,0.7948982699805068,18.574330434764217,1.056085669382861,-0.05639164920521421,1.0,48.880969938,-72.2172604135,Dolbeau-Mistassini | |
| 253 | +93012,238,4,0.8150918603012337,25.382670531701578,1.1079238343881064,-0.12237404789260117,1.0,48.422557618,-71.87132776850001,Métabetchouan--Lac-à-la-Croix | |
| 254 | +93042,2286,6,0.8025041313443663,29.775957583854023,1.1264432678832426,-0.33707942496195986,1.0,48.548642577,-71.64940249899999,Alma | |
| 255 | +94068,10794,6,0.7872989417989418,19.073245445985677,1.0638779683601711,-0.18854120739412214,1.0,48.413850183,-71.114843926,Saguenay | |
| 256 | +94240,433,5,0.7548717948717949,26.19976837183734,1.1064419574799171,-0.25623669173628566,1.0,48.531486353000005,-71.08104856349999,Saint-Honoré | |
| 257 | +94245,204,3,0.6753375474083438,31.77265312960662,1.1134962112514815,-0.19640112322516234,1.0,48.636804908,-71.096657085,Saint-David-de-Falardeau | |
| 258 | +94255,115,2,0.8416867842601852,19.91364075604485,1.0634758075346165,-0.17522623594205916,1.0,48.560310212,-71.322823794,Saint-Ambroise | |
| 259 | +96020,1363,6,0.8078230923665707,17.345307803691497,1.0486315729197053,-0.15390315960400958,1.0,49.204002921,-68.190065087,Baie-Comeau | |
| 260 | +97007,1757,6,0.8232628501201955,31.766836500102194,1.1429833751673635,-0.2706148488213218,1.0,50.217120775,-66.379328936,Sept-Îles | |
| 261 | +97022,310,4,0.8620232267511514,26.468917141651318,1.075295425288986,-0.09639715621572498,1.0,50.023658767,-66.879255019,Port-Cartier | |
| 262 | +99025,678,6,0.8681148247978436,24.873041560245397,1.08653566843947,-0.19736064134913345,1.0,49.913814422499996,-74.375484754,Chibougamau | |
added
results/reproduced/iaao_overall.csv
+8 −0
@@ -0,0 +1,8 @@ | ||
| 1 | +group,n,median_ratio,cod,prd,prb,prb_se,median_lo,median_hi,cod_lo,cod_hi,prd_lo,prd_hi,prb_lo,prb_hi | |
| 2 | +All sales 2021–2026,522769,0.7842696629213484,28.453606677566754,1.0720526265902222,-0.029231043995398567,0.001011694100494542,0.7836525,0.7849235679214402,28.32931767404846,28.565805731993105,1.0708980043366874,1.0730233208372144,-0.0311809979240684,-0.027240556459155452 | |
| 3 | +2021,115221,0.75875,26.56881340997751,1.0977977446595462,-0.10439995446335718,0.0018780611602475469,0.7573905876731963,0.76,26.351010556052213,26.81244648128587,1.096089395370703,1.0995245107667422,-0.10784763266408848,-0.10129939282929173 | |
| 4 | +2022,90627,0.6996330275229358,30.166845460558804,1.113909455968154,-0.11645566662177718,0.002565006647926422,0.6986641148325359,0.7010965205683608,29.860881164700064,30.500688556680952,1.1115376567937396,1.1168128266197954,-0.12086406687016825,-0.11072838334214413 | |
| 5 | +2023,74110,0.7678797912903873,30.158018383876275,1.0669797271549857,-0.014624967340268172,0.0029669111162585514,0.766664374101264,0.7692849755881734,29.831543249220744,30.421183921307005,1.064154926668826,1.0692306393998607,-0.01975682064425937,-0.008456585563497683 | |
| 6 | +2024,89204,0.7652173913043478,29.51630268694212,1.0608402697893429,-0.0022461064763566636,0.002801065827976032,0.7639511533088846,0.7668753472222222,29.231808427730112,29.784991266712524,1.0588161557648539,1.0631172201778432,-0.007092623264666497,0.003072440446322985 | |
| 7 | +2025,102026,0.8505050505050505,25.459880383502032,1.07018259382875,-0.038888882788478116,0.0026313803244376664,0.8495232422704633,0.8514549465240642,25.15070947846792,25.72947026310519,1.0682152399072007,1.072481009411764,-0.04395325683380448,-0.034422778196270945 | |
| 8 | +2026,51581,0.8571428571428571,25.489813580325944,1.067871694912302,-0.030491883244112023,0.0038787622967144447,0.8553654037886341,0.8584553928095873,25.057401870485677,25.86570274574058,1.0649438720501567,1.0709611050077859,-0.038324942561289106,-0.024024217864788658 | |
added
results/reproduced/paglin_fogarty.csv
+8 −0
@@ -0,0 +1,8 @@ | ||
| 1 | +,0 | |
| 2 | +estimator,Paglin–Fogarty (levels) | |
| 3 | +intercept,31729.109668061967 | |
| 4 | +intercept_se,5377.612928331011 | |
| 5 | +slope,0.750538044579637 | |
| 6 | +slope_se,0.0234789080468955 | |
| 7 | +n,522769 | |
| 8 | +r2,0.765529044655609 | |
added
results/reproduced/quantile.csv
+6 −0
@@ -0,0 +1,6 @@ | ||
| 1 | +tau,beta,se,gamma,n | |
| 2 | +0.1,0.8710259920099666,0.0011617888737700704,-0.12897400799003345,250000 | |
| 3 | +0.25,0.8576278086355942,0.0008188248060121729,-0.14237219136440582,250000 | |
| 4 | +0.5,0.8158799571081065,0.0008596249662814258,-0.18412004289189354,250000 | |
| 5 | +0.75,0.6977863205885624,0.001810213015287808,-0.3022136794114376,250000 | |
| 6 | +0.9,0.493980443120313,0.004939830233012593,-0.506019556879687,250000 | |
added
results/reproduced/robustness.csv
+12 −0
@@ -0,0 +1,12 @@ | ||
| 1 | +variant,gamma_fe,se_fe,gamma_iv,se_iv,n | |
| 2 | +Baseline,-0.3441977186584467,0.029440415405950358,-0.08342441688397817,0.02111681371535637,522769 | |
| 3 | +Condominiums only,-0.12464520941899382,0.01518233161943941,0.05839388234158127,0.016310586343762663,77976 | |
| 4 | +Excluding sales < $100k,-0.2954021582133831,0.02620799864834414,-0.04761800229908353,0.017049185795619447,507024 | |
| 5 | +Match score = 220 (max),-0.41484792659187864,0.039909750823735164,-0.09508044808955152,0.029034367201517614,210820 | |
| 6 | +Match distance <= 10 m,-0.35544067100844223,0.03044054381932686,-0.0865157304085471,0.0217104651732468,481476 | |
| 7 | +Single-family only,-0.4537825487653664,0.017669914962190263,-0.14637837847309776,0.009674772614708964,337855 | |
| 8 | +Ratio trim 5/95,-0.14708370575153007,0.022273388305055954,-0.04156000317201847,0.01559802433181914,465318 | |
| 9 | +Sales 2021-2023,-0.34991990346605073,0.029902278574860372,-0.10123470423960601,0.02502546688053367,279958 | |
| 10 | +Sales 2024-2026,-0.3371827571771222,0.028989933590428545,-0.06223166153460358,0.01639409832734001,242811 | |
| 11 | +Munis >= 300 sales,-0.3409164404961804,0.032623407694126444,-0.07722611352301967,0.022313481326094283,474004 | |
| 12 | +Cells >= 50 sales,-0.33992010068755585,0.03216956767872976,-0.07761468372097924,0.02222025306569112,476001 | |
added
results/reproduced/sample_counts.csv
+10 −0
@@ -0,0 +1,10 @@ | ||
| 1 | +,0 | |
| 2 | +n_sales,522769 | |
| 3 | +n_munis,625 | |
| 4 | +n_cells,2884 | |
| 5 | +n_condo,81461 | |
| 6 | +n_cottage,9287 | |
| 7 | +n_mobile,4701 | |
| 8 | +n_other,574 | |
| 9 | +n_plex,81693 | |
| 10 | +n_single_family,345053 | |
added
results/reproduced/summary_stats.csv
+9 −0
@@ -0,0 +1,9 @@ | ||
| 1 | +variable,n,mean,sd,p10,p50,p90 | |
| 2 | +Sale price ($),522769,462131.02316128154,331668.8849792319,175000.0,400000.0,787500.0 | |
| 3 | +Assessed value ($),522769,378576.0241311172,284509.3908358621,155000.0,309400.0,664500.0 | |
| 4 | +Assessment ratio AV/SP,522769,0.8782215447418829,0.4287885762103099,0.5793890014020876,0.7842696629213484,1.137561073733976 | |
| 5 | +Roll lag (months),522769,37.06170101351715,11.313660102159131,22.470433639947437,36.16951379763469,50.65703022339027 | |
| 6 | +Assessed land share,519330,0.30981189756147676,0.14934088670854148,0.12636418307358985,0.2959155356170383,0.5106060606060606 | |
| 7 | +Building age (years),498458,43.62546694004309,30.107767330611793,10.0,39.0,79.0 | |
| 8 | +Lot area (m2),522475,1406.4376602899658,10905.966380926626,111.71,555.8,2257.6 | |
| 9 | +Floor area (m2),515958,137.64187220665247,84.87818498574318,75.9,112.0,225.9 | |
added
results/reproduced/taxshift.csv
+11 −0
@@ -0,0 +1,11 @@ | ||
| 1 | +decile,n,mean_rel,median_rel,se | |
| 2 | +1,51007,1.0172224507626328,0.6488477482764501,0.00510136426265092 | |
| 3 | +2,52458,0.2415117210138423,0.0698547780946529,0.002333270560075986 | |
| 4 | +3,51976,0.11189928535329506,0.028146310809547326,0.0016347999883110573 | |
| 5 | +4,52385,0.06314511674551167,0.00854447439353101,0.0013607139199222748 | |
| 6 | +5,52879,0.030137573946786966,-0.008096278617191643,0.0011409831482484887 | |
| 7 | +6,51767,0.008282058967129717,-0.022904260192395776,0.0010397072479158753 | |
| 8 | +7,52236,-0.007197592292916026,-0.03155116674680325,0.0009408008322386948 | |
| 9 | +8,52364,-0.018627874530360817,-0.03821138759371412,0.0008698417304787702 | |
| 10 | +9,52123,-0.022000383569130387,-0.03809452119114054,0.0008369636786756912 | |
| 11 | +10,53574,-0.03607074517324711,-0.04728024331485037,0.0008015863097273057 | |
added
results/reproduced/vertical.csv
+4 −0
@@ -0,0 +1,4 @@ | ||
| 1 | +estimator,beta,se,gamma,n,r2 | |
| 2 | +Cheng OLS (pooled),0.8119241820644745,0.029259867729858405,-0.18807581793552552,522769,0.7053864314995516 | |
| 3 | +Cheng FE (cell),0.6558022813415533,0.029440415405950358,-0.3441977186584467,522769,0.7956390786292254 | |
| 4 | +Clapp IV (rank instrument),0.9165755831160218,0.02111681371535637,-0.08342441688397817,522769,0.4855697563551059 | |
added
results/tables/heterogeneity.tex
+39 −0
@@ -0,0 +1,39 @@ | ||
| 1 | +\begin{tabular}{lccr} | |
| 2 | +\toprule | |
| 3 | +Subsample & $\gamma$ & Std.\ error & $n$ \\ | |
| 4 | +\midrule | |
| 5 | +\multicolumn{4}{l}{\itshape Property class}\\ | |
| 6 | +\quad Single-family & -0.454*** & (0.018) & 337,855 \\ | |
| 7 | +\quad Condominium & -0.125*** & (0.015) & 77,976 \\ | |
| 8 | +\quad Plex (2--5 units) & -0.487*** & (0.054) & 69,745 \\ | |
| 9 | +\quad Cottage & -0.288*** & (0.026) & 2,114 \\ | |
| 10 | +\midrule | |
| 11 | +\multicolumn{4}{l}{\itshape Building age}\\ | |
| 12 | +\quad Age $<$ 20 y & -0.297*** & (0.041) & 117,898 \\ | |
| 13 | +\quad Age 20--60 y & -0.384*** & (0.034) & 227,857 \\ | |
| 14 | +\quad Age $>$ 60 y & -0.435*** & (0.028) & 108,049 \\ | |
| 15 | +\midrule | |
| 16 | +\multicolumn{4}{l}{\itshape Assessed land share}\\ | |
| 17 | +\quad Land share $<$ 0.2 & -0.262*** & (0.042) & 119,718 \\ | |
| 18 | +\quad Land share 0.2--0.4 & -0.372*** & (0.032) & 236,923 \\ | |
| 19 | +\quad Land share $>$ 0.4 & -0.503*** & (0.030) & 125,714 \\ | |
| 20 | +\midrule | |
| 21 | +\multicolumn{4}{l}{\itshape Roll lag at sale}\\ | |
| 22 | +\quad Roll lag $<$ 24 m & -0.335*** & (0.028) & 86,001 \\ | |
| 23 | +\quad Roll lag 24--48 m & -0.338*** & (0.033) & 327,031 \\ | |
| 24 | +\quad Roll lag $>$ 48 m & -0.361*** & (0.026) & 91,367 \\ | |
| 25 | +\midrule | |
| 26 | +\multicolumn{4}{l}{\itshape Municipality size}\\ | |
| 27 | +\quad Muni $<$ 1k sales & -0.379*** & (0.007) & 125,139 \\ | |
| 28 | +\quad Muni 1k--10k sales & -0.406*** & (0.014) & 185,111 \\ | |
| 29 | +\quad Muni $>$ 10k sales & -0.267*** & (0.043) & 212,519 \\ | |
| 30 | +\midrule | |
| 31 | +\multicolumn{4}{l}{\itshape Sale year}\\ | |
| 32 | +\quad Sales 2021 & -0.316*** & (0.027) & 115,221 \\ | |
| 33 | +\quad Sales 2022 & -0.380*** & (0.030) & 90,627 \\ | |
| 34 | +\quad Sales 2023 & -0.365*** & (0.034) & 74,110 \\ | |
| 35 | +\quad Sales 2024 & -0.333*** & (0.032) & 89,204 \\ | |
| 36 | +\quad Sales 2025 & -0.335*** & (0.026) & 102,026 \\ | |
| 37 | +\quad Sales 2026 & -0.349*** & (0.030) & 51,581 \\ | |
| 38 | +\bottomrule | |
| 39 | +\end{tabular} | |
| \ No newline at end of file | ||
added
results/tables/horizontal.tex
+14 −0
@@ -0,0 +1,14 @@ | ||
| 1 | +\begin{tabular}{lcc} | |
| 2 | +\toprule | |
| 3 | + & Coefficient & Std.\ error \\ | |
| 4 | +\midrule | |
| 5 | +Building age (decades) & +0.0075*** & (0.0012) \\ | |
| 6 | +Assessed land share & +0.0031 & (0.0298) \\ | |
| 7 | +Condominium & -0.0551*** & (0.0074) \\ | |
| 8 | +Plex (2--5 units) & +0.0247 & (0.0151) \\ | |
| 9 | +Cottage & +0.0520*** & (0.0073) \\ | |
| 10 | +\midrule | |
| 11 | +Mean of dependent variable & \multicolumn{2}{c}{0.188} \\ | |
| 12 | +Observations & \multicolumn{2}{c}{495,092} \\ | |
| 13 | +\bottomrule | |
| 14 | +\end{tabular} | |
| \ No newline at end of file | ||
added
results/tables/iaao.tex
+20 −0
@@ -0,0 +1,20 @@ | ||
| 1 | +\begin{tabular}{lrcccc} | |
| 2 | +\toprule | |
| 3 | +Sample & $n$ & Median ratio & COD & PRD & PRB \\ | |
| 4 | +\midrule | |
| 5 | +All sales 2021–2026 & 522,769 & 0.784 & 28.5 & 1.072 & -0.0292 \\ | |
| 6 | + & & \footnotesize[0.784, 0.785] & \footnotesize[28.3, 28.6] & \footnotesize[1.071, 1.073] & \footnotesize[-0.0312, -0.0272] \\ | |
| 7 | +2021 & 115,221 & 0.759 & 26.6 & 1.098 & -0.1044 \\ | |
| 8 | + & & \footnotesize[0.757, 0.760] & \footnotesize[26.4, 26.8] & \footnotesize[1.096, 1.100] & \footnotesize[-0.1078, -0.1013] \\ | |
| 9 | +2022 & 90,627 & 0.700 & 30.2 & 1.114 & -0.1165 \\ | |
| 10 | + & & \footnotesize[0.699, 0.701] & \footnotesize[29.9, 30.5] & \footnotesize[1.112, 1.117] & \footnotesize[-0.1209, -0.1107] \\ | |
| 11 | +2023 & 74,110 & 0.768 & 30.2 & 1.067 & -0.0146 \\ | |
| 12 | + & & \footnotesize[0.767, 0.769] & \footnotesize[29.8, 30.4] & \footnotesize[1.064, 1.069] & \footnotesize[-0.0198, -0.0085] \\ | |
| 13 | +2024 & 89,204 & 0.765 & 29.5 & 1.061 & -0.0022 \\ | |
| 14 | + & & \footnotesize[0.764, 0.767] & \footnotesize[29.2, 29.8] & \footnotesize[1.059, 1.063] & \footnotesize[-0.0071, 0.0031] \\ | |
| 15 | +2025 & 102,026 & 0.851 & 25.5 & 1.070 & -0.0389 \\ | |
| 16 | + & & \footnotesize[0.850, 0.851] & \footnotesize[25.2, 25.7] & \footnotesize[1.068, 1.072] & \footnotesize[-0.0440, -0.0344] \\ | |
| 17 | +2026 & 51,581 & 0.857 & 25.5 & 1.068 & -0.0305 \\ | |
| 18 | + & & \footnotesize[0.855, 0.858] & \footnotesize[25.1, 25.9] & \footnotesize[1.065, 1.071] & \footnotesize[-0.0383, -0.0240] \\ | |
| 19 | +\bottomrule | |
| 20 | +\end{tabular} | |
| \ No newline at end of file | ||
added
results/tables/iaao_cities.tex
+16 −0
@@ -0,0 +1,16 @@ | ||
| 1 | +\begin{tabular}{lrccccc} | |
| 2 | +\toprule | |
| 3 | +Municipality & $n$ & Median ratio & COD & PRD & PRB & \makecell{Share of years\\PRB $<$ 0} \\ | |
| 4 | +\midrule | |
| 5 | +Montréal & 79,581 & 0.892 & 24.9 & 1.057 & 0.0697 & 0.00 \\ | |
| 6 | +Québec & 39,927 & 0.809 & 22.0 & 1.071 & -0.0480 & 1.00 \\ | |
| 7 | +Laval & 23,569 & 0.729 & 21.0 & 1.076 & -0.1677 & 1.00 \\ | |
| 8 | +Gatineau & 21,270 & 0.798 & 16.9 & 1.050 & -0.1366 & 1.00 \\ | |
| 9 | +Longueuil & 15,343 & 0.699 & 22.9 & 1.074 & -0.1276 & 1.00 \\ | |
| 10 | +Sherbrooke & 11,413 & 0.744 & 30.5 & 1.137 & -0.3872 & 1.00 \\ | |
| 11 | +Saguenay & 10,794 & 0.787 & 19.1 & 1.064 & -0.1885 & 1.00 \\ | |
| 12 | +Lévis & 10,622 & 0.827 & 22.3 & 1.089 & -0.2369 & 1.00 \\ | |
| 13 | +Trois-Rivières & 9,070 & 0.729 & 29.4 & 1.123 & -0.3167 & 1.00 \\ | |
| 14 | +Terrebonne & 8,061 & 0.795 & 23.8 & 1.096 & -0.2896 & 1.00 \\ | |
| 15 | +\bottomrule | |
| 16 | +\end{tabular} | |
| \ No newline at end of file | ||
added
results/tables/paglin_fogarty.tex
+9 −0
@@ -0,0 +1,9 @@ | ||
| 1 | +\begin{tabular}{lcc} | |
| 2 | +\toprule | |
| 3 | + & Coefficient & Std.\ error \\ | |
| 4 | +\midrule | |
| 5 | +Intercept ($\$$) & 31,729 & 5,378 \\ | |
| 6 | +Slope on sale price & 0.751 & 0.023 \\ | |
| 7 | +Observations & \multicolumn{2}{c}{522,769} \\ | |
| 8 | +\bottomrule | |
| 9 | +\end{tabular} | |
| \ No newline at end of file | ||
added
results/tables/quantile.tex
+8 −0
@@ -0,0 +1,8 @@ | ||
| 1 | +\begin{tabular}{lccccc} | |
| 2 | +\toprule | |
| 3 | +Quantile $\tau$ & 0.10 & 0.25 & 0.50 & 0.75 & 0.90 \\ | |
| 4 | +\midrule | |
| 5 | +$\beta(\tau)$ & 0.871*** & 0.858*** & 0.816*** & 0.698*** & 0.494*** \\ | |
| 6 | + & (0.0012) & (0.0008) & (0.0009) & (0.0018) & (0.0049) \\ | |
| 7 | +\bottomrule | |
| 8 | +\end{tabular} | |
| \ No newline at end of file | ||
added
results/tables/robustness.tex
+17 −0
@@ -0,0 +1,17 @@ | ||
| 1 | +\begin{tabular}{lccccr} | |
| 2 | +\toprule | |
| 3 | +Sample variant & $\gamma_{\text{FE}}$ & Std.\ err. & $\gamma_{\text{IV}}$ & Std.\ err. & $n$ \\ | |
| 4 | +\midrule | |
| 5 | +Baseline & -0.344*** & (0.029) & -0.083*** & (0.021) & 522,769 \\ | |
| 6 | +Condominiums only & -0.125*** & (0.015) & +0.058*** & (0.016) & 77,976 \\ | |
| 7 | +Excluding sales < \$100k & -0.295*** & (0.026) & -0.048*** & (0.017) & 507,024 \\ | |
| 8 | +Match score = 220 (max) & -0.415*** & (0.040) & -0.095*** & (0.029) & 210,820 \\ | |
| 9 | +Match distance $\le$ 10 m & -0.355*** & (0.030) & -0.087*** & (0.022) & 481,476 \\ | |
| 10 | +Single-family only & -0.454*** & (0.018) & -0.146*** & (0.010) & 337,855 \\ | |
| 11 | +Ratio trim 5/95 & -0.147*** & (0.022) & -0.042*** & (0.016) & 465,318 \\ | |
| 12 | +Sales 2021-2023 & -0.350*** & (0.030) & -0.101*** & (0.025) & 279,958 \\ | |
| 13 | +Sales 2024-2026 & -0.337*** & (0.029) & -0.062*** & (0.016) & 242,811 \\ | |
| 14 | +Munis $\ge$ 300 sales & -0.341*** & (0.033) & -0.077*** & (0.022) & 474,004 \\ | |
| 15 | +Cells $\ge$ 50 sales & -0.340*** & (0.032) & -0.078*** & (0.022) & 476,001 \\ | |
| 16 | +\bottomrule | |
| 17 | +\end{tabular} | |
| \ No newline at end of file | ||
added
results/tables/summary_stats.tex
+14 −0
@@ -0,0 +1,14 @@ | ||
| 1 | +\begin{tabular}{lrrrrrr} | |
| 2 | +\toprule | |
| 3 | +Variable & $n$ & Mean & SD & P10 & Median & P90 \\ | |
| 4 | +\midrule | |
| 5 | +Sale price (\$) & 522,769 & 462,131 & 331,669 & 175,000 & 400,000 & 787,500 \\ | |
| 6 | +Assessed value (\$) & 522,769 & 378,576 & 284,509 & 155,000 & 309,400 & 664,500 \\ | |
| 7 | +Assessment ratio AV/SP & 522,769 & 0.878 & 0.429 & 0.579 & 0.784 & 1.138 \\ | |
| 8 | +Roll lag (months) & 522,769 & 37.1 & 11.3 & 22.5 & 36.2 & 50.7 \\ | |
| 9 | +Assessed land share & 519,330 & 0.310 & 0.149 & 0.126 & 0.296 & 0.511 \\ | |
| 10 | +Building age (years) & 498,458 & 43.6 & 30.1 & 10.0 & 39.0 & 79.0 \\ | |
| 11 | +Lot area (m$^2$) & 522,475 & 1,406 & 10,906 & 112 & 556 & 2,258 \\ | |
| 12 | +Floor area (m$^2$) & 515,958 & 137.6 & 84.9 & 75.9 & 112.0 & 225.9 \\ | |
| 13 | +\bottomrule | |
| 14 | +\end{tabular} | |
| \ No newline at end of file | ||
added
results/tables/taxshift.tex
+16 −0
@@ -0,0 +1,16 @@ | ||
| 1 | +\begin{tabular}{lcccc} | |
| 2 | +\toprule | |
| 3 | +Price decile & $n$ & \makecell{Mean excess\\burden (\%)} & \makecell{Median excess\\burden (\%)} & Std.\ error \\ | |
| 4 | +\midrule | |
| 5 | +1 & 51,007 & +101.7 & +64.9 & (0.51) \\ | |
| 6 | +2 & 52,458 & +24.2 & +7.0 & (0.23) \\ | |
| 7 | +3 & 51,976 & +11.2 & +2.8 & (0.16) \\ | |
| 8 | +4 & 52,385 & +6.3 & +0.9 & (0.14) \\ | |
| 9 | +5 & 52,879 & +3.0 & -0.8 & (0.11) \\ | |
| 10 | +6 & 51,767 & +0.8 & -2.3 & (0.10) \\ | |
| 11 | +7 & 52,236 & -0.7 & -3.2 & (0.09) \\ | |
| 12 | +8 & 52,364 & -1.9 & -3.8 & (0.09) \\ | |
| 13 | +9 & 52,123 & -2.2 & -3.8 & (0.08) \\ | |
| 14 | +10 & 53,574 & -3.6 & -4.7 & (0.08) \\ | |
| 15 | +\bottomrule | |
| 16 | +\end{tabular} | |
| \ No newline at end of file | ||
added
results/tables/vertical.tex
+15 −0
@@ -0,0 +1,15 @@ | ||
| 1 | +\begin{tabular}{lccc} | |
| 2 | +\toprule | |
| 3 | + & (1) & (2) & (3) \\ | |
| 4 | + & Cheng OLS & Cheng FE & Clapp IV \\ | |
| 5 | +\midrule | |
| 6 | +$\beta$ (ln sale price) & 0.812*** & 0.656*** & 0.917*** \\ | |
| 7 | + & (0.029) & (0.029) & (0.021) \\ | |
| 8 | +$\gamma = \beta - 1$ & -0.188 & -0.344 & -0.083 \\ | |
| 9 | +\midrule | |
| 10 | +Cell fixed effects & No & Yes & Yes \\ | |
| 11 | +Error-in-price robust & No & No & Yes \\ | |
| 12 | +Observations & 522,769 & 522,769 & 522,769 \\ | |
| 13 | +$R^2$ & 0.705 & 0.796 & 0.486 \\ | |
| 14 | +\bottomrule | |
| 15 | +\end{tabular} | |
| \ No newline at end of file | ||
added
scripts/01_build_sample.py
+31 −0
@@ -0,0 +1,31 @@ | ||
| 1 | +#!/usr/bin/env python3 | |
| 2 | +# Author: Simon-Pierre Boucher — contact@spboucher.ai | |
| 3 | +"""Step 01 — Build the estimation sample. | |
| 4 | + | |
| 5 | +Reads the raw matched transaction–roll snapshot | |
| 6 | +(``data/raw/transactions_700k_avec_registre_foncier.parquet``), keeps | |
| 7 | +residential arm's-length sales with a high-confidence roll match, constructs | |
| 8 | +the assessment ratio and derived regressors, applies the trims, and writes | |
| 9 | +``data/processed/analysis.parquet``. | |
| 10 | + | |
| 11 | +Usage: python scripts/01_build_sample.py | |
| 12 | +""" | |
| 13 | +import sys | |
| 14 | +from pathlib import Path | |
| 15 | + | |
| 16 | +sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src")) | |
| 17 | + | |
| 18 | +from wp10 import sample # noqa: E402 | |
| 19 | + | |
| 20 | + | |
| 21 | +def main() -> None: | |
| 22 | + s = sample.build_and_save() | |
| 23 | + print(f"\nEstimation sample written: {len(s):,} sales") | |
| 24 | + print(f" municipalities : {s['muni'].nunique():,}") | |
| 25 | + print(f" cells : {s['cell'].nunique():,}") | |
| 26 | + print(f" classes : {s.groupby('prop_class').size().to_dict()}") | |
| 27 | + print(f" median ratio : {s['ratio'].median():.3f}") | |
| 28 | + | |
| 29 | + | |
| 30 | +if __name__ == "__main__": | |
| 31 | + main() | |
added
scripts/02_iaao_stats.py
+113 −0
@@ -0,0 +1,113 @@ | ||
| 1 | +#!/usr/bin/env python3 | |
| 2 | +# Author: Simon-Pierre Boucher — contact@spboucher.ai | |
| 3 | +"""Step 02 — Descriptive statistics and IAAO ratio-study diagnostics. | |
| 4 | + | |
| 5 | +Writes to ``results/reproduced/``: | |
| 6 | + summary_stats.csv sample descriptives used in Table 1 | |
| 7 | + iaao_overall.csv province-wide median ratio / COD / PRD / PRB with | |
| 8 | + bootstrap CIs, overall and by sale year | |
| 9 | + iaao_cities.csv the ten largest markets | |
| 10 | + iaao_muni.csv every municipality with ≥ 100 sales (maps + histograms) | |
| 11 | + | |
| 12 | +Usage: python scripts/02_iaao_stats.py | |
| 13 | +""" | |
| 14 | +import sys | |
| 15 | +from pathlib import Path | |
| 16 | + | |
| 17 | +import numpy as np | |
| 18 | +import pandas as pd | |
| 19 | + | |
| 20 | +sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src")) | |
| 21 | + | |
| 22 | +from wp10 import config, iaao, sample # noqa: E402 | |
| 23 | + | |
| 24 | + | |
| 25 | +def main() -> None: | |
| 26 | + config.ensure_dirs() | |
| 27 | + df = sample.load() | |
| 28 | + out = config.REPRODUCED | |
| 29 | + | |
| 30 | + # ------------------------------------------------------------ Table 1 | |
| 31 | + desc_vars = { | |
| 32 | + "amount": "Sale price ($)", | |
| 33 | + "role_valeur_immeuble": "Assessed value ($)", | |
| 34 | + "ratio": "Assessment ratio AV/SP", | |
| 35 | + "lag_months": "Roll lag (months)", | |
| 36 | + "land_share": "Assessed land share", | |
| 37 | + "age": "Building age (years)", | |
| 38 | + "role_superficie_terrain_m2": "Lot area (m2)", | |
| 39 | + "role_aire_etages_m2": "Floor area (m2)", | |
| 40 | + } | |
| 41 | + rows = [] | |
| 42 | + for var, label in desc_vars.items(): | |
| 43 | + s = df[var].dropna() | |
| 44 | + rows.append({"variable": label, "n": len(s), "mean": s.mean(), | |
| 45 | + "sd": s.std(), "p10": s.quantile(.10), "p50": s.median(), | |
| 46 | + "p90": s.quantile(.90)}) | |
| 47 | + pd.DataFrame(rows).to_csv(out / "summary_stats.csv", index=False) | |
| 48 | + | |
| 49 | + counts = {"n_sales": len(df), "n_munis": df["muni"].nunique(), | |
| 50 | + "n_cells": df["cell"].nunique()} | |
| 51 | + for k, v in df.groupby("prop_class").size().items(): | |
| 52 | + counts[f"n_{k}"] = int(v) | |
| 53 | + pd.Series(counts).to_csv(out / "sample_counts.csv") | |
| 54 | + | |
| 55 | + # ------------------------------------------------------------ overall + by year | |
| 56 | + blocks = [("All sales 2021–2026", df)] | |
| 57 | + blocks += [(str(y), g) for y, g in df.groupby("sale_year")] | |
| 58 | + rows = [] | |
| 59 | + for label, g in blocks: | |
| 60 | + av = g["role_valeur_immeuble"].to_numpy(float) | |
| 61 | + sp = g["amount"].to_numpy(float) | |
| 62 | + r = av / sp | |
| 63 | + b, se = iaao.prb(av, sp) | |
| 64 | + row = {"group": label, "n": len(g), | |
| 65 | + "median_ratio": float(np.median(r)), "cod": iaao.cod(r), | |
| 66 | + "prd": iaao.prd(av, sp), "prb": b, "prb_se": se} | |
| 67 | + cis = iaao.bootstrap_ci(av, sp, n_boot=200) | |
| 68 | + for stat, (lo, hi) in cis.items(): | |
| 69 | + row[f"{stat}_lo"], row[f"{stat}_hi"] = lo, hi | |
| 70 | + rows.append(row) | |
| 71 | + print(f" {label:<22} n={row['n']:>8,} med={row['median_ratio']:.3f} " | |
| 72 | + f"COD={row['cod']:.1f} PRD={row['prd']:.3f} PRB={row['prb']:+.4f}") | |
| 73 | + pd.DataFrame(rows).to_csv(out / "iaao_overall.csv", index=False) | |
| 74 | + | |
| 75 | + # --------------------------------------------------------------------- | |
| 76 | + # Municipality-level statistics. Within a municipality × sale-year block | |
| 77 | + # a single roll is in force, so the roll lag is (nearly) constant and the | |
| 78 | + # COD/PRB are not inflated by market-time drift. Annual blocks are then | |
| 79 | + # aggregated to one row per municipality (median across years, total n). | |
| 80 | + def annual_then_aggregate(data: pd.DataFrame, min_n: int) -> pd.DataFrame: | |
| 81 | + blocks = iaao.group_metrics(data, ["muni", "sale_year"], min_n=min_n) | |
| 82 | + blocks["muni"] = blocks["group"].str.rsplit("_", n=1).str[0] | |
| 83 | + agg = (blocks.groupby("muni") | |
| 84 | + .agg(n=("n", "sum"), n_years=("n", "size"), | |
| 85 | + median_ratio=("median_ratio", "median"), | |
| 86 | + cod=("cod", "median"), prd=("prd", "median"), | |
| 87 | + prb=("prb", "median")) | |
| 88 | + .reset_index()) | |
| 89 | + share_neg = (blocks.assign(neg=blocks["prb"] < 0) | |
| 90 | + .groupby("muni")["neg"].mean().rename("share_years_prb_neg")) | |
| 91 | + return agg.merge(share_neg, on="muni") | |
| 92 | + | |
| 93 | + # ------------------------------------------------------------ ten largest markets | |
| 94 | + big = df[df["role_municipalite"].isin(config.BIG_CITIES)].copy() | |
| 95 | + big["muni"] = big["role_municipalite"] # aggregate by display name | |
| 96 | + tab = annual_then_aggregate(big, min_n=200) | |
| 97 | + tab.to_csv(out / "iaao_cities.csv", index=False) | |
| 98 | + | |
| 99 | + # ------------------------------------------------------------ every muni ≥ 100 sales | |
| 100 | + tab = annual_then_aggregate(df, min_n=50) | |
| 101 | + tab = tab[tab["n"] >= config.MUNI_MIN_SALES] | |
| 102 | + coords = df.groupby("muni")[["lat", "lng"]].median() | |
| 103 | + names = df.groupby("muni")["role_municipalite"].first() | |
| 104 | + tab = tab.merge(coords, left_on="muni", right_index=True) | |
| 105 | + tab = tab.merge(names.rename("name"), left_on="muni", right_index=True) | |
| 106 | + tab.to_csv(out / "iaao_muni.csv", index=False) | |
| 107 | + print(f"\nMunicipality-level metrics: {len(tab)} municipalities " | |
| 108 | + f"(median within-year COD {tab['cod'].median():.1f}, " | |
| 109 | + f"share PRB<0: {(tab['prb'] < 0).mean():.1%})") | |
| 110 | + | |
| 111 | + | |
| 112 | +if __name__ == "__main__": | |
| 113 | + main() | |
added
scripts/03_estimate_regressions.py
+133 −0
@@ -0,0 +1,133 @@ | ||
| 1 | +#!/usr/bin/env python3 | |
| 2 | +# Author: Simon-Pierre Boucher — contact@spboucher.ai | |
| 3 | +"""Step 03 — Vertical-inequity regressions, heterogeneity, horizontal | |
| 4 | +inequity and the implied tax shift. | |
| 5 | + | |
| 6 | +Writes to ``results/reproduced/``: | |
| 7 | + vertical.csv Cheng OLS / Cheng FE / Clapp IV / Paglin–Fogarty | |
| 8 | + quantile.csv quantile-regression β(τ) on within-cell data | |
| 9 | + heterogeneity.csv Cheng-FE γ by property class, age, land share, year, | |
| 10 | + roll lag and municipality size | |
| 11 | + binscatter.csv within-cell mean ln ratio by price vigintile | |
| 12 | + horizontal.csv |deviation| regression (who gets noisy assessments) | |
| 13 | + taxshift.csv over/under-taxation by within-cell price decile | |
| 14 | + robustness.csv γ across sample and measurement variants | |
| 15 | + | |
| 16 | +Usage: python scripts/03_estimate_regressions.py | |
| 17 | +""" | |
| 18 | +import sys | |
| 19 | +from pathlib import Path | |
| 20 | + | |
| 21 | +import numpy as np | |
| 22 | +import pandas as pd | |
| 23 | + | |
| 24 | +sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src")) | |
| 25 | + | |
| 26 | +from wp10 import config, models, sample # noqa: E402 | |
| 27 | + | |
| 28 | + | |
| 29 | +def main() -> None: | |
| 30 | + config.ensure_dirs() | |
| 31 | + df = sample.load() | |
| 32 | + out = config.REPRODUCED | |
| 33 | + | |
| 34 | + # ------------------------------------------------------------ main table | |
| 35 | + print("Vertical-inequity estimators") | |
| 36 | + res = [models.cheng_pooled(df), models.cheng_fe(df), models.clapp_iv(df)] | |
| 37 | + for r in res: | |
| 38 | + print(f" {r['estimator']:<28} beta={r['beta']:.4f} ({r['se']:.4f}) " | |
| 39 | + f"gamma={r['gamma']:+.4f} n={r['n']:,}") | |
| 40 | + pf = models.paglin_fogarty(df) | |
| 41 | + print(f" {pf['estimator']:<28} a={pf['intercept']:,.0f} " | |
| 42 | + f"({pf['intercept_se']:,.0f}) b={pf['slope']:.4f}") | |
| 43 | + pd.DataFrame(res).to_csv(out / "vertical.csv", index=False) | |
| 44 | + pd.Series(pf).to_csv(out / "paglin_fogarty.csv") | |
| 45 | + | |
| 46 | + # ------------------------------------------------------------ quantiles | |
| 47 | + qt = models.quantile_betas(df) | |
| 48 | + qt.to_csv(out / "quantile.csv", index=False) | |
| 49 | + print("Quantile betas:", {f"{r.tau:.2f}": round(r.beta, 3) | |
| 50 | + for r in qt.itertuples()}) | |
| 51 | + | |
| 52 | + # ------------------------------------------------------------ binscatter | |
| 53 | + d = df.copy() | |
| 54 | + d["lnr_w"] = d["ln_ratio"] - d.groupby("cell")["ln_ratio"].transform("mean") | |
| 55 | + d["lnp_w"] = d["ln_price"] - d.groupby("cell")["ln_price"].transform("mean") | |
| 56 | + d["bin"] = pd.qcut(d["lnp_w"], 20, labels=False) | |
| 57 | + (d.groupby("bin") | |
| 58 | + .agg(x=("lnp_w", "mean"), y=("lnr_w", "mean"), | |
| 59 | + se=("lnr_w", lambda s: s.std() / np.sqrt(len(s))), n=("lnr_w", "size")) | |
| 60 | + .reset_index() | |
| 61 | + .to_csv(out / "binscatter.csv", index=False)) | |
| 62 | + | |
| 63 | + # ------------------------------------------------------------ heterogeneity | |
| 64 | + muni_sales = df.groupby("muni")["muni"].transform("size") | |
| 65 | + groups = { | |
| 66 | + "Single-family": df["prop_class"] == "single_family", | |
| 67 | + "Condominium": df["prop_class"] == "condo", | |
| 68 | + "Plex (2–5 units)": df["prop_class"] == "plex", | |
| 69 | + "Cottage": df["prop_class"] == "cottage", | |
| 70 | + "Age < 20 y": df["age"] < 20, | |
| 71 | + "Age 20–60 y": df["age"].between(20, 60), | |
| 72 | + "Age > 60 y": df["age"] > 60, | |
| 73 | + "Land share < 0.2": df["land_share"] < 0.2, | |
| 74 | + "Land share 0.2–0.4": df["land_share"].between(0.2, 0.4), | |
| 75 | + "Land share > 0.4": df["land_share"] > 0.4, | |
| 76 | + "Roll lag < 24 m": df["lag_months"] < 24, | |
| 77 | + "Roll lag 24–48 m": df["lag_months"].between(24, 48), | |
| 78 | + "Roll lag > 48 m": df["lag_months"] > 48, | |
| 79 | + "Muni < 1k sales": muni_sales < 1_000, | |
| 80 | + "Muni 1k–10k sales": muni_sales.between(1_000, 10_000), | |
| 81 | + "Muni > 10k sales": muni_sales > 10_000, | |
| 82 | + } | |
| 83 | + groups.update({f"Sales {y}": df["sale_year"] == y | |
| 84 | + for y in sorted(df["sale_year"].unique())}) | |
| 85 | + het = models.gamma_by_group(df, groups) | |
| 86 | + het.to_csv(out / "heterogeneity.csv", index=False) | |
| 87 | + print(f"Heterogeneity: {len(het)} subgroups estimated") | |
| 88 | + | |
| 89 | + # ------------------------------------------------------------ horizontal | |
| 90 | + tab, meta = models.horizontal_dispersion(df) | |
| 91 | + tab.to_csv(out / "horizontal.csv") | |
| 92 | + pd.Series(meta).to_csv(out / "horizontal_meta.csv") | |
| 93 | + print("Horizontal-dispersion regression:", meta) | |
| 94 | + | |
| 95 | + # ------------------------------------------------------------ tax shift | |
| 96 | + ts = models.tax_shift(df) | |
| 97 | + ts.to_csv(out / "taxshift.csv", index=False) | |
| 98 | + print("Tax shift by decile (mean %):", | |
| 99 | + {int(r.decile): f"{r.mean_rel:+.1%}" for r in ts.itertuples()}) | |
| 100 | + | |
| 101 | + # ------------------------------------------------------------ robustness | |
| 102 | + variants = { | |
| 103 | + "Baseline": df, | |
| 104 | + "Condominiums only": df[df["prop_class"] == "condo"], | |
| 105 | + "Excluding sales < $100k": df[df["amount"] >= 100_000], | |
| 106 | + "Match score = 220 (max)": df[df["match_score"] >= 219.9], | |
| 107 | + "Match distance <= 10 m": df[df["match_dist_m"] <= 10], | |
| 108 | + "Single-family only": df[df["prop_class"] == "single_family"], | |
| 109 | + "Ratio trim 5/95": df[df["ratio"].between( | |
| 110 | + df["ratio"].quantile(.05), df["ratio"].quantile(.95))], | |
| 111 | + "Sales 2021-2023": df[df["sale_year"] <= 2023], | |
| 112 | + "Sales 2024-2026": df[df["sale_year"] >= 2024], | |
| 113 | + "Munis >= 300 sales": df[df.groupby("muni")["muni"] | |
| 114 | + .transform("size") >= 300], | |
| 115 | + "Cells >= 50 sales": df[df.groupby("cell")["cell"] | |
| 116 | + .transform("size") >= 50], | |
| 117 | + } | |
| 118 | + rows = [] | |
| 119 | + for label, sub in variants.items(): | |
| 120 | + counts = sub.groupby("cell")["cell"].transform("size") | |
| 121 | + sub = sub[counts >= config.CELL_MIN_OBS] | |
| 122 | + est = models.cheng_fe(sub) | |
| 123 | + iv = models.clapp_iv(sub) | |
| 124 | + rows.append({"variant": label, "gamma_fe": est["gamma"], | |
| 125 | + "se_fe": est["se"], "gamma_iv": iv["gamma"], | |
| 126 | + "se_iv": iv["se"], "n": est["n"]}) | |
| 127 | + print(f" {label:<26} gamma_FE={est['gamma']:+.4f} " | |
| 128 | + f"gamma_IV={iv['gamma']:+.4f} n={est['n']:,}") | |
| 129 | + pd.DataFrame(rows).to_csv(out / "robustness.csv", index=False) | |
| 130 | + | |
| 131 | + | |
| 132 | +if __name__ == "__main__": | |
| 133 | + main() | |
added
scripts/04_make_figures.py
+346 −0
@@ -0,0 +1,346 @@ | ||
| 1 | +#!/usr/bin/env python3 | |
| 2 | +# Author: Simon-Pierre Boucher — contact@spboucher.ai | |
| 3 | +"""Step 04 — Publication figures (print-journal calibre). | |
| 4 | + | |
| 5 | +Reads the analysis sample and the step-02/03 outputs, writes ten PNG figures | |
| 6 | +to ``figures/``. No titles are drawn inside single-panel figures — captions | |
| 7 | +in the manuscript carry the message; multi-panel figures use bold | |
| 8 | +"Panel A/B" headers. One accent hue per figure, doubled by linestyle or | |
| 9 | +marker so nothing is encoded by colour alone. | |
| 10 | + | |
| 11 | +Usage: python scripts/04_make_figures.py | |
| 12 | +""" | |
| 13 | +import sys | |
| 14 | +from pathlib import Path | |
| 15 | + | |
| 16 | +import numpy as np | |
| 17 | +import pandas as pd | |
| 18 | + | |
| 19 | +sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src")) | |
| 20 | + | |
| 21 | +from wp10 import config, sample # noqa: E402 | |
| 22 | +from wp10.plotstyle import (BLUE, BLUES, GREY, INK, LIGHT, RED, TEXTWIDTH, # noqa: E402 | |
| 23 | + apply_style, panel_label, ygrid) | |
| 24 | + | |
| 25 | +import matplotlib.pyplot as plt # noqa: E402 | |
| 26 | +import matplotlib.dates as mdates # noqa: E402 | |
| 27 | + | |
| 28 | +OUT = config.FIGURES | |
| 29 | +RES = config.REPRODUCED | |
| 30 | + | |
| 31 | + | |
| 32 | +def _save(fig, name): | |
| 33 | + fig.savefig(OUT / name, bbox_inches="tight", pad_inches=0.02) | |
| 34 | + plt.close(fig) | |
| 35 | + | |
| 36 | + | |
| 37 | +# ---------------------------------------------------------------- fig 1 | |
| 38 | +def fig_ratio_dist(df): | |
| 39 | + """Panel A: ratio histogram. Panel B: densities by roll lag.""" | |
| 40 | + fig, axes = plt.subplots(1, 2, figsize=(TEXTWIDTH, 2.9)) | |
| 41 | + | |
| 42 | + ax = axes[0] | |
| 43 | + ax.hist(df["ratio"], bins=120, range=(0, 2.5), color=LIGHT, | |
| 44 | + edgecolor=INK, linewidth=0.25) | |
| 45 | + med = df["ratio"].median() | |
| 46 | + ax.axvline(med, color=INK, lw=0.9) | |
| 47 | + ax.axvline(1.0, color=GREY, lw=0.8, ls=(0, (4, 3))) | |
| 48 | + ax.text(med - 0.05, ax.get_ylim()[1] * 0.97, f"median = {med:.2f}", | |
| 49 | + ha="right", va="top", fontsize=8) | |
| 50 | + ax.text(1.04, ax.get_ylim()[1] * 0.75, "AV = SP", fontsize=8, color=GREY) | |
| 51 | + ax.set_xlabel("Assessment ratio $AV/SP$") | |
| 52 | + ax.set_ylabel("Sales") | |
| 53 | + ax.set_yticks([]) | |
| 54 | + ax.spines["left"].set_visible(False) | |
| 55 | + panel_label(ax, "Panel A. All sales") | |
| 56 | + | |
| 57 | + ax = axes[1] | |
| 58 | + specs = [("Roll lag $<$ 24 m", df["lag_months"] < 24, BLUES[2], "-"), | |
| 59 | + ("24–48 m", df["lag_months"].between(24, 48), BLUES[4], (0, (5, 2))), | |
| 60 | + ("$>$ 48 m", df["lag_months"] > 48, BLUES[5], (0, (1, 1.2)))] | |
| 61 | + for label, m, color, ls in specs: | |
| 62 | + ax.hist(df.loc[m, "ratio"], bins=120, range=(0, 2.5), density=True, | |
| 63 | + histtype="step", lw=1.2, color=color, ls=ls, label=label) | |
| 64 | + ax.set_xlabel("Assessment ratio $AV/SP$") | |
| 65 | + ax.set_ylabel("Density") | |
| 66 | + ax.legend(loc="upper right", handlelength=2.4) | |
| 67 | + panel_label(ax, "Panel B. By roll lag at sale") | |
| 68 | + fig.subplots_adjust(wspace=0.25) | |
| 69 | + _save(fig, "fig_ratio_dist.png") | |
| 70 | + | |
| 71 | + | |
| 72 | +# ---------------------------------------------------------------- fig 2 | |
| 73 | +def fig_binscatter(): | |
| 74 | + """Within-cell binned scatter of ln ratio on ln price — the core fact.""" | |
| 75 | + b = pd.read_csv(RES / "binscatter.csv") | |
| 76 | + fig, ax = plt.subplots(figsize=(0.72 * TEXTWIDTH, 3.3)) | |
| 77 | + ax.axhline(0, color=GREY, lw=0.7, ls=(0, (4, 3))) | |
| 78 | + slope = np.polyfit(b["x"], b["y"], 1, w=b["n"])[0] | |
| 79 | + icept = np.average(b["y"] - slope * b["x"], weights=b["n"]) | |
| 80 | + xs = np.linspace(b["x"].min(), b["x"].max(), 50) | |
| 81 | + ax.plot(xs, slope * xs + icept, color=BLUE, lw=1.2, zorder=2) | |
| 82 | + ax.errorbar(b["x"], b["y"], yerr=1.96 * b["se"], fmt="o", ms=4, | |
| 83 | + mfc=INK, mec=INK, ecolor=INK, elinewidth=0.6, capsize=0, | |
| 84 | + lw=0, zorder=3) | |
| 85 | + ax.annotate(f"slope = ${slope:.3f}$", xy=(0.55, slope * 0.55 + icept), | |
| 86 | + xytext=(0.32, 0.12), fontsize=8.5, color=BLUE, | |
| 87 | + arrowprops=dict(arrowstyle="-", color=BLUE, lw=0.6, | |
| 88 | + shrinkA=2, shrinkB=2)) | |
| 89 | + ax.set_xlabel("ln sale price (demeaned within municipality × roll × year)") | |
| 90 | + ax.set_ylabel("ln assessment ratio (demeaned)") | |
| 91 | + _save(fig, "fig_binscatter.png") | |
| 92 | + | |
| 93 | + | |
| 94 | +# ---------------------------------------------------------------- fig 3 | |
| 95 | +def fig_time(df): | |
| 96 | + """Median ratio by sale month; sequential blues by roll vintage, | |
| 97 | + each segment labelled directly (no legend).""" | |
| 98 | + d = df.copy() | |
| 99 | + d["month"] = d["sale_date"].dt.to_period("M").dt.to_timestamp() | |
| 100 | + fig, ax = plt.subplots(figsize=(TEXTWIDTH, 2.9)) | |
| 101 | + rolls = sorted(d["roll"].unique()) | |
| 102 | + for i, roll in enumerate(rolls): | |
| 103 | + gg = d[d["roll"] == roll].groupby("month")["ratio"] | |
| 104 | + g = gg.median()[gg.size() >= 100] # drop thin months (spurious spikes) | |
| 105 | + g = g[g.index.notna()].sort_index() | |
| 106 | + if len(g) < 3: | |
| 107 | + continue | |
| 108 | + color = BLUES[min(i, len(BLUES) - 1)] | |
| 109 | + ax.plot(g.index, g.values, lw=1.3, color=color) | |
| 110 | + ax.annotate(roll, xy=(g.index[-1], g.values[-1]), | |
| 111 | + xytext=(3, 0), textcoords="offset points", | |
| 112 | + fontsize=7.5, color=color, va="center") | |
| 113 | + ax.axhline(1.0, color=GREY, lw=0.7, ls=(0, (4, 3))) | |
| 114 | + ax.text(pd.Timestamp("2021-02-01"), 1.012, "parity", fontsize=7.5, | |
| 115 | + color=GREY) | |
| 116 | + ax.set_ylabel("Median assessment ratio") | |
| 117 | + ax.xaxis.set_major_formatter(mdates.DateFormatter("%Y")) | |
| 118 | + ygrid(ax) | |
| 119 | + _save(fig, "fig_time.png") | |
| 120 | + | |
| 121 | + | |
| 122 | +# ---------------------------------------------------------------- fig 4 | |
| 123 | +def fig_prb_muni(): | |
| 124 | + """Distribution of municipality-level PRB.""" | |
| 125 | + m = pd.read_csv(RES / "iaao_muni.csv") | |
| 126 | + fig, ax = plt.subplots(figsize=(0.72 * TEXTWIDTH, 3.1)) | |
| 127 | + ax.hist(m["prb"], bins=40, color=LIGHT, edgecolor=INK, linewidth=0.3) | |
| 128 | + lo, hi = config.IAAO_PRB_RANGE | |
| 129 | + ax.axvspan(lo, hi, color="0.92", zorder=0) | |
| 130 | + ax.axvline(0, color=GREY, lw=0.7, ls=(0, (4, 3))) | |
| 131 | + med = m["prb"].median() | |
| 132 | + ax.axvline(med, color=RED, lw=1.0) | |
| 133 | + ymax = ax.get_ylim()[1] | |
| 134 | + ax.text(med - 0.008, ymax * 0.97, f"median = {med:.2f}", ha="right", | |
| 135 | + va="top", fontsize=8, color=RED) | |
| 136 | + ax.text((lo + hi) / 2, ymax * 0.55, "IAAO\nband", ha="center", | |
| 137 | + fontsize=7.5, color="0.35") | |
| 138 | + share = (m["prb"] < 0).mean() | |
| 139 | + ax.text(0.02, 0.97, f"{share:.0%} of municipalities\nhave PRB $<$ 0", | |
| 140 | + transform=ax.transAxes, fontsize=8, va="top") | |
| 141 | + ax.set_xlabel("Municipality-level PRB (median across sale years)") | |
| 142 | + ax.set_ylabel("Municipalities") | |
| 143 | + ygrid(ax) | |
| 144 | + _save(fig, "fig_prb_muni.png") | |
| 145 | + | |
| 146 | + | |
| 147 | +# ---------------------------------------------------------------- fig 5 | |
| 148 | +def fig_map(): | |
| 149 | + """Municipal PRB across the province (diverging hue, neutral midpoint).""" | |
| 150 | + m = pd.read_csv(RES / "iaao_muni.csv") | |
| 151 | + fig, ax = plt.subplots(figsize=(TEXTWIDTH, 4.4)) | |
| 152 | + v = m["prb"].clip(-0.30, 0.10) | |
| 153 | + sc = ax.scatter(m["lng"], m["lat"], c=v, s=np.sqrt(m["n"]) * 0.9, | |
| 154 | + cmap="RdBu", vmin=-0.30, vmax=0.30, | |
| 155 | + edgecolor=INK, linewidth=0.25, alpha=0.9) | |
| 156 | + cb = fig.colorbar(sc, ax=ax, shrink=0.75, pad=0.02) | |
| 157 | + cb.set_label("PRB (negative = regressive)", fontsize=8) | |
| 158 | + cb.ax.tick_params(labelsize=7.5) | |
| 159 | + cb.outline.set_linewidth(0.5) | |
| 160 | + offsets = {"Montréal": (6, -14), "Québec": (8, 4), "Gatineau": (-8, -14), | |
| 161 | + "Sherbrooke": (8, -10), "Saguenay": (8, 4)} | |
| 162 | + for _, r in m.nlargest(12, "n").iterrows(): | |
| 163 | + if r["name"] in offsets: | |
| 164 | + ax.annotate(r["name"], (r["lng"], r["lat"]), | |
| 165 | + xytext=offsets[r["name"]], textcoords="offset points", | |
| 166 | + fontsize=7.5) | |
| 167 | + ax.set_xlabel("Longitude") | |
| 168 | + ax.set_ylabel("Latitude") | |
| 169 | + ax.set_xlim(-80, -63) | |
| 170 | + ax.set_ylim(44.9, 49.6) | |
| 171 | + _save(fig, "fig_map.png") | |
| 172 | + | |
| 173 | + | |
| 174 | +# ---------------------------------------------------------------- fig 6 | |
| 175 | +def fig_quantile(): | |
| 176 | + """β(τ) with a shaded 95% band; FE/IV benchmarks labelled directly.""" | |
| 177 | + q = pd.read_csv(RES / "quantile.csv") | |
| 178 | + v = pd.read_csv(RES / "vertical.csv") | |
| 179 | + fe = v.loc[v["estimator"].str.startswith("Cheng FE"), "beta"].iloc[0] | |
| 180 | + iv = v.loc[v["estimator"].str.startswith("Clapp"), "beta"].iloc[0] | |
| 181 | + fig, ax = plt.subplots(figsize=(0.72 * TEXTWIDTH, 3.3)) | |
| 182 | + ax.fill_between(q["tau"], q["beta"] - 1.96 * q["se"], | |
| 183 | + q["beta"] + 1.96 * q["se"], color=BLUE, alpha=0.18, lw=0) | |
| 184 | + ax.plot(q["tau"], q["beta"], "o-", color=BLUE, ms=4, lw=1.3) | |
| 185 | + x1, x0 = q["tau"].max(), q["tau"].min() | |
| 186 | + for yv, lab, ls, xa, ha in [ | |
| 187 | + (1.0, r"$\beta = 1$ (proportional)", (0, (4, 3)), x1, "right"), | |
| 188 | + (iv, f"Clapp IV ({iv:.2f})", (0, (1, 1.2)), x1, "right"), | |
| 189 | + (fe, f"Cheng FE ({fe:.2f})", (0, (6, 2)), x0, "left")]: | |
| 190 | + ax.axhline(yv, color=GREY, lw=0.8, ls=ls) | |
| 191 | + ax.annotate(lab, xy=(xa, yv), xytext=(0, 3), textcoords="offset points", | |
| 192 | + ha=ha, fontsize=7.5, color="0.35") | |
| 193 | + ax.set_xlabel(r"Quantile $\tau$ of the conditional $\ln AV$ distribution") | |
| 194 | + ax.set_ylabel(r"$\beta(\tau)$") | |
| 195 | + ax.set_xticks(q["tau"]) | |
| 196 | + _save(fig, "fig_quantile.png") | |
| 197 | + | |
| 198 | + | |
| 199 | +# ---------------------------------------------------------------- fig 7 | |
| 200 | +def fig_heterogeneity(): | |
| 201 | + """Forest plot of Cheng-FE γ by subgroup, with panel groupings.""" | |
| 202 | + h = pd.read_csv(RES / "heterogeneity.csv").set_index("group") | |
| 203 | + panels = [ | |
| 204 | + ("Property class", ["Single-family", "Condominium", "Plex (2–5 units)", | |
| 205 | + "Cottage"]), | |
| 206 | + ("Building age", ["Age < 20 y", "Age 20–60 y", "Age > 60 y"]), | |
| 207 | + ("Assessed land share", ["Land share < 0.2", "Land share 0.2–0.4", | |
| 208 | + "Land share > 0.4"]), | |
| 209 | + ("Roll lag at sale", ["Roll lag < 24 m", "Roll lag 24–48 m", | |
| 210 | + "Roll lag > 48 m"]), | |
| 211 | + ("Municipality size", ["Muni < 1k sales", "Muni 1k–10k sales", | |
| 212 | + "Muni > 10k sales"]), | |
| 213 | + ("Sale year", [f"Sales {y}" for y in range(2021, 2027)]), | |
| 214 | + ] | |
| 215 | + rows, ypos, headers, seps = [], [], [], [] | |
| 216 | + y = 0 | |
| 217 | + for name, keys in panels: | |
| 218 | + headers.append((y, name)) | |
| 219 | + y -= 1 | |
| 220 | + for k in keys: | |
| 221 | + if k in h.index: | |
| 222 | + rows.append((y, k, h.loc[k])) | |
| 223 | + ypos.append(y) | |
| 224 | + y -= 1 | |
| 225 | + seps.append(y + 0.5) | |
| 226 | + y -= 0.6 | |
| 227 | + fig, ax = plt.subplots(figsize=(0.85 * TEXTWIDTH, 5.6)) | |
| 228 | + for yy, k, r in rows: | |
| 229 | + ax.errorbar(r["gamma"], yy, xerr=1.96 * r["se"], fmt="o", ms=3.8, | |
| 230 | + mfc=INK, mec=INK, ecolor=INK, elinewidth=0.8, capsize=1.5) | |
| 231 | + ax.axvline(0, color=GREY, lw=0.7, ls=(0, (4, 3))) | |
| 232 | + for yy, name in headers: | |
| 233 | + ax.text(-0.72, yy, name, fontsize=8.5, fontweight="bold", va="center") | |
| 234 | + labels = {yy: k.replace("<", "$<$").replace(">", "$>$") | |
| 235 | + for yy, k, _ in rows} | |
| 236 | + ax.set_yticks(list(labels.keys())) | |
| 237 | + ax.set_yticklabels(labels.values(), fontsize=8) | |
| 238 | + ax.set_ylim(y + 0.4, 1.0) | |
| 239 | + ax.set_xlim(-0.72, 0.12) | |
| 240 | + ax.set_xlabel(r"$\gamma$ = elasticity of the assessment ratio with respect" | |
| 241 | + " to price (negative = regressive)") | |
| 242 | + ax.spines["left"].set_visible(False) | |
| 243 | + ax.tick_params(axis="y", length=0) | |
| 244 | + _save(fig, "fig_heterogeneity.png") | |
| 245 | + | |
| 246 | + | |
| 247 | +# ---------------------------------------------------------------- fig 8 | |
| 248 | +def fig_taxshift(): | |
| 249 | + """Median excess tax burden by within-market price decile (polarity).""" | |
| 250 | + t = pd.read_csv(RES / "taxshift.csv") | |
| 251 | + fig, ax = plt.subplots(figsize=(0.85 * TEXTWIDTH, 3.1)) | |
| 252 | + vals = 100 * t["median_rel"] | |
| 253 | + colors = [RED if v > 0 else BLUE for v in vals] | |
| 254 | + ax.bar(t["decile"], vals, width=0.72, color=colors, edgecolor=INK, | |
| 255 | + linewidth=0.4) | |
| 256 | + ax.axhline(0, color=INK, lw=0.7) | |
| 257 | + for d, v in zip(t["decile"], vals): | |
| 258 | + va = "bottom" if v > 0 else "top" | |
| 259 | + off = 1.2 if v > 0 else -1.2 | |
| 260 | + ax.text(d, v + off, f"{v:+.1f}", ha="center", va=va, fontsize=7.5) | |
| 261 | + ax.text(2.5, 45, "over-taxed", fontsize=8, color=RED, ha="center") | |
| 262 | + ax.text(8.5, 14, "under-taxed", fontsize=8, color=BLUE, ha="center") | |
| 263 | + ax.set_xticks(t["decile"]) | |
| 264 | + ax.set_xlabel("Within-market sale-price decile") | |
| 265 | + ax.set_ylabel("Median excess tax burden (%)") | |
| 266 | + ax.set_ylim(min(vals) - 8, max(vals) + 9) | |
| 267 | + _save(fig, "fig_taxshift.png") | |
| 268 | + | |
| 269 | + | |
| 270 | +# ---------------------------------------------------------------- fig 9 | |
| 271 | +def fig_cod(): | |
| 272 | + """Panel A: COD distribution. Panel B: COD vs market size.""" | |
| 273 | + m = pd.read_csv(RES / "iaao_muni.csv") | |
| 274 | + fig, axes = plt.subplots(1, 2, figsize=(TEXTWIDTH, 2.9)) | |
| 275 | + | |
| 276 | + ax = axes[0] | |
| 277 | + ax.hist(m["cod"], bins=40, color=LIGHT, edgecolor=INK, linewidth=0.3) | |
| 278 | + ax.axvline(config.IAAO_COD_MAX_SF, color=RED, lw=1.0) | |
| 279 | + ymax = ax.get_ylim()[1] | |
| 280 | + ax.text(config.IAAO_COD_MAX_SF + 0.6, ymax * 0.95, | |
| 281 | + f"IAAO ceiling ({config.IAAO_COD_MAX_SF:.0f})", fontsize=7.5, | |
| 282 | + color=RED, va="top") | |
| 283 | + med = m["cod"].median() | |
| 284 | + ax.axvline(med, color=INK, lw=0.9, ls=(0, (5, 2))) | |
| 285 | + ax.text(med + 0.6, ymax * 0.72, f"median = {med:.1f}", fontsize=7.5) | |
| 286 | + ax.set_xlabel("Municipality COD (median across sale years)") | |
| 287 | + ax.set_ylabel("Municipalities") | |
| 288 | + panel_label(ax, "Panel A. Distribution") | |
| 289 | + | |
| 290 | + ax = axes[1] | |
| 291 | + ax.scatter(m["n"], m["cod"], s=7, facecolor="none", edgecolor=INK, | |
| 292 | + linewidth=0.5, alpha=0.65) | |
| 293 | + ax.axhline(config.IAAO_COD_MAX_SF, color=RED, lw=0.9) | |
| 294 | + ax.set_xscale("log") | |
| 295 | + ax.set_xlabel("Sales in municipality, 2021–2026 (log scale)") | |
| 296 | + ax.set_ylabel("COD") | |
| 297 | + panel_label(ax, "Panel B. COD and market size") | |
| 298 | + fig.subplots_adjust(wspace=0.25) | |
| 299 | + _save(fig, "fig_cod.png") | |
| 300 | + | |
| 301 | + | |
| 302 | +# ---------------------------------------------------------------- fig 10 | |
| 303 | +def fig_robustness(): | |
| 304 | + """γ across sample variants — FE (filled circles) vs IV (open squares).""" | |
| 305 | + r = pd.read_csv(RES / "robustness.csv") | |
| 306 | + fig, ax = plt.subplots(figsize=(0.85 * TEXTWIDTH, 3.6)) | |
| 307 | + y = np.arange(len(r))[::-1].astype(float) | |
| 308 | + ax.errorbar(r["gamma_fe"], y + 0.16, xerr=1.96 * r["se_fe"], fmt="o", | |
| 309 | + ms=4, mfc=BLUE, mec=BLUE, ecolor=BLUE, elinewidth=0.8, | |
| 310 | + capsize=1.5, lw=0, label="Cheng FE") | |
| 311 | + ax.errorbar(r["gamma_iv"], y - 0.16, xerr=1.96 * r["se_iv"], fmt="s", | |
| 312 | + ms=4, mfc="white", mec=RED, ecolor=RED, elinewidth=0.8, | |
| 313 | + capsize=1.5, lw=0, label="Clapp IV") | |
| 314 | + ax.axvline(0, color=GREY, lw=0.7, ls=(0, (4, 3))) | |
| 315 | + ax.set_yticks(y) | |
| 316 | + labels = [str(v).replace("<=", "$\\leq$").replace(">=", "$\\geq$") | |
| 317 | + .replace("<", "$<$").replace("$100k", "\\$100k") | |
| 318 | + for v in r["variant"]] | |
| 319 | + ax.set_yticklabels(labels, fontsize=8) | |
| 320 | + ax.set_xlabel(r"$\gamma$ (negative = regressive)") | |
| 321 | + ax.legend(loc="lower left", markerscale=1.1) | |
| 322 | + ax.spines["left"].set_visible(False) | |
| 323 | + ax.tick_params(axis="y", length=0) | |
| 324 | + _save(fig, "fig_robustness.png") | |
| 325 | + | |
| 326 | + | |
| 327 | +def main() -> None: | |
| 328 | + config.ensure_dirs() | |
| 329 | + apply_style() | |
| 330 | + df = sample.load() | |
| 331 | + fig_ratio_dist(df) | |
| 332 | + fig_binscatter() | |
| 333 | + fig_time(df) | |
| 334 | + fig_prb_muni() | |
| 335 | + fig_map() | |
| 336 | + fig_quantile() | |
| 337 | + fig_heterogeneity() | |
| 338 | + fig_taxshift() | |
| 339 | + fig_cod() | |
| 340 | + fig_robustness() | |
| 341 | + made = sorted(p.name for p in OUT.glob("fig_*.png")) | |
| 342 | + print(f"{len(made)} figures written:", ", ".join(made)) | |
| 343 | + | |
| 344 | + | |
| 345 | +if __name__ == "__main__": | |
| 346 | + main() | |
added
scripts/05_make_tables.py
+241 −0
@@ -0,0 +1,241 @@ | ||
| 1 | +#!/usr/bin/env python3 | |
| 2 | +# Author: Simon-Pierre Boucher — contact@spboucher.ai | |
| 3 | +"""Step 05 — LaTeX tables. | |
| 4 | + | |
| 5 | +Converts the step-02/03 CSV outputs into booktabs tables under | |
| 6 | +``results/tables/``. The paper inputs these files directly, so every number | |
| 7 | +in the manuscript is machine-generated. | |
| 8 | + | |
| 9 | +Usage: python scripts/05_make_tables.py | |
| 10 | +""" | |
| 11 | +import sys | |
| 12 | +from pathlib import Path | |
| 13 | + | |
| 14 | +import pandas as pd | |
| 15 | + | |
| 16 | +sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src")) | |
| 17 | + | |
| 18 | +from wp10 import config # noqa: E402 | |
| 19 | + | |
| 20 | +RES = config.REPRODUCED | |
| 21 | +TAB = config.TABLES | |
| 22 | + | |
| 23 | + | |
| 24 | +def _stars(coef: float, se: float) -> str: | |
| 25 | + t = abs(coef / se) if se > 0 else 0 | |
| 26 | + return "***" if t > 2.576 else "**" if t > 1.96 else "*" if t > 1.645 else "" | |
| 27 | + | |
| 28 | + | |
| 29 | +def _f(x, nd=3): | |
| 30 | + return f"{x:,.{nd}f}" | |
| 31 | + | |
| 32 | + | |
| 33 | +def t_summary(): | |
| 34 | + s = pd.read_csv(RES / "summary_stats.csv") | |
| 35 | + lines = [r"\begin{tabular}{lrrrrrr}", r"\toprule", | |
| 36 | + r"Variable & $n$ & Mean & SD & P10 & Median & P90 \\", | |
| 37 | + r"\midrule"] | |
| 38 | + for _, r in s.iterrows(): | |
| 39 | + big = r["mean"] > 1000 | |
| 40 | + nd = 0 if big else 3 if r["mean"] < 10 else 1 | |
| 41 | + label = (str(r["variable"]).replace("($)", "(\\$)") | |
| 42 | + .replace("(m2)", "(m$^2$)")) | |
| 43 | + lines.append( | |
| 44 | + f"{label} & {r['n']:,.0f} & {_f(r['mean'], nd)} & " | |
| 45 | + f"{_f(r['sd'], nd)} & {_f(r['p10'], nd)} & {_f(r['p50'], nd)} & " | |
| 46 | + f"{_f(r['p90'], nd)} \\\\") | |
| 47 | + lines += [r"\bottomrule", r"\end{tabular}"] | |
| 48 | + (TAB / "summary_stats.tex").write_text("\n".join(lines)) | |
| 49 | + | |
| 50 | + | |
| 51 | +def t_iaao(): | |
| 52 | + d = pd.read_csv(RES / "iaao_overall.csv") | |
| 53 | + lines = [r"\begin{tabular}{lrcccc}", r"\toprule", | |
| 54 | + r"Sample & $n$ & Median ratio & COD & PRD & PRB \\", | |
| 55 | + r"\midrule"] | |
| 56 | + for _, r in d.iterrows(): | |
| 57 | + lines.append( | |
| 58 | + f"{r['group']} & {r['n']:,.0f} & {_f(r['median_ratio'])} & " | |
| 59 | + f"{_f(r['cod'], 1)} & {_f(r['prd'])} & {_f(r['prb'], 4)} \\\\") | |
| 60 | + lines.append( | |
| 61 | + f" & & \\footnotesize[{_f(r['median_lo'])}, {_f(r['median_hi'])}]" | |
| 62 | + f" & \\footnotesize[{_f(r['cod_lo'], 1)}, {_f(r['cod_hi'], 1)}]" | |
| 63 | + f" & \\footnotesize[{_f(r['prd_lo'])}, {_f(r['prd_hi'])}]" | |
| 64 | + f" & \\footnotesize[{_f(r['prb_lo'], 4)}, {_f(r['prb_hi'], 4)}] \\\\") | |
| 65 | + lines += [r"\bottomrule", r"\end{tabular}"] | |
| 66 | + (TAB / "iaao.tex").write_text("\n".join(lines)) | |
| 67 | + | |
| 68 | + | |
| 69 | +def t_cities(): | |
| 70 | + d = pd.read_csv(RES / "iaao_cities.csv").sort_values("n", ascending=False) | |
| 71 | + lines = [r"\begin{tabular}{lrccccc}", r"\toprule", | |
| 72 | + r"Municipality & $n$ & Median ratio & COD & PRD & PRB & " | |
| 73 | + r"\makecell{Share of years\\PRB $<$ 0} \\", | |
| 74 | + r"\midrule"] | |
| 75 | + for _, r in d.iterrows(): | |
| 76 | + lines.append( | |
| 77 | + f"{r['muni']} & {r['n']:,.0f} & {_f(r['median_ratio'])} & " | |
| 78 | + f"{_f(r['cod'], 1)} & {_f(r['prd'])} & {_f(r['prb'], 4)} & " | |
| 79 | + f"{_f(r['share_years_prb_neg'], 2)} \\\\") | |
| 80 | + lines += [r"\bottomrule", r"\end{tabular}"] | |
| 81 | + (TAB / "iaao_cities.tex").write_text("\n".join(lines)) | |
| 82 | + | |
| 83 | + | |
| 84 | +def t_vertical(): | |
| 85 | + v = pd.read_csv(RES / "vertical.csv") | |
| 86 | + pf = pd.read_csv(RES / "paglin_fogarty.csv", index_col=0).squeeze() | |
| 87 | + pf = {k: (v if k == "estimator" else float(v)) for k, v in pf.items()} | |
| 88 | + q = pd.read_csv(RES / "quantile.csv") | |
| 89 | + | |
| 90 | + def row(label, vals): | |
| 91 | + return label + " & " + " & ".join(vals) + r" \\" | |
| 92 | + | |
| 93 | + cols = list(v["estimator"]) | |
| 94 | + lines = [r"\begin{tabular}{l" + "c" * len(cols) + "}", r"\toprule", | |
| 95 | + row("", [f"({i+1})" for i in range(len(cols))]), | |
| 96 | + row("", [c.replace(" (pooled)", "").replace(" (cell)", "") | |
| 97 | + .replace(" (rank instrument)", "") for c in cols]), | |
| 98 | + r"\midrule", | |
| 99 | + row(r"$\beta$ (ln sale price)", | |
| 100 | + [f"{_f(r['beta'])}{_stars(r['beta'] - 1, r['se'])}" | |
| 101 | + for _, r in v.iterrows()]), | |
| 102 | + row("", [f"({_f(r['se'])})" for _, r in v.iterrows()]), | |
| 103 | + row(r"$\gamma = \beta - 1$", | |
| 104 | + [f"{r['gamma']:+.3f}" for _, r in v.iterrows()]), | |
| 105 | + r"\midrule", | |
| 106 | + row("Cell fixed effects", | |
| 107 | + ["No", "Yes", "Yes"]), | |
| 108 | + row("Error-in-price robust", ["No", "No", "Yes"]), | |
| 109 | + row("Observations", [f"{r['n']:,.0f}" for _, r in v.iterrows()]), | |
| 110 | + row("$R^2$", [f"{_f(r['r2'])}" for _, r in v.iterrows()]), | |
| 111 | + r"\bottomrule", r"\end{tabular}"] | |
| 112 | + (TAB / "vertical.tex").write_text("\n".join(lines)) | |
| 113 | + # stars in this table test H0: beta = 1 (proportionality), noted in caption | |
| 114 | + | |
| 115 | + lines = [r"\begin{tabular}{lcc}", r"\toprule", | |
| 116 | + r" & Coefficient & Std.\ error \\", r"\midrule", | |
| 117 | + f"Intercept ($\\$$) & {pf['intercept']:,.0f} & " | |
| 118 | + f"{pf['intercept_se']:,.0f} \\\\", | |
| 119 | + f"Slope on sale price & {_f(pf['slope'])} & " | |
| 120 | + f"{_f(pf['slope_se'])} \\\\", | |
| 121 | + f"Observations & \\multicolumn{{2}}{{c}}{{{pf['n']:,.0f}}} \\\\", | |
| 122 | + r"\bottomrule", r"\end{tabular}"] | |
| 123 | + (TAB / "paglin_fogarty.tex").write_text("\n".join(lines)) | |
| 124 | + | |
| 125 | + lines = [r"\begin{tabular}{lccccc}", r"\toprule", | |
| 126 | + "Quantile $\\tau$ & " + " & ".join(f"{t:.2f}" for t in q["tau"]) | |
| 127 | + + r" \\", r"\midrule", | |
| 128 | + r"$\beta(\tau)$ & " + " & ".join( | |
| 129 | + f"{_f(r['beta'])}{_stars(r['beta'] - 1, r['se'])}" | |
| 130 | + for _, r in q.iterrows()) + r" \\", | |
| 131 | + " & " + " & ".join(f"({_f(r['se'], 4)})" for _, r in q.iterrows()) | |
| 132 | + + r" \\", | |
| 133 | + r"\bottomrule", r"\end{tabular}"] | |
| 134 | + (TAB / "quantile.tex").write_text("\n".join(lines)) | |
| 135 | + | |
| 136 | + | |
| 137 | +def t_heterogeneity(): | |
| 138 | + h = pd.read_csv(RES / "heterogeneity.csv") | |
| 139 | + panels = { | |
| 140 | + "Property class": ["Single-family", "Condominium", "Plex (2–5 units)", | |
| 141 | + "Cottage"], | |
| 142 | + "Building age": ["Age < 20 y", "Age 20–60 y", "Age > 60 y"], | |
| 143 | + "Assessed land share": ["Land share < 0.2", "Land share 0.2–0.4", | |
| 144 | + "Land share > 0.4"], | |
| 145 | + "Roll lag at sale": ["Roll lag < 24 m", "Roll lag 24–48 m", | |
| 146 | + "Roll lag > 48 m"], | |
| 147 | + "Municipality size": ["Muni < 1k sales", "Muni 1k–10k sales", | |
| 148 | + "Muni > 10k sales"], | |
| 149 | + "Sale year": [f"Sales {y}" for y in range(2021, 2027)], | |
| 150 | + } | |
| 151 | + lines = [r"\begin{tabular}{lccr}", r"\toprule", | |
| 152 | + r"Subsample & $\gamma$ & Std.\ error & $n$ \\"] | |
| 153 | + for panel, keys in panels.items(): | |
| 154 | + lines.append(r"\midrule") | |
| 155 | + lines.append(r"\multicolumn{4}{l}{\itshape " + panel + r"}\\") | |
| 156 | + for k in keys: | |
| 157 | + r = h[h["group"] == k] | |
| 158 | + if r.empty: | |
| 159 | + continue | |
| 160 | + r = r.iloc[0] | |
| 161 | + label = (k.replace("<", "$<$").replace(">", "$>$") | |
| 162 | + .replace("–", "--")) | |
| 163 | + lines.append( | |
| 164 | + f"\\quad {label} & {r['gamma']:+.3f}" | |
| 165 | + f"{_stars(r['gamma'], r['se'])} & ({_f(r['se'])}) & " | |
| 166 | + f"{r['n']:,.0f} \\\\") | |
| 167 | + lines += [r"\bottomrule", r"\end{tabular}"] | |
| 168 | + (TAB / "heterogeneity.tex").write_text("\n".join(lines)) | |
| 169 | + | |
| 170 | + | |
| 171 | +def t_taxshift(): | |
| 172 | + t = pd.read_csv(RES / "taxshift.csv") | |
| 173 | + lines = [r"\begin{tabular}{lcccc}", r"\toprule", | |
| 174 | + r"Price decile & $n$ & \makecell{Mean excess\\burden (\%)} & " | |
| 175 | + r"\makecell{Median excess\\burden (\%)} & Std.\ error \\", | |
| 176 | + r"\midrule"] | |
| 177 | + for _, r in t.iterrows(): | |
| 178 | + lines.append( | |
| 179 | + f"{int(r['decile'])} & {r['n']:,.0f} & " | |
| 180 | + f"{100 * r['mean_rel']:+.1f} & {100 * r['median_rel']:+.1f} & " | |
| 181 | + f"({100 * r['se']:.2f}) \\\\") | |
| 182 | + lines += [r"\bottomrule", r"\end{tabular}"] | |
| 183 | + (TAB / "taxshift.tex").write_text("\n".join(lines)) | |
| 184 | + | |
| 185 | + | |
| 186 | +def t_horizontal(): | |
| 187 | + h = pd.read_csv(RES / "horizontal.csv", index_col=0) | |
| 188 | + meta = pd.read_csv(RES / "horizontal_meta.csv", index_col=0).squeeze() | |
| 189 | + labels = {"age_dec": "Building age (decades)", | |
| 190 | + "land_share": "Assessed land share", | |
| 191 | + "is_condo": "Condominium", | |
| 192 | + "is_plex": "Plex (2--5 units)", | |
| 193 | + "is_cottage": "Cottage"} | |
| 194 | + lines = [r"\begin{tabular}{lcc}", r"\toprule", | |
| 195 | + r" & Coefficient & Std.\ error \\", r"\midrule"] | |
| 196 | + for k, lab in labels.items(): | |
| 197 | + r = h.loc[k] | |
| 198 | + lines.append(f"{lab} & {r['coef']:+.4f}{_stars(r['coef'], r['se'])} & " | |
| 199 | + f"({_f(r['se'], 4)}) \\\\") | |
| 200 | + lines += [r"\midrule", | |
| 201 | + f"Mean of dependent variable & \\multicolumn{{2}}{{c}}" | |
| 202 | + f"{{{float(meta['mean_dep']):.3f}}} \\\\", | |
| 203 | + f"Observations & \\multicolumn{{2}}{{c}}{{{float(meta['n']):,.0f}}} \\\\", | |
| 204 | + r"\bottomrule", r"\end{tabular}"] | |
| 205 | + (TAB / "horizontal.tex").write_text("\n".join(lines)) | |
| 206 | + | |
| 207 | + | |
| 208 | +def t_robustness(): | |
| 209 | + r = pd.read_csv(RES / "robustness.csv") | |
| 210 | + lines = [r"\begin{tabular}{lccccr}", r"\toprule", | |
| 211 | + r"Sample variant & $\gamma_{\text{FE}}$ & Std.\ err. & " | |
| 212 | + r"$\gamma_{\text{IV}}$ & Std.\ err. & $n$ \\", | |
| 213 | + r"\midrule"] | |
| 214 | + for _, x in r.iterrows(): | |
| 215 | + label = (str(x["variant"]).replace("<=", "$\\le$") | |
| 216 | + .replace(">=", "$\\ge$").replace("$100k", "\\$100k")) | |
| 217 | + lines.append( | |
| 218 | + f"{label} & {x['gamma_fe']:+.3f}{_stars(x['gamma_fe'], x['se_fe'])}" | |
| 219 | + f" & ({_f(x['se_fe'])}) & " | |
| 220 | + f"{x['gamma_iv']:+.3f}{_stars(x['gamma_iv'], x['se_iv'])} & " | |
| 221 | + f"({_f(x['se_iv'])}) & {x['n']:,.0f} \\\\") | |
| 222 | + lines += [r"\bottomrule", r"\end{tabular}"] | |
| 223 | + (TAB / "robustness.tex").write_text("\n".join(lines)) | |
| 224 | + | |
| 225 | + | |
| 226 | +def main() -> None: | |
| 227 | + config.ensure_dirs() | |
| 228 | + t_summary() | |
| 229 | + t_iaao() | |
| 230 | + t_cities() | |
| 231 | + t_vertical() | |
| 232 | + t_heterogeneity() | |
| 233 | + t_taxshift() | |
| 234 | + t_horizontal() | |
| 235 | + t_robustness() | |
| 236 | + made = sorted(p.name for p in TAB.glob("*.tex")) | |
| 237 | + print(f"{len(made)} tables written:", ", ".join(made)) | |
| 238 | + | |
| 239 | + | |
| 240 | +if __name__ == "__main__": | |
| 241 | + main() | |
added
src/wp10/__init__.py
+11 −0
@@ -0,0 +1,11 @@ | ||
| 1 | +# Author: Simon-Pierre Boucher — contact@spboucher.ai | |
| 2 | +"""WP10 — The Assessment Gap in Quebec. | |
| 3 | + | |
| 4 | +Vertical and horizontal inequity in municipal property valuation, estimated | |
| 5 | +from 600k+ residential transactions matched to the provincial assessment roll | |
| 6 | +(rôle d'évaluation foncière), 2021–2026. | |
| 7 | +""" | |
| 8 | + | |
| 9 | +__version__ = "1.0" | |
| 10 | +__author__ = "Simon-Pierre Boucher" | |
| 11 | +__email__ = "contact@spboucher.ai" | |
added
src/wp10/config.py
+54 −0
@@ -0,0 +1,54 @@ | ||
| 1 | +# Author: Simon-Pierre Boucher — contact@spboucher.ai | |
| 2 | +"""Paths and global constants for the WP10 pipeline. | |
| 3 | + | |
| 4 | +All paths are relative to the repository root, so the pipeline runs on any | |
| 5 | +machine after cloning. Override the root with the ``WP10_ROOT`` environment | |
| 6 | +variable if the scripts are launched from elsewhere. | |
| 7 | +""" | |
| 8 | +import os | |
| 9 | +from pathlib import Path | |
| 10 | + | |
| 11 | +ROOT = Path(os.environ.get("WP10_ROOT", Path(__file__).resolve().parents[2])) | |
| 12 | + | |
| 13 | +RAW_PARQUET = ROOT / "data" / "raw" / "transactions_700k_avec_registre_foncier.parquet" | |
| 14 | +PROCESSED = ROOT / "data" / "processed" | |
| 15 | +ANALYSIS_PARQUET = PROCESSED / "analysis.parquet" | |
| 16 | +FIGURES = ROOT / "figures" | |
| 17 | +RESULTS = ROOT / "results" | |
| 18 | +REPRODUCED = RESULTS / "reproduced" | |
| 19 | +TABLES = RESULTS / "tables" | |
| 20 | + | |
| 21 | +# ---------------------------------------------------------------- sample | |
| 22 | +# Residential CUBF use codes kept in the estimation sample | |
| 23 | +# 1000 Logement | 1100 Chalet ou maison de villégiature | 1211 Maison mobile | |
| 24 | +# 1990 Autres immeubles résidentiels | |
| 25 | +RESIDENTIAL_CUBF = {"1000", "1100", "1211", "1990"} | |
| 26 | + | |
| 27 | +MATCH_MAX_DIST_M = 50.0 # roll–transaction geocoding distance ceiling | |
| 28 | +MATCH_MIN_SCORE = 150.0 # matcher confidence floor (max attainable: 220) | |
| 29 | + | |
| 30 | +PRICE_MIN = 50_000 # source feed already truncates below 50k | |
| 31 | +RATIO_TRIM = (0.01, 0.99) # symmetric trim on the assessment ratio, by roll vintage | |
| 32 | + | |
| 33 | +CELL_MIN_OBS = 20 # minimum sales per municipality×roll×year cell (FE cells) | |
| 34 | +MUNI_MIN_SALES = 100 # minimum sales for municipality-level IAAO statistics | |
| 35 | + | |
| 36 | +# ---------------------------------------------------------------- inference | |
| 37 | +SEED_BOOT = 20260809 # bootstrap seed (IAAO confidence intervals) | |
| 38 | +N_BOOT = 500 # bootstrap replications | |
| 39 | +QUANTILES = [0.10, 0.25, 0.50, 0.75, 0.90] # quantile-regression grid | |
| 40 | + | |
| 41 | +# IAAO (2013) acceptable ranges, quoted in the paper's tables | |
| 42 | +IAAO_COD_MAX_SF = 15.0 # single-family, heterogeneous areas | |
| 43 | +IAAO_PRD_RANGE = (0.98, 1.03) | |
| 44 | +IAAO_PRB_RANGE = (-0.05, 0.05) | |
| 45 | + | |
| 46 | +# Ten largest markets reported individually in the IAAO table | |
| 47 | +BIG_CITIES = ["Montréal", "Québec", "Laval", "Gatineau", "Longueuil", | |
| 48 | + "Lévis", "Sherbrooke", "Saguenay", "Trois-Rivières", "Terrebonne"] | |
| 49 | + | |
| 50 | + | |
| 51 | +def ensure_dirs() -> None: | |
| 52 | + """Create every output directory the pipeline writes to.""" | |
| 53 | + for p in (PROCESSED, FIGURES, REPRODUCED, TABLES): | |
| 54 | + p.mkdir(parents=True, exist_ok=True) | |
added
src/wp10/iaao.py
+109 −0
@@ -0,0 +1,109 @@ | ||
| 1 | +# Author: Simon-Pierre Boucher — contact@spboucher.ai | |
| 2 | +"""IAAO ratio-study statistics. | |
| 3 | + | |
| 4 | +Implements the standard diagnostics of the IAAO (2013) *Standard on Ratio | |
| 5 | +Studies*: the median assessment ratio, the coefficient of dispersion (COD), | |
| 6 | +the price-related differential (PRD), and the coefficient of price-related | |
| 7 | +bias (PRB), together with nonparametric bootstrap confidence intervals. | |
| 8 | + | |
| 9 | +Notation: r_i = AV_i / SP_i is the assessment ratio of sale i. | |
| 10 | +""" | |
| 11 | +from __future__ import annotations | |
| 12 | + | |
| 13 | +import numpy as np | |
| 14 | +import pandas as pd | |
| 15 | + | |
| 16 | +from . import config | |
| 17 | + | |
| 18 | + | |
| 19 | +# ---------------------------------------------------------------- point stats | |
| 20 | +def cod(ratio: np.ndarray) -> float: | |
| 21 | + """Coefficient of dispersion: 100 × mean |r − med| / med (horizontal equity).""" | |
| 22 | + med = np.median(ratio) | |
| 23 | + return 100.0 * np.mean(np.abs(ratio - med)) / med | |
| 24 | + | |
| 25 | + | |
| 26 | +def prd(av: np.ndarray, sp: np.ndarray) -> float: | |
| 27 | + """Price-related differential: mean ratio ÷ sale-weighted mean ratio. | |
| 28 | + | |
| 29 | + PRD > 1 indicates regressivity (low-priced properties assessed at higher | |
| 30 | + ratios); the IAAO acceptable range is [0.98, 1.03]. | |
| 31 | + """ | |
| 32 | + r = av / sp | |
| 33 | + return float(np.mean(r) / (np.sum(av) / np.sum(sp))) | |
| 34 | + | |
| 35 | + | |
| 36 | +def prb(av: np.ndarray, sp: np.ndarray) -> tuple[float, float]: | |
| 37 | + """Coefficient of price-related bias (IAAO 2013, Appendix on PRB). | |
| 38 | + | |
| 39 | + Regress the proportional deviation of the ratio from its median on the | |
| 40 | + log (base 2) of a value proxy that blends the sale price and the | |
| 41 | + median-deflated assessment: | |
| 42 | + | |
| 43 | + (r_i − med) / med = α + PRB · ln2( 0.5·SP_i + 0.5·AV_i/med ) + u_i | |
| 44 | + | |
| 45 | + Returns (PRB, robust standard error). PRB = −0.03 means ratios fall by | |
| 46 | + 3% of the median with every doubling of value: regressive if PRB < 0. | |
| 47 | + """ | |
| 48 | + r = av / sp | |
| 49 | + med = np.median(r) | |
| 50 | + y = (r - med) / med | |
| 51 | + proxy = 0.5 * sp + 0.5 * av / med | |
| 52 | + x = np.log2(proxy) | |
| 53 | + X = np.column_stack([np.ones_like(x), x]) | |
| 54 | + beta, *_ = np.linalg.lstsq(X, y, rcond=None) | |
| 55 | + resid = y - X @ beta | |
| 56 | + # HC1 robust standard error of the slope | |
| 57 | + XtX_inv = np.linalg.inv(X.T @ X) | |
| 58 | + meat = (X * (resid ** 2)[:, None]).T @ X | |
| 59 | + k = len(y) / (len(y) - 2) | |
| 60 | + se = float(np.sqrt(k * (XtX_inv @ meat @ XtX_inv)[1, 1])) | |
| 61 | + return float(beta[1]), se | |
| 62 | + | |
| 63 | + | |
| 64 | +# ---------------------------------------------------------------- bootstrap | |
| 65 | +def bootstrap_ci(av: np.ndarray, sp: np.ndarray, | |
| 66 | + n_boot: int = config.N_BOOT, | |
| 67 | + seed: int = config.SEED_BOOT) -> dict: | |
| 68 | + """Percentile bootstrap 95% CIs for the median ratio, COD, PRD and PRB.""" | |
| 69 | + rng = np.random.default_rng(seed) | |
| 70 | + n = len(av) | |
| 71 | + stats = {"median": [], "cod": [], "prd": [], "prb": []} | |
| 72 | + for _ in range(n_boot): | |
| 73 | + idx = rng.integers(0, n, n) | |
| 74 | + a, s = av[idx], sp[idx] | |
| 75 | + r = a / s | |
| 76 | + stats["median"].append(np.median(r)) | |
| 77 | + stats["cod"].append(cod(r)) | |
| 78 | + stats["prd"].append(prd(a, s)) | |
| 79 | + stats["prb"].append(prb(a, s)[0]) | |
| 80 | + return {k: (float(np.percentile(v, 2.5)), float(np.percentile(v, 97.5))) | |
| 81 | + for k, v in stats.items()} | |
| 82 | + | |
| 83 | + | |
| 84 | +# ---------------------------------------------------------------- group table | |
| 85 | +def group_metrics(df: pd.DataFrame, by: str | list[str], | |
| 86 | + min_n: int = 50, ci: bool = False) -> pd.DataFrame: | |
| 87 | + """IAAO statistics computed within each group of ``by``. | |
| 88 | + | |
| 89 | + Returns one row per group with n, median ratio, COD, PRD, PRB (and its | |
| 90 | + SE); optionally percentile-bootstrap CIs (slow — reserve for headline | |
| 91 | + rows). | |
| 92 | + """ | |
| 93 | + rows = [] | |
| 94 | + for key, g in df.groupby(by): | |
| 95 | + if len(g) < min_n: | |
| 96 | + continue | |
| 97 | + av = g["role_valeur_immeuble"].to_numpy(float) | |
| 98 | + sp = g["amount"].to_numpy(float) | |
| 99 | + r = av / sp | |
| 100 | + b, se = prb(av, sp) | |
| 101 | + row = {"group": key if isinstance(key, str) else "_".join(map(str, key)), | |
| 102 | + "n": len(g), "median_ratio": float(np.median(r)), | |
| 103 | + "cod": cod(r), "prd": prd(av, sp), "prb": b, "prb_se": se} | |
| 104 | + if ci: | |
| 105 | + cis = bootstrap_ci(av, sp) | |
| 106 | + for stat, (lo, hi) in cis.items(): | |
| 107 | + row[f"{stat}_lo"], row[f"{stat}_hi"] = lo, hi | |
| 108 | + rows.append(row) | |
| 109 | + return pd.DataFrame(rows) | |
added
src/wp10/models.py
+202 −0
@@ -0,0 +1,202 @@ | ||
| 1 | +# Author: Simon-Pierre Boucher — contact@spboucher.ai | |
| 2 | +"""Econometric estimators for WP10. | |
| 3 | + | |
| 4 | +Every vertical-inequity estimator ultimately measures how the assessment | |
| 5 | +ratio r = AV/SP moves with market value. The workhorse is the log-log | |
| 6 | +(Cheng 1974) regression | |
| 7 | + | |
| 8 | + ln AV_i = α_c(i) + β · ln SP_i + ε_i , | |
| 9 | + | |
| 10 | +where α_c(i) is a municipality × roll vintage × sale-year fixed effect that | |
| 11 | +absorbs the mechanical drift of ratios between triennial reference dates. | |
| 12 | +β < 1 ⇒ regressive assessment. We report γ ≡ β − 1, the elasticity of the | |
| 13 | +assessment *ratio* with respect to price (γ < 0 ⇒ regressive), estimated by | |
| 14 | + | |
| 15 | + - pooled OLS (no fixed effects), | |
| 16 | + - absorbing least squares with cell fixed effects, | |
| 17 | + - Clapp's (1990) rank-based IV, which purges the attenuation/mean-reversion | |
| 18 | + bias that pushes OLS toward spurious regressivity when sale prices carry | |
| 19 | + idiosyncratic noise, | |
| 20 | + - quantile regression across the conditional distribution. | |
| 21 | + | |
| 22 | +Inference is clustered by municipality throughout. | |
| 23 | +""" | |
| 24 | +from __future__ import annotations | |
| 25 | + | |
| 26 | +import numpy as np | |
| 27 | +import pandas as pd | |
| 28 | +import statsmodels.api as sm | |
| 29 | +from linearmodels.iv import IV2SLS | |
| 30 | +from linearmodels.iv.absorbing import AbsorbingLS | |
| 31 | + | |
| 32 | +from . import config | |
| 33 | + | |
| 34 | + | |
| 35 | +# ---------------------------------------------------------------- helpers | |
| 36 | +def _demean(df: pd.DataFrame, cols: list[str], by: str = "cell") -> pd.DataFrame: | |
| 37 | + """Within-transform ``cols`` inside groups of ``by`` (suffix ``_w``).""" | |
| 38 | + out = df.copy() | |
| 39 | + for c in cols: | |
| 40 | + out[c + "_w"] = df[c] - df.groupby(by)[c].transform("mean") | |
| 41 | + return out | |
| 42 | + | |
| 43 | + | |
| 44 | +def _cluster_ols(y: np.ndarray, X: np.ndarray, clusters: np.ndarray, | |
| 45 | + dof_adjust: int = 0): | |
| 46 | + """OLS with CRVE (cluster-robust) inference via statsmodels.""" | |
| 47 | + model = sm.OLS(y, X) | |
| 48 | + return model.fit(cov_type="cluster", cov_kwds={"groups": clusters}) | |
| 49 | + | |
| 50 | + | |
| 51 | +# ---------------------------------------------------------------- Cheng / FE | |
| 52 | +def cheng_pooled(df: pd.DataFrame) -> dict: | |
| 53 | + """Pooled Cheng regression: ln AV on ln SP, no fixed effects.""" | |
| 54 | + X = sm.add_constant(df["ln_price"].to_numpy()) | |
| 55 | + res = _cluster_ols(df["ln_av"].to_numpy(), X, df["muni"].to_numpy()) | |
| 56 | + return {"estimator": "Cheng OLS (pooled)", "beta": res.params[1], | |
| 57 | + "se": res.bse[1], "gamma": res.params[1] - 1.0, | |
| 58 | + "n": int(res.nobs), "r2": res.rsquared} | |
| 59 | + | |
| 60 | + | |
| 61 | +def cheng_fe(df: pd.DataFrame, extra_controls: list[str] | None = None) -> dict: | |
| 62 | + """Cheng regression absorbing municipality × roll × sale-year cells.""" | |
| 63 | + dep = df["ln_av"] | |
| 64 | + exog_cols = ["ln_price"] + (extra_controls or []) | |
| 65 | + exog = df[exog_cols] | |
| 66 | + absorb = pd.DataFrame({"cell": df["cell"].astype("category")}) | |
| 67 | + mod = AbsorbingLS(dep, exog, absorb=absorb) | |
| 68 | + res = mod.fit(cov_type="clustered", clusters=df["muni"].astype("category")) | |
| 69 | + return {"estimator": "Cheng FE (cell)", "beta": float(res.params["ln_price"]), | |
| 70 | + "se": float(res.std_errors["ln_price"]), | |
| 71 | + "gamma": float(res.params["ln_price"]) - 1.0, | |
| 72 | + "n": int(res.nobs), "r2": float(res.rsquared)} | |
| 73 | + | |
| 74 | + | |
| 75 | +def paglin_fogarty(df: pd.DataFrame) -> dict: | |
| 76 | + """Levels regression AV = a + b·SP (Paglin & Fogarty 1972). | |
| 77 | + | |
| 78 | + A positive intercept with b below the overall assessment level indicates | |
| 79 | + regressivity in levels. | |
| 80 | + """ | |
| 81 | + X = sm.add_constant(df["amount"].to_numpy(float)) | |
| 82 | + res = _cluster_ols(df["role_valeur_immeuble"].to_numpy(float), X, | |
| 83 | + df["muni"].to_numpy()) | |
| 84 | + return {"estimator": "Paglin–Fogarty (levels)", | |
| 85 | + "intercept": res.params[0], "intercept_se": res.bse[0], | |
| 86 | + "slope": res.params[1], "slope_se": res.bse[1], | |
| 87 | + "n": int(res.nobs), "r2": res.rsquared} | |
| 88 | + | |
| 89 | + | |
| 90 | +# ---------------------------------------------------------------- Clapp IV | |
| 91 | +def clapp_iv(df: pd.DataFrame) -> dict: | |
| 92 | + """Clapp (1990) rank-based IV on within-cell demeaned data. | |
| 93 | + | |
| 94 | + The instrument z ∈ {−1, 0, +1} flags sales in the bottom/top third of | |
| 95 | + *both* the ln AV and ln SP within-cell distributions. Because z carries | |
| 96 | + only coarse rank information, it is (near-)orthogonal to the transitory | |
| 97 | + component of either variable, undoing the attenuation that biases OLS | |
| 98 | + toward regressivity (β̂ < β) when prices are noisy. | |
| 99 | + """ | |
| 100 | + d = _demean(df, ["ln_av", "ln_price"]) | |
| 101 | + g_av = d.groupby("cell")["ln_av"].rank(pct=True) | |
| 102 | + g_sp = d.groupby("cell")["ln_price"].rank(pct=True) | |
| 103 | + z = np.zeros(len(d)) | |
| 104 | + z[(g_av <= 1 / 3) & (g_sp <= 1 / 3)] = -1.0 | |
| 105 | + z[(g_av > 2 / 3) & (g_sp > 2 / 3)] = 1.0 | |
| 106 | + d["z"] = z | |
| 107 | + res = IV2SLS(d["ln_av_w"], None, d[["ln_price_w"]], d[["z"]]).fit( | |
| 108 | + cov_type="clustered", clusters=d["muni"].astype("category")) | |
| 109 | + beta = float(res.params["ln_price_w"]) | |
| 110 | + return {"estimator": "Clapp IV (rank instrument)", "beta": beta, | |
| 111 | + "se": float(res.std_errors["ln_price_w"]), "gamma": beta - 1.0, | |
| 112 | + "n": int(res.nobs), "r2": float(res.rsquared)} | |
| 113 | + | |
| 114 | + | |
| 115 | +# ---------------------------------------------------------------- quantiles | |
| 116 | +def quantile_betas(df: pd.DataFrame, taus: list[float] = config.QUANTILES, | |
| 117 | + max_n: int = 250_000, seed: int = config.SEED_BOOT) -> pd.DataFrame: | |
| 118 | + """Quantile regressions of demeaned ln AV on demeaned ln SP. | |
| 119 | + | |
| 120 | + Estimated on a seeded random subsample (IRLS on the full 600k sample is | |
| 121 | + needlessly slow; the subsample SEs are already microscopic). | |
| 122 | + """ | |
| 123 | + d = _demean(df, ["ln_av", "ln_price"]) | |
| 124 | + if len(d) > max_n: | |
| 125 | + d = d.sample(max_n, random_state=seed) | |
| 126 | + X = sm.add_constant(d["ln_price_w"].to_numpy()) | |
| 127 | + y = d["ln_av_w"].to_numpy() | |
| 128 | + rows = [] | |
| 129 | + for tau in taus: | |
| 130 | + r = sm.QuantReg(y, X).fit(q=tau, max_iter=2000) | |
| 131 | + rows.append({"tau": tau, "beta": r.params[1], "se": r.bse[1], | |
| 132 | + "gamma": r.params[1] - 1.0, "n": len(d)}) | |
| 133 | + return pd.DataFrame(rows) | |
| 134 | + | |
| 135 | + | |
| 136 | +# ---------------------------------------------------------------- subgroups | |
| 137 | +def gamma_by_group(df: pd.DataFrame, groups: dict[str, pd.Series]) -> pd.DataFrame: | |
| 138 | + """Cheng-FE γ estimated separately on each labelled subsample. | |
| 139 | + | |
| 140 | + ``groups`` maps a label to a boolean mask over ``df``. Subsamples keep | |
| 141 | + only cells that retain ≥ CELL_MIN_OBS sales after masking. | |
| 142 | + """ | |
| 143 | + rows = [] | |
| 144 | + for label, mask in groups.items(): | |
| 145 | + sub = df[mask] | |
| 146 | + counts = sub.groupby("cell")["cell"].transform("size") | |
| 147 | + sub = sub[counts >= config.CELL_MIN_OBS] | |
| 148 | + if len(sub) < 2_000: | |
| 149 | + continue | |
| 150 | + est = cheng_fe(sub) | |
| 151 | + rows.append({"group": label, "gamma": est["gamma"], "se": est["se"], | |
| 152 | + "n": est["n"]}) | |
| 153 | + return pd.DataFrame(rows) | |
| 154 | + | |
| 155 | + | |
| 156 | +# ---------------------------------------------------------------- horizontal | |
| 157 | +def horizontal_dispersion(df: pd.DataFrame) -> tuple[pd.DataFrame, dict]: | |
| 158 | + """Horizontal inequity: who gets the noisiest assessments? | |
| 159 | + | |
| 160 | + Regresses the absolute log deviation of a sale's ratio from its cell | |
| 161 | + median, |ln r_i − med_c ln r|, on property characteristics with cell | |
| 162 | + fixed effects. Positive coefficients = less uniform assessment. | |
| 163 | + Returns (coefficient table, fit metadata). | |
| 164 | + """ | |
| 165 | + d = df.copy() | |
| 166 | + d["abs_dev"] = (d["ln_ratio"] | |
| 167 | + - d.groupby("cell")["ln_ratio"].transform("median")).abs() | |
| 168 | + d["age_dec"] = d["age"] / 10.0 | |
| 169 | + d["is_condo"] = (d["prop_class"] == "condo").astype(float) | |
| 170 | + d["is_plex"] = (d["prop_class"] == "plex").astype(float) | |
| 171 | + d["is_cottage"] = (d["prop_class"] == "cottage").astype(float) | |
| 172 | + d = d.dropna(subset=["abs_dev", "age_dec", "land_share"]) | |
| 173 | + exog_cols = ["age_dec", "land_share", "is_condo", "is_plex", "is_cottage"] | |
| 174 | + mod = AbsorbingLS(d["abs_dev"], d[exog_cols], | |
| 175 | + absorb=pd.DataFrame({"cell": d["cell"].astype("category")})) | |
| 176 | + res = mod.fit(cov_type="clustered", clusters=d["muni"].astype("category")) | |
| 177 | + tab = pd.DataFrame({"coef": res.params, "se": res.std_errors, | |
| 178 | + "tstat": res.tstats}) | |
| 179 | + return tab, {"n": int(res.nobs), "r2": float(res.rsquared), | |
| 180 | + "mean_dep": float(d["abs_dev"].mean())} | |
| 181 | + | |
| 182 | + | |
| 183 | +# ---------------------------------------------------------------- tax shift | |
| 184 | +def tax_shift(df: pd.DataFrame, n_bins: int = 10) -> pd.DataFrame: | |
| 185 | + """Implied property-tax shift from differential assessment. | |
| 186 | + | |
| 187 | + Within a taxing cell the levy is proportional to AV, so a property whose | |
| 188 | + ratio exceeds the cell median by x% pays x% more tax than under uniform | |
| 189 | + assessment. We compute rel_i = r_i / med_c(r) − 1 and average it by | |
| 190 | + within-cell sale-price decile. | |
| 191 | + """ | |
| 192 | + d = df.copy() | |
| 193 | + d["rel"] = d["ratio"] / d.groupby("cell")["ratio"].transform("median") - 1.0 | |
| 194 | + d["decile"] = (d.groupby("cell")["amount"] | |
| 195 | + .rank(pct=True) | |
| 196 | + .mul(n_bins).add(1 - 1e-9).astype(int).clip(1, n_bins)) | |
| 197 | + out = (d.groupby("decile") | |
| 198 | + .agg(n=("rel", "size"), mean_rel=("rel", "mean"), | |
| 199 | + median_rel=("rel", "median"), | |
| 200 | + se=("rel", lambda s: s.std() / np.sqrt(len(s)))) | |
| 201 | + .reset_index()) | |
| 202 | + return out | |
added
src/wp10/plotstyle.py
+75 −0
@@ -0,0 +1,75 @@ | ||
| 1 | +# Author: Simon-Pierre Boucher — contact@spboucher.ai | |
| 2 | +"""Shared matplotlib style for all WP10 figures — print-journal calibre. | |
| 3 | + | |
| 4 | +Conventions (Journal of Finance house style): | |
| 5 | + - no titles inside figures (captions carry the message); multi-panel | |
| 6 | + figures use bold "Panel A." headers set flush left above each axes; | |
| 7 | + - Times-compatible serif text with STIX math, 8–9 pt; | |
| 8 | + - thin, recessive axes (0.6 pt), outward ticks, no top/right spines; | |
| 9 | + - one accent hue per figure; a second hue only for genuine polarity or a | |
| 10 | + second series, always doubled by a linestyle/marker difference; | |
| 11 | + - shaded confidence bands rather than cap-heavy error bars; | |
| 12 | + - direct labels instead of legend boxes wherever the geometry allows. | |
| 13 | + | |
| 14 | +The two data hues (#2e6da4, #c04848) pass the full colour-vision validation | |
| 15 | +suite (lightness band, chroma floor, CVD separation, contrast) on a white | |
| 16 | +surface; INK and GREY are reserved for marks-as-ink and reference lines. | |
| 17 | +""" | |
| 18 | +import matplotlib | |
| 19 | + | |
| 20 | +matplotlib.use("Agg") | |
| 21 | +import matplotlib.pyplot as plt # noqa: E402 | |
| 22 | + | |
| 23 | +INK = "#1a1a1a" # primary marks (points, bars, text) | |
| 24 | +BLUE = "#2e6da4" # accent series / fitted lines | |
| 25 | +RED = "#c04848" # contrast series / polarity | |
| 26 | +GREY = "#8a8a8a" # reference lines | |
| 27 | +LIGHT = "#d9d9d9" # fills, bands | |
| 28 | +GRID = "#e3e3e3" # gridlines | |
| 29 | + | |
| 30 | +# Sequential blues for ordered series (light → dark, one hue) | |
| 31 | +BLUES = ["#c6d7e8", "#9dbcd8", "#74a1c8", "#4b86b8", "#2e6da4", "#1d4a75"] | |
| 32 | + | |
| 33 | +TEXTWIDTH = 6.3 # \textwidth in inches (1in margins, letter paper) | |
| 34 | + | |
| 35 | + | |
| 36 | +def apply_style() -> None: | |
| 37 | + plt.rcParams.update({ | |
| 38 | + "font.family": "serif", | |
| 39 | + "font.serif": ["Times New Roman", "Times", "STIXGeneral", "DejaVu Serif"], | |
| 40 | + "mathtext.fontset": "stix", | |
| 41 | + "font.size": 9, | |
| 42 | + "axes.labelsize": 9, | |
| 43 | + "xtick.labelsize": 8, | |
| 44 | + "ytick.labelsize": 8, | |
| 45 | + "legend.fontsize": 8, | |
| 46 | + "figure.dpi": 150, | |
| 47 | + "savefig.dpi": 300, | |
| 48 | + "axes.spines.top": False, | |
| 49 | + "axes.spines.right": False, | |
| 50 | + "axes.linewidth": 0.6, | |
| 51 | + "xtick.major.width": 0.6, | |
| 52 | + "ytick.major.width": 0.6, | |
| 53 | + "xtick.major.size": 3, | |
| 54 | + "ytick.major.size": 3, | |
| 55 | + "xtick.direction": "out", | |
| 56 | + "ytick.direction": "out", | |
| 57 | + "axes.grid": False, | |
| 58 | + "grid.color": GRID, | |
| 59 | + "grid.linewidth": 0.5, | |
| 60 | + "legend.frameon": False, | |
| 61 | + "lines.linewidth": 1.3, | |
| 62 | + "lines.markersize": 4.5, | |
| 63 | + "figure.constrained_layout.use": False, | |
| 64 | + }) | |
| 65 | + | |
| 66 | + | |
| 67 | +def panel_label(ax, text: str) -> None: | |
| 68 | + """Bold flush-left panel header above the axes (JoF style).""" | |
| 69 | + ax.set_title(text, loc="left", fontsize=9, fontweight="bold", pad=8) | |
| 70 | + | |
| 71 | + | |
| 72 | +def ygrid(ax) -> None: | |
| 73 | + """Recessive horizontal gridlines drawn beneath the data.""" | |
| 74 | + ax.grid(axis="y", linewidth=0.5, color=GRID) | |
| 75 | + ax.set_axisbelow(True) | |
added
src/wp10/sample.py
+128 −0
@@ -0,0 +1,128 @@ | ||
| 1 | +# Author: Simon-Pierre Boucher — contact@spboucher.ai | |
| 2 | +"""Estimation-sample construction for WP10. | |
| 3 | + | |
| 4 | +Reads the raw matched transaction–roll snapshot, restricts to residential | |
| 5 | +arm's-length sales with a high-confidence roll match, builds the assessment | |
| 6 | +ratio and every derived regressor, applies the trims, and writes | |
| 7 | +``data/processed/analysis.parquet``. | |
| 8 | + | |
| 9 | +Key variable definitions | |
| 10 | +------------------------ | |
| 11 | +ratio AV / SP where AV = ``role_valeur_immeuble`` (total assessed value | |
| 12 | + on the roll in force at the sale date) and SP = ``amount``. | |
| 13 | +ln_ratio log(ratio). | |
| 14 | +cell municipality × roll vintage × sale year — the market-timing cell | |
| 15 | + inside which assessed values share a common reference date, so | |
| 16 | + the roll lag is constant and ratio comparisons are clean. | |
| 17 | +lag_months months elapsed between the roll's market-condition reference | |
| 18 | + date (July 1, by statute) and the sale date. | |
| 19 | +land_share assessed land value / total assessed value. | |
| 20 | +age sale year minus year built (from the roll where available). | |
| 21 | +""" | |
| 22 | +import numpy as np | |
| 23 | +import pandas as pd | |
| 24 | + | |
| 25 | +from . import config | |
| 26 | + | |
| 27 | + | |
| 28 | +def _property_class(df: pd.DataFrame) -> pd.Series: | |
| 29 | + """Harmonised dwelling class from the listing type, CUBF code and unit count.""" | |
| 30 | + cls = pd.Series("other", index=df.index, dtype="object") | |
| 31 | + cls[df["role_cubf"] == "1100"] = "cottage" | |
| 32 | + cls[df["role_cubf"] == "1211"] = "mobile" | |
| 33 | + is_dwelling = df["role_cubf"] == "1000" | |
| 34 | + cls[is_dwelling & (df["propertyType"] == "condo")] = "condo" | |
| 35 | + cls[is_dwelling & (df["propertyType"] == "plex")] = "plex" | |
| 36 | + cls[is_dwelling & (df["propertyType"] == "unifamilial")] = "single_family" | |
| 37 | + # dwellings the feed left untyped: use the roll's unit count | |
| 38 | + untyped = is_dwelling & (cls == "other") | |
| 39 | + cls[untyped & (df["role_nb_logements"] == 1)] = "single_family" | |
| 40 | + cls[untyped & (df["role_nb_logements"].between(2, 5))] = "plex" | |
| 41 | + return cls | |
| 42 | + | |
| 43 | + | |
| 44 | +def build(raw: pd.DataFrame | None = None) -> pd.DataFrame: | |
| 45 | + """Apply every sample restriction and return the analysis DataFrame.""" | |
| 46 | + df = raw if raw is not None else pd.read_parquet(config.RAW_PARQUET) | |
| 47 | + n0 = len(df) | |
| 48 | + log = [("raw snapshot", n0)] | |
| 49 | + | |
| 50 | + # -------------------------------------------------- residential use codes | |
| 51 | + df = df[df["role_cubf"].isin(config.RESIDENTIAL_CUBF)] | |
| 52 | + log.append(("residential CUBF (1000/1100/1211/1990)", len(df))) | |
| 53 | + | |
| 54 | + # -------------------------------------------------- match confidence | |
| 55 | + df = df[(df["match_dist_m"] <= config.MATCH_MAX_DIST_M) | |
| 56 | + & (df["match_score"] >= config.MATCH_MIN_SCORE)] | |
| 57 | + log.append((f"match dist ≤ {config.MATCH_MAX_DIST_M:.0f} m & score ≥ " | |
| 58 | + f"{config.MATCH_MIN_SCORE:.0f}", len(df))) | |
| 59 | + | |
| 60 | + # -------------------------------------------------- valid AV and SP | |
| 61 | + df = df[(df["amount"] >= config.PRICE_MIN) | |
| 62 | + & (df["role_valeur_immeuble"] > 5_000) | |
| 63 | + & df["role_date_cond_marche"].notna()] | |
| 64 | + log.append(("positive AV, SP ≥ 50k, dated roll", len(df))) | |
| 65 | + | |
| 66 | + # -------------------------------------------------- derived variables | |
| 67 | + df = df.copy() | |
| 68 | + df["sale_date"] = pd.to_datetime(df["date"]) | |
| 69 | + df["sale_year"] = df["tx_year"].astype(int) | |
| 70 | + df["ratio"] = df["role_valeur_immeuble"] / df["amount"] | |
| 71 | + df["ln_ratio"] = np.log(df["ratio"]) | |
| 72 | + df["ln_price"] = np.log(df["amount"].astype(float)) | |
| 73 | + df["ln_av"] = np.log(df["role_valeur_immeuble"]) | |
| 74 | + df["lag_months"] = ((df["sale_date"] - df["role_date_cond_marche"]).dt.days | |
| 75 | + / 30.44) | |
| 76 | + df["land_share"] = (df["role_valeur_terrain"] | |
| 77 | + / df["role_valeur_immeuble"]).clip(0, 1) | |
| 78 | + df["age"] = (df["sale_year"] | |
| 79 | + - pd.to_numeric(df["role_annee_construction"], errors="coerce")) | |
| 80 | + df.loc[(df["age"] < 0) | (df["age"] > 300), "age"] = np.nan | |
| 81 | + df["prop_class"] = _property_class(df) | |
| 82 | + df["muni"] = df["role_code_mun"] | |
| 83 | + df["roll"] = df["role_anrole"] | |
| 84 | + df["cell"] = (df["muni"] + "_" + df["roll"] + "_" | |
| 85 | + + df["sale_year"].astype(str)) | |
| 86 | + | |
| 87 | + # -------------------------------------------------- ratio trim (by roll vintage, | |
| 88 | + # so the mechanical drift of ratios across reference dates is not trimmed away) | |
| 89 | + lo, hi = config.RATIO_TRIM | |
| 90 | + q = df.groupby("roll")["ratio"].quantile([lo, hi]).unstack() | |
| 91 | + df = df.join(q.rename(columns={lo: "_qlo", hi: "_qhi"}), on="roll") | |
| 92 | + df = df[(df["ratio"] >= df["_qlo"]) & (df["ratio"] <= df["_qhi"])] | |
| 93 | + df = df.drop(columns=["_qlo", "_qhi"]) | |
| 94 | + log.append((f"ratio inside [{lo:.0%}, {hi:.0%}] of its roll vintage", len(df))) | |
| 95 | + | |
| 96 | + # -------------------------------------------------- market-timing cells | |
| 97 | + counts = df.groupby("cell")["cell"].transform("size") | |
| 98 | + df = df[counts >= config.CELL_MIN_OBS] | |
| 99 | + log.append((f"cells (muni × roll × year) with ≥ {config.CELL_MIN_OBS} sales", | |
| 100 | + len(df))) | |
| 101 | + | |
| 102 | + df.attrs["selection_log"] = log | |
| 103 | + keep = ["id", "sale_date", "sale_year", "amount", "ln_price", | |
| 104 | + "role_valeur_immeuble", "role_valeur_terrain", "role_valeur_batiment", | |
| 105 | + "totalArValue", "previousValue", "ln_av", "ratio", "ln_ratio", | |
| 106 | + "lag_months", "land_share", "age", "prop_class", | |
| 107 | + "muni", "roll", "cell", "city", "role_municipalite", | |
| 108 | + "lat", "lng", "role_superficie_terrain_m2", "role_aire_etages_m2", | |
| 109 | + "role_nb_logements", "match_dist_m", "match_score", | |
| 110 | + "match_valeur_exacte", "ownerType", "role_cubf"] | |
| 111 | + return df[keep].reset_index(drop=True) | |
| 112 | + | |
| 113 | + | |
| 114 | +def build_and_save() -> pd.DataFrame: | |
| 115 | + """Build the sample, print the selection log, persist to parquet.""" | |
| 116 | + config.ensure_dirs() | |
| 117 | + s = build() | |
| 118 | + for step, n in s.attrs["selection_log"]: | |
| 119 | + print(f" {n:>9,} after: {step}") | |
| 120 | + s.to_parquet(config.ANALYSIS_PARQUET, index=False) | |
| 121 | + return s | |
| 122 | + | |
| 123 | + | |
| 124 | +def load() -> pd.DataFrame: | |
| 125 | + """Load the processed analysis sample (build it first if missing).""" | |
| 126 | + if not config.ANALYSIS_PARQUET.exists(): | |
| 127 | + return build_and_save() | |
| 128 | + return pd.read_parquet(config.ANALYSIS_PARQUET) | |
| 129 | ||