SPB Git forge

spb/wp14_uqo

Public
3commits 1branches 1releases
1.5 MBsize
maindefault branch
1 h agolast push
Python 52.4% TeX 47.6%
12.4 KB

# 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