SPB Git
6.2 KB · 108 lines python
1"""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.2Sélection des comparables (identique au chapitre 16), modèle hédonique de référence, prix implicites et ajustements dérivés."""3from __future__ import annotations4import json, pathlib, sys5import numpy as np, pandas as pd67ROOT = pathlib.Path(__file__).resolve().parents[3]8D = ROOT / "data/synthetic"9sys.path.insert(0, str(ROOT / "analysis/python/cases"))1011def charger() -> dict:12    v = pd.read_csv(D / "cas1_gatineau_ventes.csv", parse_dates=["date_vente"])13    s = json.loads((D / "cas1_gatineau_sujet.json").read_text())14    c = pd.read_csv(D / "cas1_gatineau_couts.csv")15    return {"ventes": v, "sujet": s, "couts": c}1617def selection(k: int = 5) -> dict:18    """Les comparables du chapitre 16 (mêmes filtres, mêmes échelles et poids)."""19    import ch16_selection as c1620    v, s = c16.charger(); v = c16.preparer(v, s)21    sel = c16.selectionner(v, s, k=k)22    return {"ventes": v, "sujet": s, **sel}2324SECTEURS = ["Hull", "Gatineau", "Aylmer", "Buckingham", "Masson-Angers"]25REF_DATE = pd.Timestamp("2024-07-01")   # référence temporelle du jeu (date de référence du rôle fictif)2627def matrice(df: pd.DataFrame, date_eval: pd.Timestamp | None = None) -> pd.DataFrame:28    """Régresseurs du modèle hédonique de référence (semi-log).29    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."""30    X = pd.DataFrame(index=df.index)31    X["const"] = 1.032    X["ln_S"] = np.log(df["superficie_habitable_m2"] / 140.0)33    X["ln_T"] = np.log(df["superficie_terrain_m2"] / 560.0)34    X["age"] = df["age"].astype(float)35    X["age2_100"] = df["age"].astype(float) ** 2 / 100.036    X["qualite"] = df["qualite_1a5"].astype(float) - 3.037    X["garage"] = df["garage_places"].astype(float)38    X["sous_sol"] = df["sous_sol_fini"].astype(float)39    X["piscine"] = df["piscine"].astype(float)40    X["etages"] = (df["type"] == "à étages").astype(float)41    X["dist_centre"] = np.clip(df["dist_centre_km"].astype(float) - 3.0, 0, None)42    for sct in SECTEURS[1:]:43        X[f"sect_{sct}"] = (df["secteur"] == sct).astype(float)44    d = df["date_vente"] if "date_vente" in df else pd.Series([date_eval] * len(df), index=df.index)45    X["mois"] = ((pd.to_datetime(d) - REF_DATE).dt.days / 30.4375).astype(float)46    return X4748def hedonique(v: pd.DataFrame) -> dict:49    """MCO semi-log avec erreurs-types HC1 (traite.hedonique.mco)."""50    from . import hedonique as h51    X = matrice(v); y = np.log(v["prix_vente"].astype(float))52    r = h.mco(X.values, y.values)53    coef = pd.DataFrame({"coef": r["beta"], "se_hc1": r["se_hc1"]}, index=X.columns)54    coef["t"] = coef["coef"] / coef["se_hc1"]55    coef["effet_pct"] = np.exp(coef["coef"]) - 156    return {"coef": coef, "r2": r["r2"], "r2_ajuste": r["r2_ajuste"], "sigma": float(np.sqrt(r["sigma2"])), "n": r["n"], "k": r["k"],57            "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)}5859def sujet_df(s: dict, date_eval: str | None = None) -> pd.DataFrame:60    d = dict(s); d["date_vente"] = pd.Timestamp(date_eval or s["date_evaluation"])61    return pd.DataFrame([d])6263def predire(modele: dict, df: pd.DataFrame, duan: bool = True) -> np.ndarray:64    """Prédiction en niveau ; correction de retransformation de Duan (smearing) si demandée."""65    X = matrice(df)[modele["X"].columns]66    lnp = X.values @ modele["beta"]67    facteur = float(np.mean(np.exp(modele["residus"]))) if duan else 1.068    return np.exp(lnp) * facteur6970def intervalle_prediction(modele: dict, df: pd.DataFrame, niveau: float = 0.95) -> pd.DataFrame:71    """Intervalle de prédiction (log-normal) : ln P ± z·sqrt(σ² + x'Vx), retransformé."""72    from scipy.stats import norm73    X = matrice(df)[modele["X"].columns].values74    lnp = X @ modele["beta"]75    var = modele["sigma"] ** 2 + np.einsum("ij,jk,ik->i", X, modele["V_hc1"], X)76    z = norm.ppf(0.5 + niveau / 2)77    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))})7879CARACTERISTIQUES_AJUSTEMENT = ["mois", "dist", "ln_S", "ln_T", "age", "qualite", "garage", "sous_sol", "piscine", "etages"]8081def ajustements_hedoniques(modele: dict, comps: pd.DataFrame, s: dict, date_eval: str | None = None) -> pd.DataFrame:82    """Ajustements monétaires de chaque comparable vers le sujet, dérivés du modèle hédonique :83    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é84    (conditions du marché d'abord, puis localisation, puis caractéristiques physiques). Signe du point de vue du sujet."""85    sd = sujet_df(s, date_eval); Xs = matrice(sd).iloc[0]86    Xc = matrice(comps); beta = pd.Series(modele["beta"], index=modele["X"].columns)87    groupes = {"conditions_marche": ["mois"], "localisation": ["dist_centre"] + [f"sect_{x}" for x in SECTEURS[1:]],88               "superficie": ["ln_S"], "terrain": ["ln_T"], "age": ["age", "age2_100"], "qualite": ["qualite"],89               "garage": ["garage"], "sous_sol": ["sous_sol"], "piscine": ["piscine"], "type": ["etages"]}90    rows = []91    for i, c in comps.iterrows():92        base = float(c["prix_vente"]); row = {"id_vente": c["id_vente"], "prix_vente": base}93        for g, cols in groupes.items():94            dlog = float(sum(beta[col] * (Xs[col] - Xc.loc[i, col]) for col in cols))95            adj = base * (np.exp(dlog) - 1.0)96            row[g] = adj; base += adj97        row["prix_ajuste"] = base98        row["ajust_brut_pct"] = sum(abs(row[g]) for g in groupes) / row["prix_vente"]99        row["ajust_net_pct"] = (base - row["prix_vente"]) / row["prix_vente"]100        rows.append(row)101    return pd.DataFrame(rows)102103def tout() -> dict:104    d = charger(); sel = selection(); m = hedonique(d["ventes"])105    adj = ajustements_hedoniques(m, sel["comparables"], d["sujet"])106    pred = predire(m, sujet_df(d["sujet"]))[0]; ip = intervalle_prediction(m, sujet_df(d["sujet"])).iloc[0]107    return {**d, "selection": sel, "modele": m, "ajustements": adj, "prediction_hedonique": float(pred), "intervalle": ip}108
Files · cd76781f94cbc7113926bd8a4bd920d14c17c982
Menu

Theme