# Méthodologie — Modèle hédonique de prix des maisons (Québec) *Pipeline : Realtor.ca + DuProprio → nettoyage → feature engineering (règles + LLM + vision) → régression hédonique log-linéaire à effets fixes.* --- ## 1. Origine des données | Source | Fichier | Nb enreg. | Nature | |---|---|---|---| | **Realtor.ca** | `dataset_realtor-property-crawler_...json` (161 Mo) | 27 302 | Annonces MLS via crawler Realtor (Apify) | | **DuProprio** | `dataset_duproprio-property-crawler_...json` (87 Mo) | 7 693 | Annonces vente sans intermédiaire (Apify) | - **Collecte** : scrapers Apify, extraction le **2026-07-03**. Chaque enregistrement est un objet JSON structuré. - **Champs communs** : `source`, `url`, `listingId`, `title`, `price`/`priceNumber`, `currency`, `address{full,street,city,province,postalCode,...}`, `property{...}`, `features[]`, `description`, `images[]`, `scrapedAt`. - **Spécificités** : - Realtor : `property.buildingType`, `storeys`, `yearBuilt`, `parkingSpaces`, **`rooms[]`** (dimensions par pièce), pas de coordonnées GPS, images sans caption utile. - DuProprio : `property.type`, `levels`, **`coordinates{lat,lon}`** (7 691 présentes), images **avec captions** (`Kitchen`, etc.), pas de `rooms`. - **Instantané transversal** (cross-section) : un seul point dans le temps → pas de dimension temporelle, donc pas d'effet fixe temporel nécessaire. **Identifiant unique** construit : `uid = source + ":" + listingId` (évite toute collision entre les deux espaces d'ID). --- ## 2. Construction du dataset combiné « maisons » 1. **Fusion** des deux tableaux JSON (schéma harmonisé). 2. **Définition de « maison »** (choix utilisateur : unifamiliales + townhouses) : - Realtor : `buildingType ∈ {House, Row / Townhouse}` - DuProprio : `type ∈ {Home, Bungalow, Semi Detached, Intergenerational, Country Home, Cottage, Villa, Townhouse}` - Exclus : condos, plex (duplex→quintuplex), terrains/lots, fermes, commercial, mobile homes. 3. **Filtre prix présent** : `priceNumber` non nul et > 0. → **19 273 maisons avec prix** (15 470 Realtor + 3 803 DuProprio). --- ## 3. Nettoyage des aberrations Les prix scrappés contiennent des erreurs de parsing (min 899 $, max 368 G$). Règles appliquées : | Variable | Règle | Justification | |---|---|---| | Prix | garder **50 k$ – 15 M$** | plage plausible d'une maison au QC ; retire loyers mal parsés et concaténations | | Superficie habitable | 300 – 20 000 ft² (sinon → manquant) | valeurs physiquement possibles | | Terrain | 200 – 5 000 000 ft² (sinon → manquant) | idem | | Chambres / sdb | 0–15 / 0–12 (sinon → manquant) | plafonds réalistes | | **Prix/pied²** | garder **P0,5 – P99,5** (≈ 73–1 276 $/ft²) sur les biens à surface réelle | retire les erreurs résiduelles de parsing prix ou surface | → **19 115 maisons** conservées (158 retirées). **Principe** : on borne plutôt que de supprimer quand une seule variable est aberrante (on met la variable à *manquant* et on l'impute), pour ne pas perdre l'observation entière. --- ## 4. Construction des variables ### 4.1 Variable cible `ln_price = ln(priceNumber)` — transformation logarithmique (rend la distribution symétrique, linéarise les effets multiplicatifs, coefficients ≈ **% d'impact sur le prix**). ### 4.2 Variables structurelles (règles déterministes) - **Surface & terrain** : parsing d'unités hétérogènes → tout en **pieds carrés**. - `"1535 sqft"`, `"1,333.2 ft²"`, `"11 561.52 ft²"` (séparateurs de milliers variés), `"961.6 m2"` (m² × 10,7639). - **Reconstruction de surface via les pièces (Realtor)** : dimensions `"23 ft ,4 in x 13 ft ,8 in"` → pieds-décimaux → aire ; somme sur toutes les pièces. Calibration sur le recouvrement (ratio médian surface réelle / somme pièces ≈ **0,98**) → sert à combler `squareFeet` manquant. - Constat : les annonces à pièces détaillées ≈ celles qui ont déjà `squareFeet` → gain de couverture faible, mais les **comptes/types de pièces** restent informatifs. - **Autres** : `beds`, `baths`, `parking` (Realtor), `storeys`/`levels`, `age = 2026 − yearBuilt` (+ `age²` pour la courbure), drapeaux `has_*` marquant les valeurs imputées. ### 4.3 Aménités (règles par mots-clés bilingues) 17 dummies détectés dans `features[] + description + title` (FR/EN) : piscine, bord de l'eau, garage, foyer, climatisation/thermopompe, sous-sol fini, cul-de-sac, golf, ski, proximité parc/école/autoroute/transport, absence de voisin arrière. ### 4.4 Détail des pièces (Realtor, `rooms[]`) Nombre de pièces, nombre de niveaux, chambre principale, sous-sol, bureau, salle familiale, atelier, **cellier à vin**, solarium, mezzanine, bibliothèque, buanderie. ### 4.5 Signaux de listing Nombre de photos (`n_images`, écrêté à 60), présence de visite virtuelle — proxies de gamme/qualité de la mise en marché. ### 4.6 Variables géographiques (effets fixes) - **Ville** nettoyée (Realtor : on retire le quartier entre parenthèses ; minuscules ; normalisation). Villes avec **≥ 20 annonces** → dummy propre ; les rares → `__other__`. ≈ 194 villes en effets fixes. - **Type de propriété** harmonisé, regroupé si < 30 annonces. - **Source** (Realtor/DuProprio) → dummy captant les différences systématiques entre plateformes. ### 4.7 Imputation Valeurs manquantes après nettoyage : imputées par **médiane par type de propriété** (puis médiane globale), **avec un drapeau `has_*`** pour que le modèle distingue « valeur observée » de « valeur imputée ». --- ## 5. Mécanismes IA pour le feature engineering Deux couches IA extraient des variables **impossibles à obtenir par règles** — l'une du texte, l'autre des images. ### 5.1 Extraction de specs depuis la description — **GPT-4o-mini** (OpenAI) - **But** : transformer la description libre en 20 attributs structurés (état : à rénover→neuf, clé en main, cuisine/sdb rénovée, revêtements de sol, comptoirs quartz/granit, chauffage, vue sur l'eau, revenus locatifs, grand terrain, secteur tranquille, + mots-clés de vente). - **Mécanisme** : appel Chat Completions avec **`response_format = json_schema` strict** (schéma imposé, énumérations fermées) → sortie JSON valide garantie, pas de parsing fragile. `temperature = 0` (déterminisme). Système : « extracteur factuel, uniquement d'après le texte ; absence → false/inconnu/[] ». - **Ingénierie** : ~600 tokens in / ~110 out par annonce ; **parallèle** (24 threads), **résumable** (cache `specs.jsonl` clé = uid), **retry** exponentiel sur 429/5xx. - **Coût/temps** : ~$3 / ~19 k annonces. ### 5.2 Attributs visuels de façade — **Claude Haiku 4.5** (Anthropic, vision) - **But** : lire ce que le texte ne dit pas — **état visuel réel, attrait extérieur (curb appeal), niveau de rénovation, matériaux, aménagement paysager, cadre (urbain/banlieue/rural/bord d'eau/boisé), indice de luxe, qualité de la photo**. - **Sélection d'image** : 1 photo/annonce = la façade. Realtor → 1re photo. DuProprio → photo dont la caption est l'adresse (pas une pièce), via exclusion de mots-clés de pièces. - **Mécanisme** : Messages API, image passée par **URL** (`source.type = url`, pas de téléchargement), **`output_config.format = json_schema`** (12 attributs, énumérations fermées). Système : « évaluateur objectif ; scores fondés sur critères concrets (surfaces, matériaux, aménagement), pas sur un goût personnel ; si non-extérieur → is_exterior=false ». - **Ingénierie** : ~2 470 tokens in / ~100 out par image ; **parallèle** (16 threads), **résumable** (cache `facade_specs.jsonl`), **retry** sur 429/5xx/529. - **Coût/temps** : ~$57 / ~19 k images. - **Pourquoi Haiku** : modèle vision le moins cher et rapide, largement suffisant pour de la classification d'attributs concrets. ### 5.3 Pourquoi le JSON structuré strict est central Le schéma imposé (types + `enum` + champs requis + `additionalProperties:false`) fait valider la sortie **au niveau de l'API** : le modèle est forcé de renvoyer exactement les champs attendus, dans le bon domaine de valeurs. On obtient des variables **directement tabulables**, reproductibles, sans post-traitement de texte libre. ### 5.4 Intégration au dataset Les sorties LLM (clé `uid`) sont converties en variables numériques : booléens → 0/1, `condition` → score ordinal 0-5, revêtements → dummies (bois franc, quartz/granit), vue → dummy eau, etc., **avec un drapeau `has_gpt` / `is_exterior`** pour marquer la disponibilité. --- ## 6. Modèle économétrique ### 6.1 Spécification — hédonique log-linéaire à effets fixes $$\ln(P_i) = \alpha + \beta\,\ln(\text{surface}_i) + \gamma\,\ln(\text{terrain}_i) + \sum_k \delta_k X_{ki} + \sum_c \lambda_c \mathbb{1}[\text{ville}_i=c] + \varepsilon_i$$ - **Log-log** sur surface et terrain → coefficients = **élasticités** (% prix par % surface). - **Log-niveau** sur le reste → un coefficient $b$ ≈ **$(e^b-1)\times100$ % d'impact** sur le prix. - **Effets fixes ville** ($\lambda_c$) : absorbent tout le différentiel de localisation (le plus gros déterminant du prix). Chaque coefficient = prime/décote de la ville vs la ville de référence. - **Effets fixes type de propriété** et **source**. ### 6.2 Double spécification (couverture vs richesse) Realtor ne rapporte `squareFeet` que pour ~38 % des annonces → deux modèles, prédictions combinées : - **Modèle A (riche)** : inclut `ln(surface)`, estimé sur les biens à surface **mesurée** (réelle ou reconstruite via pièces). Meilleur ajustement. - **Modèle B (large)** : sans surface, estimé sur **toutes** les maisons → sert à pricer celles sans surface (via beds/baths/terrain/pièces + specs). - Prédiction finale : A si surface mesurée, sinon B. ### 6.3 Estimation & inférence - **MCO (OLS)** sur `ln_price`. - **Erreurs-types robustes à l'hétéroscédasticité HC3** (les résidus immobiliers sont hétéroscédastiques ; HC3 est conservateur en petit/moyen échantillon). - **Correction du biais de rétrotransformation** : $\hat{P} = e^{\hat{\ln P}} \times e^{\hat\sigma^2/2}$ (correction normale de Duan) — sinon $e^{\hat{\ln P}}$ sous-estime systématiquement le niveau. ### 6.4 Interprétation Coefficients rapportés en **% d'impact prix** + significativité (p-values sur SE HC3). Effets fixes ville présentés comme primes de localisation. --- ## 7. Statistiques & diagnostics | Indicateur | Signification | |---|---| | **R² / R²-ajusté** | part de variance de `ln_price` expliquée | | **Écart-type des résidus (log)** | → **erreur typique en %** = $(e^{\sigma}-1)\times100$ | | **Erreur absolue médiane** | \|prix réel / prix prédit − 1\| médian sur toutes les prédictions | | **% à ±15 % / ±25 %** | part des prédictions dans la fourchette | | **`resid_pct`** par annonce | surprix/sous-prix vs modèle → repère aubaines & surévaluations | Progression observée : v2 (structurel) R²≈0,70 → v3 (+ pièces/photos) R²≈0,72 → v4 (+ specs GPT) → v5 (+ vision) attendu. --- ## 8. Limites & biais (transparence) - **Endogénéité / non-causalité** : de belles photos ou des specs riches corrèlent avec des courtiers haut de gamme et des biens chers → coefficients **prédictifs**, pas causaux. À ne pas lire comme « ajouter une piscine ⇒ +X % ». - **Biais de sélection** : uniquement des biens **en vente** (prix demandé, pas prix de transaction) ; les biens sans surface/pièces sont non aléatoires (annonces moins complètes). - **Imputation** : les médianes par type réduisent la variance des variables imputées (atténuation possible des coefficients) — d'où les drapeaux `has_*`. - **Extraction LLM** : erreurs/hallucinations possibles ; `temperature=0` + schéma strict + énumérations fermées les limitent, mais pas de validation humaine systématique. La vision juge sur **une seule** photo. - **Instantané unique** : pas de dynamique de marché ; primes de localisation figées au 2026-07-03. - **Couverture géographique** : villes < 20 annonces regroupées en `__other__` (moins de finesse spatiale là où les données sont rares). --- ## 9. Fichiers produits | Fichier | Contenu | |---|---| | `hedonic.py`, `hedonic_v4.py` | pipelines reproductibles (nettoyage + modèles) | | `extract_specs.py`, `facade_vision.py` | extracteurs LLM (texte / vision), parallèles & résumables | | `specs.jsonl`, `facade_specs.jsonl` | sorties LLM brutes (clé uid) | | `maisons_hedonic*.csv` | dataset final + prix prédit + `resid_pct` | | `hedonic_*_summary.txt` | sorties statistiques complètes |