"""Cas 1 — maison unifamiliale à Gatineau (données synthétiques) : calculs PARTAGÉS par les chapitres 9, 14, 16-20, 24, 25, 35, 37, 39 et 45. Sélection des comparables (identique au chapitre 16), modèle hédonique de référence, prix implicites et ajustements dérivés.""" from __future__ import annotations import json, pathlib, sys import numpy as np, pandas as pd ROOT = pathlib.Path(__file__).resolve().parents[3] D = ROOT / "data/synthetic" sys.path.insert(0, str(ROOT / "analysis/python/cases")) def charger() -> dict: v = pd.read_csv(D / "cas1_gatineau_ventes.csv", parse_dates=["date_vente"]) s = json.loads((D / "cas1_gatineau_sujet.json").read_text()) c = pd.read_csv(D / "cas1_gatineau_couts.csv") return {"ventes": v, "sujet": s, "couts": c} def selection(k: int = 5) -> dict: """Les comparables du chapitre 16 (mêmes filtres, mêmes échelles et poids).""" import ch16_selection as c16 v, s = c16.charger(); v = c16.preparer(v, s) sel = c16.selectionner(v, s, k=k) return {"ventes": v, "sujet": s, **sel} SECTEURS = ["Hull", "Gatineau", "Aylmer", "Buckingham", "Masson-Angers"] REF_DATE = pd.Timestamp("2024-07-01") # référence temporelle du jeu (date de référence du rôle fictif) def matrice(df: pd.DataFrame, date_eval: pd.Timestamp | None = None) -> pd.DataFrame: """Régresseurs du modèle hédonique de référence (semi-log). mois = mois écoulés depuis REF_DATE (tendance linéaire du marché) ; secteur de référence : Hull ; type de référence : plain-pied.""" X = pd.DataFrame(index=df.index) X["const"] = 1.0 X["ln_S"] = np.log(df["superficie_habitable_m2"] / 140.0) X["ln_T"] = np.log(df["superficie_terrain_m2"] / 560.0) X["age"] = df["age"].astype(float) X["age2_100"] = df["age"].astype(float) ** 2 / 100.0 X["qualite"] = df["qualite_1a5"].astype(float) - 3.0 X["garage"] = df["garage_places"].astype(float) X["sous_sol"] = df["sous_sol_fini"].astype(float) X["piscine"] = df["piscine"].astype(float) X["etages"] = (df["type"] == "à étages").astype(float) X["dist_centre"] = np.clip(df["dist_centre_km"].astype(float) - 3.0, 0, None) for sct in SECTEURS[1:]: X[f"sect_{sct}"] = (df["secteur"] == sct).astype(float) d = df["date_vente"] if "date_vente" in df else pd.Series([date_eval] * len(df), index=df.index) X["mois"] = ((pd.to_datetime(d) - REF_DATE).dt.days / 30.4375).astype(float) return X def hedonique(v: pd.DataFrame) -> dict: """MCO semi-log avec erreurs-types HC1 (traite.hedonique.mco).""" from . import hedonique as h X = matrice(v); y = np.log(v["prix_vente"].astype(float)) r = h.mco(X.values, y.values) coef = pd.DataFrame({"coef": r["beta"], "se_hc1": r["se_hc1"]}, index=X.columns) coef["t"] = coef["coef"] / coef["se_hc1"] coef["effet_pct"] = np.exp(coef["coef"]) - 1 return {"coef": coef, "r2": r["r2"], "r2_ajuste": r["r2_ajuste"], "sigma": float(np.sqrt(r["sigma2"])), "n": r["n"], "k": r["k"], "residus": r["residus"], "X": X, "y": y.values, "beta": r["beta"], "V_hc1": r["V_hc1"], "croissance_mensuelle": float(np.exp(r["beta"][list(X.columns).index("mois")]) - 1)} def sujet_df(s: dict, date_eval: str | None = None) -> pd.DataFrame: d = dict(s); d["date_vente"] = pd.Timestamp(date_eval or s["date_evaluation"]) return pd.DataFrame([d]) def predire(modele: dict, df: pd.DataFrame, duan: bool = True) -> np.ndarray: """Prédiction en niveau ; correction de retransformation de Duan (smearing) si demandée.""" X = matrice(df)[modele["X"].columns] lnp = X.values @ modele["beta"] facteur = float(np.mean(np.exp(modele["residus"]))) if duan else 1.0 return np.exp(lnp) * facteur def intervalle_prediction(modele: dict, df: pd.DataFrame, niveau: float = 0.95) -> pd.DataFrame: """Intervalle de prédiction (log-normal) : ln P ± z·sqrt(σ² + x'Vx), retransformé.""" from scipy.stats import norm X = matrice(df)[modele["X"].columns].values lnp = X @ modele["beta"] var = modele["sigma"] ** 2 + np.einsum("ij,jk,ik->i", X, modele["V_hc1"], X) z = norm.ppf(0.5 + niveau / 2) return pd.DataFrame({"ln_pred": lnp, "bas": np.exp(lnp - z * np.sqrt(var)), "point": np.exp(lnp), "haut": np.exp(lnp + z * np.sqrt(var))}) CARACTERISTIQUES_AJUSTEMENT = ["mois", "dist", "ln_S", "ln_T", "age", "qualite", "garage", "sous_sol", "piscine", "etages"] def ajustements_hedoniques(modele: dict, comps: pd.DataFrame, s: dict, date_eval: str | None = None) -> pd.DataFrame: """Ajustements monétaires de chaque comparable vers le sujet, dérivés du modèle hédonique : pour la caractéristique k, A_k = P_comp × [exp(β_k (x_s,k − x_c,k)) − 1] appliqué en cascade dans l'ordre déclaré (conditions du marché d'abord, puis localisation, puis caractéristiques physiques). Signe du point de vue du sujet.""" sd = sujet_df(s, date_eval); Xs = matrice(sd).iloc[0] Xc = matrice(comps); beta = pd.Series(modele["beta"], index=modele["X"].columns) groupes = {"conditions_marche": ["mois"], "localisation": ["dist_centre"] + [f"sect_{x}" for x in SECTEURS[1:]], "superficie": ["ln_S"], "terrain": ["ln_T"], "age": ["age", "age2_100"], "qualite": ["qualite"], "garage": ["garage"], "sous_sol": ["sous_sol"], "piscine": ["piscine"], "type": ["etages"]} rows = [] for i, c in comps.iterrows(): base = float(c["prix_vente"]); row = {"id_vente": c["id_vente"], "prix_vente": base} for g, cols in groupes.items(): dlog = float(sum(beta[col] * (Xs[col] - Xc.loc[i, col]) for col in cols)) adj = base * (np.exp(dlog) - 1.0) row[g] = adj; base += adj row["prix_ajuste"] = base row["ajust_brut_pct"] = sum(abs(row[g]) for g in groupes) / row["prix_vente"] row["ajust_net_pct"] = (base - row["prix_vente"]) / row["prix_vente"] rows.append(row) return pd.DataFrame(rows) def tout() -> dict: d = charger(); sel = selection(); m = hedonique(d["ventes"]) adj = ajustements_hedoniques(m, sel["comparables"], d["sujet"]) pred = predire(m, sujet_df(d["sujet"]))[0]; ip = intervalle_prediction(m, sujet_df(d["sujet"])).iloc[0] return {**d, "selection": sel, "modele": m, "ajustements": adj, "prediction_hedonique": float(pred), "intervalle": ip}