Ch. 34 livré et validé (compilation isolée, tests, fusion des registres)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
70 changed files +3,828 −128
added
analysis/python/cases/ch37_ml.py
+411 −0
@@ -0,0 +1,411 @@ | ||
| 1 | +#!/usr/bin/env python3 | |
| 2 | +"""Chapitre 37 — Apprentissage automatique et explicabilité : exemple intégral sur les 600 ventes du cas 1 | |
| 3 | +(Gatineau, DONNÉES SYNTHÉTIQUES, graine 20261031). | |
| 4 | + | |
| 5 | +Le modèle hédonique de référence est `traite.cas1.hedonique` (semi-log, MCO, HC1) ; il n'est PAS redéfini ici. Ce script lui | |
| 6 | +oppose un arbre de régression, une forêt aléatoire (Breiman 2001), un gradient boosting (Friedman 2001 ; scikit-learn) et XGBoost | |
| 7 | +(Chen et Guestrin 2016), sous trois schémas de validation : plis aléatoires (les MÊMES dix plis que `ch18_hedonique.validation_croisee`, | |
| 8 | +graine 20261031), blocs spatiaux stratifiés par secteur (k-moyennes sur les coordonnées), et partition temporelle (ventes de 2026 | |
| 9 | +prédites à partir de 2023-2025). Il ajoute la validation « un secteur exclu » (extrapolation hors support), un scénario où la loi de | |
| 10 | +génération est perturbée par une interaction et une rupture déclarées (le boosting y gagne), les valeurs SHAP (TreeSHAP) du boosting, | |
| 11 | +la dépendance partielle, l'importance par permutation, la prédiction du sujet par chaque modèle et les chiffres des exercices. | |
| 12 | +Usage (racine du dépôt) : PYTHONPATH=analysis/python python3 analysis/python/cases/ch37_ml.py""" | |
| 13 | +from __future__ import annotations | |
| 14 | +import sys, math, pathlib, warnings | |
| 15 | +import numpy as np, pandas as pd | |
| 16 | +sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1])) | |
| 17 | +from traite import cas1 | |
| 18 | +from traite import hedonique as h | |
| 19 | + | |
| 20 | +warnings.filterwarnings("ignore", category=FutureWarning) | |
| 21 | +ROOT = pathlib.Path(__file__).resolve().parents[3] | |
| 22 | +GRAINE = 20261031 | |
| 23 | +N_ARBRES_FORET = 500 | |
| 24 | + | |
| 25 | +# ----------------------------------------------------------------------------- paramètres de génération (README synthétique) | |
| 26 | +_g = (1 + 9.06 / 100) ** 0.5 - 1 | |
| 27 | +VRAIS = {"ln_S": 0.55, "ln_T": 0.10, "age": -0.0055, "age2": 0.00003, "qualite": 0.06, "garage": 0.035, "sous_sol": 0.03, "piscine": 0.02, | |
| 28 | + "etages": 0.02, "dist_centre": -0.006, "sigma": 0.085, "mois": math.log1p(_g) / 12, | |
| 29 | + "sect": {"Hull": 0.0, "Gatineau": math.log(464_900 / 475_000), "Aylmer": math.log(530_000 / 475_000), | |
| 30 | + "Buckingham": math.log(395_000 / 475_000), "Masson-Angers": math.log(395_000 / 475_000) - 0.02}} | |
| 31 | +# scénario non linéaire déclaré (section 37.6) : prime « patrimoine rénové » et « mur de la mi-vie » | |
| 32 | +SCENARIO = {"prime_qualite_age": 0.12, "qualite_min": 4, "age_min_prime": 40, "mur": -0.10, "age_mur": (25, 45)} | |
| 33 | + | |
| 34 | +# ----------------------------------------------------------------------------- régresseurs | |
| 35 | +VARIABLES_ML = ["superficie", "terrain", "age", "qualite", "garage", "sous_sol", "piscine", "etages", "dist_centre", "lat", "lon", "mois"] + \ | |
| 36 | + [f"sect_{s}" for s in cas1.SECTEURS] | |
| 37 | +ETIQUETTES = {"superficie": "Superficie habitable (m²)", "terrain": "Terrain (m²)", "age": "Âge (années)", "qualite": "Qualité (1-5)", | |
| 38 | + "garage": "Garage (places)", "sous_sol": "Sous-sol fini", "piscine": "Piscine", "etages": "À étages", "dist_centre": "Distance au centre (km)", | |
| 39 | + "lat": "Latitude", "lon": "Longitude", "mois": "Mois depuis 2024-07", "sect_Hull": "Secteur Hull", "sect_Gatineau": "Secteur Gatineau", | |
| 40 | + "sect_Aylmer": "Secteur Aylmer", "sect_Buckingham": "Secteur Buckingham", "sect_Masson-Angers": "Secteur Masson-Angers"} | |
| 41 | + | |
| 42 | +def matrice_ml(df: pd.DataFrame, s: dict | None = None) -> pd.DataFrame: | |
| 43 | + """Régresseurs bruts pour les méthodes à base d'arbres : aucune transformation (les arbres sont invariants aux transformations | |
| 44 | + monotones), secteur en indicatrices complètes, coordonnées brutes, temps en mois depuis la date de référence du jeu.""" | |
| 45 | + X = pd.DataFrame(index=df.index) | |
| 46 | + X["superficie"] = df["superficie_habitable_m2"].astype(float); X["terrain"] = df["superficie_terrain_m2"].astype(float) | |
| 47 | + X["age"] = df["age"].astype(float); X["qualite"] = df["qualite_1a5"].astype(float); X["garage"] = df["garage_places"].astype(float) | |
| 48 | + X["sous_sol"] = df["sous_sol_fini"].astype(float); X["piscine"] = df["piscine"].astype(float) | |
| 49 | + X["etages"] = (df["type"] == "à étages").astype(float); X["dist_centre"] = df["dist_centre_km"].astype(float) | |
| 50 | + X["lat"] = df["lat"].astype(float); X["lon"] = df["lon"].astype(float) | |
| 51 | + d = df["date_vente"] if "date_vente" in df else pd.Series([pd.Timestamp(s["date_evaluation"])] * len(df), index=df.index) | |
| 52 | + X["mois"] = ((pd.to_datetime(d) - cas1.REF_DATE).dt.days / 30.4375).astype(float) | |
| 53 | + for sct in cas1.SECTEURS: | |
| 54 | + X[f"sect_{sct}"] = (df["secteur"] == sct).astype(float) | |
| 55 | + return X[VARIABLES_ML] | |
| 56 | + | |
| 57 | +# ----------------------------------------------------------------------------- modèles | |
| 58 | +class Hedonique: | |
| 59 | + """Enveloppe du modèle hédonique de référence (traite.hedonique.mco sur traite.cas1.matrice) avec l'interface fit/predict.""" | |
| 60 | + def __init__(self, colonnes_exclues=()): self.excl = list(colonnes_exclues) | |
| 61 | + def fit(self, X: pd.DataFrame, y): | |
| 62 | + self.cols = [c for c in X.columns if c not in self.excl] | |
| 63 | + self.r = h.mco(X[self.cols].values, np.asarray(y, float)); self.beta = self.r["beta"]; return self | |
| 64 | + def predict(self, X: pd.DataFrame): return X[self.cols].values @ self.beta | |
| 65 | + | |
| 66 | +def _foret(**k): | |
| 67 | + from sklearn.ensemble import RandomForestRegressor | |
| 68 | + return RandomForestRegressor(**{**dict(n_estimators=N_ARBRES_FORET, min_samples_leaf=2, max_features=0.5, random_state=GRAINE, n_jobs=-1), **k}) | |
| 69 | + | |
| 70 | +def _boosting(**k): | |
| 71 | + from sklearn.ensemble import GradientBoostingRegressor | |
| 72 | + return GradientBoostingRegressor(**{**dict(n_estimators=1200, learning_rate=0.03, max_depth=2, subsample=0.8, min_samples_leaf=5, random_state=GRAINE), **k}) | |
| 73 | + | |
| 74 | +def _xgboost(**k): | |
| 75 | + import xgboost as xgb | |
| 76 | + return xgb.XGBRegressor(**{**dict(n_estimators=1200, learning_rate=0.03, max_depth=2, subsample=0.8, colsample_bytree=0.8, min_child_weight=5, | |
| 77 | + reg_lambda=1.0, random_state=GRAINE, n_jobs=4, verbosity=0), **k}) | |
| 78 | + | |
| 79 | +def _arbre(**k): | |
| 80 | + from sklearn.tree import DecisionTreeRegressor | |
| 81 | + return DecisionTreeRegressor(**{**dict(max_depth=3, min_samples_leaf=5, random_state=GRAINE), **k}) | |
| 82 | + | |
| 83 | +HYPERPARAMETRES = {"Arbre (profondeur 3)": "profondeur 3, feuilles ≥ 5", "Forêt aléatoire": f"{N_ARBRES_FORET} arbres, feuilles ≥ 2, 50 % des variables par division, bootstrap", | |
| 84 | + "Gradient boosting": "1 200 arbres de profondeur 2, ν = 0,03, sous-échantillon 80 %, feuilles ≥ 5", | |
| 85 | + "XGBoost": "1 200 arbres de profondeur 2, ν = 0,03, sous-échantillon 80 %, 80 % des colonnes, poids min. 5, λ = 1"} | |
| 86 | +MODELES = {"Hédonique (référence)": ("hedo", Hedonique), "Arbre (profondeur 3)": ("ml", _arbre), "Forêt aléatoire": ("ml", _foret), | |
| 87 | + "Gradient boosting": ("ml", _boosting), "XGBoost": ("ml", _xgboost)} | |
| 88 | + | |
| 89 | +# ----------------------------------------------------------------------------- plis | |
| 90 | +def plis_aleatoires(n: int, nfolds: int = 10, graine: int = GRAINE) -> list[np.ndarray]: | |
| 91 | + """Les mêmes dix plis que ch18_hedonique.validation_croisee (même graine, même permutation, même découpage).""" | |
| 92 | + rng = np.random.default_rng(graine); idx = rng.permutation(n); return list(np.array_split(idx, nfolds)) | |
| 93 | + | |
| 94 | +def plis_spatiaux(v: pd.DataFrame, par_secteur: int = 2, graine: int = GRAINE) -> tuple[list[np.ndarray], np.ndarray]: | |
| 95 | + """Blocs spatiaux stratifiés : dans chaque secteur, k-moyennes (k = 2) sur les coordonnées en km → 10 blocs contigus. | |
| 96 | + Chaque secteur reste représenté à l'estimation (par l'autre moitié), mais le voisinage immédiat d'une vente testée ne l'est jamais.""" | |
| 97 | + from sklearn.cluster import KMeans | |
| 98 | + km = h.coordonnees_km(v["lat"].to_numpy(), v["lon"].to_numpy()); bloc = np.zeros(len(v), int); b = 0 | |
| 99 | + for sct in cas1.SECTEURS: | |
| 100 | + ii = np.where(v["secteur"].to_numpy() == sct)[0] | |
| 101 | + lab = KMeans(par_secteur, n_init=10, random_state=graine).fit_predict(km[ii]); bloc[ii] = b + lab; b += par_secteur | |
| 102 | + return [np.where(bloc == j)[0] for j in range(b)], bloc | |
| 103 | + | |
| 104 | +def plis_secteurs(v: pd.DataFrame) -> list[np.ndarray]: | |
| 105 | + """Un secteur exclu à la fois (extrapolation hors support spatial).""" | |
| 106 | + return [np.where(v["secteur"].to_numpy() == sct)[0] for sct in cas1.SECTEURS] | |
| 107 | + | |
| 108 | +def partition_temporelle(v: pd.DataFrame, date: str = "2026-01-01") -> tuple[np.ndarray, np.ndarray]: | |
| 109 | + test = np.where(v["date_vente"] >= pd.Timestamp(date))[0]; return np.setdiff1d(np.arange(len(v)), test), test | |
| 110 | + | |
| 111 | +# ----------------------------------------------------------------------------- métriques | |
| 112 | +def metriques(pred_log: np.ndarray, y: np.ndarray, facteur: float = 1.0) -> dict: | |
| 113 | + """RMSE du logarithme, R² hors échantillon, biais moyen (log), RMSE en dollars, MdAPE, statistiques de ratio IAAO | |
| 114 | + (traite.hedonique.statistiques_ratio). `facteur` : correction de retransformation (Duan) appliquée aux prédictions en niveau.""" | |
| 115 | + m = ~np.isnan(pred_log); e = pred_log[m] - y[m]; P = np.exp(y[m]); V = np.exp(pred_log[m]) * facteur | |
| 116 | + r = h.statistiques_ratio(V, P) | |
| 117 | + return {"n": int(m.sum()), "rmse_log": float(np.sqrt(np.mean(e ** 2))), "biais_log": float(e.mean()), | |
| 118 | + "r2_hors": float(1 - np.sum(e ** 2) / np.sum((y[m] - y[m].mean()) ** 2)), "rmse_dollars": float(np.sqrt(np.mean((V - P) ** 2))), | |
| 119 | + "mdape": float(np.median(np.abs(V / P - 1)) * 100), "COD": r["COD"], "PRD": r["PRD"], "PRB": r["PRB"], "PRB_se": r["PRB_se"], | |
| 120 | + "ratio_median": r["ratio_median"]} | |
| 121 | + | |
| 122 | +def valider(constructeur, genre: str, Xh: pd.DataFrame, Xm: pd.DataFrame, y: np.ndarray, plis: list[np.ndarray]) -> np.ndarray: | |
| 123 | + """Prédictions hors échantillon (chaque pli prédit par un modèle estimé sans lui).""" | |
| 124 | + X = Xh if genre == "hedo" else Xm; n = len(y); pred = np.full(n, np.nan); tous = np.arange(n) | |
| 125 | + for f in plis: | |
| 126 | + tr = np.setdiff1d(tous, f); m = constructeur(); m.fit(X.iloc[tr], y[tr]); pred[f] = np.asarray(m.predict(X.iloc[f]), float) | |
| 127 | + return pred | |
| 128 | + | |
| 129 | +def facteur_duan(pred_log: np.ndarray, y: np.ndarray) -> float: | |
| 130 | + """Facteur de retransformation estimé sur les résidus hors échantillon (honnête pour les modèles qui surajustent en échantillon).""" | |
| 131 | + m = ~np.isnan(pred_log); return float(np.mean(np.exp(y[m] - pred_log[m]))) | |
| 132 | + | |
| 133 | +def comparaison(v: pd.DataFrame, Xh: pd.DataFrame, Xm: pd.DataFrame, y: np.ndarray) -> dict: | |
| 134 | + """Trois schémas de validation pour les cinq modèles. Retourne les prédictions hors échantillon et les métriques.""" | |
| 135 | + n = len(y); pa = plis_aleatoires(n); ps, bloc = plis_spatiaux(v); tr_t, te_t = partition_temporelle(v) | |
| 136 | + out = {"plis": {"aleatoire": pa, "spatial": ps, "bloc_spatial": bloc, "temporel": (tr_t, te_t)}, "pred": {}, "metriques": {}, "duan": {}} | |
| 137 | + for nom, (genre, ctor) in MODELES.items(): | |
| 138 | + p_a = valider(ctor, genre, Xh, Xm, y, pa); F = facteur_duan(p_a, y) | |
| 139 | + p_s = valider(ctor, genre, Xh, Xm, y, ps) | |
| 140 | + p_t = valider(ctor, genre, Xh, Xm, y, [te_t]) # un seul pli de test : les ventes de 2026 | |
| 141 | + out["pred"][nom] = {"aleatoire": p_a, "spatial": p_s, "temporel": p_t}; out["duan"][nom] = F | |
| 142 | + out["metriques"][nom] = {"aleatoire": metriques(p_a, y, F), "spatial": metriques(p_s, y, F), "temporel": metriques(p_t, y, F)} | |
| 143 | + # biais temporel par mois de 2026 pour le boosting et l'hédonique | |
| 144 | + mois = Xm["mois"].to_numpy() | |
| 145 | + out["biais_mensuel_2026"] = {nom: pd.Series(out["pred"][nom]["temporel"][te_t] - y[te_t]).groupby(np.floor(mois[te_t])).mean().to_dict() | |
| 146 | + for nom in ("Hédonique (référence)", "Gradient boosting", "Forêt aléatoire")} | |
| 147 | + return out | |
| 148 | + | |
| 149 | +def boosting_detendance(v: pd.DataFrame, Xh: pd.DataFrame, Xm: pd.DataFrame, y: np.ndarray) -> dict: | |
| 150 | + """Remède à l'extrapolation temporelle : boosting estimé sur ln P − β̂_mois·mois (tendance de l'hédonique estimée sur l'entraînement), | |
| 151 | + sans la variable mois, puis tendance rajoutée. Variante « feuilles ≥ 1 » pour montrer l'effet de la feuille extrême.""" | |
| 152 | + tr, te = partition_temporelle(v); n = len(y); cols = list(Xh.columns) | |
| 153 | + hd = Hedonique().fit(Xh.iloc[tr], y[tr]); bm = hd.beta[cols.index("mois")]; mois = Xm["mois"].to_numpy() | |
| 154 | + Xs = Xm.drop(columns=["mois"]) | |
| 155 | + g = _boosting().fit(Xs.iloc[tr], y[tr] - bm * mois[tr]); p = np.full(n, np.nan); p[te] = g.predict(Xs.iloc[te]) + bm * mois[te] | |
| 156 | + g1 = _boosting(min_samples_leaf=1).fit(Xm.iloc[tr], y[tr]); p1 = np.full(n, np.nan); p1[te] = g1.predict(Xm.iloc[te]) | |
| 157 | + return {"beta_mois_entrainement": float(bm), "pred_detendance": p, "metriques_detendance": metriques(p, y), "pred_feuille1": p1, | |
| 158 | + "metriques_feuille1": metriques(p1, y), "n_entrainement_apres_mois_17": int((mois[tr] > 17).sum())} | |
| 159 | + | |
| 160 | +def validation_secteurs(v: pd.DataFrame, Xh: pd.DataFrame, Xm: pd.DataFrame, y: np.ndarray) -> dict: | |
| 161 | + """Un secteur exclu à la fois. L'hédonique n'a aucune information sur un secteur absent : son indicatrice est retirée et le secteur | |
| 162 | + est prédit au niveau du secteur de référence (Hull) ; si Hull est exclu, Gatineau devient la référence. Ce choix est un JUGEMENT | |
| 163 | + de l'analyste, explicite ; les arbres font implicitement le leur à partir des coordonnées et de la distance au centre.""" | |
| 164 | + n = len(y); plis = plis_secteurs(v); out = {"pred": {}, "metriques": {}, "biais_secteur": {}} | |
| 165 | + for nom, (genre, ctor) in MODELES.items(): | |
| 166 | + pred = np.full(n, np.nan) | |
| 167 | + for sct, f in zip(cas1.SECTEURS, plis): | |
| 168 | + tr = np.setdiff1d(np.arange(n), f) | |
| 169 | + if genre == "hedo": | |
| 170 | + drop = f"sect_{sct}" if sct != "Hull" else "sect_Gatineau"; m = Hedonique([drop]).fit(Xh.iloc[tr], y[tr]); pred[f] = m.predict(Xh.iloc[f]) | |
| 171 | + else: | |
| 172 | + m = ctor(); m.fit(Xm.iloc[tr], y[tr]); pred[f] = m.predict(Xm.iloc[f]) | |
| 173 | + out["pred"][nom] = pred; out["metriques"][nom] = metriques(pred, y) | |
| 174 | + out["biais_secteur"][nom] = {sct: float(np.mean(pred[f] - y[f])) for sct, f in zip(cas1.SECTEURS, plis)} | |
| 175 | + return out | |
| 176 | + | |
| 177 | +# ----------------------------------------------------------------------------- scénario non linéaire déclaré | |
| 178 | +def perturbation_scenario(v: pd.DataFrame) -> np.ndarray: | |
| 179 | + """ln P† = ln P + 0,12·1[qualité ≥ 4 et âge ≥ 40] − 0,10·1[25 ≤ âge ≤ 45] (hypothèse pédagogique déclarée).""" | |
| 180 | + q = v["qualite_1a5"].to_numpy(float); a = v["age"].to_numpy(float); S = SCENARIO | |
| 181 | + return S["prime_qualite_age"] * ((q >= S["qualite_min"]) & (a >= S["age_min_prime"])) + S["mur"] * ((a >= S["age_mur"][0]) & (a <= S["age_mur"][1])) | |
| 182 | + | |
| 183 | +def scenario_nonlineaire(v: pd.DataFrame, Xh: pd.DataFrame, Xm: pd.DataFrame, y: np.ndarray) -> dict: | |
| 184 | + """Mêmes plis aléatoires ; hédonique de référence (mal spécifié désormais), forêt, boosting, XGBoost, et hédonique respécifié | |
| 185 | + avec les deux indicatrices (ce que l'analyste ferait après avoir lu la dépendance partielle de l'âge et les interactions SHAP).""" | |
| 186 | + delta = perturbation_scenario(v); y2 = y + delta; pa = plis_aleatoires(len(y)) | |
| 187 | + q = v["qualite_1a5"].to_numpy(float); a = v["age"].to_numpy(float); S = SCENARIO | |
| 188 | + Xh2 = Xh.copy(); Xh2["prime"] = ((q >= S["qualite_min"]) & (a >= S["age_min_prime"])).astype(float); Xh2["mur"] = ((a >= S["age_mur"][0]) & (a <= S["age_mur"][1])).astype(float) | |
| 189 | + out = {"delta": delta, "y": y2, "n_prime": int(Xh2["prime"].sum()), "n_mur": int(Xh2["mur"].sum()), "pred": {}, "metriques": {}, "Xh2": Xh2} | |
| 190 | + for nom, (genre, ctor) in MODELES.items(): | |
| 191 | + if nom == "Arbre (profondeur 3)": continue | |
| 192 | + p = valider(ctor, genre, Xh, Xm, y2, pa); out["pred"][nom] = p; out["metriques"][nom] = metriques(p, y2, facteur_duan(p, y2)) | |
| 193 | + p = valider(Hedonique, "hedo", Xh2, Xm, y2, pa); out["pred"]["Hédonique respécifié"] = p; out["metriques"]["Hédonique respécifié"] = metriques(p, y2, facteur_duan(p, y2)) | |
| 194 | + r = Hedonique().fit(Xh2, y2); cols = list(Xh2.columns) | |
| 195 | + out["coef_respecifie"] = {"prime": float(r.beta[cols.index("prime")]), "mur": float(r.beta[cols.index("mur")]), "se_prime": float(r.r["se_hc1"][cols.index("prime")]), | |
| 196 | + "se_mur": float(r.r["se_hc1"][cols.index("mur")]), "sigma": float(np.sqrt(r.r["sigma2"])), "r2": float(r.r["r2"])} | |
| 197 | + out["gbm_scenario"] = _boosting().fit(Xm, y2) | |
| 198 | + out["hedo_scenario"] = Hedonique().fit(Xh, y2) | |
| 199 | + return out | |
| 200 | + | |
| 201 | +# ----------------------------------------------------------------------------- explicabilité | |
| 202 | +def shap_boosting(gbm, Xm: pd.DataFrame, xs: pd.DataFrame) -> dict: | |
| 203 | + """TreeSHAP (Lundberg et al. 2020) du gradient boosting : valeurs SHAP des 600 ventes, du sujet, valeur de base, importance | |
| 204 | + moyenne |φ|, interactions SHAP moyennes. Vérifie l'axiome d'efficacité (somme des φ + base = prédiction).""" | |
| 205 | + import shap | |
| 206 | + ex = shap.TreeExplainer(gbm); sv = np.asarray(ex.shap_values(Xm)); base = float(np.ravel(ex.expected_value)[0]) | |
| 207 | + svs = np.asarray(ex.shap_values(xs))[0] | |
| 208 | + si = np.asarray(ex.shap_interaction_values(Xm)); inter = np.abs(si).mean(0).copy(); np.fill_diagonal(inter, 0.0) | |
| 209 | + inter_df = pd.DataFrame(inter, index=Xm.columns, columns=Xm.columns) | |
| 210 | + paires = inter_df.where(np.triu(np.ones_like(inter, bool), 1)).stack().sort_values(ascending=False) | |
| 211 | + return {"valeurs": sv, "base": base, "sujet": pd.Series(svs, index=Xm.columns), "importance": pd.Series(np.abs(sv).mean(0), index=Xm.columns).sort_values(ascending=False), | |
| 212 | + "interactions": paires, "efficacite_max_err": float(np.abs(sv.sum(1) + base - gbm.predict(Xm)).max()), | |
| 213 | + "efficacite_sujet_err": float(abs(svs.sum() + base - gbm.predict(xs)[0]))} | |
| 214 | + | |
| 215 | +def importance_permutation(Xh: pd.DataFrame, Xm: pd.DataFrame, y: np.ndarray, plis: list[np.ndarray], repetitions: int = 10, graine: int = GRAINE) -> pd.DataFrame: | |
| 216 | + """Importance par permutation (Breiman 2001) mesurée HORS échantillon : pour chaque pli, la variable j est permutée dans les | |
| 217 | + ventes testées et l'accroissement de l'erreur quadratique (log) est moyenné. Boosting et forêt.""" | |
| 218 | + rng = np.random.default_rng(graine); n = len(y); res = {} | |
| 219 | + for nom, ctor in (("Gradient boosting", _boosting), ("Forêt aléatoire", _foret)): | |
| 220 | + gain = np.zeros(Xm.shape[1]); base_err = 0.0 | |
| 221 | + for f in plis: | |
| 222 | + tr = np.setdiff1d(np.arange(n), f); m = ctor(); m.fit(Xm.iloc[tr], y[tr]); Xt = Xm.iloc[f].to_numpy(); e0 = np.mean((m.predict(Xt) - y[f]) ** 2); base_err += e0 * len(f) / n | |
| 223 | + for j in range(Xm.shape[1]): | |
| 224 | + acc = 0.0 | |
| 225 | + for _ in range(repetitions): | |
| 226 | + Xp = Xt.copy(); Xp[:, j] = rng.permutation(Xp[:, j]); acc += np.mean((m.predict(Xp) - y[f]) ** 2) | |
| 227 | + gain[j] += (acc / repetitions - e0) * len(f) / n | |
| 228 | + res[nom] = pd.Series(gain, index=Xm.columns); res[nom + " (EQM de base)"] = base_err | |
| 229 | + return res | |
| 230 | + | |
| 231 | +def dependance_partielle(modele, X: pd.DataFrame, variable: str, grille: np.ndarray) -> np.ndarray: | |
| 232 | + """PDP (Friedman 2001) : moyenne des prédictions lorsque la variable est fixée à chaque valeur de la grille, les autres variables | |
| 233 | + gardant leurs valeurs observées. Retourne la courbe en logarithme du prix.""" | |
| 234 | + out = np.empty(len(grille)) | |
| 235 | + for i, g in enumerate(grille): | |
| 236 | + Xg = X.copy(); Xg[variable] = g; out[i] = float(np.mean(modele.predict(Xg))) | |
| 237 | + return out | |
| 238 | + | |
| 239 | +def pdp_vraie(v: pd.DataFrame, variable: str, grille: np.ndarray, scenario: bool = False) -> np.ndarray: | |
| 240 | + """Dépendance partielle de la LOI DE GÉNÉRATION (connue) : utile pour juger les modèles sur données synthétiques.""" | |
| 241 | + a0 = v["age"].to_numpy(float); S0 = v["superficie_habitable_m2"].to_numpy(float); q = v["qualite_1a5"].to_numpy(float); out = np.empty(len(grille)) | |
| 242 | + for i, g in enumerate(grille): | |
| 243 | + a = np.full_like(a0, g) if variable == "age" else a0; S = np.full_like(S0, g) if variable == "superficie" else S0 | |
| 244 | + f = VRAIS["ln_S"] * np.log(S / 140.0) + VRAIS["age"] * a + VRAIS["age2"] * a ** 2 | |
| 245 | + if scenario: | |
| 246 | + f = f + SCENARIO["prime_qualite_age"] * ((q >= SCENARIO["qualite_min"]) & (a >= SCENARIO["age_min_prime"])) + SCENARIO["mur"] * ((a >= SCENARIO["age_mur"][0]) & (a <= SCENARIO["age_mur"][1])) | |
| 247 | + out[i] = float(np.mean(f)) | |
| 248 | + return out | |
| 249 | + | |
| 250 | +def courbes_apprentissage(Xm: pd.DataFrame, y: np.ndarray, plis: list[np.ndarray], profondeurs=(1, 2, 4)) -> dict: | |
| 251 | + """Erreur quadratique (log) d'entraînement et de validation croisée selon le nombre d'arbres du boosting (staged_predict), pour | |
| 252 | + plusieurs profondeurs : surapprentissage et arrêt précoce.""" | |
| 253 | + n = len(y); out = {} | |
| 254 | + for d in profondeurs: | |
| 255 | + tr_err = np.zeros(1200); te_err = np.zeros(1200) | |
| 256 | + for f in plis: | |
| 257 | + tr = np.setdiff1d(np.arange(n), f); m = _boosting(max_depth=d).fit(Xm.iloc[tr], y[tr]) | |
| 258 | + for k, (pt, pv) in enumerate(zip(m.staged_predict(Xm.iloc[tr]), m.staged_predict(Xm.iloc[f]))): | |
| 259 | + tr_err[k] += np.mean((pt - y[tr]) ** 2) / len(plis); te_err[k] += np.mean((pv - y[f]) ** 2) * len(f) / n | |
| 260 | + out[d] = {"entrainement": np.sqrt(tr_err), "validation": np.sqrt(te_err), "optimum": int(np.argmin(te_err)) + 1, "rmse_min": float(np.sqrt(te_err.min()))} | |
| 261 | + return out | |
| 262 | + | |
| 263 | +def sensibilite_hyperparametres(Xm: pd.DataFrame, y: np.ndarray, plis: list[np.ndarray]) -> pd.DataFrame: | |
| 264 | + """Grille d'hyperparamètres du boosting (profondeur, taux d'apprentissage, nombre d'arbres, taille minimale des feuilles) et de la | |
| 265 | + forêt (feuilles, variables par division) : RMSE log et COD en validation croisée aléatoire.""" | |
| 266 | + lignes = [] | |
| 267 | + grille_b = [dict(max_depth=1, n_estimators=1200), dict(max_depth=2, n_estimators=300), dict(max_depth=2, n_estimators=1200), dict(max_depth=2, n_estimators=3000), | |
| 268 | + dict(max_depth=3, n_estimators=1200), dict(max_depth=4, n_estimators=1200), dict(max_depth=2, n_estimators=1200, learning_rate=0.1), | |
| 269 | + dict(max_depth=2, n_estimators=1200, min_samples_leaf=1), dict(max_depth=2, n_estimators=1200, subsample=1.0)] | |
| 270 | + for k in grille_b: | |
| 271 | + p = valider(lambda: _boosting(**k), "ml", None, Xm, y, plis); m = metriques(p, y, facteur_duan(p, y)) | |
| 272 | + lignes.append({"modele": "Gradient boosting", "profondeur": k.get("max_depth", 2), "arbres": k.get("n_estimators", 1200), "nu": k.get("learning_rate", 0.03), | |
| 273 | + "feuille_min": k.get("min_samples_leaf", 5), "sous_ech": k.get("subsample", 0.8), "variables": "toutes", **m}) | |
| 274 | + for k in [dict(min_samples_leaf=1, max_features=1.0), dict(min_samples_leaf=2, max_features=0.5), dict(min_samples_leaf=5, max_features=0.33), dict(min_samples_leaf=2, max_features=0.5, n_estimators=100)]: | |
| 275 | + p = valider(lambda: _foret(**k), "ml", None, Xm, y, plis); m = metriques(p, y, facteur_duan(p, y)) | |
| 276 | + lignes.append({"modele": "Forêt aléatoire", "profondeur": "max", "arbres": k.get("n_estimators", N_ARBRES_FORET), "nu": np.nan, "feuille_min": k["min_samples_leaf"], | |
| 277 | + "sous_ech": "bootstrap", "variables": f"{int(round(k['max_features'] * 100))} %", **m}) | |
| 278 | + return pd.DataFrame(lignes) | |
| 279 | + | |
| 280 | +# ----------------------------------------------------------------------------- sujet | |
| 281 | +def prediction_sujet(modeles_ajustes: dict, duan: dict, Xh_s: pd.DataFrame, Xm_s: pd.DataFrame, ref: dict, s: dict) -> pd.DataFrame: | |
| 282 | + """Prédiction du sujet par chaque modèle estimé sur les 600 ventes : logarithme, niveau avec correction de retransformation | |
| 283 | + (facteur hors échantillon ; pour l'hédonique, traite.cas1.predire donne la valeur de référence 637 953 $).""" | |
| 284 | + rows = [] | |
| 285 | + for nom, m in modeles_ajustes.items(): | |
| 286 | + lp = float(m.predict(Xh_s if nom.startswith("Hédonique") else Xm_s)[0]); F = duan[nom] | |
| 287 | + rows.append({"modele": nom, "ln_pred": lp, "naif": math.exp(lp), "facteur": F, "prediction": math.exp(lp) * F}) | |
| 288 | + df = pd.DataFrame(rows).set_index("modele") | |
| 289 | + df.loc["Hédonique (référence)", "prediction_cas1"] = float(cas1.predire(ref, cas1.sujet_df(s))[0]) | |
| 290 | + return df | |
| 291 | + | |
| 292 | +# ----------------------------------------------------------------------------- exercices | |
| 293 | +def exercices(v: pd.DataFrame, comp: dict, secteurs: dict, scen: dict, sh: dict) -> dict: | |
| 294 | + e = {} | |
| 295 | + # 37.1 : division d'un nœud sur huit ventes (ln P, superficie) — réduction de la somme des carrés | |
| 296 | + sub = v.sort_values("id_vente").iloc[:8]; lnP = np.log(sub["prix_vente"].to_numpy(float)); S = sub["superficie_habitable_m2"].to_numpy(float) | |
| 297 | + ordre = np.argsort(S); S, lnP = S[ordre], lnP[ordre]; sse0 = float(np.sum((lnP - lnP.mean()) ** 2)); best = None; essais = [] | |
| 298 | + for k in range(1, 8): | |
| 299 | + g, d = lnP[:k], lnP[k:]; sse = float(np.sum((g - g.mean()) ** 2) + np.sum((d - d.mean()) ** 2)); seuil = float((S[k - 1] + S[k]) / 2) | |
| 300 | + essais.append({"seuil": seuil, "sse": sse, "gain": sse0 - sse}) | |
| 301 | + if best is None or sse < best["sse"]: best = {"seuil": seuil, "sse": sse, "gain": sse0 - sse, "k": k, "c_gauche": float(g.mean()), "c_droite": float(d.mean())} | |
| 302 | + e["1"] = {"ids": list(sub.sort_values("superficie_habitable_m2")["id_vente"]), "S": S.tolist(), "lnP": lnP.tolist(), "prix": sub.sort_values("superficie_habitable_m2")["prix_vente"].tolist(), | |
| 303 | + "sse0": sse0, "essais": essais, "best": best} | |
| 304 | + # 37.2 : variance d'une moyenne d'arbres corrélés | |
| 305 | + s2, rho = 0.12 ** 2, 0.30; e["2"] = {"sigma": 0.12, "rho": rho, **{f"B{B}": rho * s2 + (1 - rho) * s2 / B for B in (1, 10, 100, 500)}, "limite": rho * s2, "ecart_type_limite": math.sqrt(rho * s2), | |
| 306 | + "B_pour_1pct": (1 - rho) / 0.01 / rho if rho else np.inf} | |
| 307 | + # 37.3 : boosting à la main | |
| 308 | + nu = 0.1; F = 12.80; yv = 13.00; traj = [F] | |
| 309 | + for _ in range(3): F = F + nu * (yv - F); traj.append(F) | |
| 310 | + e["3"] = {"nu": nu, "F": traj, "residus": [yv - t for t in traj], "m_1pct": math.log(0.01) / math.log(1 - nu), "F_nu1": yv} | |
| 311 | + # 37.4 : valeurs de Shapley d'un jeu à trois joueurs (S, Q, L) ; v(S) en milliers de dollars au-dessus de la base | |
| 312 | + vS = {(): 0.0, ("S",): 60.0, ("Q",): 30.0, ("L",): 20.0, ("S", "Q"): 100.0, ("S", "L"): 85.0, ("Q", "L"): 45.0, ("S", "Q", "L"): 130.0} | |
| 313 | + import itertools | |
| 314 | + joueurs = ["S", "Q", "L"]; phi = {} | |
| 315 | + for j in joueurs: | |
| 316 | + tot = 0.0 | |
| 317 | + for perm in itertools.permutations(joueurs): | |
| 318 | + avant = tuple(sorted(perm[:perm.index(j)])); apres = tuple(sorted(avant + (j,))) | |
| 319 | + tot += vS[apres] - vS[avant] | |
| 320 | + phi[j] = tot / 6 | |
| 321 | + e["4"] = {"v": {"+".join(k) if k else "∅": val for k, val in vS.items()}, "phi": phi, "somme": sum(phi.values()), "total": vS[("S", "Q", "L")]} | |
| 322 | + # 37.5 : dix prédictions d'un AVM fictif | |
| 323 | + prix = np.array([312000, 358500, 401000, 445000, 489500, 532000, 598000, 655000, 742000, 895000], float) | |
| 324 | + pred = np.array([335000, 349000, 388000, 468000, 471000, 561000, 575000, 702000, 718000, 839000], float) | |
| 325 | + r = h.statistiques_ratio(pred, prix); el = np.log(pred / prix) | |
| 326 | + e["5"] = {"prix": prix.tolist(), "pred": pred.tolist(), "ratios": (pred / prix).tolist(), "rmse_log": float(np.sqrt(np.mean(el ** 2))), "mdape": float(np.median(np.abs(pred / prix - 1)) * 100), | |
| 327 | + "mape": float(np.mean(np.abs(pred / prix - 1)) * 100), **{k: float(val) for k, val in r.items()}} | |
| 328 | + # 37.6 : validation par secteur vs aléatoire (chiffres du script) | |
| 329 | + e["6"] = {nom: {"aleatoire": comp["metriques"][nom]["aleatoire"]["COD"], "spatial": comp["metriques"][nom]["spatial"]["COD"], "secteur": secteurs["metriques"][nom]["COD"]} | |
| 330 | + for nom in ("Hédonique (référence)", "Forêt aléatoire", "Gradient boosting")} | |
| 331 | + e["6"]["biais_secteur"] = secteurs["biais_secteur"] | |
| 332 | + # 37.7 : scénario — respécification | |
| 333 | + e["7"] = {nom: scen["metriques"][nom]["COD"] for nom in scen["metriques"]}; e["7"]["coef"] = scen["coef_respecifie"]; e["7"]["interactions_top"] = sh["scenario_interactions"].head(3).to_dict() | |
| 334 | + return e | |
| 335 | + | |
| 336 | +# ----------------------------------------------------------------------------- orchestration | |
| 337 | +def main(verbose: bool = True, complet: bool = True) -> dict: | |
| 338 | + d = cas1.charger(); v = d["ventes"].reset_index(drop=True); s = d["sujet"] | |
| 339 | + ref = cas1.hedonique(v); Xh = cas1.matrice(v); Xm = matrice_ml(v); y = np.log(v["prix_vente"].astype(float).to_numpy()) | |
| 340 | + sd = cas1.sujet_df(s); Xh_s = cas1.matrice(sd)[Xh.columns]; Xm_s = matrice_ml(sd, s) | |
| 341 | + comp = comparaison(v, Xh, Xm, y) | |
| 342 | + det = boosting_detendance(v, Xh, Xm, y) | |
| 343 | + sect = validation_secteurs(v, Xh, Xm, y) | |
| 344 | + scen = scenario_nonlineaire(v, Xh, Xm, y) | |
| 345 | + ajustes = {nom: (ctor().fit(Xh if genre == "hedo" else Xm, y)) for nom, (genre, ctor) in MODELES.items()} | |
| 346 | + sujet = prediction_sujet(ajustes, comp["duan"], Xh_s, Xm_s, ref, s) | |
| 347 | + sh = shap_boosting(ajustes["Gradient boosting"], Xm, Xm_s) | |
| 348 | + sh_sc = shap_boosting(scen["gbm_scenario"], Xm, Xm_s); sh["scenario_interactions"] = sh_sc["interactions"]; sh["scenario_importance"] = sh_sc["importance"] | |
| 349 | + pa = comp["plis"]["aleatoire"] | |
| 350 | + perm = importance_permutation(Xh, Xm, y, pa) if complet else None | |
| 351 | + grille_S = np.linspace(80, 260, 37); grille_A = np.linspace(1, 95, 48) | |
| 352 | + pdp = {"superficie": {"grille": grille_S, "Gradient boosting": dependance_partielle(ajustes["Gradient boosting"], Xm, "superficie", grille_S), | |
| 353 | + "Forêt aléatoire": dependance_partielle(ajustes["Forêt aléatoire"], Xm, "superficie", grille_S), | |
| 354 | + "Hédonique (référence)": dependance_partielle(ajustes["Hédonique (référence)"], Xh, "ln_S", np.log(grille_S / 140.0)), "vraie": pdp_vraie(v, "superficie", grille_S)}, | |
| 355 | + "age": {"grille": grille_A, "Gradient boosting": dependance_partielle(ajustes["Gradient boosting"], Xm, "age", grille_A), | |
| 356 | + "Forêt aléatoire": dependance_partielle(ajustes["Forêt aléatoire"], Xm, "age", grille_A), "vraie": pdp_vraie(v, "age", grille_A)}} | |
| 357 | + # hédonique : l'âge entre par deux colonnes (age, age2_100) → PDP par substitution conjointe | |
| 358 | + def pdp_hedo_age(modele, Xh_, grille): | |
| 359 | + out = np.empty(len(grille)) | |
| 360 | + for i, g in enumerate(grille): | |
| 361 | + Xg = Xh_.copy(); Xg["age"] = g; Xg["age2_100"] = g ** 2 / 100.0; out[i] = float(np.mean(modele.predict(Xg))) | |
| 362 | + return out | |
| 363 | + pdp["age"]["Hédonique (référence)"] = pdp_hedo_age(ajustes["Hédonique (référence)"], Xh, grille_A) | |
| 364 | + pdp["age_scenario"] = {"grille": grille_A, "Gradient boosting": dependance_partielle(scen["gbm_scenario"], Xm, "age", grille_A), | |
| 365 | + "Hédonique (référence)": pdp_hedo_age(scen["hedo_scenario"], Xh, grille_A), "vraie": pdp_vraie(v, "age", grille_A, scenario=True)} | |
| 366 | + courbes = courbes_apprentissage(Xm, y, pa) if complet else None | |
| 367 | + sens = sensibilite_hyperparametres(Xm, y, pa) if complet else None | |
| 368 | + ex = exercices(v, comp, sect, scen, sh) | |
| 369 | + out = {"ventes": v, "sujet": s, "modele_ref": ref, "Xh": Xh, "Xm": Xm, "y": y, "Xh_s": Xh_s, "Xm_s": Xm_s, "comparaison": comp, "detendance": det, "secteurs": sect, | |
| 370 | + "scenario": scen, "ajustes": ajustes, "sujet_pred": sujet, "shap": sh, "permutation": perm, "pdp": pdp, "courbes": courbes, "sensibilite": sens, "exercices": ex} | |
| 371 | + if verbose: | |
| 372 | + pd.set_option("display.width", 220); pd.set_option("display.max_columns", 30) | |
| 373 | + print("== Modèle hédonique de référence : R² %.3f, σ %.4f, n %d, k %d" % (ref["r2"], ref["sigma"], ref["n"], ref["k"])) | |
| 374 | + for schema in ("aleatoire", "spatial", "temporel"): | |
| 375 | + print(f"== Validation {schema} :") | |
| 376 | + for nom, m in comp["metriques"].items(): | |
| 377 | + mm = m[schema]; print(f" {nom:24s} n={mm['n']:3d} RMSElog={mm['rmse_log']:.4f} R²hors={mm['r2_hors']:.3f} biais={mm['biais_log']:+.4f} RMSE$={mm['rmse_dollars']:,.0f} MdAPE={mm['mdape']:.2f} COD={mm['COD']:.2f} PRD={mm['PRD']:.3f} PRB={mm['PRB']:.3f} méd={mm['ratio_median']:.3f} Duan={comp['duan'][nom]:.4f}") | |
| 378 | + print(" biais mensuel 2026 :", {k: {int(kk): round(vv, 3) for kk, vv in vals.items()} for k, vals in comp["biais_mensuel_2026"].items()}) | |
| 379 | + md = det["metriques_detendance"]; m1 = det["metriques_feuille1"] | |
| 380 | + print(f"== Boosting détendancé (test 2026) : biais={md['biais_log']:+.4f} RMSElog={md['rmse_log']:.4f} COD={md['COD']:.2f} MdAPE={md['mdape']:.2f} ; β_mois entraînement {det['beta_mois_entrainement']:.5f} ; n(mois>17)={det['n_entrainement_apres_mois_17']}") | |
| 381 | + print(f" Boosting feuilles ≥ 1 (test 2026) : biais={m1['biais_log']:+.4f} RMSElog={m1['rmse_log']:.4f} COD={m1['COD']:.2f}") | |
| 382 | + print("== Un secteur exclu :") | |
| 383 | + for nom, m in sect["metriques"].items(): | |
| 384 | + print(f" {nom:24s} RMSElog={m['rmse_log']:.4f} MdAPE={m['mdape']:.2f} COD={m['COD']:.2f} biais/secteur=", {k: round(vv, 3) for k, vv in sect["biais_secteur"][nom].items()}) | |
| 385 | + print(f"== Scénario non linéaire (n prime={scen['n_prime']}, n mur={scen['n_mur']}) :") | |
| 386 | + for nom, m in scen["metriques"].items(): | |
| 387 | + print(f" {nom:24s} RMSElog={m['rmse_log']:.4f} MdAPE={m['mdape']:.2f} COD={m['COD']:.2f} PRB={m['PRB']:.3f}") | |
| 388 | + print(" coefficients respécifiés :", {k: round(vv, 4) for k, vv in scen["coef_respecifie"].items()}) | |
| 389 | + print("== Sujet :"); print(sujet.round(4).to_string()) | |
| 390 | + print(f"== SHAP (boosting) : base {sh['base']:.4f} (exp {math.exp(sh['base']):,.0f} $) ; efficacité max err {sh['efficacite_max_err']:.2e}") | |
| 391 | + print(" importance moyenne |φ| :", sh["importance"].round(4).to_dict()) | |
| 392 | + print(" sujet φ :", sh["sujet"].sort_values().round(4).to_dict()) | |
| 393 | + print(" interactions (base) :", sh["interactions"].head(4).round(4).to_dict()); print(" interactions (scénario) :", sh["scenario_interactions"].head(4).round(4).to_dict()) | |
| 394 | + if perm is not None: | |
| 395 | + print("== Importance par permutation (ΔEQM log, hors échantillon) :") | |
| 396 | + for nom in ("Gradient boosting", "Forêt aléatoire"): print(f" {nom} (EQM base {perm[nom + ' (EQM de base)']:.5f}) :", perm[nom].sort_values(ascending=False).round(5).head(8).to_dict()) | |
| 397 | + if courbes is not None: | |
| 398 | + print("== Courbes d'apprentissage :", {d_: (c["optimum"], round(c["rmse_min"], 4), round(float(c["entrainement"][-1]), 4)) for d_, c in courbes.items()}) | |
| 399 | + if sens is not None: | |
| 400 | + print("== Sensibilité aux hyperparamètres :"); print(sens[["modele", "profondeur", "arbres", "nu", "feuille_min", "sous_ech", "variables", "rmse_log", "mdape", "COD", "PRB"]].round(4).to_string(index=False)) | |
| 401 | + print("== Exercices :") | |
| 402 | + print(" 37.1", {k: (round(vv, 4) if isinstance(vv, float) else vv) for k, vv in ex["1"]["best"].items()}, "SSE0", round(ex["1"]["sse0"], 4)); print(" ventes", ex["1"]["ids"], ex["1"]["S"], [round(x, 4) for x in ex["1"]["lnP"]]) | |
| 403 | + print(" 37.2", {k: (round(vv, 6) if isinstance(vv, float) else vv) for k, vv in ex["2"].items()}) | |
| 404 | + print(" 37.3", {k: (np.round(vv, 4).tolist() if isinstance(vv, list) else round(vv, 4)) for k, vv in ex["3"].items()}) | |
| 405 | + print(" 37.4", {k: (round(vv, 3) if isinstance(vv, float) else vv) for k, vv in ex["4"].items()}) | |
| 406 | + print(" 37.5", {k: (round(vv, 4) if isinstance(vv, float) else vv) for k, vv in ex["5"].items() if k not in ("prix", "pred", "ratios")}, [round(r_, 3) for r_ in ex["5"]["ratios"]]) | |
| 407 | + print(" 37.6", ex["6"]); print(" 37.7", ex["7"]) | |
| 408 | + return out | |
| 409 | + | |
| 410 | +if __name__ == "__main__": | |
| 411 | + main() | |
modified
analysis/python/cases/ch39_bayes.py
+60 −29
@@ -35,18 +35,25 @@ LN_MEDIANE_HULL = math.log(475_000) # médiane APCIQ T3 2026 secteur H | ||
| 35 | 35 | # 1. Régression linéaire bayésienne conjuguée (normale–inverse-gamma) |
| 36 | 36 | # ============================================================================================================================= |
| 37 | 37 | |
| 38 | +def prior_reference(colonnes) -> dict: | |
| 39 | + """A priori de référence (impropre, Jeffreys) : p(β, σ²) ∝ 1/σ². Cas limite de la NIG avec V0⁻¹ = 0, a0 = −k/2, b0 = 0 : la | |
| 40 | + postérieure est NIG(β̂, (X'X)⁻¹, (n−k)/2, SCR/2) et la prédictive coïncide avec l'intervalle de prédiction fréquentiste classique.""" | |
| 41 | + k = len(colonnes) | |
| 42 | + return {"m0": np.zeros(k), "V0inv": np.zeros((k, k)), "a0": -k / 2, "b0": 0.0, "nom": "référence (impropre)"} | |
| 43 | + | |
| 38 | 44 | def prior_faible(colonnes, v0: float = 1e4, a0: float = 1e-3, b0: float = 1e-3) -> dict: |
| 39 | − """A priori faiblement informatif : β | σ² ~ N(m0, σ² V0), σ² ~ IG(a0, b0). m0 = ln(médiane APCIQ de Hull) pour la constante, | |
| 40 | − 0 ailleurs ; V0 = v0·I (écart-type a priori ≈ 100 σ ≈ 8 en logarithme : pratiquement plat).""" | |
| 45 | + """A priori faiblement informatif PROPRE : β | σ² ~ N(m0, σ² V0), σ² ~ IG(a0, b0). m0 = ln(médiane APCIQ de Hull) pour la constante, | |
| 46 | + 0 ailleurs ; V0 = v0·I (écart-type a priori ≈ 100 σ ≈ 8 en logarithme : pratiquement plat). Prix d'un a priori propre et vague : | |
| 47 | + a_n = a0 + n/2 au lieu de (n−k)/2, donc E[σ²] ≈ SCR/(n−2) légèrement inférieur à s² = SCR/(n−k).""" | |
| 41 | 48 | k = len(colonnes); m0 = np.zeros(k) |
| 42 | 49 | if "const" in colonnes: m0[list(colonnes).index("const")] = LN_MEDIANE_HULL |
| 43 | − return {"m0": m0, "V0": np.eye(k) * v0, "a0": a0, "b0": b0, "nom": "faiblement informatif"} | |
| 50 | + return {"m0": m0, "V0inv": np.eye(k) / v0, "a0": a0, "b0": b0, "nom": "faiblement informatif (propre)"} | |
| 44 | 51 | |
| 45 | 52 | def nig_posterieure(X: np.ndarray, y: np.ndarray, prior: dict) -> dict: |
| 46 | − """Postérieure NIG : V_n = (V0⁻¹ + X'X)⁻¹ ; m_n = V_n (V0⁻¹ m0 + X'y) ; a_n = a0 + n/2 ; | |
| 53 | + """Postérieure NIG : V_n = (V0⁻¹ + X'X)⁻¹ ; m_n = V_n (V0⁻¹ m0 + X'y) ; a_n = a0 + n/2 ; (prior : m0, V0inv, a0, b0) | |
| 47 | 54 | b_n = b0 + ½ (y'y + m0' V0⁻¹ m0 − m_n' V_n⁻¹ m_n). Marginale de β : Student-t à 2a_n ddl, échelle² = (b_n/a_n) diag(V_n).""" |
| 48 | 55 | X = np.asarray(X, float); y = np.asarray(y, float); n, k = X.shape |
| 49 | − V0inv = np.linalg.inv(prior["V0"]); m0 = prior["m0"] | |
| 56 | + V0inv = prior["V0inv"]; m0 = prior["m0"] | |
| 50 | 57 | Vn_inv = V0inv + X.T @ X; Vn = np.linalg.inv(Vn_inv) |
| 51 | 58 | mn = Vn @ (V0inv @ m0 + X.T @ y) |
| 52 | 59 | an = prior["a0"] + n / 2 |
@@ -88,7 +95,7 @@ def prior_marche_voisin(post_voisin: dict, colonnes_voisin, colonnes_cible, kapp | ||
| 88 | 95 | m0[pos] = post_voisin["m_n"][idx] |
| 89 | 96 | V0[np.ix_(pos, pos)] = kappa * post_voisin["V_n"][np.ix_(idx, idx)] |
| 90 | 97 | ic = cc.index("const"); m0[ic] = LN_MEDIANE_HULL; V0[ic, ic] = v0_const |
| 91 | − return {"m0": m0, "V0": V0, "a0": post_voisin["a_n"], "b0": post_voisin["b_n"], "nom": f"marché voisin, κ = {kappa:g}"} | |
| 98 | + return {"m0": m0, "V0inv": np.linalg.inv(V0), "a0": post_voisin["a_n"], "b0": post_voisin["b_n"], "nom": f"marché voisin, κ = {kappa:g}"} | |
| 92 | 99 | |
| 93 | 100 | def loo_exact_nig(X: np.ndarray, y: np.ndarray, prior: dict) -> dict: |
| 94 | 101 | """LOO exact du modèle conjugué : Σ_i log p(y_i | y_{−i}) par n réestimations en forme fermée (prédictive Student-t).""" |
@@ -115,17 +122,21 @@ def matrice_hier(df: pd.DataFrame, moyennes: pd.Series | None = None): | ||
| 115 | 122 | |
| 116 | 123 | def gibbs_hierarchique(y: np.ndarray, X: np.ndarray, z: np.ndarray, J: int = 5, n_iter: int = 3000, brulage: int = 1000, |
| 117 | 124 | chaines: int = 4, graine: int = GRAINE, prior_tau: str = "uniforme", tau_fixe: float | None = None, |
| 118 | − ig_a: float = 1e-3, ig_b: float = 1e-3) -> dict: | |
| 125 | + ig_a: float = 1e-3, ig_b: float = 1e-3, bloc: bool = True) -> dict: | |
| 119 | 126 | """ln P_i = x_i'β + α_{s(i)} + ε_i, ε ~ N(0, σ²) ; α_s | α0, τ² ~ N(α0, τ²) ; p(β) ∝ 1, p(α0) ∝ 1, p(σ²) ∝ 1/σ² ; |
| 120 | − τ : p(τ) ∝ 1 (uniforme sur τ, BDA3 §5.7 ; « uniforme ») ou τ² ~ IG(ig_a, ig_b) (« ig ») ou τ fixé (« fixe », bayésien empirique). | |
| 127 | + τ : p(τ) ∝ 1 (uniforme sur τ, BDA3 §5.7 ; « uniforme ») ou τ² ~ IG(ig_a, ig_b) (« ig ») ou τ fixé (bayésien empirique). | |
| 121 | 128 | Conditionnelles complètes (toutes conjuguées) : |
| 122 | 129 | β | · ~ N((X'X)⁻¹X'(y − Zα), σ²(X'X)⁻¹) |
| 123 | 130 | α_s | · ~ N([n_s r̄_s/σ² + α0/τ²] / [n_s/σ² + 1/τ²], 1/[n_s/σ² + 1/τ²]), r̄_s = moyenne de (y − Xβ) dans le secteur s |
| 124 | 131 | α0 | · ~ N(moyenne des α_s, τ²/J) |
| 125 | 132 | σ² | · ~ IG(n/2, ½ Σ (y − Xβ − Zα)²) |
| 126 | 133 | τ² | · ~ IG((J−1)/2, ½ Σ (α_s − α0)²) [uniforme sur τ] ou IG(a + J/2, b + ½ Σ (α_s − α0)²) [IG] |
| 134 | + bloc=True : (β, α) sont tirés ENSEMBLE de leur conditionnelle jointe normale (Gibbs par blocs), ce qui supprime la lenteur de | |
| 135 | + mélange due à la colinéarité entre la distance au centre et les niveaux des secteurs périphériques ; bloc=False : un à la fois. | |
| 127 | 136 | Retourne les tirages après brûlage, de forme (chaînes, n_iter − brulage, ·).""" |
| 128 | 137 | y = np.asarray(y, float); X = np.asarray(X, float); n, k = X.shape |
| 138 | + Z = np.zeros((n, J)); Z[np.arange(n), z] = 1.0 | |
| 139 | + W = np.hstack([X, Z]); WtW = W.T @ W; Wty = W.T @ y | |
| 129 | 140 | XtX_inv = np.linalg.inv(X.T @ X); L_beta = np.linalg.cholesky(XtX_inv) |
| 130 | 141 | ns = np.bincount(z, minlength=J).astype(float) |
| 131 | 142 | garder = n_iter - brulage |
@@ -133,21 +144,31 @@ def gibbs_hierarchique(y: np.ndarray, X: np.ndarray, z: np.ndarray, J: int = 5, | ||
| 133 | 144 | "sigma2": np.empty((chaines, garder)), "tau2": np.empty((chaines, garder))} |
| 134 | 145 | for c in range(chaines): |
| 135 | 146 | rng = np.random.default_rng([graine, c, 39]) |
| 136 | − # valeurs initiales dispersées (BDA3 : points de départ surdispersés pour que R̂ ait un sens) | |
| 147 | + # valeurs initiales surdispersées (BDA3 : points de départ dispersés pour que R̂ ait un sens) | |
| 137 | 148 | beta = XtX_inv @ X.T @ y + rng.normal(0, 0.05, k) |
| 138 | 149 | alpha = np.full(J, float(np.mean(y))) + rng.normal(0, 0.3, J); alpha0 = float(np.mean(alpha)) |
| 139 | 150 | sigma2 = float(np.exp(rng.normal(math.log(0.01), 0.5))); tau2 = float(np.exp(rng.normal(math.log(0.02), 1.0))) |
| 140 | 151 | if tau_fixe is not None: tau2 = tau_fixe ** 2 |
| 141 | 152 | for it in range(n_iter): |
| 142 | − # β | α, σ² | |
| 143 | − r = y - alpha[z] | |
| 144 | − beta = XtX_inv @ X.T @ r + math.sqrt(sigma2) * (L_beta @ rng.standard_normal(k)) | |
| 145 | − # α_s | β, α0, σ², τ² | |
| 146 | − e = y - X @ beta | |
| 147 | − somme = np.bincount(z, weights=e, minlength=J) | |
| 148 | − prec = ns / sigma2 + 1.0 / tau2 | |
| 149 | − moy = (somme / sigma2 + alpha0 / tau2) / prec | |
| 150 | − alpha = moy + rng.standard_normal(J) / np.sqrt(prec) | |
| 153 | + if bloc: | |
| 154 | + # (β, α) | α0, σ², τ² : précision = W'W/σ² + diag(0_k, 1/τ² 1_J) ; moyenne = Préc⁻¹ (W'y/σ² + (0, α0/τ² 1_J)) | |
| 155 | + prec = WtW / sigma2; prec[k:, k:] += np.eye(J) / tau2 | |
| 156 | + b = Wty / sigma2; b[k:] += alpha0 / tau2 | |
| 157 | + Lp = np.linalg.cholesky(prec) | |
| 158 | + moy = np.linalg.solve(Lp.T, np.linalg.solve(Lp, b)) | |
| 159 | + theta = moy + np.linalg.solve(Lp.T, rng.standard_normal(k + J)) | |
| 160 | + beta, alpha = theta[:k], theta[k:] | |
| 161 | + e = y - X @ beta | |
| 162 | + else: | |
| 163 | + # β | α, σ² | |
| 164 | + r = y - alpha[z] | |
| 165 | + beta = XtX_inv @ X.T @ r + math.sqrt(sigma2) * (L_beta @ rng.standard_normal(k)) | |
| 166 | + # α_s | β, α0, σ², τ² | |
| 167 | + e = y - X @ beta | |
| 168 | + somme = np.bincount(z, weights=e, minlength=J) | |
| 169 | + prec_a = ns / sigma2 + 1.0 / tau2 | |
| 170 | + moy_a = (somme / sigma2 + alpha0 / tau2) / prec_a | |
| 171 | + alpha = moy_a + rng.standard_normal(J) / np.sqrt(prec_a) | |
| 151 | 172 | # α0 | α, τ² |
| 152 | 173 | alpha0 = float(alpha.mean() + rng.standard_normal() * math.sqrt(tau2 / J)) |
| 153 | 174 | # σ² | β, α |
@@ -164,7 +185,7 @@ def gibbs_hierarchique(y: np.ndarray, X: np.ndarray, z: np.ndarray, J: int = 5, | ||
| 164 | 185 | out["beta"][c, j] = beta; out["alpha"][c, j] = alpha; out["alpha0"][c, j] = alpha0 |
| 165 | 186 | out["sigma2"][c, j] = sigma2; out["tau2"][c, j] = tau2 |
| 166 | 187 | out["meta"] = {"n": n, "k": k, "J": J, "ns": ns, "n_iter": n_iter, "brulage": brulage, "chaines": chaines, "prior_tau": prior_tau, |
| 167 | − "tau_fixe": tau_fixe} | |
| 188 | + "tau_fixe": tau_fixe, "bloc": bloc} | |
| 168 | 189 | return out |
| 169 | 190 | |
| 170 | 191 | def facteur_retrecissement(n_s: float, sigma2: float, tau2: float) -> float: |
@@ -284,12 +305,17 @@ def sous_echantillons(v: pd.DataFrame, s: dict) -> dict: | ||
| 284 | 305 | return {"tout": v, "hull": v[v["secteur"] == "Hull"], "voisin": v[v["secteur"] != "Hull"], "micro": micro} |
| 285 | 306 | |
| 286 | 307 | def colonnes_actives(X: pd.DataFrame) -> list: |
| 287 | − return [c for c in X.columns if c == "const" or X[c].std() > 0] | |
| 308 | + """Colonnes non constantes ; si le secteur de référence (Hull) est absent du sous-échantillon, les indicatrices restantes somment | |
| 309 | + à 1 et l'une d'elles (la première) est retirée pour éviter la colinéarité avec la constante.""" | |
| 310 | + cols = [c for c in X.columns if c == "const" or X[c].std() > 0] | |
| 311 | + sect = [c for c in cols if c.startswith("sect_")] | |
| 312 | + if sect and np.allclose(X[sect].sum(axis=1), 1.0): cols.remove(sect[0]) | |
| 313 | + return cols | |
| 288 | 314 | |
| 289 | 315 | def ajuster_nig(df: pd.DataFrame, prior_fn=None, colonnes=None) -> dict: |
| 290 | 316 | X = cas1.matrice(df); cols = colonnes or colonnes_actives(X); X = X[cols] |
| 291 | 317 | y = np.log(df["prix_vente"].astype(float)).values |
| 292 | − prior = prior_fn(cols) if prior_fn else prior_faible(cols) | |
| 318 | + prior = prior_fn(cols) if prior_fn else prior_reference(cols) | |
| 293 | 319 | p = nig_posterieure(X.values, y, prior); p["colonnes"] = cols |
| 294 | 320 | return p |
| 295 | 321 | |
@@ -304,7 +330,8 @@ def exemple_conjugue(v: pd.DataFrame, s: dict, ref: dict) -> dict: | ||
| 304 | 330 | """Étapes 1-2 : postérieure NIG sur 600 ventes (a priori faible) ; petit échantillon (40 ventes à 3 km / 18 mois) avec a priori |
| 305 | 331 | faible vs informatif emprunté aux autres secteurs (κ = 1, 4, 16) ; prédictive du sujet.""" |
| 306 | 332 | se = sous_echantillons(v, s) |
| 307 | − p600 = ajuster_nig(se["tout"]); cols = p600["colonnes"] | |
| 333 | + p600 = ajuster_nig(se["tout"]); cols = p600["colonnes"] # a priori de référence | |
| 334 | + p600_propre = ajuster_nig(se["tout"], prior_fn=prior_faible, colonnes=cols) # a priori propre vague (comparaison) | |
| 308 | 335 | xs = x_sujet(s, cols) |
| 309 | 336 | tab = pd.DataFrame({"mco": ref["coef"]["coef"].reindex(cols), "se_hc1": ref["coef"]["se_hc1"].reindex(cols), |
| 310 | 337 | "post_moy": p600["m_n"], "post_sd": p600["sd"], "ic_bas": p600["ic95_bas"], "ic_haut": p600["ic95_haut"], |
@@ -314,7 +341,7 @@ def exemple_conjugue(v: pd.DataFrame, s: dict, ref: dict) -> dict: | ||
| 314 | 341 | # cohérence séquentielle : 2023-2024 puis 2025-2026 |
| 315 | 342 | anc = v[v["date_vente"] < "2025-01-01"]; rec = v[v["date_vente"] >= "2025-01-01"] |
| 316 | 343 | p_anc = ajuster_nig(anc, colonnes=cols) |
| 317 | − prior_seq = {"m0": p_anc["m_n"], "V0": p_anc["V_n"], "a0": p_anc["a_n"], "b0": p_anc["b_n"], "nom": "séquentiel"} | |
| 344 | + prior_seq = {"m0": p_anc["m_n"], "V0inv": np.linalg.inv(p_anc["V_n"]), "a0": p_anc["a_n"], "b0": p_anc["b_n"], "nom": "séquentiel"} | |
| 318 | 345 | p_seq = ajuster_nig(rec, prior_fn=lambda c: prior_seq, colonnes=cols) |
| 319 | 346 | # petit échantillon : 40 ventes autour du sujet ; a priori emprunté aux autres secteurs |
| 320 | 347 | p_vois = ajuster_nig(se["voisin"]) |
@@ -326,7 +353,7 @@ def exemple_conjugue(v: pd.DataFrame, s: dict, ref: dict) -> dict: | ||
| 326 | 353 | petits[nom] = {"post": p, "pred": pr, "largeur95_pct": (pr["haut95"] - pr["bas95"]) / pr["mediane"] * 100} |
| 327 | 354 | # Hull seul (186), a priori faible |
| 328 | 355 | p_hull = ajuster_nig(se["hull"]); pred_hull = predictive_t(p_hull, x_sujet(s, p_hull["colonnes"])) |
| 329 | − return {"p600": p600, "cols": cols, "xs": xs, "tab600": tab, "pred600": pred600, "p_anc": p_anc, "p_rec_seq": p_seq, "n_anc": len(anc), | |
| 356 | + return {"p600": p600, "p600_propre": p600_propre, "pred600_propre": predictive_t(p600_propre, xs), "cols": cols, "xs": xs, "tab600": tab, "pred600": pred600, "p_anc": p_anc, "p_rec_seq": p_seq, "n_anc": len(anc), | |
| 330 | 357 | "n_rec": len(rec), "p_vois": p_vois, "micro": micro, "cols_micro": cols_m, "petits": petits, "p_hull": p_hull, |
| 331 | 358 | "pred_hull": pred_hull, "n_micro": len(micro)} |
| 332 | 359 | |
@@ -334,8 +361,10 @@ def exemple_hierarchique(v: pd.DataFrame, s: dict, ref: dict, n_iter: int = 3000 | ||
| 334 | 361 | """Étapes 3-4 : Gibbs sur les 600 ventes ; effets relatifs à Hull vs effets fixes (ch. 18) ; prédictive du sujet ; |
| 335 | 362 | expérience Masson-Angers réduit à 5, 10, 20 ventes ; sensibilité à l'a priori sur τ.""" |
| 336 | 363 | Xc, moy, z = matrice_hier(v); y = np.log(v["prix_vente"].astype(float)).values; cols = list(Xc.columns) |
| 337 | − g = gibbs_hierarchique(y, Xc.values, z, n_iter=n_iter, brulage=brulage) | |
| 338 | − res = resume_gibbs(g, cols); rel = effets_relatifs(g) | |
| 364 | + g = gibbs_hierarchique(y, Xc.values, z, n_iter=n_iter, brulage=brulage) # Gibbs par blocs (résultats du chapitre) | |
| 365 | + g_naif = gibbs_hierarchique(y, Xc.values, z, n_iter=n_iter, brulage=brulage, bloc=False) # un paramètre à la fois (diagnostics) | |
| 366 | + res = resume_gibbs(g, cols); rel = effets_relatifs(g); res_naif = resume_gibbs(g_naif, cols) | |
| 367 | + diag = pd.DataFrame({"rhat_bloc": res["rhat"], "ess_bloc": res["ess"], "rhat_naif": res_naif["rhat"], "ess_naif": res_naif["ess"]}) | |
| 339 | 368 | fixes = ref["coef"].loc[[f"sect_{x}" for x in SECTEURS[1:]], ["coef", "se_hc1"]]; fixes.index = SECTEURS[1:] |
| 340 | 369 | rel["fixe"] = fixes["coef"]; rel["fixe_se"] = fixes["se_hc1"]; rel["vrai"] = [VRAIS[f"sect_{x}"] for x in rel.index] |
| 341 | 370 | # prédictive du sujet (Hull) : ln P* = (x_s − x̄)'β + α_Hull + ε |
@@ -396,7 +425,7 @@ def exemple_hierarchique(v: pd.DataFrame, s: dict, ref: dict, n_iter: int = 3000 | ||
| 396 | 425 | "ma_600": r600["moyenne"], "ma_600_sd": r600["sd"], "tau_med_5": float(np.median(np.sqrt(g5["tau2"]))), |
| 397 | 426 | "ma_5": r5["moyenne"], "ma_5_sd": r5["sd"], "ma_5_bas": r5["q025"], "ma_5_haut": r5["q975"]}) |
| 398 | 427 | sensibilite = pd.DataFrame(sens_rows).set_index("prior") |
| 399 | − return {"g": g, "cols": cols, "moyennes": moy, "z": z, "y": y, "Xc": Xc, "resume": res, "relatifs": rel, "pred": pred, "B": B, | |
| 428 | + return {"g": g, "g_naif": g_naif, "diag": diag, "cols": cols, "moyennes": moy, "z": z, "y": y, "Xc": Xc, "resume": res, "relatifs": rel, "pred": pred, "B": B, | |
| 400 | 429 | "sigma2_med": s2m, "tau2_med": t2m, "experience": experience, "dist_ma": dist_ma, "sensibilite": sensibilite, "tau_eb": tau_eb, |
| 401 | 430 | "perm_ma": perm} |
| 402 | 431 | |
@@ -405,7 +434,7 @@ def criteres(v: pd.DataFrame, hier: dict, ex: dict) -> pd.DataFrame: | ||
| 405 | 434 | y = hier["y"] |
| 406 | 435 | # pooling complet : matrice de référence sans indicatrices de secteur |
| 407 | 436 | X = cas1.matrice(v); cols_pool = [c for c in X.columns if not c.startswith("sect_")] |
| 408 | − p_pool = nig_posterieure(X[cols_pool].values, y, prior_faible(cols_pool)); p_pool["colonnes"] = cols_pool | |
| 437 | + p_pool = nig_posterieure(X[cols_pool].values, y, prior_reference(cols_pool)); p_pool["colonnes"] = cols_pool | |
| 409 | 438 | p_fixe = ex["p600"] |
| 410 | 439 | lignes = [] |
| 411 | 440 | for nom, lp, loo in [("pooling complet (sans secteur)", log_vrais_pointwise_nig(p_pool), loo_exact_nig(p_pool["X"], y, p_pool["prior"])), |
@@ -487,6 +516,7 @@ def imprimer(r: dict) -> None: | ||
| 487 | 516 | print(t.round(4).to_string()) |
| 488 | 517 | print(f"couverture des vrais paramètres (IC 95 %) : {int(t['couvre'].sum())}/{int(t['vrai'].notna().sum())}") |
| 489 | 518 | p = ex["p600"]; print(f"a_n = {p['a_n']:.1f} ; b_n = {p['b_n']:.4f} ; ν = {p['nu']:.0f} ; E[σ] ≈ {p['sigma_moy']:.4f} ; MCO σ = {r['modele']['sigma']:.4f}") |
| 519 | + pp = ex["p600_propre"]; print(f"a priori propre vague : a_n = {pp['a_n']:.3f} ; ν = {pp['nu']:.0f} ; E[σ] ≈ {pp['sigma_moy']:.4f} ; max|m_n − β̂| = {np.abs(pp['m_n'] - r['modele']['beta']).max():.2e} ; sujet 95 % [{_d(ex['pred600_propre']['bas95'])} ; {_d(ex['pred600_propre']['haut95'])}]") | |
| 490 | 520 | pr = ex["pred600"] |
| 491 | 521 | print(f"prédictive du sujet : loc {pr['loc']:.4f}, échelle {pr['echelle']:.5f}, médiane {_d(pr['mediane'])}, part coefficients {pr['part_coefficients']*100:.2f} %") |
| 492 | 522 | print(f" crédibilité 90 % : [{_d(pr['bas90'])} ; {_d(pr['haut90'])}] 95 % : [{_d(pr['bas95'])} ; {_d(pr['haut95'])}] (ch. 18 : [{_d(IC18['bas95'])} ; {_d(IC18['haut95'])}])") |
@@ -497,8 +527,9 @@ def imprimer(r: dict) -> None: | ||
| 497 | 527 | print(f" {nom:9s}: ln_S {po['m_n'][i]:.3f} ({po['sd'][i]:.3f}) ; qualité {po['m_n'][iq]:.3f} ({po['sd'][iq]:.3f}) ; garage {po['m_n'][ig]:.3f} ({po['sd'][ig]:.3f}) ; " |
| 498 | 528 | f"E[σ] {po['sigma_moy']:.4f} ; sujet médiane {_d(pe['pred']['mediane'])} [{_d(pe['pred']['bas95'])} ; {_d(pe['pred']['haut95'])}] largeur {pe['largeur95_pct']:.1f} %") |
| 499 | 529 | ph = ex["pred_hull"]; print(f" Hull seul ({ex['p_hull']['n']}) : médiane {_d(ph['mediane'])} [{_d(ph['bas95'])} ; {_d(ph['haut95'])}]") |
| 500 | − print("\n=== modèle hiérarchique (Gibbs) : résumé ===") | |
| 530 | + print("\n=== modèle hiérarchique (Gibbs par blocs) : résumé ===") | |
| 501 | 531 | print(hi["resume"].round(4).to_string()) |
| 532 | + print("\ndiagnostics : Gibbs par blocs vs un paramètre à la fois"); print(hi["diag"].round(3).to_string()) | |
| 502 | 533 | print("\neffets relatifs à Hull (hiérarchique vs effets fixes ch. 18 vs vrai) :"); print(hi["relatifs"].round(4).to_string()) |
| 503 | 534 | print(f"σ² médian {hi['sigma2_med']:.5f} (σ {math.sqrt(hi['sigma2_med']):.4f}) ; τ² médian {hi['tau2_med']:.5f} (τ {math.sqrt(hi['tau2_med']):.4f})") |
| 504 | 535 | print("facteurs de rétrécissement B_s :", {k: round(v_, 4) for k, v_ in hi["B"].items()}) |
modified
analysis/python/cases/ch40_montecarlo.py
+20 −9
@@ -196,7 +196,7 @@ def comparer_lhs(r: dict, n: int = 1000, k: int = 100) -> pd.DataFrame: | ||
| 196 | 196 | rows = [] |
| 197 | 197 | for s in ["moyenne", "mediane", "q05", "q95", "ecart_type", "p_rcd1_120", "p_V_2M"]: |
| 198 | 198 | rows.append({"statistique": s, "moy_simple": simple[s].mean(), "moy_lhs": lhs[s].mean(), "se_simple": simple[s].std(ddof=1), "se_lhs": lhs[s].std(ddof=1), |
| 199 | − "ratio_variance": simple[s].var(ddof=1) / lhs[s].var(ddof=1)}) | |
| 199 | + "ratio_variance": (simple[s].var(ddof=1) / lhs[s].var(ddof=1)) if lhs[s].var(ddof=1) > 0 else float("inf")}) | |
| 200 | 200 | return pd.DataFrame(rows) |
| 201 | 201 | |
| 202 | 202 | # ----------------------------------------------------------------------------- 40.5 mesures de risque, vérification analytique, stress tests |
@@ -272,17 +272,17 @@ NOMS_SOBOL = ["inoccupation_pertes", "croissance_loyers_marche", "rotation_annue | ||
| 272 | 272 | def _completer(x: dict) -> dict: |
| 273 | 273 | x = dict(x); x["taux_terminal"] = np.maximum(x["taux_actualisation"] - x["ecart_terminal"], R_T_PLANCHER); return x |
| 274 | 274 | |
| 275 | −def sobol(r: dict, n: int = N_MC, graine: int = GRAINE) -> pd.DataFrame: | |
| 275 | +def sobol(r: dict, n: int = 2 * N_MC, graine: int = GRAINE) -> pd.DataFrame: | |
| 276 | 276 | """Indices de Sobol de premier ordre S_i et totaux S_Ti par échantillons appariés : deux matrices indépendantes A et B (n tirages |
| 277 | 277 | chacune), puis A_B^(i) = A où la seule colonne i vient de B. S_i ≈ (1/n) Σ f(B)·[f(A_B^(i)) − f(A)] / Var ; S_Ti ≈ (1/2n) Σ [f(A) − f(A_B^(i))]² / Var. |
| 278 | 278 | Les intrants de base (r et delta, non r et R_T) sont indépendants, condition de validité de la décomposition. Le facteur CAPEX est traité en groupe (3 facteurs).""" |
| 279 | 279 | nb = len(r["hyp"]["capex"]) |
| 280 | 280 | A = tirer(n, graine, nb); B = tirer(n, graine + 1, nb) |
| 281 | 281 | fA = evaluer(r, A)["valeur"]; fB = evaluer(r, B)["valeur"] |
| 282 | − var = np.concatenate([fA, fB]).var(ddof=1); rows = [] | |
| 282 | + tous = np.concatenate([fA, fB]); var = tous.var(ddof=1); mu = tous.mean(); rows = [] | |
| 283 | 283 | for nom in NOMS_SOBOL: |
| 284 | 284 | AB = dict(A); AB[nom] = B[nom]; fAB = evaluer(r, _completer(AB))["valeur"] |
| 285 | − S1 = float(np.mean(fB * (fAB - fA)) / var); ST = float(0.5 * np.mean((fA - fAB) ** 2) / var) | |
| 285 | + S1 = float(np.mean((fB - mu) * (fAB - fA)) / var); ST = float(0.5 * np.mean((fA - fAB) ** 2) / var) # centrage : même espérance, variance réduite | |
| 286 | 286 | rows.append({"intrant": nom, "S1": S1, "ST": ST}) |
| 287 | 287 | d = pd.DataFrame(rows); d.attrs["somme_S1"] = float(d["S1"].sum()); d.attrs["variance"] = float(var); d.attrs["n_evaluations"] = n * (2 + len(NOMS_SOBOL)) |
| 288 | 288 | return d |
@@ -309,7 +309,7 @@ def regression_standardisee(mc: dict) -> dict: | ||
| 309 | 309 | |
| 310 | 310 | def simuler_portefeuille(pf: pd.DataFrame, n: int = N_MC, graine: int = GRAINE, sigma_R: float = 0.005, rho_rmr: float = 0.60, rho_prov: float = 0.30, |
| 311 | 311 | croissance: pd.DataFrame | None = None, sigma_g_defaut: float = 0.03, choc_taux_renouvellement: float = 0.02, horizon_renouvellement: int = 2028, |
| 312 | − amortissement_mois: int = 300, seuil_rcd: float = 1.20) -> dict: | |
| 312 | + amortissement_mois: int = 480, seuil_rcd: float = 1.20) -> dict: | |
| 313 | 313 | """Simulation à un an de la valeur et de la couverture de la dette d'un portefeuille d'immeubles à revenus avec corrélation régionale. |
| 314 | 314 | |
| 315 | 315 | Modèle (hypothèses pédagogiques déclarées au ch. 40, §40.8) : pour l'immeuble i de la RMR k, |
@@ -339,6 +339,8 @@ def simuler_portefeuille(pf: pd.DataFrame, n: int = N_MC, graine: int = GRAINE, | ||
| 339 | 339 | sd = np.array([fin.Hypotheque(float(d), float(t) + (choc_taux_renouvellement if int(ech) <= horizon_renouvellement else 0.0), amortissement_mois).service_dette_annuel |
| 340 | 340 | for d, t, ech in zip(pf["dette"], pf["taux_dette"], pf["echeance_dette"])]) |
| 341 | 341 | rcd = rne / sd |
| 342 | + sd0 = np.array([fin.Hypotheque(float(d), float(t), amortissement_mois).service_dette_annuel for d, t in zip(pf["dette"], pf["taux_dette"])]) | |
| 343 | + rcd_det = pf["rne_2026"].values / sd0 | |
| 342 | 344 | VP = V.sum(1); V0 = float(pf["valeur_2026"].sum()); VP_det = float((pf["rne_2026"].values * np.exp(g) / pf["taux_global"].values).sum()) |
| 343 | 345 | q = lambda a, p: float(np.quantile(a, p)) |
| 344 | 346 | stats_ = {"n": n, "N_immeubles": N, "valeur_2026": V0, "valeur_deterministe": VP_det, "mediane": q(VP, 0.5), "moyenne": float(VP.mean()), "ecart_type": float(VP.std(ddof=1)), |
@@ -346,7 +348,10 @@ def simuler_portefeuille(pf: pd.DataFrame, n: int = N_MC, graine: int = GRAINE, | ||
| 346 | 348 | "somme_ecarts_types": float(V.std(0, ddof=1).sum()), "nb_rcd_sous_seuil_moyen": float((rcd < seuil_rcd).sum(1).mean()), "nb_rcd_sous_1_moyen": float((rcd < 1.0).sum(1).mean()), |
| 347 | 349 | "P_au_moins_un_rcd_sous_1": float(((rcd < 1.0).any(1)).mean()), "P_au_moins_un_rcd_sous_seuil": float(((rcd < seuil_rcd).any(1)).mean()), |
| 348 | 350 | "rcd_portefeuille_mediane": q(rne.sum(1) / sd.sum(), 0.5), "P_rcd_portefeuille_sous_seuil": float((rne.sum(1) / sd.sum() < seuil_rcd).mean()), |
| 349 | − "nb_renouvellements_stress": int((pf["echeance_dette"] <= horizon_renouvellement).sum())} | |
| 351 | + "nb_renouvellements_stress": int((pf["echeance_dette"] <= horizon_renouvellement).sum()), | |
| 352 | + "nb_rcd_det_sous_seuil": int((rcd_det < seuil_rcd).sum()), "nb_rcd_det_sous_1": int((rcd_det < 1.0).sum()), "rcd_portefeuille_det": float(pf["rne_2026"].sum() / sd0.sum()), | |
| 353 | + "nb_rcd_sous_seuil_q95": float(np.quantile((rcd < seuil_rcd).sum(1), 0.95)), "nb_rcd_sous_1_q95": float(np.quantile((rcd < 1.0).sum(1), 0.95)), | |
| 354 | + "P_plus_de_10_sous_1": float(((rcd < 1.0).sum(1) > 10).mean()), "rpv_portefeuille": float(pf["dette"].sum() / V0)} | |
| 350 | 355 | stats_["ratio_diversification"] = stats_["ecart_type"] / stats_["somme_ecarts_types"] |
| 351 | 356 | return {"valeurs": V, "valeur_portefeuille": VP, "rne": rne, "rcd": rcd, "service_dette": sd, "stats": stats_, |
| 352 | 357 | "par_rmr": pd.DataFrame({"rmr": rmrs.cat.categories, "ecart_type_valeur": [V[:, k == j].sum(1).std(ddof=1) for j in range(K)], "valeur_2026": [pf.loc[k == j, "valeur_2026"].sum() for j in range(K)]})} |
@@ -356,7 +361,7 @@ def comparer_structures_correlation(pf: pd.DataFrame, croissance: pd.DataFrame, | ||
| 356 | 361 | rows = [] |
| 357 | 362 | for nom, rp, rr in [("Indépendance", 0.0, 0.0), ("Régionale (0,30 ; 0,60)", 0.30, 0.60), ("Parfaite", 1.0, 1.0)]: |
| 358 | 363 | s = simuler_portefeuille(pf, n, GRAINE, rho_rmr=rr, rho_prov=rp, croissance=croissance)["stats"] |
| 359 | − rows.append({"structure": nom, "rho_prov": rp, "rho_rmr": rr, **{k: s[k] for k in ["mediane", "ecart_type", "q05", "VaR05", "CVaR05", "ratio_diversification", "nb_rcd_sous_seuil_moyen", "nb_rcd_sous_1_moyen", "P_au_moins_un_rcd_sous_1", "P_rcd_portefeuille_sous_seuil"]}}) | |
| 364 | + rows.append({"structure": nom, "rho_prov": rp, "rho_rmr": rr, **{k: s[k] for k in ["mediane", "ecart_type", "q05", "VaR05", "CVaR05", "ratio_diversification", "nb_rcd_sous_seuil_moyen", "nb_rcd_sous_seuil_q95", "nb_rcd_sous_1_moyen", "nb_rcd_sous_1_q95", "P_plus_de_10_sous_1", "rcd_portefeuille_mediane"]}}) | |
| 360 | 365 | return pd.DataFrame(rows) |
| 361 | 366 | |
| 362 | 367 | def variance_somme_correlee(sigmas: np.ndarray, rho: float) -> float: |
@@ -400,8 +405,13 @@ def tout(rapide: bool = False) -> dict: | ||
| 400 | 405 | D = {**r, "mc": mc, "tornade": torn, "lois": tableau_lois(x), "corr_rRT": correlation_r_RT(x), "erreurs": erreurs_theoriques(mc), |
| 401 | 406 | "risque": mesures_risque(mc, r["dcf"]["valeur"]), "rcd_analytique": rcd1_analytique(r), "stress": stress_deterministes(r, mc), "inverse": stress_inverse(r), |
| 402 | 407 | "sobol": sobol(r), "rangs": correlations_rang(mc), "reg": regression_standardisee(mc), "pert_rotation": pert(0.08, 0.15, 0.25)} |
| 403 | − k_rep = 20 if rapide else 100; k_se = 10 if rapide else 40 | |
| 404 | − D["repetitions"] = repetitions(r, N_MC, k_rep, graine0=1) | |
| 408 | + k_rep = 20 if rapide else 100; k_se = 10 if rapide else 40; k_med = 40 if rapide else 300 | |
| 409 | + D["repetitions"] = repetitions(r, N_MC, k_med, graine0=1) | |
| 410 | + rep = D["repetitions"]; D["graine_traite"] = {"mediane": mc["stats"]["mediane"], "moyenne_des_medianes": float(rep["mediane"].mean()), "se_mediane_empirique": float(rep["mediane"].std(ddof=1)), | |
| 411 | + "z_mediane": float((mc["stats"]["mediane"] - rep["mediane"].mean()) / rep["mediane"].std(ddof=1)), | |
| 412 | + "z_q95": float((mc["stats"]["q95"] - rep["q95"].mean()) / rep["q95"].std(ddof=1)), "z_q05": float((mc["stats"]["q05"] - rep["q05"].mean()) / rep["q05"].std(ddof=1)), | |
| 413 | + "rang_mediane": int((rep["mediane"] < mc["stats"]["mediane"]).sum()), "k": len(rep), | |
| 414 | + "moyenne_tiree_r": float(x["taux_actualisation"].mean()), "moyenne_tiree_gm": float(x["croissance_loyers_marche"].mean())} | |
| 405 | 415 | D["convergence"] = convergence(r, N_max=20_000 if rapide else 100_000) |
| 406 | 416 | D["se_N"] = se_empirique_selon_N(r, k=k_se) |
| 407 | 417 | D["lhs"] = comparer_lhs(r, n=1000, k=k_rep) |
@@ -429,6 +439,7 @@ def imprimer(D: dict) -> None: | ||
| 429 | 439 | rep = D["repetitions"] |
| 430 | 440 | print(f"\nRépétitions ({len(rep)} graines, N = 10 000) : écart-type empirique — moyenne {rep['moyenne'].std(ddof=1):,.0f} | médiane {rep['mediane'].std(ddof=1):,.0f} | q05 {rep['q05'].std(ddof=1):,.0f} | q95 {rep['q95'].std(ddof=1):,.0f} | P(RCD1<1,20) {rep['p_rcd1_120'].std(ddof=1):.4f}") |
| 431 | 441 | print(f" moyenne des médianes {rep['mediane'].mean():,.0f} ; min {rep['mediane'].min():,.0f} ; max {rep['mediane'].max():,.0f} ; P(RCD1<1,20) moyenne {rep['p_rcd1_120'].mean():.4f}") |
| 442 | + print(" graine du traité (20261031) :", {k: (round(v, 4) if isinstance(v, float) else v) for k, v in D["graine_traite"].items()}) | |
| 432 | 443 | print("\nConvergence (une suite, graine 20261031) :\n", D["convergence"].to_string(index=False)) |
| 433 | 444 | print(f"\nÉcart-type empirique selon N (pente log-log médiane = {D['se_N'].attrs['pente_log_mediane']:.3f}) :\n", D["se_N"].to_string(index=False)) |
| 434 | 445 | print("\n=== 40.4 Hypercube latin vs simple (N = 1 000, répétitions) :\n", D["lhs"].to_string(index=False)) |
added
analysis/python/cases/ch42_copro.py
+326 −0
@@ -0,0 +1,326 @@ | ||
| 1 | +#!/usr/bin/env python3 | |
| 2 | +"""Chapitre 42 — Copropriétés et conversions : exemples chiffrés (DONNÉES SYNTHÉTIQUES, graine 20261031). | |
| 3 | + | |
| 4 | +Réutilise l'immeuble fictif de huit fractions du chapitre 7 (analysis/python/cases/ch07_servitudes_copro.py : quotes-parts, | |
| 5 | +répartition des charges, cotisation de seuil, déficit de capitalisation) et y ajoute quatre blocs, tous générés ici | |
| 6 | +(aucun fichier externe ; aucune statistique de marché n'est reproduite) : | |
| 7 | + A. Évaluation de la fraction F4 par comparaison : deux ventes dans l'immeuble, trois ventes dans d'autres immeubles fictifs ; | |
| 8 | + ajustements de date, d'étage, d'orientation, de stationnement, de superficie, de charges courantes (capitalisation) et de | |
| 9 | + situation financière du syndicat (déficit du fonds de prévoyance, cotisation spéciale) ; réconciliation (traite.comparables). | |
| 10 | + B. Effet d'une cotisation spéciale égale au déficit de capitalisation, répartie selon les quotes-parts (figure). | |
| 11 | + C. Copropriété locative : revenu net d'exploitation et taux implicite d'une fraction louée (traite.finance). | |
| 12 | + D. Conversion d'un immeuble locatif de huit logements en copropriété divise : valeur en bloc (capitalisation directe) contre | |
| 13 | + somme des fractions nette des coûts, des délais et des locataires (rotation, préemption, vente occupée) ; sensibilités. | |
| 14 | +Toutes les fonctions financières viennent de analysis/python/traite/ (finance.py, comparables.py) ; rien n'est réécrit. | |
| 15 | +Usage (racine du dépôt) : PYTHONPATH=analysis/python python3 analysis/python/cases/ch42_copro.py | |
| 16 | +""" | |
| 17 | +from __future__ import annotations | |
| 18 | +import sys, math, pathlib | |
| 19 | +import numpy as np | |
| 20 | +HERE = pathlib.Path(__file__).resolve() | |
| 21 | +sys.path.insert(0, str(HERE.parents[1])); sys.path.insert(0, str(HERE.parent)) | |
| 22 | +from traite import comparables as cp | |
| 23 | +from traite import finance as fin | |
| 24 | +import ch07_servitudes_copro as c7 | |
| 25 | + | |
| 26 | +SEED = 20261031 | |
| 27 | +DATE_EVAL = "2026-09-01" | |
| 28 | + | |
| 29 | +# ----------------------------------------------------------------------------- marché fictif : paramètres déclarés | |
| 30 | +# Prix implicites « du marché fictif » (hypothèses pédagogiques ; l'évaluateur les tient d'une analyse de ventes appariées / hédonique). | |
| 31 | +PRIX_M2_REF = 3_650.0 # $/m² pour une fraction de 98 m², 2e étage, orientation est/ouest, sans stationnement, immeuble bien capitalisé | |
| 32 | +ELASTICITE_S = 0.60 # élasticité prix-superficie (< 1) ; prix marginal au point de référence = 0,60 × 3 650 = 2 190 $/m² | |
| 33 | +S_REF = 98.0 | |
| 34 | +COEF_ETAGE_MARCHE = {"RDC": -0.04, "2e": 0.00, "3e": 0.02} # proportion du prix (immeubles sans ascenseur) | |
| 35 | +COEF_ORIENT_MARCHE = {"nord": -0.03, "est": 0.00, "ouest": 0.00, "sud": 0.03} | |
| 36 | +STATIONNEMENT = 28_000.0 # $ ; case intérieure à usage restreint | |
| 37 | +G_MARCHE = 0.02 # croissance annuelle des prix des copropriétés sur le marché fictif (ajustement de conditions du marché) | |
| 38 | +R_CHARGES = 0.08 # taux de capitalisation des écarts de charges courantes (hypothèse pédagogique ; à extraire du marché) | |
| 39 | +KAPPA = 1.0 # coefficient de capitalisation du passif du syndicat (1 = dollar pour dollar, convention du ch. 7) | |
| 40 | +CHARGES_REF_M2 = 40_000.0 / 710.0 # 56,34 $/m²/an : charges courantes de l'immeuble fictif (hors fonds), référence du marché | |
| 41 | +SIGMA_BRUIT = 0.02 # bruit de transaction (log) dans la génération des prix des comparables | |
| 42 | + | |
| 43 | +# Situation financière des syndicats fictifs : déficit de capitalisation (ch. 7) ou cotisation spéciale votée non payée | |
| 44 | +QP = c7.quotes_parts() | |
| 45 | +INC = c7.incidence_valeur(QP) # déficit de l'immeuble fictif du ch. 7 : 150 908 $ | |
| 46 | +DEFICIT_IMMEUBLE = float(INC["deficit"]) | |
| 47 | +Q_F4 = float(QP["q"][3]); Q_F2 = float(QP["q"][1]); Q_F7 = float(QP["q"][6]) | |
| 48 | + | |
| 49 | +SUJET = dict(nom="F4 (sujet)", immeuble="A (fictif, 8 fractions)", S=98.0, etage="2e", orient="sud", stat=True, | |
| 50 | + charges_m2=CHARGES_REF_M2, passif=Q_F4 * DEFICIT_IMMEUBLE, mois=0.0) | |
| 51 | + | |
| 52 | +COMPARABLES = [ # (nom, immeuble, S, étage, orientation, stationnement, charges courantes $/m²/an, passif de la fraction ($), ancienneté (mois), date) | |
| 53 | + dict(nom="C1 = F2", immeuble="A (même immeuble)", S=98.0, etage="RDC", orient="sud", stat=True, charges_m2=CHARGES_REF_M2, passif=Q_F2 * DEFICIT_IMMEUBLE, mois=6.0, date="2026-03-02"), | |
| 54 | + dict(nom="C2 = F7", immeuble="A (même immeuble)", S=85.0, etage="2e", orient="est", stat=True, charges_m2=CHARGES_REF_M2, passif=Q_F7 * DEFICIT_IMMEUBLE, mois=10.0, date="2025-11-03"), | |
| 55 | + dict(nom="C3", immeuble="B (24 fractions, ascenseur)", S=96.0, etage="2e", orient="sud", stat=True, charges_m2=78.0, passif=0.0, mois=3.0, date="2026-06-01"), | |
| 56 | + dict(nom="C4", immeuble="C (12 fractions)", S=100.0, etage="3e", orient="ouest", stat=False, charges_m2=52.0, passif=0.09 * 60_000.0, mois=4.0, date="2026-05-04"), | |
| 57 | + dict(nom="C5", immeuble="D (8 fractions)", S=92.0, etage="RDC", orient="sud", stat=True, charges_m2=58.0, passif=0.13 * 240_000.0, mois=8.0, date="2026-01-05"), | |
| 58 | +] | |
| 59 | + | |
| 60 | + | |
| 61 | +def valeur_modele(u: dict) -> float: | |
| 62 | + """Valeur « vraie » d'une fraction dans le marché fictif à la date d'évaluation (hypothèse de génération). | |
| 63 | + V = base(S) × (1 + c_étage + c_orient) + stationnement − κ × passif − (c_m² − c_ref) × S / R_c.""" | |
| 64 | + base = PRIX_M2_REF * S_REF * (u["S"] / S_REF) ** ELASTICITE_S | |
| 65 | + v = base * (1.0 + COEF_ETAGE_MARCHE[u["etage"]] + COEF_ORIENT_MARCHE[u["orient"]]) | |
| 66 | + v += STATIONNEMENT if u["stat"] else 0.0 | |
| 67 | + v -= KAPPA * u["passif"] | |
| 68 | + v -= (u["charges_m2"] - CHARGES_REF_M2) * u["S"] / R_CHARGES | |
| 69 | + return v | |
| 70 | + | |
| 71 | + | |
| 72 | +def generer_prix(comparables=COMPARABLES, seed: int = SEED) -> list[dict]: | |
| 73 | + """Prix observés = valeur du modèle déflatée à la date de vente (croissance g) × bruit, arrondis à 500 $.""" | |
| 74 | + rng = np.random.default_rng(seed) | |
| 75 | + out = [] | |
| 76 | + for c in comparables: | |
| 77 | + v = valeur_modele(c) | |
| 78 | + p = v * (1.0 + G_MARCHE) ** (-c["mois"] / 12.0) * math.exp(rng.normal(0.0, SIGMA_BRUIT)) | |
| 79 | + d = dict(c); d["prix"] = float(np.round(p / 500.0) * 500.0); d["valeur_modele"] = v | |
| 80 | + out.append(d) | |
| 81 | + return out | |
| 82 | + | |
| 83 | + | |
| 84 | +def prix_marginal_m2(S: float = S_REF) -> float: | |
| 85 | + """Prix implicite marginal d'un m² au voisinage de la superficie du sujet : dV/dS = ε × base(S) / S.""" | |
| 86 | + base = PRIX_M2_REF * S_REF * (S / S_REF) ** ELASTICITE_S | |
| 87 | + return ELASTICITE_S * base / S | |
| 88 | + | |
| 89 | + | |
| 90 | +def ajuster(comp: dict, sujet: dict = SUJET, r_charges: float = R_CHARGES, kappa: float = KAPPA, g: float = G_MARCHE) -> cp.ComparableAjuste: | |
| 91 | + """Grille : ajustement de conditions du marché (%, séquentiel) puis ajustements de propriété en dollars, signe du point de vue du sujet.""" | |
| 92 | + p_t = comp["prix"] * (1.0 + g) ** (comp["mois"] / 12.0) # prix ramené à la date d'évaluation | |
| 93 | + A = {"marché": p_t - comp["prix"]} | |
| 94 | + A["étage"] = p_t * (COEF_ETAGE_MARCHE[sujet["etage"]] - COEF_ETAGE_MARCHE[comp["etage"]]) | |
| 95 | + A["orientation"] = p_t * (COEF_ORIENT_MARCHE[sujet["orient"]] - COEF_ORIENT_MARCHE[comp["orient"]]) | |
| 96 | + A["stationnement"] = STATIONNEMENT * (int(sujet["stat"]) - int(comp["stat"])) | |
| 97 | + A["superficie"] = prix_marginal_m2() * (sujet["S"] - comp["S"]) | |
| 98 | + A["charges"] = (comp["charges_m2"] - sujet["charges_m2"]) * sujet["S"] / r_charges | |
| 99 | + A["syndicat"] = kappa * (comp["passif"] - sujet["passif"]) | |
| 100 | + return cp.ComparableAjuste(comp["nom"], comp["prix"], A) | |
| 101 | + | |
| 102 | + | |
| 103 | +def grille_fraction(r_charges: float = R_CHARGES, kappa: float = KAPPA, g: float = G_MARCHE, poids: str = "inverse_brut") -> dict: | |
| 104 | + comps = generer_prix() | |
| 105 | + ajustes = [ajuster(c, SUJET, r_charges, kappa, g) for c in comps] | |
| 106 | + w = cp.poids_inverse_brut(ajustes) if poids == "inverse_brut" else None | |
| 107 | + rec = cp.reconciliation(ajustes, w) | |
| 108 | + return {"comparables": comps, "ajustes": ajustes, "reconciliation": rec, "poids": rec["poids"], | |
| 109 | + "valeur_modele_sujet": valeur_modele(SUJET), "prix_marginal_m2": prix_marginal_m2()} | |
| 110 | + | |
| 111 | + | |
| 112 | +def sensibilite_fraction(r_charges_list=(0.05, 0.06, 0.08, 0.10, 0.12), kappas=(0.0, 0.5, 0.75, 1.0)) -> dict: | |
| 113 | + grille = {k: [grille_fraction(r, k)["reconciliation"]["valeur_ponderee"] for r in r_charges_list] for k in kappas} | |
| 114 | + return {"r_charges": list(r_charges_list), "kappas": list(kappas), "grille": grille} | |
| 115 | + | |
| 116 | + | |
| 117 | +# ----------------------------------------------------------------------------- B. cotisation spéciale | |
| 118 | +def cotisation_speciale(montant: float | None = None, qp: dict = QP) -> dict: | |
| 119 | + """Répartition d'une cotisation spéciale (par défaut égale au déficit de capitalisation du ch. 7) selon les quotes-parts ; | |
| 120 | + comparaison avec les charges annuelles normalisées (cotisation de seuil) de chaque fraction.""" | |
| 121 | + montant = DEFICIT_IMMEUBLE if montant is None else montant | |
| 122 | + A_star = c7.cotisation_seuil() | |
| 123 | + rep = c7.repartition_charges(A_star, qp) | |
| 124 | + part = qp["q"] * montant | |
| 125 | + return {"montant": montant, "par_fraction": part, "charges_annuelles": rep["par_fraction_total"], | |
| 126 | + "ratio_annees": part / rep["par_fraction_total"], "A_star": A_star, "rep": rep} | |
| 127 | + | |
| 128 | + | |
| 129 | +# ----------------------------------------------------------------------------- C. copropriété locative (F4 louée) | |
| 130 | +LOYER_F4 = 1_700.0 # $/mois ; hypothèse pédagogique dans l'ordre de grandeur des loyers SCHL (oct. 2025) des copropriétés offertes en location, 2 ch. | |
| 131 | +INOCC_COPRO = 0.021 # SCHL, RMR de Montréal, copropriétés offertes en location, octobre 2025 (tableau 4.1.1) | |
| 132 | +TAXES_F4 = 3_300.0 # $/an ; ordre de grandeur du compte de taxes moyen d'une copropriété à Montréal en 2026 (La Presse, 2026-10-19, source secondaire) | |
| 133 | +ASSURANCE_COPRO = 600.0 # $/an ; hypothèse (BAC 2022 : 544 $ pour un copropriétaire, partiellement vérifié) | |
| 134 | +GESTION = 0.05 # part du revenu brut effectif (forfait usuel) | |
| 135 | +TAUX_PLEX = 0.0525 # taux global de référence des petits immeubles locatifs du marché fictif (dans la fourchette CBRE Québec Low Rise B T3 2026 : 5,00-6,00 %) | |
| 136 | + | |
| 137 | + | |
| 138 | +def copro_locative(valeur: float, loyer: float = LOYER_F4, inocc: float = INOCC_COPRO, taxes: float = TAXES_F4, | |
| 139 | + assurance: float = ASSURANCE_COPRO, gestion: float = GESTION, frais_copro: float | None = None) -> dict: | |
| 140 | + """RNE d'une fraction louée (convention de l'ouvrage : la cotisation au fonds de prévoyance normalisée tient lieu de réserve).""" | |
| 141 | + if frais_copro is None: | |
| 142 | + frais_copro = float(c7.repartition_charges(c7.cotisation_seuil(), QP)["par_fraction_total"][3]) # F4 : 10 218 $ | |
| 143 | + rbp = loyer * 12.0 | |
| 144 | + rbe = fin.revenu_brut_effectif(rbp, inocc) | |
| 145 | + de = taxes + assurance + frais_copro + gestion * rbe | |
| 146 | + noi = fin.rne(rbe, de) | |
| 147 | + fonds = float(c7.repartition_charges(c7.cotisation_seuil(), QP)["par_fraction_fonds"][3]) # part « fonds de prévoyance » des frais (3 082 $) | |
| 148 | + return {"rbp": rbp, "rbe": rbe, "depenses": de, "frais_copro": frais_copro, "noi": noi, "noi_sans_reserve": noi + fonds, "fonds": fonds, | |
| 149 | + "taux_implicite_sans_reserve": fin.taux_global_extrait(noi + fonds, valeur), | |
| 150 | + "taux_implicite": fin.taux_global_extrait(noi, valeur), "ratio_depenses": fin.ratio_depenses(de, rbe), | |
| 151 | + "valeur_investissement_5_25": fin.capitalisation_directe(noi, TAUX_PLEX), | |
| 152 | + "mrb": fin.multiplicateur_revenu_brut(valeur, rbp)} | |
| 153 | + | |
| 154 | + | |
| 155 | +# ----------------------------------------------------------------------------- D. conversion locatif → copropriété divise | |
| 156 | +# L'immeuble fictif de huit fractions, vu AVANT conversion comme un immeuble locatif de 8 logements (hypothèses déclarées). | |
| 157 | +LOYERS_LOG = {"F1": 1_250.0, "F2": 1_650.0, "F3": 1_300.0, "F4": 1_700.0, "F5": 1_450.0, "F6": 1_850.0, "F7": 1_500.0, "F8": 1_525.0} # $/mois, loyers contractuels | |
| 158 | +INOCC_LOCATIF = 0.03 # inoccupation + mauvaises créances (hypothèse ; SCHL RMR 2025 : 2,4 % Québec, 2,9 % Montréal, 3,8 % Gatineau) | |
| 159 | +RATIO_DEPENSES_BLOC = 0.42 # dépenses d'exploitation + réserve / RBE (hypothèse pédagogique pour un immeuble de 8 logements des années 1990) | |
| 160 | +LOCATAIRES = {"F1": True, "F2": False, "F3": True, "F4": False, "F5": True, "F6": True, "F7": True, "F8": False} # occupé par un locataire (bail en cours) ? | |
| 161 | +PREEMPTION = {"F6"} # locataire(s) qui acceptent l'offre de vente (art. 54.7) à la valeur libre | |
| 162 | +ROTATION = 0.12 # part des logements occupés libérés chaque année (départ volontaire ; aucune éviction : moratoire jusqu'au 6 juin 2027) | |
| 163 | +DECOTE_OCCUPE = 0.20 # décote d'une fraction vendue occupée (bail maintenu, art. 1937 C.c.Q.) à un investisseur — hypothèse | |
| 164 | +R_PROMOTEUR = 0.10 # taux d'actualisation exigé par le convertisseur | |
| 165 | +FRAIS_VENTE = 0.04 # courtage et frais de vente (proportion du prix) — hypothèse | |
| 166 | +COUTS_CONVERSION = { # dollars de 2026, hypothèses pédagogiques | |
| 167 | + "Cadastre vertical et certificats de localisation (arpenteur-géomètre)": 28_000.0, | |
| 168 | + "Déclaration de copropriété, publication et honoraires juridiques": 22_000.0, | |
| 169 | + "Rapport d'expert et circulaire d'information (art. 54.6 Loi TAL)": 9_000.0, | |
| 170 | + "Frais de demande (TAL, municipalité), budget prévisionnel, valeur relative": 6_000.0, | |
| 171 | + "Individualisation des services et mise aux normes (8 logements)": 120_000.0, | |
| 172 | + "Rafraîchissement des parties communes": 35_000.0, | |
| 173 | + "Dotation initiale du fonds de prévoyance (solde d'ouverture requis, ch. 7)": 85_000.0, | |
| 174 | +} | |
| 175 | +HORIZON_CONV = 6 # années de projection des ventes | |
| 176 | +PORTAGE_PCT = 0.015 # coûts de portage annuels (taxes, assurance, intérêts nets des loyers résiduels) en proportion de la valeur des fractions invendues — hypothèse | |
| 177 | + | |
| 178 | + | |
| 179 | +def unites_fictives() -> list[dict]: | |
| 180 | + """Les huit fractions de l'immeuble du ch. 7 comme unités du marché fictif (charges de référence, passif nul après dotation du fonds).""" | |
| 181 | + out = [] | |
| 182 | + for i, (n, S, e, o, st) in enumerate(c7.FRACTIONS): | |
| 183 | + out.append(dict(nom=n, S=S, etage=e, orient=o, stat=st, charges_m2=CHARGES_REF_M2, passif=0.0, loyer=LOYERS_LOG[n], | |
| 184 | + locataire=LOCATAIRES[n], preemption=n in PREEMPTION, q=float(QP["q"][i]))) | |
| 185 | + return out | |
| 186 | + | |
| 187 | + | |
| 188 | +def valeur_bloc(taux: float = TAUX_PLEX, inocc: float = INOCC_LOCATIF, ratio_dep: float = RATIO_DEPENSES_BLOC) -> dict: | |
| 189 | + u = unites_fictives() | |
| 190 | + rbp = 12.0 * sum(x["loyer"] for x in u) | |
| 191 | + rbe = fin.revenu_brut_effectif(rbp, inocc) | |
| 192 | + de = ratio_dep * rbe | |
| 193 | + noi = fin.rne(rbe, de) | |
| 194 | + return {"rbp": rbp, "rbe": rbe, "depenses": de, "noi": noi, "taux": taux, "valeur": fin.capitalisation_directe(noi, taux), | |
| 195 | + "mrb": fin.multiplicateur_revenu_brut(fin.capitalisation_directe(noi, taux), rbp), "n": len(u)} | |
| 196 | + | |
| 197 | + | |
| 198 | +def calendrier_ventes(rotation: float = ROTATION, decote_occ: float = DECOTE_OCCUPE, vendre_occupe: bool = False, | |
| 199 | + horizon: int = HORIZON_CONV, unites=None) -> dict: | |
| 200 | + """Produit brut attendu par année (fin d'année), avant frais de vente. | |
| 201 | + An 1 : fractions libres et locataires qui exercent la préemption, à la valeur libre. Fractions occupées : si `vendre_occupe`, | |
| 202 | + vendues à l'an 1 avec décote ; sinon vendues libres l'année où le locataire part (probabilité ρ par an), le reliquat à l'horizon | |
| 203 | + étant vendu occupé avec décote. Valeurs libres = modèle du marché fictif (passif nul après dotation du fonds).""" | |
| 204 | + u = unites or unites_fictives() | |
| 205 | + produit = np.zeros(horizon + 1); invendu = np.zeros(horizon + 1) | |
| 206 | + valeurs = {x["nom"]: valeur_modele(x) for x in u} | |
| 207 | + libres_an1 = [x for x in u if (not x["locataire"]) or x["preemption"]] | |
| 208 | + occupes = [x for x in u if x["locataire"] and not x["preemption"]] | |
| 209 | + produit[1] += sum(valeurs[x["nom"]] for x in libres_an1) | |
| 210 | + V_occ = sum(valeurs[x["nom"]] for x in occupes) | |
| 211 | + if vendre_occupe: | |
| 212 | + produit[1] += V_occ * (1.0 - decote_occ) | |
| 213 | + else: | |
| 214 | + reste = 1.0 | |
| 215 | + for t in range(1, horizon + 1): | |
| 216 | + if t < horizon: | |
| 217 | + produit[t] += V_occ * reste * rotation # fractions libérées pendant l'année t, vendues libres en fin d'année | |
| 218 | + reste *= (1.0 - rotation) | |
| 219 | + else: | |
| 220 | + produit[t] += V_occ * reste * (1.0 - decote_occ) # reliquat vendu occupé à l'horizon | |
| 221 | + reste = 0.0 | |
| 222 | + invendu[t] = V_occ * reste | |
| 223 | + # valeur libre des fractions invendues au début de chaque année (assiette des coûts de portage) | |
| 224 | + base_portage = np.zeros(horizon + 1) | |
| 225 | + total = sum(valeurs.values()) | |
| 226 | + base_portage[1] = total | |
| 227 | + if not vendre_occupe: | |
| 228 | + reste = 1.0 | |
| 229 | + for t in range(2, horizon + 1): | |
| 230 | + reste *= (1.0 - rotation) | |
| 231 | + base_portage[t] = V_occ * reste | |
| 232 | + return {"produit": produit, "valeurs": valeurs, "somme_libre": total, "V_occ": V_occ, "n_libres_an1": len(libres_an1), | |
| 233 | + "n_occupes": len(occupes), "base_portage": base_portage} | |
| 234 | + | |
| 235 | + | |
| 236 | +def faisabilite(rotation: float = ROTATION, decote_occ: float = DECOTE_OCCUPE, r: float = R_PROMOTEUR, vendre_occupe: bool = False, | |
| 237 | + frais_vente: float = FRAIS_VENTE, couts=COUTS_CONVERSION, portage: float = PORTAGE_PCT, horizon: int = HORIZON_CONV) -> dict: | |
| 238 | + """Valeur de conversion V_conv = Σ_t [produit_t (1 − f) − portage_t] / (1+r)^t − coûts de conversion (t = 0) ; prime = V_conv − V_bloc.""" | |
| 239 | + cal = calendrier_ventes(rotation, decote_occ, vendre_occupe, horizon) | |
| 240 | + flux = np.zeros(horizon + 1) | |
| 241 | + flux[0] = -sum(couts.values()) | |
| 242 | + for t in range(1, horizon + 1): | |
| 243 | + flux[t] = cal["produit"][t] * (1.0 - frais_vente) - portage * cal["base_portage"][t] | |
| 244 | + v_conv = fin.van(list(flux), r) | |
| 245 | + bloc = valeur_bloc() | |
| 246 | + return {"flux": flux, "v_conv": v_conv, "v_bloc": bloc["valeur"], "prime": v_conv - bloc["valeur"], | |
| 247 | + "somme_libre": cal["somme_libre"], "couts": sum(couts.values()), "frais_vente_total": float(sum(cal["produit"]) * frais_vente), | |
| 248 | + "portage_total": float(sum(portage * cal["base_portage"][1:])), "produit": cal["produit"], "calendrier": cal, "bloc": bloc, | |
| 249 | + "marge_sur_bloc": (v_conv - bloc["valeur"]) / bloc["valeur"], | |
| 250 | + "somme_nette_non_actualisee": float(sum(cal["produit"]) * (1 - frais_vente) - sum(couts.values()) - sum(portage * cal["base_portage"][1:]))} | |
| 251 | + | |
| 252 | + | |
| 253 | +def sensibilite_conversion(rotations=(0.05, 0.08, 0.12, 0.20, 0.30), decotes=(0.10, 0.20, 0.30), r: float = R_PROMOTEUR) -> dict: | |
| 254 | + grille = {d: [faisabilite(rho, d, r)["v_conv"] for rho in rotations] for d in decotes} | |
| 255 | + occ = {d: faisabilite(ROTATION, d, r, vendre_occupe=True)["v_conv"] for d in decotes} | |
| 256 | + return {"rotations": list(rotations), "decotes": list(decotes), "grille": grille, "vente_occupee_an1": occ, "v_bloc": valeur_bloc()["valeur"]} | |
| 257 | + | |
| 258 | + | |
| 259 | +def seuil_prix(r: float = R_PROMOTEUR) -> float: | |
| 260 | + """Facteur multiplicatif uniforme des valeurs libres pour lequel V_conv = V_bloc (point mort de la conversion).""" | |
| 261 | + global PRIX_M2_REF | |
| 262 | + p0 = PRIX_M2_REF | |
| 263 | + lo, hi = 0.3 * p0, 1.5 * p0 | |
| 264 | + for _ in range(100): | |
| 265 | + mid = 0.5 * (lo + hi); PRIX_M2_REF = mid | |
| 266 | + if faisabilite(r=r)["prime"] > 0: hi = mid | |
| 267 | + else: lo = mid | |
| 268 | + PRIX_M2_REF = p0 | |
| 269 | + return hi / p0 | |
| 270 | + | |
| 271 | + | |
| 272 | +# ----------------------------------------------------------------------------- impression | |
| 273 | +def fmt(x): return f"{x:,.0f}".replace(",", " ") | |
| 274 | + | |
| 275 | + | |
| 276 | +def main() -> dict: | |
| 277 | + out = {} | |
| 278 | + g = grille_fraction(); out["grille"] = g | |
| 279 | + print(f"== A. Fraction F4 (sujet) — marché fictif, graine {SEED}, date d'évaluation {DATE_EVAL}") | |
| 280 | + print(f" passif du syndicat A (déficit {fmt(DEFICIT_IMMEUBLE)} $) : part de F4 = q4 {Q_F4:.4f} × déficit = {fmt(SUJET['passif'])} $ ; prix marginal {g['prix_marginal_m2']:,.0f} $/m²") | |
| 281 | + print(f" valeur du modèle pour F4 (contrôle de génération) : {fmt(g['valeur_modele_sujet'])} $") | |
| 282 | + for c, a, w in zip(g["comparables"], g["ajustes"], g["poids"]): | |
| 283 | + print(f" {c['nom']:8s} {c['immeuble']:28s} S {c['S']:5.0f} {c['etage']:3s} {c['orient']:5s} stat {int(c['stat'])} charges {c['charges_m2']:5.1f} $/m² passif {fmt(c['passif']):>7} $ " | |
| 284 | + f"{c['mois']:4.0f} mois prix {fmt(c['prix']):>8} $ (modèle {fmt(c['valeur_modele'])})") | |
| 285 | + print(" ajustements : " + ", ".join(f"{k} {v:+,.0f}" for k, v in a.ajustements.items()).replace(",", " ").replace(" $", "$")) | |
| 286 | + print(f" prix ajusté {fmt(a.prix_ajuste)} $ ; brut {a.brut_pct:.1%} ; net {a.net_pct:+.1%} ; poids {w:.3f}") | |
| 287 | + rec = g["reconciliation"] | |
| 288 | + print(f" réconciliation : pondérée {fmt(rec['valeur_ponderee'])} $ ; moyenne {fmt(rec['moyenne'])} ; médiane {fmt(rec['mediane'])} ; min {fmt(rec['min'])} ; max {fmt(rec['max'])} ; étendue {rec['etendue_pct']:.1%}") | |
| 289 | + sf = sensibilite_fraction(); out["sens_fraction"] = sf | |
| 290 | + print(" sensibilité (valeur pondérée) : lignes κ, colonnes R_c " + " ".join(f"{r:.0%}" for r in sf["r_charges"])) | |
| 291 | + for k, row in sf["grille"].items(): | |
| 292 | + print(f" κ={k:.2f} " + " ".join(f"{v:9,.0f}" for v in row).replace(",", " ")) | |
| 293 | + cs = cotisation_speciale(); out["cotisation"] = cs | |
| 294 | + print(f"\n== B. Cotisation spéciale de {fmt(cs['montant'])} $ (déficit du ch. 7) répartie selon q_i ; charges annuelles normalisées (A* = {fmt(cs['A_star'])} $)") | |
| 295 | + for n, p, ch, r in zip(QP["noms"], cs["par_fraction"], cs["charges_annuelles"], cs["ratio_annees"]): | |
| 296 | + print(f" {n} : cotisation {fmt(p):>7} $ ; charges {fmt(ch):>6} $/an ; = {r:.2f} années de charges") | |
| 297 | + v_f4 = rec["valeur_ponderee"] | |
| 298 | + cl = copro_locative(v_f4); out["locative"] = cl | |
| 299 | + print(f"\n== C. F4 louée {fmt(LOYER_F4)} $/mois : RBP {fmt(cl['rbp'])} ; RBE {fmt(cl['rbe'])} ; dépenses {fmt(cl['depenses'])} (frais copro {fmt(cl['frais_copro'])}) ; RNE {fmt(cl['noi'])} $") | |
| 300 | + print(f" RNE hors cotisation au fonds ({fmt(cl['fonds'])} $) = {fmt(cl['noi_sans_reserve'])} $ → taux {cl['taux_implicite_sans_reserve']:.2%}") | |
| 301 | + print(f" taux implicite sur {fmt(v_f4)} $ = {cl['taux_implicite']:.2%} ; ratio de dépenses {cl['ratio_depenses']:.1%} ; MRB {cl['mrb']:.1f} ; valeur d'investissement à {TAUX_PLEX:.2%} = {fmt(cl['valeur_investissement_5_25'])} $") | |
| 302 | + fb = faisabilite(); out["faisabilite"] = fb | |
| 303 | + b = fb["bloc"] | |
| 304 | + print(f"\n== D. Conversion — immeuble locatif de 8 logements : RBP {fmt(b['rbp'])} ; RBE {fmt(b['rbe'])} ; DE {fmt(b['depenses'])} ; RNE {fmt(b['noi'])} ; R {b['taux']:.2%} → V_bloc {fmt(b['valeur'])} $ ({fmt(b['valeur']/8)} $/log., MRB {b['mrb']:.1f})") | |
| 305 | + cal = fb["calendrier"] | |
| 306 | + print(" valeurs libres des fractions : " + ", ".join(f"{k} {fmt(v)}" for k, v in cal["valeurs"].items()) + f" ; somme {fmt(cal['somme_libre'])} $") | |
| 307 | + print(f" libres ou préemption à l'an 1 : {cal['n_libres_an1']} fractions ; occupées : {cal['n_occupes']} (valeur libre {fmt(cal['V_occ'])} $) ; ρ = {ROTATION:.0%}, décote occupée {DECOTE_OCCUPE:.0%}, r = {R_PROMOTEUR:.0%}") | |
| 308 | + print(" produit brut par année : " + ", ".join(f"an {t} {fmt(p)}" for t, p in enumerate(fb["produit"]) if t > 0)) | |
| 309 | + print(" flux : " + ", ".join(f"t{t} {f:,.0f}".replace(",", " ") for t, f in enumerate(fb["flux"]))) | |
| 310 | + print(f" coûts de conversion {fmt(fb['couts'])} ; frais de vente {fmt(fb['frais_vente_total'])} ; portage {fmt(fb['portage_total'])} ; somme nette non actualisée {fmt(fb['somme_nette_non_actualisee'])}") | |
| 311 | + print(f" V_conv = {fmt(fb['v_conv'])} $ ; V_bloc = {fmt(fb['v_bloc'])} $ ; prime {fmt(fb['prime'])} $ ({fb['marge_sur_bloc']:+.1%})") | |
| 312 | + fo = faisabilite(vendre_occupe=True); out["faisabilite_occ"] = fo | |
| 313 | + print(f" variante : tout vendre à l'an 1 (occupées avec décote) → V_conv {fmt(fo['v_conv'])} $ ; prime {fmt(fo['prime'])} $") | |
| 314 | + sc = sensibilite_conversion(); out["sens_conv"] = sc | |
| 315 | + print(" sensibilité V_conv : lignes décote occupée, colonnes ρ " + " ".join(f"{r:.0%}" for r in sc["rotations"]) + " | vente occupée an 1") | |
| 316 | + for d, row in sc["grille"].items(): | |
| 317 | + print(f" δ={d:.0%} " + " ".join(f"{v:10,.0f}" for v in row).replace(",", " ") + f" | {sc['vente_occupee_an1'][d]:10,.0f}".replace(",", " ")) | |
| 318 | + sp = seuil_prix(); out["seuil_prix"] = sp | |
| 319 | + print(f" point mort : les valeurs libres peuvent baisser jusqu'à {sp:.1%} de leur niveau (−{1-sp:.1%}) avant que V_conv < V_bloc") | |
| 320 | + for r in (0.08, 0.12): | |
| 321 | + print(f" r = {r:.0%} : V_conv {fmt(faisabilite(r=r)['v_conv'])} $") | |
| 322 | + return out | |
| 323 | + | |
| 324 | + | |
| 325 | +if __name__ == "__main__": | |
| 326 | + main() | |
added
analysis/python/cases/ch43_risques.py
+386 −0
@@ -0,0 +1,386 @@ | ||
| 1 | +#!/usr/bin/env python3 | |
| 2 | +"""Chapitre 43 — Risques environnementaux et sinistres : DONNÉES SYNTHÉTIQUES et calculs de l'exemple intégral. | |
| 3 | + | |
| 4 | +Quatre blocs, tous reproductibles (graine 20261031) : | |
| 5 | + | |
| 6 | +1. Cas 4 (portefeuille, `traite.cas4`) : lecture du coefficient `inondable` et `sol` de `effets_regionaux` | |
| 7 | + (prime de risque dans le log du taux global ; loi déclarée du générateur : +5 % du taux en zone inondable, | |
| 8 | + +3 % si contamination), part de la valeur du portefeuille située en zone inondable. | |
| 9 | +2. Cas 4, doubles différences : on injecte sur les séries trimestrielles de RNE des trois immeubles | |
| 10 | + `zone_inondable == 1` un choc d'inondation déclaré au printemps 2019 (chute transitoire puis perte | |
| 11 | + permanente de 5 % du RNE), puis on l'estime par DiD 2 × 2 (naïf et par strate de RMR), par étude | |
| 12 | + d'événement à effets fixes immeuble et RMR × trimestre (`traite.panel`) et par inférence de | |
| 13 | + randomisation exacte (toutes les affectations de 3 immeubles traités parmi 48). | |
| 14 | +3. Municipalité riveraine fictive « Rivière-Fictive » : 625 lots dont 175 en zone inondable (classes très | |
| 15 | + élevée / élevée / modérée / faible) le long d'une rivière et 75 lots riverains d'un lac non inondable, ventes | |
| 16 | + 2016-2026 avec décote déclarée par classe, stigmate après une inondation le 1er mai 2019 et aménité « vue sur | |
| 17 | + l'eau » (rivière et lac) qui brouille la mesure si on l'omet. Quatre méthodes de mesure : | |
| 18 | + ventes appariées, hédonique à indicatrice (avec et sans contrôle de l'aménité), hédonique par classe avec | |
| 19 | + interaction classe × après, doubles différences (cellules 2 × 2 et régression). | |
| 20 | +4. Modèles de valeur : terrain contaminé (coût de réhabilitation + délai + stigmate), immeuble sinistré | |
| 21 | + (remise en état + perte d'usage + stigmate résiduel ; rôle de l'assurance), désuétude vs coût de correction. | |
| 22 | + | |
| 23 | +Écrit data/synthetic/ch43_riverain_ventes.csv et ch43_riverain_lots.csv s'ils n'existent pas. | |
| 24 | +Usage : python3 analysis/python/cases/ch43_risques.py | |
| 25 | +""" | |
| 26 | +from __future__ import annotations | |
| 27 | +import sys, pathlib, math, itertools | |
| 28 | +from datetime import date, timedelta | |
| 29 | +import numpy as np | |
| 30 | +import pandas as pd | |
| 31 | + | |
| 32 | +ROOT = pathlib.Path(__file__).resolve().parents[3] | |
| 33 | +sys.path.insert(0, str(ROOT / "analysis/python")) | |
| 34 | +from traite import cas4, comparables as cp, hedonique as h, panel # noqa: E402 | |
| 35 | + | |
| 36 | +SEED = 20261031 | |
| 37 | +D = ROOT / "data/synthetic" | |
| 38 | + | |
| 39 | +# =============================================================================== 1. cas 4 : prime de risque dans le taux | |
| 40 | + | |
| 41 | +def prime_taux_cas4() -> dict: | |
| 42 | + """Coefficients `inondable` et `sol` de cas4.effets_regionaux : effet en % du taux, IC à 95 % (HC1), | |
| 43 | + comparaison à la loi déclarée (+5 % / +3 %) et part de la valeur en zone inondable.""" | |
| 44 | + d = cas4.charger(); pf = d["portefeuille"] | |
| 45 | + eff = cas4.effets_regionaux(pf); som = cas4.sommaire(pf) | |
| 46 | + out = {"n": len(pf), "n_inondable": int(pf["zone_inondable"].sum()), "n_sol": int(pf["contamination_sol"].sum()), | |
| 47 | + "part_valeur_inondable": som["total"]["part_zone_inondable"], "valeur_totale": som["total"]["valeur"]} | |
| 48 | + for v, declare in [("inondable", 0.05), ("sol", 0.03)]: | |
| 49 | + b, se = float(eff.loc[v, "coef"]), float(eff.loc[v, "se_hc1"]) | |
| 50 | + out[v] = {"coef": b, "se": se, "effet_pct": math.exp(b) - 1, "ic_bas_pct": math.exp(b - 1.96 * se) - 1, | |
| 51 | + "ic_haut_pct": math.exp(b + 1.96 * se) - 1, "declare_pct": declare, "declare_log": math.log(1 + declare), | |
| 52 | + "t_vs_declare": (b - math.log(1 + declare)) / se, | |
| 53 | + # correction de Kennedy (variance non négligeable) : exp(b − V/2) − 1 | |
| 54 | + "effet_kennedy_pct": math.exp(b - 0.5 * se ** 2) - 1} | |
| 55 | + # décote de valeur impliquée par la prime de taux, à RNE donné : V = RNE/R → ΔlnV = −Δln R | |
| 56 | + out["decote_valeur_inondable_pct"] = math.exp(-out["inondable"]["coef"]) - 1 | |
| 57 | + out["decote_valeur_declaree_pct"] = 1 / 1.05 - 1 | |
| 58 | + out["immeubles_inondables"] = pf.loc[pf["zone_inondable"] == 1, ["id_immeuble", "rmr", "nb_logements", "valeur_2026", "rne_2026", "taux_global"]].reset_index(drop=True) | |
| 59 | + out["immeubles_sol"] = pf.loc[pf["contamination_sol"] == 1, ["id_immeuble", "rmr", "nb_logements", "valeur_2026", "rne_2026", "taux_global"]].reset_index(drop=True) | |
| 60 | + out["portefeuille"] = pf | |
| 61 | + return out | |
| 62 | + | |
| 63 | +# =============================================================================== 2. cas 4 : DiD sur les séries de RNE | |
| 64 | + | |
| 65 | +CHOC_2019 = {"2019Q2": -0.25, "2019Q3": -0.12, "2019Q4": -0.06} # ln RNE, transitoire (perte d'usage, remise en état) | |
| 66 | +CHOC_PERMANENT = -0.05 # ln RNE à partir de 2020Q1 (loyers, inoccupation, assurance) | |
| 67 | +T_EVENEMENT = "2019Q2" | |
| 68 | + | |
| 69 | +def panel_choc(seed: int = SEED) -> pd.DataFrame: | |
| 70 | + """Panel long (48 immeubles × 42 trimestres) de ln RNE avec le choc déclaré injecté sur les immeubles | |
| 71 | + `zone_inondable == 1` ; colonnes : id, rmr, t (0..41), trimestre, traite, post, ln_rne, ln_rne_sans_choc, g.""" | |
| 72 | + d = cas4.charger(); pf, se = d["portefeuille"], d["series"].copy() | |
| 73 | + se["trimestre"] = se["trimestre"].astype(str) | |
| 74 | + se = se.merge(pf[["id_immeuble", "rmr", "zone_inondable"]], on="id_immeuble") | |
| 75 | + trims = sorted(se["trimestre"].unique()); idx = {q: i for i, q in enumerate(trims)} | |
| 76 | + se["t"] = se["trimestre"].map(idx); t_ev = idx[T_EVENEMENT] | |
| 77 | + se["traite"] = se["zone_inondable"].astype(int); se["post"] = (se["t"] >= t_ev).astype(int) | |
| 78 | + se["ln_rne_sans_choc"] = np.log(se["rne_trimestriel"].astype(float)) | |
| 79 | + choc = np.zeros(len(se)) | |
| 80 | + for q, c in CHOC_2019.items(): | |
| 81 | + choc[(se["trimestre"] == q).values & (se["traite"] == 1).values] = c | |
| 82 | + choc[(se["t"] >= idx["2020Q1"]).values & (se["traite"] == 1).values] = CHOC_PERMANENT | |
| 83 | + se["choc"] = choc; se["ln_rne"] = se["ln_rne_sans_choc"] + choc | |
| 84 | + se["g"] = np.where(se["traite"] == 1, float(t_ev), np.nan) | |
| 85 | + se["unite"] = se["id_immeuble"] | |
| 86 | + return se.sort_values(["id_immeuble", "t"]).reset_index(drop=True) | |
| 87 | + | |
| 88 | +def did_cas4(pan: pd.DataFrame) -> dict: | |
| 89 | + trims = sorted(pan["trimestre"].unique()); idx = {q: i for i, q in enumerate(trims)} | |
| 90 | + avant = list(range(idx["2017Q1"], idx["2019Q1"] + 1)); apres = list(range(idx["2020Q1"], idx["2026Q2"] + 1)) | |
| 91 | + r = panel.did_2x2(pan, "ln_rne", "unite", "t", "traite", avant, apres) | |
| 92 | + # version par strate : Δ_i centré sur la moyenne des témoins de la même RMR | |
| 93 | + dl = r["deltas"].merge(pan[["unite", "rmr"]].drop_duplicates(), on="unite") | |
| 94 | + m_rmr = dl[dl["traite"] == 0].groupby("rmr")["dy"].mean() | |
| 95 | + dl["dy_strate"] = dl["dy"] - dl["rmr"].map(m_rmr) | |
| 96 | + est_strate = float(dl.loc[dl["traite"] == 1, "dy_strate"].mean()) | |
| 97 | + # inférence de randomisation exacte : toutes les affectations de 3 traités parmi 48 (C(48,3) = 17 296) | |
| 98 | + dy = dl["dy"].to_numpy(); ids = np.arange(len(dy)); n_tr = int(dl["traite"].sum()) | |
| 99 | + tot = dy.sum(); n = len(dy) | |
| 100 | + combos = np.array(list(itertools.combinations(ids, n_tr))) | |
| 101 | + s_tr = dy[combos].sum(axis=1) | |
| 102 | + est_all = s_tr / n_tr - (tot - s_tr) / (n - n_tr) | |
| 103 | + obs = r["estimateur"] | |
| 104 | + p_ri = float(np.mean(np.abs(est_all) >= abs(obs) - 1e-12)) | |
| 105 | + # idem pour l'estimateur par strate | |
| 106 | + dys = dl["dy_strate"].to_numpy() # témoins centrés par RMR ; pour les placebos on recentre à chaque tirage | |
| 107 | + rmr_codes = dl["rmr"].astype("category").cat.codes.to_numpy() | |
| 108 | + est_strate_all = np.empty(len(combos)) | |
| 109 | + for k, c in enumerate(combos): | |
| 110 | + mask = np.zeros(n, bool); mask[c] = True | |
| 111 | + # moyenne des témoins par RMR | |
| 112 | + num = np.bincount(rmr_codes[~mask], weights=dy[~mask], minlength=rmr_codes.max() + 1) | |
| 113 | + den = np.bincount(rmr_codes[~mask], minlength=rmr_codes.max() + 1) | |
| 114 | + mr = np.where(den > 0, num / np.maximum(den, 1), 0.0) | |
| 115 | + est_strate_all[k] = float(np.mean(dy[mask] - mr[rmr_codes[mask]])) | |
| 116 | + p_ri_strate = float(np.mean(np.abs(est_strate_all) >= abs(est_strate) - 1e-12)) | |
| 117 | + # étude d'événement : effets fixes immeuble + RMR × trimestre, fenêtre −8..+12, référence −1 | |
| 118 | + es = panel.event_study(pan, "ln_rne", "unite", "t", "g", fenetre=(-8, 12), ref=-1, fe_supp="rmr") | |
| 119 | + # TWFE statique avec RMR × trimestre | |
| 120 | + pan2 = pan.copy(); pan2["traite_post"] = pan2["traite"] * pan2["post"] | |
| 121 | + pan2["traite_permanent"] = pan2["traite"] * (pan2["t"] >= idx["2020Q1"]).astype(int) | |
| 122 | + pan2["traite_transitoire"] = pan2["traite"] * pan2["trimestre"].isin(list(CHOC_2019)).astype(int) | |
| 123 | + tw = panel.twfe(pan2, "ln_rne", ["traite_transitoire", "traite_permanent"], "unite", "t", fe_supp="rmr") | |
| 124 | + # lien avec la valeur : Δln V = Δln RNE − Δln R | |
| 125 | + pr = prime_taux_cas4() | |
| 126 | + return {"cellules": r["cellules"], "estimateur_2x2": obs, "coef_reg": r["coef_reg"], "se_hc1": r["se_hc1"], | |
| 127 | + "estimateur_strate": est_strate, "p_ri": p_ri, "p_ri_strate": p_ri_strate, "n_combinaisons": int(len(combos)), | |
| 128 | + "rang_obs": int(np.sum(est_all <= obs)), "n_traites": r["n_traites"], "n_temoins": r["n_temoins"], | |
| 129 | + "deltas": dl, "event_study": es, "twfe": tw["coef"], "placebo_dist": est_all, "placebo_dist_strate": est_strate_all, | |
| 130 | + "verite_permanente": CHOC_PERMANENT, "verite_permanente_pct": math.exp(CHOC_PERMANENT) - 1, | |
| 131 | + "verite_transitoire_moy": float(np.mean(list(CHOC_2019.values()))), | |
| 132 | + "effet_valeur_estime_log": tw["coef"].loc["traite_permanent", "coef"] - pr["inondable"]["coef"], | |
| 133 | + "effet_valeur_estime_pct": math.exp(tw["coef"].loc["traite_permanent", "coef"] - pr["inondable"]["coef"]) - 1, | |
| 134 | + "effet_valeur_declare_log": CHOC_PERMANENT - math.log(1.05), | |
| 135 | + "effet_valeur_declare_pct": math.exp(CHOC_PERMANENT - math.log(1.05)) - 1, | |
| 136 | + "idx": idx, "trims": trims} | |
| 137 | + | |
| 138 | +# =============================================================================== 3. Rivière-Fictive : ventes riveraines | |
| 139 | + | |
| 140 | +NCOL, NLIG, PAS = 25, 25, 40.0 # 625 lots de 40 m × 40 m ; la rivière longe le bord y = 0, un lac (non inondable) le bord x = 24 | |
| 141 | +COLS_LAC = {22, 23, 24} # lots riverains du lac : aménité « vue sur l'eau » sans exposition à l'inondation | |
| 142 | +CLASSES = ["très élevée", "élevée", "modérée", "faible", "hors zone"] | |
| 143 | +LIGNES_CLASSE = {0: "très élevée", 1: "élevée", 2: "élevée", 3: "modérée", 4: "modérée", 5: "faible", 6: "faible"} | |
| 144 | +DECOTE_AVANT = {"très élevée": -0.10, "élevée": -0.07, "modérée": -0.04, "faible": -0.015, "hors zone": 0.0} # ln P, hypothèses | |
| 145 | +STIGMATE = 0.5 # après l'inondation du 1er mai 2019, la décote est multipliée par (1 + STIGMATE) | |
| 146 | +AMENITE_RIVE = 0.04 # ln P, lots à moins de 120 m de la rivière OU riverains du lac (vue, accès) ; indépendant du risque | |
| 147 | +T_INOND = date(2019, 5, 1) | |
| 148 | +DATE_DEBUT, DATE_FIN, DATE_REF = date(2016, 1, 1), date(2026, 8, 31), date(2021, 1, 1) | |
| 149 | +PRIX_BASE, TAUX_MARCHE, SIGMA = 390_000.0, 0.035, 0.07 | |
| 150 | +BETA = {"ln_hab": 0.55, "ln_terr": 0.10, "age": -0.005, "qualite": 0.06, "garage": 0.035} | |
| 151 | + | |
| 152 | +def generer_riverain(seed: int = SEED) -> tuple[pd.DataFrame, pd.DataFrame]: | |
| 153 | + rng = np.random.default_rng(seed) | |
| 154 | + lots = [] | |
| 155 | + for y in range(NLIG): | |
| 156 | + for x in range(NCOL): | |
| 157 | + cl = LIGNES_CLASSE.get(y, "hors zone") | |
| 158 | + dist = (y + 0.5) * PAS + rng.uniform(-8, 8) | |
| 159 | + lots.append({"id_lot": f"RF{y:02d}{x:02d}", "x": x, "y": y, "x_m": (x + 0.5) * PAS, "y_m": (y + 0.5) * PAS, | |
| 160 | + "dist_riviere_m": round(dist, 1), "classe": cl, "expose": int(cl != "hors zone"), "rive": int(dist < 120 or x in COLS_LAC)}) | |
| 161 | + lots = pd.DataFrame(lots) | |
| 162 | + # ventes : chaque lot se vend en moyenne 1,6 fois sur la période (Poisson), dates uniformes | |
| 163 | + ventes = [] | |
| 164 | + n_jours = (DATE_FIN - DATE_DEBUT).days | |
| 165 | + k = 0 | |
| 166 | + for _, L in lots.iterrows(): | |
| 167 | + nv = rng.poisson(1.6) | |
| 168 | + hab = float(np.exp(rng.normal(np.log(140), 0.22))); terr = float(np.clip(rng.normal(560, 110), 300, 1200)) | |
| 169 | + annee = int(np.clip(rng.normal(1985, 18), 1930, 2016)); qual = int(np.clip(round(rng.normal(3, 0.8)), 1, 5)); gar = int(rng.random() < 0.55) | |
| 170 | + for _ in range(nv): | |
| 171 | + dv = DATE_DEBUT + timedelta(days=int(rng.integers(0, n_jours + 1))) | |
| 172 | + age = dv.year - annee; post = int(dv >= T_INOND) | |
| 173 | + decote = DECOTE_AVANT[L["classe"]] * (1 + STIGMATE * post) | |
| 174 | + lnp = (math.log(PRIX_BASE) + BETA["ln_hab"] * math.log(hab / 140) + BETA["ln_terr"] * math.log(terr / 560) + BETA["age"] * age | |
| 175 | + + BETA["qualite"] * (qual - 3) + BETA["garage"] * gar + AMENITE_RIVE * L["rive"] + decote | |
| 176 | + + math.log(1 + TAUX_MARCHE) * ((dv - DATE_REF).days / 365.25) + rng.normal(0, SIGMA)) | |
| 177 | + k += 1 | |
| 178 | + ventes.append({"id_vente": f"V{k:04d}", "id_lot": L["id_lot"], "date_vente": dv.isoformat(), "prix_vente": round(math.exp(lnp) / 500) * 500, | |
| 179 | + "sup_habitable_m2": round(hab, 1), "sup_terrain_m2": round(terr), "annee_construction": annee, "age": age, "qualite": qual, | |
| 180 | + "garage": gar, "classe": L["classe"], "expose": L["expose"], "rive": L["rive"], "dist_riviere_m": L["dist_riviere_m"], | |
| 181 | + "post": post, "decote_declaree": round(decote, 5)}) | |
| 182 | + ventes = pd.DataFrame(ventes).sort_values("date_vente").reset_index(drop=True) | |
| 183 | + return lots, ventes | |
| 184 | + | |
| 185 | +def charger_ou_generer(ecrire: bool = True) -> tuple[pd.DataFrame, pd.DataFrame]: | |
| 186 | + fl, fv = D / "ch43_riverain_lots.csv", D / "ch43_riverain_ventes.csv" | |
| 187 | + lots, ventes = generer_riverain() | |
| 188 | + if ecrire and not (fl.exists() and fv.exists()): | |
| 189 | + lots.to_csv(fl, index=False); ventes.to_csv(fv, index=False) | |
| 190 | + return lots, ventes | |
| 191 | + | |
| 192 | +def _X(v: pd.DataFrame, colonnes_sup: list[pd.Series]) -> np.ndarray: | |
| 193 | + t = (pd.to_datetime(v["date_vente"]) - pd.Timestamp(DATE_REF)).dt.days / 365.25 | |
| 194 | + base = [np.ones(len(v)), np.log(v["sup_habitable_m2"] / 140), np.log(v["sup_terrain_m2"] / 560), v["age"].astype(float), | |
| 195 | + (v["qualite"] - 3).astype(float), v["garage"].astype(float), t] | |
| 196 | + return np.column_stack(base + [np.asarray(c, float) for c in colonnes_sup]) | |
| 197 | + | |
| 198 | +def apparier(ventes: pd.DataFrame, fenetre_mois: float = 12.0) -> pd.DataFrame: | |
| 199 | + """Ventes appariées après l'inondation : chaque vente exposée est appariée à la vente hors zone la plus proche | |
| 200 | + (distance d'attributs : superficies, âge, qualité, garage, date ≤ 12 mois) ; témoin ajusté du temps et des écarts | |
| 201 | + physiques par les coefficients hédoniques estimés sur les témoins (ch. 17).""" | |
| 202 | + post = ventes[ventes["post"] == 1].copy(); post["d"] = pd.to_datetime(post["date_vente"]) | |
| 203 | + ex = post[post["expose"] == 1]; te = post[post["expose"] == 0].copy() | |
| 204 | + # coefficients physiques sur les témoins seulement (hors zone) | |
| 205 | + r = h.mco(_X(te, []), np.log(te["prix_vente"].values)); b = r["beta"] | |
| 206 | + ech = {"sup_habitable_m2": 25, "sup_terrain_m2": 150, "age": 10, "qualite": 1, "garage": 1} | |
| 207 | + rows = []; pris = set() | |
| 208 | + for _, s in ex.iterrows(): | |
| 209 | + cand = te[(te["d"] - s["d"]).abs() <= pd.Timedelta(days=int(fenetre_mois * 30.4375))] | |
| 210 | + cand = cand[~cand["id_vente"].isin(pris)] | |
| 211 | + if cand.empty: continue | |
| 212 | + dist = sum(((cand[k] - s[k]) / e) ** 2 for k, e in ech.items()) + ((cand["d"] - s["d"]).dt.days / 182.6) ** 2 | |
| 213 | + tem = cand.loc[dist.idxmin()]; pris.add(tem["id_vente"]) | |
| 214 | + mois = (s["d"] - tem["d"]).days / 30.4375 | |
| 215 | + aj_temps = cp.ajustement_temps_taux(tem["prix_vente"], TAUX_MARCHE, mois) | |
| 216 | + fact = math.exp(b[1] * math.log(s["sup_habitable_m2"] / tem["sup_habitable_m2"]) + b[2] * math.log(s["sup_terrain_m2"] / tem["sup_terrain_m2"]) | |
| 217 | + + b[3] * (s["age"] - tem["age"]) + b[4] * (s["qualite"] - tem["qualite"]) + b[5] * (s["garage"] - tem["garage"])) | |
| 218 | + p_aj = (tem["prix_vente"] + aj_temps) * fact | |
| 219 | + rows.append({"id_expose": s["id_vente"], "id_temoin": tem["id_vente"], "classe": s["classe"], "rive": s["rive"], "prix_expose": s["prix_vente"], | |
| 220 | + "prix_temoin": tem["prix_vente"], "ajust_temps": aj_temps, "facteur_physique": fact, "prix_temoin_ajuste": p_aj, | |
| 221 | + "difference": s["prix_vente"] - p_aj, "difference_pct": s["prix_vente"] / p_aj - 1, "decote_declaree": s["decote_declaree"]}) | |
| 222 | + return pd.DataFrame(rows) | |
| 223 | + | |
| 224 | +def resume_paires(p: pd.DataFrame) -> dict: | |
| 225 | + r = cp.ventes_appariees(p["prix_expose"].tolist(), p["prix_temoin_ajuste"].tolist()) | |
| 226 | + pct = p["difference_pct"]; n = len(p) | |
| 227 | + out = {"n": n, "moyenne": r["moyenne"], "mediane": r["mediane"], "ecart_type": r["ecart_type"], "erreur_type": r["ecart_type"] / math.sqrt(n), | |
| 228 | + "moyenne_pct": float(pct.mean()), "mediane_pct": float(pct.median()), "et_pct": float(pct.std(ddof=1) / math.sqrt(n)), | |
| 229 | + "verite_pct": float(np.exp(p["decote_declaree"]).mean() - 1), "verite_log": float(p["decote_declaree"].mean())} | |
| 230 | + out["ic95_pct"] = (out["moyenne_pct"] - 1.96 * out["et_pct"], out["moyenne_pct"] + 1.96 * out["et_pct"]) | |
| 231 | + out["par_classe"] = p.groupby("classe")["difference_pct"].agg(["count", "mean"]).rename(columns={"count": "n", "mean": "moyenne_pct"}) | |
| 232 | + out["par_rive"] = p.groupby("rive")["difference_pct"].agg(["count", "mean"]).rename(columns={"count": "n", "mean": "moyenne_pct"}) | |
| 233 | + return out | |
| 234 | + | |
| 235 | +def hedoniques(ventes: pd.DataFrame) -> dict: | |
| 236 | + """(a) indicatrice `exposé` sans contrôle de l'aménité ; (b) avec `rive` ; (c) par classe avec interactions classe × après ; | |
| 237 | + (d) DiD par régression exposé × après (avec rive).""" | |
| 238 | + v = ventes; y = np.log(v["prix_vente"].values); out = {} | |
| 239 | + ra = h.mco(_X(v, [v["expose"]]), y) | |
| 240 | + out["a_sans_rive"] = {"coef": ra["beta"][7], "se": ra["se_hc1"][7], "r2": ra["r2"], "sigma": math.sqrt(ra["sigma2"])} | |
| 241 | + rb = h.mco(_X(v, [v["expose"], v["rive"]]), y) | |
| 242 | + out["b_avec_rive"] = {"coef": rb["beta"][7], "se": rb["se_hc1"][7], "coef_rive": rb["beta"][8], "se_rive": rb["se_hc1"][8], "r2": rb["r2"], "sigma": math.sqrt(rb["sigma2"])} | |
| 243 | + cls = ["très élevée", "élevée", "modérée", "faible"] | |
| 244 | + cols = [(v["classe"] == c).astype(float) for c in cls] + [(v["classe"] == c).astype(float) * v["post"] for c in cls] + [v["rive"]] | |
| 245 | + rc = h.mco(_X(v, cols), y) | |
| 246 | + noms = [f"{c}" for c in cls] + [f"{c} × après" for c in cls] + ["rive"] | |
| 247 | + out["c_classes"] = pd.DataFrame({"coef": rc["beta"][7:], "se": rc["se_hc1"][7:]}, index=noms) | |
| 248 | + out["c_classes"]["effet_pct"] = np.exp(out["c_classes"]["coef"]) - 1 | |
| 249 | + out["c_classes"]["verite"] = [DECOTE_AVANT[c] for c in cls] + [DECOTE_AVANT[c] * STIGMATE for c in cls] + [AMENITE_RIVE] | |
| 250 | + out["c_classes"]["r2"] = rc["r2"] | |
| 251 | + rd = h.mco(_X(v, [v["expose"], v["expose"] * v["post"], v["rive"]]), y) # `post` seul est colinéaire avec la tendance ? non : on l'ajoute | |
| 252 | + rd = h.mco(_X(v, [v["expose"], v["post"], v["expose"] * v["post"], v["rive"]]), y) | |
| 253 | + out["d_did_reg"] = {"coef_expose": rd["beta"][7], "se_expose": rd["se_hc1"][7], "coef_post": rd["beta"][8], "coef_did": rd["beta"][9], "se_did": rd["se_hc1"][9], | |
| 254 | + "coef_rive": rd["beta"][10], "r2": rd["r2"], "sigma": math.sqrt(rd["sigma2"])} | |
| 255 | + # cellules 2 × 2 sur ln P (sans contrôle) | |
| 256 | + m = v.assign(lnp=y).groupby(["expose", "post"])["lnp"].mean() | |
| 257 | + out["d_did_2x2"] = {"moyennes": m, "estimateur": float((m[(1, 1)] - m[(1, 0)]) - (m[(0, 1)] - m[(0, 0)])), | |
| 258 | + "effectifs": v.groupby(["expose", "post"]).size().to_dict()} | |
| 259 | + # vérités : décote moyenne (log) des ventes exposées avant / après, stigmate moyen | |
| 260 | + ex = v[v["expose"] == 1] | |
| 261 | + out["verite"] = {"decote_avant_log": float(ex.loc[ex["post"] == 0, "decote_declaree"].mean()), "decote_apres_log": float(ex.loc[ex["post"] == 1, "decote_declaree"].mean()), | |
| 262 | + "decote_toutes_log": float(ex["decote_declaree"].mean())} | |
| 263 | + out["verite"]["stigmate_log"] = out["verite"]["decote_apres_log"] - out["verite"]["decote_avant_log"] | |
| 264 | + out["effectifs"] = {"ventes": len(v), "exposees": int(v["expose"].sum()), "rive": int(v["rive"].sum()), "apres": int(v["post"].sum())} | |
| 265 | + return out | |
| 266 | + | |
| 267 | +def effets_par_methode(ventes: pd.DataFrame, paires: pd.DataFrame) -> pd.DataFrame: | |
| 268 | + """Tableau des estimations de la décote (ln P) des ventes exposées après l'inondation, par méthode, avec IC à 95 %.""" | |
| 269 | + he = hedoniques(ventes); rp = resume_paires(paires) | |
| 270 | + lignes = [] | |
| 271 | + lignes.append({"methode": "Ventes appariées (après 2019, sans contrôle de l'aménité)", "coef": math.log(1 + rp["moyenne_pct"]), "se": rp["et_pct"] / (1 + rp["moyenne_pct"]), "cible": "décote après"}) | |
| 272 | + lignes.append({"methode": "Hédonique, indicatrice exposé (sans « rive »)", "coef": he["a_sans_rive"]["coef"], "se": he["a_sans_rive"]["se"], "cible": "décote moyenne"}) | |
| 273 | + lignes.append({"methode": "Hédonique, indicatrice exposé + « rive »", "coef": he["b_avec_rive"]["coef"], "se": he["b_avec_rive"]["se"], "cible": "décote moyenne"}) | |
| 274 | + d = he["d_did_reg"] | |
| 275 | + lignes.append({"methode": "DiD régression : exposé (avant)", "coef": d["coef_expose"], "se": d["se_expose"], "cible": "décote avant"}) | |
| 276 | + lignes.append({"methode": "DiD régression : exposé × après (stigmate)", "coef": d["coef_did"], "se": d["se_did"], "cible": "stigmate"}) | |
| 277 | + lignes.append({"methode": "DiD régression : décote après = somme", "coef": d["coef_expose"] + d["coef_did"], "se": float("nan"), "cible": "décote après"}) | |
| 278 | + lignes.append({"methode": "DiD 2 × 2 sur les moyennes de ln P (stigmate)", "coef": he["d_did_2x2"]["estimateur"], "se": float("nan"), "cible": "stigmate"}) | |
| 279 | + t = pd.DataFrame(lignes) | |
| 280 | + ver = he["verite"] | |
| 281 | + t["verite"] = t["cible"].map({"décote après": ver["decote_apres_log"], "décote moyenne": ver["decote_toutes_log"], "décote avant": ver["decote_avant_log"], "stigmate": ver["stigmate_log"]}) | |
| 282 | + t["effet_pct"] = np.exp(t["coef"]) - 1; t["ic_bas"] = t["coef"] - 1.96 * t["se"]; t["ic_haut"] = t["coef"] + 1.96 * t["se"] | |
| 283 | + return t | |
| 284 | + | |
| 285 | +# =============================================================================== 4. modèles de valeur | |
| 286 | + | |
| 287 | +def valeur_terrain_contamine(v_propre: float, cout_rehab: float, delai_mois: float, r: float, stigmate: float, contingence: float = 0.0, perte_revenu_mensuelle: float = 0.0) -> dict: | |
| 288 | + """V_c = V_p (1 − s)(1 + r)^(−T) − C(1 + k)(1 + r)^(−T/2) − Σ pertes de revenu actualisées pendant les travaux. | |
| 289 | + T en années ; C payé à mi-parcours ; pertes mensuelles actualisées au taux périodique équivalent.""" | |
| 290 | + T = delai_mois / 12.0; rm = (1 + r) ** (1 / 12) - 1 | |
| 291 | + va_propre = v_propre * (1 - stigmate) / (1 + r) ** T | |
| 292 | + va_cout = cout_rehab * (1 + contingence) / (1 + r) ** (T / 2) | |
| 293 | + va_perte = sum(perte_revenu_mensuelle / (1 + rm) ** m for m in range(1, int(round(delai_mois)) + 1)) | |
| 294 | + v = va_propre - va_cout - va_perte | |
| 295 | + return {"valeur": v, "va_propre_stigmatisee": va_propre, "va_cout": va_cout, "va_perte_revenu": va_perte, "cout_delai": v_propre * (1 - stigmate) - va_propre, | |
| 296 | + "cout_stigmate": v_propre * stigmate, "ecart_total": v_propre - v, "ecart_pct": v / v_propre - 1} | |
| 297 | + | |
| 298 | +def valeur_apres_sinistre(v_avant: float, cout_remise: float, loyer_mensuel_perdu: float, mois_perte: float, r: float, stigmate: float, indemnite: float = 0.0) -> dict: | |
| 299 | + """Valeur de l'immeuble sinistré non réparé : V_avant − coût de remise en état − VA de la perte d'usage − stigmate résiduel × V_avant ; | |
| 300 | + position du propriétaire assuré : valeur + indemnité (l'indemnité couvre des coûts, jamais le stigmate).""" | |
| 301 | + rm = (1 + r) ** (1 / 12) - 1 | |
| 302 | + va_perte = sum(loyer_mensuel_perdu / (1 + rm) ** m for m in range(1, int(round(mois_perte)) + 1)) | |
| 303 | + stig = stigmate * v_avant | |
| 304 | + v = v_avant - cout_remise - va_perte - stig | |
| 305 | + return {"valeur": v, "va_perte_usage": va_perte, "stigmate_dollars": stig, "perte_totale": v_avant - v, "perte_pct": v / v_avant - 1, | |
| 306 | + "position_assure": v + indemnite, "perte_nette_assure": v_avant - (v + indemnite), "part_non_assuree": (v_avant - (v + indemnite)) / (v_avant - v)} | |
| 307 | + | |
| 308 | +def desuetude_vs_correction(cout_correction: float, perte_usage: float, perte_rne_annuelle: float, R: float, curable: bool) -> dict: | |
| 309 | + """Pathologie curable : D = coût de correction + perte d'usage ; incurable : D = perte de RNE / R (capitalisation) — ch. 24.""" | |
| 310 | + d_cur = cout_correction + perte_usage; d_inc = perte_rne_annuelle / R | |
| 311 | + return {"curable": d_cur, "incurable": d_inc, "retenu": d_cur if curable else d_inc, "regle": "curable si coût de correction ≤ gain de valeur"} | |
| 312 | + | |
| 313 | +def exemple_contamination() -> dict: | |
| 314 | + """Hypothèses déclarées (fictives) : terrain d'un ancien garage avec immeuble de 8 logements à construire ou existant ; | |
| 315 | + valeur propre 2 100 000 $ ; phase I 3 500 $, phase II 18 000 $, réhabilitation 185 000 $ (+ 25 % contingence) ; 15 mois ; 6,5 % ; stigmate 5 %.""" | |
| 316 | + hyp = {"v_propre": 2_100_000.0, "phase1": 3_500.0, "phase2": 18_000.0, "rehab": 185_000.0, "contingence": 0.25, "delai_mois": 15, "r": 0.065, "stigmate": 0.05, "perte_mensuelle": 0.0} | |
| 317 | + c_total = hyp["phase1"] + hyp["phase2"] + hyp["rehab"] | |
| 318 | + base = valeur_terrain_contamine(hyp["v_propre"], c_total, hyp["delai_mois"], hyp["r"], hyp["stigmate"], hyp["contingence"], hyp["perte_mensuelle"]) | |
| 319 | + sens = {} | |
| 320 | + for s in [0.0, 0.05, 0.10, 0.15]: | |
| 321 | + for T in [6, 15, 24]: | |
| 322 | + sens[(s, T)] = valeur_terrain_contamine(hyp["v_propre"], c_total, T, hyp["r"], s, hyp["contingence"])["valeur"] | |
| 323 | + sens_cout = {k: valeur_terrain_contamine(hyp["v_propre"], c_total * k, hyp["delai_mois"], hyp["r"], hyp["stigmate"], hyp["contingence"])["valeur"] for k in [0.7, 1.0, 1.5, 2.0]} | |
| 324 | + return {"hyp": hyp, "cout_total": c_total, "base": base, "sensibilite": sens, "sensibilite_cout": sens_cout} | |
| 325 | + | |
| 326 | +def exemple_sinistre() -> dict: | |
| 327 | + """Immeuble P41 du cas 4 (Montréal, zone inondable) : inondation fictive ; hypothèses déclarées.""" | |
| 328 | + pf = cas4.charger()["portefeuille"]; p = pf[pf["id_immeuble"] == "P41"].iloc[0] | |
| 329 | + hyp = {"v_avant": float(p["valeur_2026"]), "rne": float(p["rne_2026"]), "nb_logements": int(p["nb_logements"]), "loyer": float(p["loyer_mensuel_moyen"]), | |
| 330 | + "cout_remise": 240_000.0, "logements_touches": 4, "mois_perte": 5, "r": 0.065, "stigmate": 0.05, "franchise": 10_000.0, "part_couverte": 0.85} | |
| 331 | + loyer_perdu = hyp["loyer"] * hyp["logements_touches"] | |
| 332 | + indem = max(0.0, hyp["part_couverte"] * hyp["cout_remise"] - hyp["franchise"]) | |
| 333 | + base = valeur_apres_sinistre(hyp["v_avant"], hyp["cout_remise"], loyer_perdu, hyp["mois_perte"], hyp["r"], hyp["stigmate"], indem) | |
| 334 | + sens = {s: valeur_apres_sinistre(hyp["v_avant"], hyp["cout_remise"], loyer_perdu, hyp["mois_perte"], hyp["r"], s, indem)["valeur"] for s in [0.0, 0.025, 0.05, 0.10, 0.15]} | |
| 335 | + sans_assurance = valeur_apres_sinistre(hyp["v_avant"], hyp["cout_remise"], loyer_perdu, hyp["mois_perte"], hyp["r"], hyp["stigmate"], 0.0) | |
| 336 | + return {"hyp": hyp, "loyer_perdu_mensuel": loyer_perdu, "indemnite": indem, "base": base, "sensibilite_stigmate": sens, "sans_assurance": sans_assurance, "immeuble": p} | |
| 337 | + | |
| 338 | +def exemple_pyrite() -> dict: | |
| 339 | + """Pyrite (renvoi ch. 11 et 24) : excavation 25 000 $, perte d'usage 2 000 $ ; variante incurable : perte de RNE 1 800 $/an capitalisée à 5,5 %.""" | |
| 340 | + return {"curable": desuetude_vs_correction(25_000, 2_000, 0, 0.055, True), "incurable": desuetude_vs_correction(0, 0, 1_800, 0.055, False)} | |
| 341 | + | |
| 342 | +# =============================================================================== main | |
| 343 | + | |
| 344 | +def main(ecrire: bool = True, verbeux: bool = True) -> dict: | |
| 345 | + pr = prime_taux_cas4(); pan = panel_choc(); dd = did_cas4(pan) | |
| 346 | + lots, ventes = charger_ou_generer(ecrire); paires = apparier(ventes); rp = resume_paires(paires); he = hedoniques(ventes); epm = effets_par_methode(ventes, paires) | |
| 347 | + ct = exemple_contamination(); sn = exemple_sinistre(); py = exemple_pyrite() | |
| 348 | + res = {"prime": pr, "panel": pan, "did": dd, "lots": lots, "ventes": ventes, "paires": paires, "resume_paires": rp, "hedoniques": he, "effets_methodes": epm, | |
| 349 | + "contamination": ct, "sinistre": sn, "pyrite": py} | |
| 350 | + if not verbeux: return res | |
| 351 | + P = print | |
| 352 | + P("=== 1. Cas 4 — prime de risque dans le taux global (effets_regionaux) ===") | |
| 353 | + P(f"n = {pr['n']} immeubles ; inondables = {pr['n_inondable']} ; contaminés = {pr['n_sol']} ; part de la valeur en zone inondable = {pr['part_valeur_inondable']*100:.2f} %") | |
| 354 | + for v in ["inondable", "sol"]: | |
| 355 | + e = pr[v] | |
| 356 | + P(f"{v:10s}: coef = {e['coef']:.4f} (se HC1 {e['se']:.4f}) → +{e['effet_pct']*100:.2f} % du taux [IC95 {e['ic_bas_pct']*100:.2f} ; {e['ic_haut_pct']*100:.2f}] ; Kennedy {e['effet_kennedy_pct']*100:.2f} % ; déclaré +{e['declare_pct']*100:.0f} % (t = {e['t_vs_declare']:.2f})") | |
| 357 | + P(f"décote de valeur impliquée (à RNE donné) : {pr['decote_valeur_inondable_pct']*100:.2f} % (déclarée {pr['decote_valeur_declaree_pct']*100:.2f} %)") | |
| 358 | + P(pr["immeubles_inondables"].to_string(index=False)) | |
| 359 | + P("\n=== 2. Cas 4 — DiD sur ln RNE, choc 2019 injecté ===") | |
| 360 | + P(f"vérité : transitoire {CHOC_2019} ; permanent {CHOC_PERMANENT} ({dd['verite_permanente_pct']*100:.2f} %)") | |
| 361 | + P("cellules (ln RNE moyen) :"); P(dd["cellules"].to_string()) | |
| 362 | + P(f"DiD 2×2 naïf = {dd['estimateur_2x2']:.4f} ; régression Δ (se HC1) = {dd['coef_reg']:.4f} ({dd['se_hc1']:.4f}) ; par strate RMR = {dd['estimateur_strate']:.4f}") | |
| 363 | + P(f"inférence de randomisation exacte : {dd['n_combinaisons']} affectations ; p (naïf) = {dd['p_ri']:.4f} ; p (strate) = {dd['p_ri_strate']:.4f}") | |
| 364 | + P("TWFE immeuble + RMR×trimestre :"); P(dd["twfe"].to_string()) | |
| 365 | + P("étude d'événement (k, coef, se grappes) :"); P(dd["event_study"][["coef", "se_grappes", "ic_bas", "ic_haut"]].round(4).to_string()) | |
| 366 | + P(f"effet sur la valeur (Δln RNE − Δln R) : estimé {dd['effet_valeur_estime_pct']*100:.2f} % ; déclaré {dd['effet_valeur_declare_pct']*100:.2f} %") | |
| 367 | + P("\n=== 3. Rivière-Fictive — ventes riveraines ===") | |
| 368 | + P(f"lots {len(lots)} ; exposés {int(lots['expose'].sum())} ; par classe {lots['classe'].value_counts().to_dict()}") | |
| 369 | + P(f"ventes {he['effectifs']} ; vérités (ln) : avant {he['verite']['decote_avant_log']:.4f}, après {he['verite']['decote_apres_log']:.4f}, stigmate {he['verite']['stigmate_log']:.4f}") | |
| 370 | + P(f"paires : n = {rp['n']} ; moyenne {rp['moyenne']:.0f} $ ({rp['moyenne_pct']*100:.2f} %) ; médiane {rp['mediane_pct']*100:.2f} % ; IC95 [{rp['ic95_pct'][0]*100:.2f} ; {rp['ic95_pct'][1]*100:.2f}] ; vérité {rp['verite_pct']*100:.2f} %") | |
| 371 | + P(rp["par_classe"].to_string()); P(rp["par_rive"].to_string()) | |
| 372 | + P(f"hédonique (a) exposé sans rive : {he['a_sans_rive']['coef']:.4f} ({he['a_sans_rive']['se']:.4f}) ; (b) avec rive : {he['b_avec_rive']['coef']:.4f} ({he['b_avec_rive']['se']:.4f}), rive {he['b_avec_rive']['coef_rive']:.4f} ({he['b_avec_rive']['se_rive']:.4f}) ; σ = {he['b_avec_rive']['sigma']:.4f}, R² = {he['b_avec_rive']['r2']:.3f}") | |
| 373 | + P("(c) par classe :"); P(he["c_classes"].round(4).to_string()) | |
| 374 | + d = he["d_did_reg"]; P(f"(d) DiD régression : exposé {d['coef_expose']:.4f} ({d['se_expose']:.4f}) ; exposé×après {d['coef_did']:.4f} ({d['se_did']:.4f}) ; post {d['coef_post']:.4f} ; rive {d['coef_rive']:.4f}") | |
| 375 | + P(f" DiD 2×2 : {he['d_did_2x2']['estimateur']:.4f} ; effectifs {he['d_did_2x2']['effectifs']}") | |
| 376 | + P("effets par méthode :"); P(epm.round(4).to_string(index=False)) | |
| 377 | + P("\n=== 4. Modèles de valeur ===") | |
| 378 | + b = ct["base"]; P(f"contamination : coût total {ct['cout_total']:.0f} $ ; V_c = {b['valeur']:.0f} $ ({b['ecart_pct']*100:.2f} %) ; VA propre stigmatisée {b['va_propre_stigmatisee']:.0f} ; VA coût {b['va_cout']:.0f} ; coût du délai {b['cout_delai']:.0f} ; stigmate {b['cout_stigmate']:.0f}") | |
| 379 | + P("sensibilité (stigmate, mois) → valeur :"); P({k: round(v) for k, v in ct["sensibilite"].items()}); P("coût ×k :"); P({k: round(v) for k, v in ct["sensibilite_cout"].items()}) | |
| 380 | + s = sn["base"]; P(f"sinistre P41 : V_avant {sn['hyp']['v_avant']:.0f} ; loyer perdu {sn['loyer_perdu_mensuel']:.0f} $/mois × {sn['hyp']['mois_perte']} ; VA perte {s['va_perte_usage']:.0f} ; stigmate {s['stigmate_dollars']:.0f} ; V_après {s['valeur']:.0f} ({s['perte_pct']*100:.2f} %) ; indemnité {sn['indemnite']:.0f} ; position assuré {s['position_assure']:.0f} ; perte nette {s['perte_nette_assure']:.0f} ; part non assurée {s['part_non_assuree']*100:.1f} %") | |
| 381 | + P("sensibilité stigmate :"); P({k: round(v) for k, v in sn["sensibilite_stigmate"].items()}) | |
| 382 | + P(f"pyrite : curable {py['curable']['retenu']:.0f} $ ; incurable {py['incurable']['retenu']:.0f} $") | |
| 383 | + return res | |
| 384 | + | |
| 385 | +if __name__ == "__main__": | |
| 386 | + main() | |
modified
analysis/python/figures/ch20_figures.py
+11 −10
@@ -68,18 +68,19 @@ def fig_residus_elargissement(r): | ||
| 68 | 68 | |
| 69 | 69 | |
| 70 | 70 | def fig_fourchettes(r): |
| 71 | − b, bs, co, cb, vt = r["base"], r["bootstrap"], r["cout"], r["combinaison"], r["verite"] | |
| 71 | + b, bs, co, cb, vt, bk = r["base"], r["bootstrap"], r["cout"], r["combinaison"], r["verite"], r["bootstrap_cout"] | |
| 72 | 72 | ip = b["ip_hed"] |
| 73 | 73 | z90 = 1.6449 |
| 74 | 74 | lignes = [ |
| 75 | 75 | ("Prix de vente probable\n(intervalle de prédiction à 95 %, ch. 18)", b["V_hed"], ip["bas"], ip["haut"], palette.cat[3], 0.5), |
| 76 | − ("Coût (hypothèses α, vie économique :\nétendue de la grille)", co["valeur"], co["min_grille"], co["max_grille"], palette.cat[2], 1.0), | |
| 76 | + ("Coût, lecture ventilée (ch. 11), écartée\n(point)", co["ventilee"], np.nan, np.nan, palette.cat[2], 0.6), | |
| 77 | + ("Coût, dépréciation extraite (ch. 24)\n(bootstrap, 90 %)", co["valeur"], bk["resume"]["q05"], bk["resume"]["q95"], palette.cat[2], 1.0), | |
| 77 | 78 | ("Comparaison, 5 comparables\n(bootstrap conjoint, 90 %)", b["V_comp"], bs["resume"]["conjoint"]["q05"], bs["resume"]["conjoint"]["q95"], palette.cat[1], 1.0), |
| 78 | 79 | ("Comparaison élargie, 16 candidats\n(point)", r["elargissement"]["valeur_ponderee"].iloc[-1], np.nan, np.nan, palette.cat[1], 1.0), |
| 79 | 80 | ("Hédonique (estimation + spécification,\n± 1,645 écart-type)", b["V_hed"], b["V_hed"] - z90 * cb["s"][1], b["V_hed"] + z90 * cb["s"][1], palette.cat[0], 1.0), |
| 80 | − ("Conclusion : valeur la plus probable,\nfourchette conclue et bootstrap 90 %", cb["conclusion"], cb["fourchette_conclue"][0], cb["fourchette_conclue"][1], palette.brand, 1.0), | |
| 81 | + ("Conclusion : valeur la plus probable,\nfourchette conclue (bande : bootstrap 90 %)", cb["conclusion"], cb["fourchette_conclue"][0], cb["fourchette_conclue"][1], palette.brand, 1.0), | |
| 81 | 82 | ] |
| 82 | − fig, ax = plt.subplots(figsize=(LARGEUR, 3.6)) | |
| 83 | + fig, ax = plt.subplots(figsize=(LARGEUR, 3.9)) | |
| 83 | 84 | y = np.arange(len(lignes))[::-1] |
| 84 | 85 | for yi, (lab, pt, lo, hi, col, alpha) in zip(y, lignes): |
| 85 | 86 | if not np.isnan(lo): |
@@ -94,7 +95,7 @@ def fig_fourchettes(r): | ||
| 94 | 95 | ax.axvline(vt["V_true"], color=palette.ink2, lw=0.8, ls=":", zorder=0) |
| 95 | 96 | ax.text(vt["V_true"] + 3000, -0.55, "espérance de la loi\nde génération", fontsize=7, color=palette.ink2, ha="left", va="center") |
| 96 | 97 | ax.set_ylim(-0.9, len(lignes) - 0.3); ax.set_yticks(y); ax.set_yticklabels([l[0] for l in lignes], fontsize=7.2) |
| 97 | − ax.set_xlim(270000, 770000); ax.xaxis.set_major_formatter(FuncFormatter(fmt_k)); ax.set_xlabel("Dollars (synthétiques), 1er septembre 2026") | |
| 98 | + ax.set_xlim(420000, 770000); ax.xaxis.set_major_formatter(FuncFormatter(fmt_k)); ax.set_xlabel("Dollars (synthétiques), 1er septembre 2026") | |
| 98 | 99 | ax.grid(axis="y", visible=False) |
| 99 | 100 | fig.tight_layout() |
| 100 | 101 | savefig(fig, "ch20/ch20-fig-fourchettes-methodes.pdf") |
@@ -106,18 +107,18 @@ def fig_sensibilite_poids(r): | ||
| 106 | 107 | ax = axes[0] |
| 107 | 108 | for i, (wc, courbe) in enumerate(cb["courbes"].items()): |
| 108 | 109 | lab = "poids du coût = " + (f"{wc*100:.0f} %") |
| 109 | − ax.plot(cb["wh"], courbe, color=palette.cat[i], lw=1.6 if wc == 0.05 else 1.1, ls="-" if wc == 0.05 else "--", label=lab) | |
| 110 | + ax.plot(cb["wh"], courbe, color=palette.cat[i], lw=1.6 if wc == 0.15 else 1.1, ls="-" if wc == 0.15 else "--", label=lab) | |
| 110 | 111 | tab = cb["tableau"] |
| 111 | 112 | marqueurs = {"raisonnée (retenue)": ("o", palette.brand), "inverse de la variance (indépendance)": ("s", palette.cat[0]), "variance minimale avec corrélation (MCG)": ("^", palette.cat[0]), "égale (un tiers chacune)": ("D", palette.cat[2])} |
| 112 | 113 | for _, row in tab.iterrows(): |
| 113 | 114 | if row["schema"] in marqueurs: |
| 114 | 115 | m, c = marqueurs[row["schema"]] |
| 115 | 116 | ax.plot(row["w_hed"], row["valeur"], m, color=c, ms=6, mec="white", mew=0.6, zorder=5) |
| 116 | − ax.annotate("raisonnée\n(0,40 / 0,55 / 0,05)", (0.55, tab.loc[tab["schema"] == "raisonnée (retenue)", "valeur"].iloc[0]), xytext=(0.62, 572000), fontsize=7, arrowprops=dict(arrowstyle="-", color=palette.ink2, lw=0.6)) | |
| 117 | − ax.annotate("inverse de la variance\net MCG", (0.70, tab.loc[tab["schema"] == "inverse de la variance (indépendance)", "valeur"].iloc[0]), xytext=(0.42, 628000), fontsize=7, arrowprops=dict(arrowstyle="-", color=palette.ink2, lw=0.6)) | |
| 118 | − ax.annotate("égale", (1 / 3, tab.loc[tab["schema"] == "égale (un tiers chacune)", "valeur"].iloc[0]), xytext=(0.12, 540000), fontsize=7, arrowprops=dict(arrowstyle="-", color=palette.ink2, lw=0.6)) | |
| 117 | + ax.annotate("raisonnée\n(0,40 / 0,45 / 0,15)", (0.45, tab.loc[tab["schema"] == "raisonnée (retenue)", "valeur"].iloc[0]), xytext=(0.62, 588000), fontsize=7, arrowprops=dict(arrowstyle="-", color=palette.ink2, lw=0.6)) | |
| 118 | + ax.annotate("inverse de la variance\net MCG", (0.616, tab.loc[tab["schema"] == "inverse de la variance (indépendance)", "valeur"].iloc[0]), xytext=(0.30, 630000), fontsize=7, arrowprops=dict(arrowstyle="-", color=palette.ink2, lw=0.6)) | |
| 119 | + ax.annotate("égale (un tiers au coût)", (1 / 3, tab.loc[tab["schema"] == "égale (un tiers chacune)", "valeur"].iloc[0]), xytext=(0.03, 622000), fontsize=7, arrowprops=dict(arrowstyle="-", color=palette.ink2, lw=0.6)) | |
| 119 | 120 | ax.set_xlabel("Poids de l'indication hédonique $w_h$ (le reste à la comparaison)"); ax.set_ylabel("Valeur combinée") |
| 120 | − ax.yaxis.set_major_formatter(FuncFormatter(fmt_k)); ax.set_xlim(0, 1); ax.legend(loc="lower right", fontsize=7, frameon=False) | |
| 121 | + ax.yaxis.set_major_formatter(FuncFormatter(fmt_k)); ax.set_xlim(0, 1); ax.legend(loc="upper left", fontsize=7, frameon=False) | |
| 121 | 122 | ax.set_title("(a) Conclusion selon les poids", loc="left", fontsize=8.5) |
| 122 | 123 | ax = axes[1] |
| 123 | 124 | s = cb["s"] |
modified
analysis/python/figures/ch36_figures.py
+2 −2
@@ -45,13 +45,13 @@ def fig_event_study(res): | ||
| 45 | 45 | ax.fill_between(es.index, es["ic_bas"], es["ic_haut"], color=palette.cat[0], alpha=0.18, lw=0) |
| 46 | 46 | ax.plot(es.index, es["coef"], "o-", color=palette.cat[0], ms=3.8, lw=1.4, label="Coefficient estimé (IC à 95 %, grappes)") |
| 47 | 47 | ax.step(es.index, es["vrai"], where="mid", color=palette.ink, lw=1.1, ls="--", label="Effet déclaré δ(k)") |
| 48 | − ax.set_title("(a) Étude d'événement TWFE (RMR × trimestre)", fontsize=8.5); ax.set_xlabel("Trimestres depuis la mise en service, k") | |
| 48 | + ax.set_title("(a) Étude d'événement TWFE (RMR × trimestre)", fontsize=8); ax.set_xlabel("Trimestres depuis la mise en service, k") | |
| 49 | 49 | ax.set_ylabel("Effet sur ln RNE (points de log)") |
| 50 | 50 | ax = axes[1] |
| 51 | 51 | ax.fill_between(cs.index, cs["ic_bas"], cs["ic_haut"], color=palette.cat[1], alpha=0.18, lw=0) |
| 52 | 52 | ax.plot(cs.index, cs["att"], "s-", color=palette.cat[1], ms=3.4, lw=1.4, label="ATT(k) de Callaway–Sant'Anna (IC bootstrap)") |
| 53 | 53 | kk = np.arange(-8, 16); ax.step(kk, c36.effet_vrai(kk), where="mid", color=palette.ink, lw=1.1, ls="--", label="Effet déclaré δ(k)") |
| 54 | − ax.set_title("(b) Callaway–Sant'Anna, témoins de la même RMR", fontsize=8.5); ax.set_xlabel("Trimestres depuis la mise en service, k") | |
| 54 | + ax.set_title("(b) Callaway–Sant'Anna (témoins de la même RMR)", fontsize=8); ax.set_xlabel("Trimestres depuis la mise en service, k") | |
| 55 | 55 | for ax in axes: |
| 56 | 56 | ax.axhline(0, color=palette.ink2, lw=0.8); ax.axvline(-0.5, color=palette.ink2, lw=0.8, ls=":"); ax.legend(fontsize=6.8, loc="upper left"); ax.grid(True, lw=0.4) |
| 57 | 57 | savefig(fig, "ch36/ch36-fig-event-study.pdf") |
modified
analysis/python/figures/ch38_figures.py
+14 −12
@@ -64,7 +64,7 @@ def fig_carte_cas1(r): | ||
| 64 | 64 | _km(ax, xy); ax.set_xlabel("Est, NAD83 / MTM zone 9 (km) — coordonnées synthétiques"); ax.set_ylabel("Nord (km)") |
| 65 | 65 | leg = [Patch(color=palette.seq[5], alpha=0.6, label="Zone d'exposition fictive, classe A (≤ 350 m)"), Patch(color=palette.seq[1], alpha=0.55, label="Classe B (350–900 m)"), |
| 66 | 66 | Line2D([], [], color=palette.cat[1], lw=1.4, label="Corridor de transport fictif et tampon de 300 m"), |
| 67 | − Line2D([], [], marker="^", color="none", markerfacecolor=palette.cat[2], markeredgecolor=palette.ink, markersize=7, label="Pôles fictifs (taille ∝ masse)"), | |
| 67 | + Line2D([], [], marker="^", color="none", markerfacecolor=palette.cat[2], markeredgecolor=palette.ink, markersize=7, label="Pôles fictifs (taille selon la masse)"), | |
| 68 | 68 | Line2D([], [], marker="o", color="none", markerfacecolor=palette.seq[9], markersize=5, label="Vente en classe A"), |
| 69 | 69 | Line2D([], [], marker="o", color="none", markerfacecolor=palette.seq[4], markersize=5, label="Vente en classe B"), |
| 70 | 70 | Line2D([], [], marker="o", color="none", markerfacecolor=palette.ink2, markersize=4, alpha=0.6, label="Autre vente"), |
@@ -80,25 +80,25 @@ def _carte_classes(ax, cells, z, bornes, cmap_colors, titre, fmt=lambda x: fmt_m | ||
| 80 | 80 | ax.fill(ext[:, 0], ext[:, 1], color=cmap(normc(val)), lw=0.3, ec=palette.surface) |
| 81 | 81 | ax.set_title(titre, fontsize=8.5) |
| 82 | 82 | handles = [Patch(color=cmap(i), label=f"{fmt(bornes[i])} – {fmt(bornes[i + 1])}") for i in range(len(bornes) - 1)] |
| 83 | − ax.legend(handles=handles, fontsize=6, loc="lower right", frameon=True, framealpha=0.9, edgecolor="none", facecolor=palette.surface, title_fontsize=6.5) | |
| 83 | + ax.legend(handles=handles, fontsize=5.8, loc="upper left", frameon=True, framealpha=0.9, edgecolor="none", facecolor=palette.surface, handlelength=1.2, borderpad=0.3, labelspacing=0.25) | |
| 84 | 84 | |
| 85 | 85 | |
| 86 | 86 | def fig_choroplethe(r): |
| 87 | 87 | g = r["grille"]; ok = g["cartographiees"]; occ = g["occupees"]; b = r["base"]; xy = b["xy_m"] / 1000 |
| 88 | 88 | z = ok["prix_m2_med"].values; k = c38.K_CLASSES |
| 89 | 89 | seq = [palette.seq[i] for i in np.linspace(0, 9, k).round().astype(int)] |
| 90 | − fig, axes = plt.subplots(2, 2, figsize=(LARGEUR, 6.4), sharex=True, sharey=True) | |
| 91 | − titres = {"intervalles égaux": "(a) Intervalles égaux", "quantiles": "(b) Quantiles", "Jenks": "(c) Ruptures naturelles (Fisher–Jenks)"} | |
| 90 | + fig, axes = plt.subplots(2, 2, figsize=(LARGEUR, 4.0), sharex=True, sharey=True) | |
| 91 | + titres = {"intervalles égaux": "(a) Intervalles égaux", "quantiles": "(b) Quantiles", "Jenks": "(c) Fisher–Jenks"} | |
| 92 | 92 | for ax, (nom, cl) in zip(axes.flat[:3], g["classif"].items()): |
| 93 | 93 | for geom in occ.loc[occ["n"] < c38.MIN_VENTES_CELLULE].geometry: |
| 94 | 94 | ext = np.asarray(geom.exterior.coords) / 1000; ax.fill(ext[:, 0], ext[:, 1], facecolor="none", edgecolor=palette.ink2, hatch="////", lw=0.3) |
| 95 | − _carte_classes(ax, ok, z, cl["bornes"], seq, f"{titres[nom]} — GVF = {cl['gvf']:.3f}") | |
| 95 | + _carte_classes(ax, ok, z, cl["bornes"], seq, f"{titres[nom]}, GVF = {cl['gvf']:.3f}".replace(".", ",")) | |
| 96 | 96 | ax.scatter(xy[:, 0], xy[:, 1], s=1.2, color=palette.ink, alpha=0.25, lw=0) |
| 97 | 97 | ax = axes.flat[3]; res = ok["residu_moy"].values; vmax = float(np.abs(res).max()); bornes = np.linspace(-vmax, vmax, 7) |
| 98 | 98 | div = [palette.div["neg"], "#7fa8e0", "#c5d4ea", "#ead0cf", "#eb9a99", palette.div["pos"]] |
| 99 | 99 | for geom in occ.loc[occ["n"] < c38.MIN_VENTES_CELLULE].geometry: |
| 100 | 100 | ext = np.asarray(geom.exterior.coords) / 1000; ax.fill(ext[:, 0], ext[:, 1], facecolor="none", edgecolor=palette.ink2, hatch="////", lw=0.3) |
| 101 | − _carte_classes(ax, ok, res, bornes, div, "(d) Résidu moyen du modèle de référence (divergent)", fmt=lambda x: f"{x:+.3f}".replace(".", ",")) | |
| 101 | + _carte_classes(ax, ok, res, bornes, div, "(d) Résidu moyen, modèle de référence", fmt=lambda x: f"{x:+.3f}".replace(".", ",")) | |
| 102 | 102 | ax.scatter(xy[:, 0], xy[:, 1], s=1.2, color=palette.ink, alpha=0.25, lw=0) |
| 103 | 103 | for ax in axes.flat: |
| 104 | 104 | _km(ax, xy, marge=1.0) |
@@ -106,8 +106,8 @@ def fig_choroplethe(r): | ||
| 106 | 106 | ax.set_xlabel("Est, MTM 9 (km)") |
| 107 | 107 | for ax in axes[:, 0]: |
| 108 | 108 | ax.set_ylabel("Nord (km)") |
| 109 | − fig.text(0.5, 0.005, f"Cellules hexagonales de {g['aire_cellule_km2']:.2f} km² ; hachures : cellules de moins de {c38.MIN_VENTES_CELLULE} ventes (non cartographiées) ; points : ventes", ha="center", fontsize=7, color=palette.ink2) | |
| 110 | − fig.subplots_adjust(hspace=0.18, wspace=0.08) | |
| 109 | + fig.text(0.5, 0.0, f"Cellules hexagonales de {g['aire_cellule_km2']:.2f} km² ; hachures : cellules de moins de {c38.MIN_VENTES_CELLULE} ventes (non cartographiées) ; points : ventes", ha="center", fontsize=7, color=palette.ink2) | |
| 110 | + fig.subplots_adjust(hspace=0.28, wspace=0.06, bottom=0.12, top=0.95) | |
| 111 | 111 | savefig(fig, "ch38/ch38-fig-choroplethe.pdf") |
| 112 | 112 | |
| 113 | 113 | |
@@ -122,7 +122,7 @@ def fig_faux_positifs(r): | ||
| 122 | 122 | for s in (-1.96, 1.96): |
| 123 | 123 | ax.axvline(s, color=palette.ink2, lw=0.7, ls=":") |
| 124 | 124 | ax.set_xlabel("Statistique $t$ de l'indicatrice d'un disque aléatoire de 1 km"); ax.set_ylabel("Densité"); ax.set_title("(a) Distribution des $t$ (346 disques)", fontsize=8.5) |
| 125 | − ax.legend(fontsize=6.8, loc="upper left") | |
| 125 | + ax.legend(fontsize=6.8, loc="upper right") | |
| 126 | 126 | ax = axes[1] |
| 127 | 127 | for i, spec in enumerate(["référence", "sans secteur", "sans secteur ni gradient"]): |
| 128 | 128 | w = tab[(tab["spec"] == spec) & (tab["erreur_type"] == "classique")].sort_values("rayon_km") |
@@ -161,13 +161,15 @@ def fig_cas4(r): | ||
| 161 | 161 | ax.scatter(x[m2], y[m2], marker="s", s=s[m2] + 70, facecolor="none", edgecolor=palette.cat[3], lw=1.3, zorder=4) |
| 162 | 162 | for rmr, w in g.groupby("rmr"): |
| 163 | 163 | cx, cy = w.geometry.x.mean() / 1000, w.geometry.y.mean() / 1000 |
| 164 | − ax.annotate(f"{rmr} ({len(w)})", (cx, cy), xytext=(0, 16 if rmr != "Montréal" else -22), textcoords="offset points", fontsize=7, ha="center", color=palette.ink2) | |
| 164 | + dx, dy, ha = (0, 14, "center") if rmr not in ("Montréal", "Sherbrooke") else ((0, -20, "center") if rmr == "Montréal" else (14, -4, "left")) | |
| 165 | + ax.annotate(f"{rmr} ({len(w)})", (cx, cy), xytext=(dx, dy), textcoords="offset points", fontsize=7, ha=ha, color=palette.ink2) | |
| 165 | 166 | ax.set_aspect("equal"); ax.grid(True, lw=0.4); ax.set_xlabel("Est, NAD83 / Québec Lambert (km)"); ax.set_ylabel("Nord (km)") |
| 166 | − ax.set_title("(a) 48 immeubles, taille ∝ valeur 2026", fontsize=8.5) | |
| 167 | + ax.set_title("(a) 48 immeubles, taille selon la valeur 2026", fontsize=8.5) | |
| 167 | 168 | leg = [Line2D([], [], marker="o", color="none", markerfacecolor=palette.cat[0], alpha=0.6, markersize=7, label="Immeuble"), |
| 168 | 169 | Line2D([], [], marker="o", color="none", markerfacecolor="none", markeredgecolor=palette.cat[5], markeredgewidth=1.6, markersize=9, label="Zone inondable (3)"), |
| 169 | 170 | Line2D([], [], marker="s", color="none", markerfacecolor="none", markeredgecolor=palette.cat[3], markeredgewidth=1.3, markersize=9, label="Contamination du sol (2)")] |
| 170 | − ax.legend(handles=leg, fontsize=6.8, loc="lower right") | |
| 171 | + ax.legend(handles=leg, fontsize=6.8, loc="upper left") | |
| 172 | + ax.set_ylim(y.min() - 55, y.max() + 60); ax.set_xlim(x.min() - 60, x.max() + 90) | |
| 171 | 173 | ax = axes[1]; ordre = par.index[::-1]; yy = np.arange(len(ordre)) |
| 172 | 174 | ax.barh(yy, par.loc[ordre, "part_inond_rmr"] * 100, color=palette.cat[5], alpha=0.85, height=0.6) |
| 173 | 175 | for yi, rmr in zip(yy, ordre): |
added
analysis/python/figures/ch39_figures.py
+146 −0
@@ -0,0 +1,146 @@ | ||
| 1 | +#!/usr/bin/env python3 | |
| 2 | +"""Figures du chapitre 39 — Modèles bayésiens et intervalles (cas 1, Gatineau, DONNÉES SYNTHÉTIQUES, graine 20261031). | |
| 3 | +Produit dans assets/figures/ch39/ : | |
| 4 | + ch39-fig-posterieures.pdf densités marginales a posteriori (modèle conjugué, 600 ventes) vs MCO ± 1,96 e.-t. et paramètre vrai | |
| 5 | + ch39-fig-prior-informatif.pdf petit échantillon (40 ventes) : postérieures sous a priori de référence et a priori « marché voisin » | |
| 6 | + ch39-fig-traces.pdf traces de quatre chaînes, Gibbs un paramètre à la fois vs par blocs, avec R̂ et ESS | |
| 7 | + ch39-fig-retrecissement.pdf effet de Masson-Angers selon le nombre de ventes (effets fixes vs hiérarchique) et facteur B(n) | |
| 8 | + ch39-fig-predictive.pdf distribution prédictive du prix du sujet (hiérarchique et conjuguée) vs IC fréquentiste du ch. 18 | |
| 9 | +Usage (racine) : PYTHONPATH=analysis/python python3 analysis/python/figures/ch39_figures.py""" | |
| 10 | +from __future__ import annotations | |
| 11 | +import sys, pathlib, math | |
| 12 | +ROOT = pathlib.Path(__file__).resolve().parents[3] | |
| 13 | +sys.path.insert(0, str(ROOT / "analysis/python")); sys.path.insert(0, str(ROOT / "analysis/python/cases")) | |
| 14 | +import numpy as np | |
| 15 | +import matplotlib.pyplot as plt | |
| 16 | +from matplotlib.ticker import FuncFormatter | |
| 17 | +from scipy import stats | |
| 18 | +from traite.style import setup, palette, savefig, LARGEUR | |
| 19 | +import ch39_bayes as c39 | |
| 20 | + | |
| 21 | +def _k(x, pos=None): return f"{x/1000:,.0f}".replace(",", " ") | |
| 22 | +ETIQ = {"ln_S": "Élasticité superficie habitable", "qualite": "Échelon de qualité", "garage": "Place de garage", | |
| 23 | + "sect_Masson-Angers": "Secteur Masson-Angers (vs Hull)", "dist_centre": "Distance au centre (> 3 km, par km)", "sigma": "Écart-type résiduel σ"} | |
| 24 | + | |
| 25 | +def densite_t(post, j, grille): | |
| 26 | + return stats.t.pdf(grille, post["nu"], loc=post["m_n"][j], scale=post["echelle"][j]) | |
| 27 | + | |
| 28 | +def densite_sigma(post, grille): | |
| 29 | + # σ² ~ IG(a_n, b_n) → densité de σ : f(σ) = 2σ · IG(σ²) | |
| 30 | + return 2 * grille * stats.invgamma.pdf(grille ** 2, post["a_n"], scale=post["b_n"]) | |
| 31 | + | |
| 32 | +def fig_posterieures(r): | |
| 33 | + ex = r["conjugue"]; p = ex["p600"]; cols = ex["cols"]; coef = r["modele"]["coef"] | |
| 34 | + fig, axes = plt.subplots(2, 3, figsize=(LARGEUR, 3.9)) | |
| 35 | + for ax, nom in zip(axes.ravel(), ["ln_S", "qualite", "garage", "sect_Masson-Angers", "dist_centre", "sigma"]): | |
| 36 | + if nom == "sigma": | |
| 37 | + g = np.linspace(0.074, 0.093, 300); d = densite_sigma(p, g); vrai = c39.VRAIS["sigma"] | |
| 38 | + mco, se = r["modele"]["sigma"], None | |
| 39 | + else: | |
| 40 | + j = cols.index(nom); m, s = p["m_n"][j], p["echelle"][j] | |
| 41 | + g = np.linspace(m - 4.2 * s, m + 4.2 * s, 300); d = densite_t(p, j, g); vrai = c39.VRAIS[nom] | |
| 42 | + mco, se = coef.loc[nom, "coef"], coef.loc[nom, "se_hc1"] | |
| 43 | + ax.fill_between(g, d, color=palette.cat[0], alpha=0.18, lw=0); ax.plot(g, d, color=palette.cat[0], lw=1.6, label="Postérieure (a priori de référence)") | |
| 44 | + ax.axvline(vrai, color=palette.cat[2], ls="--", lw=1.3, label="Paramètre de génération") | |
| 45 | + ymax = d.max() | |
| 46 | + if se is not None: | |
| 47 | + ax.errorbar([mco], [-0.06 * ymax], xerr=[[1.96 * se], [1.96 * se]], fmt="o", color=palette.ink, ms=3.5, capsize=2, lw=1.1, label="MCO ± 1,96 e.-t. HC1") | |
| 48 | + else: | |
| 49 | + ax.plot([mco], [-0.06 * ymax], "o", color=palette.ink, ms=3.5) | |
| 50 | + ax.set_ylim(-0.14 * ymax, 1.12 * ymax); ax.set_yticks([]); ax.set_title(ETIQ[nom], fontsize=8.5) | |
| 51 | + ax.grid(False); ax.spines["left"].set_visible(False) | |
| 52 | + h, l = axes[0, 0].get_legend_handles_labels() | |
| 53 | + fig.legend(h, l, loc="lower center", ncol=3, bbox_to_anchor=(0.5, -0.04), frameon=False) | |
| 54 | + fig.tight_layout(); savefig(fig, "ch39/ch39-fig-posterieures.pdf") | |
| 55 | + | |
| 56 | +def fig_prior_informatif(r): | |
| 57 | + ex = r["conjugue"]; cols_m = ex["cols_micro"]; cols = ex["cols"]; p600 = ex["p600"] | |
| 58 | + fig, axes = plt.subplots(1, 2, figsize=(LARGEUR, 3.0)) | |
| 59 | + styles = {"faible": ("Référence (plat), 40 ventes", palette.cat[1], "-"), "kappa_1": ("Marché voisin, κ = 1", palette.cat[0], "-"), | |
| 60 | + "kappa_4": ("Marché voisin, κ = 4", palette.cat[0], "--"), "kappa_16": ("Marché voisin, κ = 16", palette.cat[0], ":")} | |
| 61 | + for ax, nom in zip(axes, ["ln_S", "qualite"]): | |
| 62 | + j6 = cols.index(nom); m6, s6 = p600["m_n"][j6], p600["echelle"][j6] | |
| 63 | + jm = cols_m.index(nom); pf = ex["petits"]["faible"]["post"] | |
| 64 | + lo = min(m6 - 3 * s6, pf["m_n"][jm] - 3 * pf["echelle"][jm]); hi = max(m6 + 3 * s6, pf["m_n"][jm] + 3 * pf["echelle"][jm]) | |
| 65 | + g = np.linspace(lo, hi, 400) | |
| 66 | + ax.plot(g, densite_t(p600, j6, g), color=palette.ink2, lw=1.2, alpha=0.8, label="600 ventes (référence)") | |
| 67 | + for k_, (lab, col, ls) in styles.items(): | |
| 68 | + ax.plot(g, densite_t(ex["petits"][k_]["post"], jm, g), color=col, ls=ls, lw=1.6, label=lab) | |
| 69 | + ax.axvline(c39.VRAIS[nom], color=palette.cat[2], ls="--", lw=1.2, label="Paramètre de génération") | |
| 70 | + ax.set_title(ETIQ[nom], fontsize=8.5); ax.set_yticks([]); ax.grid(False); ax.spines["left"].set_visible(False) | |
| 71 | + h, l = axes[0].get_legend_handles_labels() | |
| 72 | + fig.legend(h, l, loc="lower center", ncol=3, bbox_to_anchor=(0.5, -0.1), frameon=False) | |
| 73 | + fig.tight_layout(); savefig(fig, "ch39/ch39-fig-prior-informatif.pdf") | |
| 74 | + | |
| 75 | +def fig_traces(r): | |
| 76 | + hi = r["hier"]; cols = hi["cols"]; j = cols.index("dist_centre"); jma = c39.SECTEURS.index("Masson-Angers") | |
| 77 | + fig, axes = plt.subplots(2, 2, figsize=(LARGEUR, 3.9), sharex=True) | |
| 78 | + T = 1000 | |
| 79 | + for row, (g, nom) in enumerate([(hi["g_naif"], "Gibbs, un paramètre à la fois"), (hi["g"], "Gibbs par blocs (β, α ensemble)")]): | |
| 80 | + for col, (x, lab) in enumerate([(g["alpha"][:, :, jma] - g["alpha"][:, :, 0], "Effet de Masson-Angers (α_MA − α_Hull)"), (g["beta"][:, :, j], "Distance au centre (par km)")]): | |
| 81 | + ax = axes[row, col] | |
| 82 | + for c in range(x.shape[0]): ax.plot(np.arange(T), x[c, :T], lw=0.5, alpha=0.8, color=palette.cat[c % 6]) | |
| 83 | + rh = c39.rhat_scinde(x); es = c39.ess(x) | |
| 84 | + ax.text(0.98, 0.95, f"R̂ = {rh:.3f} ESS = {es:,.0f}".replace(",", " "), transform=ax.transAxes, ha="right", va="top", fontsize=7.5, | |
| 85 | + bbox=dict(facecolor=palette.surface, edgecolor="none", pad=1.5)) | |
| 86 | + if row == 0: ax.set_title(lab, fontsize=8.5) | |
| 87 | + if col == 0: ax.set_ylabel(nom, fontsize=7.5) | |
| 88 | + for ax in axes[1]: ax.set_xlabel("Itération (après brûlage)") | |
| 89 | + fig.tight_layout(); savefig(fig, "ch39/ch39-fig-traces.pdf") | |
| 90 | + | |
| 91 | +def fig_retrecissement(r): | |
| 92 | + hi = r["hier"]; e = hi["experience"]; n = e.index.values.astype(float); pos = np.arange(len(n), dtype=float) | |
| 93 | + fig, axes = plt.subplots(1, 2, figsize=(LARGEUR, 3.3), gridspec_kw={"width_ratios": [1.1, 1.0]}) | |
| 94 | + ax = axes[0] | |
| 95 | + ax.errorbar(pos - 0.08, e["fixe"], yerr=[e["fixe"] - e["fixe_bas"], e["fixe_haut"] - e["fixe"]], fmt="s", color=palette.cat[1], capsize=2.5, lw=1.2, ms=4, label="Effet fixe (MCO), IC 95 %") | |
| 96 | + ax.errorbar(pos + 0.08, e["hier"], yerr=[e["hier"] - e["hier_bas"], e["hier_haut"] - e["hier"]], fmt="o", color=palette.cat[0], capsize=2.5, lw=1.2, ms=4, label="Effet aléatoire (Gibbs), crédibilité 95 %") | |
| 97 | + ax.axhline(c39.VRAIS["sect_Masson-Angers"], color=palette.cat[2], ls="--", lw=1.2, label="Paramètre de génération") | |
| 98 | + a0 = float(hi["g"]["alpha0"].mean() - hi["g"]["alpha"][:, :, 0].mean()) | |
| 99 | + ax.axhline(a0, color=palette.ink2, ls=":", lw=1.1, label="Moyenne des secteurs α₀ − α_Hull") | |
| 100 | + ax.set_xticks(pos); ax.set_xticklabels([f"{int(x)}" for x in n]); ax.set_xlim(-0.5, len(n) - 0.5) | |
| 101 | + ax.set_xlabel("Nombre de ventes conservées à Masson-Angers"); ax.set_ylabel("Effet relatif à Hull (log)") | |
| 102 | + ax.set_ylim(-0.56, 0.22); ax.legend(fontsize=6.3, loc="upper left", ncol=1, handlelength=1.6) | |
| 103 | + ax = axes[1] | |
| 104 | + grille = np.linspace(1, 60, 300); s2 = hi["sigma2_med"] | |
| 105 | + for i, (tau, lab) in enumerate([(0.05, "τ = 0,05"), (0.10, "τ = 0,10"), (math.sqrt(hi["tau2_med"]), f"τ = {math.sqrt(hi['tau2_med']):.2f} (médiane a posteriori)".replace(".", ","))]): | |
| 106 | + ax.plot(grille, [c39.facteur_retrecissement(x, s2, tau ** 2) for x in grille], color=palette.seq[2 + 2 * i] if i < 2 else palette.cat[0], lw=1.6, label=lab) | |
| 107 | + ax.plot(n, e["B"], "o", color=palette.cat[0], ms=4, label="Expérience (B à τ médian)") | |
| 108 | + ax.set_ylim(0.3, 1.02); ax.set_xlim(0, 60); ax.set_xlabel("Ventes dans le secteur $n_s$"); ax.set_ylabel("Facteur de rétrécissement $B_s$") | |
| 109 | + ax.legend(fontsize=6.3, loc="lower right", handlelength=1.6) | |
| 110 | + fig.tight_layout(); savefig(fig, "ch39/ch39-fig-retrecissement.pdf") | |
| 111 | + | |
| 112 | +def fig_predictive(r): | |
| 113 | + hi = r["hier"]; pr = hi["pred"]; ex = r["conjugue"]; pt = ex["pred600"] | |
| 114 | + P = pr["tirages"] | |
| 115 | + fig, ax = plt.subplots(figsize=(LARGEUR, 3.3)) | |
| 116 | + g = np.linspace(430_000, 900_000, 600) | |
| 117 | + kde = stats.gaussian_kde(P, bw_method=0.12) | |
| 118 | + ax.fill_between(g, kde(g), color=palette.cat[0], alpha=0.15, lw=0) | |
| 119 | + ax.plot(g, kde(g), color=palette.cat[0], lw=1.8, label="Prédictive hiérarchique (tirages de Gibbs)") | |
| 120 | + # prédictive conjuguée : log-t → densité en dollars | |
| 121 | + dens_t = stats.t.pdf((np.log(g) - pt["loc"]) / pt["echelle"], pt["nu"]) / (pt["echelle"] * g) | |
| 122 | + ax.plot(g, dens_t, color=palette.cat[1], ls="--", lw=1.5, label="Prédictive conjuguée (Student-t, 584 ddl)") | |
| 123 | + for lo, hi_, lab, al in [(pr["bas95"], pr["haut95"], "Crédibilité 95 %", 0.10), (pr["bas90"], pr["haut90"], "Crédibilité 90 %", 0.16)]: | |
| 124 | + m = (g >= lo) & (g <= hi_); ax.fill_between(g[m], kde(g[m]), color=palette.cat[0], alpha=al, lw=0) | |
| 125 | + ymax = kde(g).max() | |
| 126 | + for x, lab, col, ls in [(c39.IC18["bas95"], "IC fréquentiste 95 % (ch. 18)", palette.ink, "-."), (c39.IC18["haut95"], None, palette.ink, "-."), | |
| 127 | + (c39.IC18["point"], "Prédiction de Duan 637 953 $", palette.cat[2], ":")]: | |
| 128 | + ax.axvline(x, color=col, ls=ls, lw=1.1, label=lab) | |
| 129 | + ax.annotate(f"médiane a posteriori {_k(pr['mediane'])} k$", (pr["mediane"] - 6000, ymax * 1.06), ha="right", fontsize=7.5) | |
| 130 | + ax.annotate("90 %", ((pr["bas90"] + pr["haut90"]) / 2, ymax * 0.35), ha="center", fontsize=7.5, color=palette.ink) | |
| 131 | + ax.annotate(f"{_k(pr['bas90'])}", (pr["bas90"], ymax * 0.05), ha="right", fontsize=7, color=palette.ink2) | |
| 132 | + ax.annotate(f"{_k(pr['haut90'])}", (pr["haut90"], ymax * 0.05), ha="left", fontsize=7, color=palette.ink2) | |
| 133 | + ax.set_ylim(0, ymax * 1.15); ax.set_yticks([]); ax.grid(False); ax.spines["left"].set_visible(False) | |
| 134 | + ax.xaxis.set_major_formatter(FuncFormatter(_k)); ax.set_xlabel("Prix du sujet (milliers de dollars)") | |
| 135 | + ax.legend(fontsize=7.5, loc="upper left") | |
| 136 | + fig.tight_layout(); savefig(fig, "ch39/ch39-fig-predictive.pdf") | |
| 137 | + | |
| 138 | +def main(): | |
| 139 | + setup() | |
| 140 | + r = c39.main(verbose=False) | |
| 141 | + fig_posterieures(r); fig_prior_informatif(r); fig_traces(r); fig_retrecissement(r); fig_predictive(r) | |
| 142 | + print("figures ch39 écrites dans assets/figures/ch39/") | |
| 143 | + return r | |
| 144 | + | |
| 145 | +if __name__ == "__main__": | |
| 146 | + main() | |
added
analysis/python/figures/ch40_figures.py
+139 −0
@@ -0,0 +1,139 @@ | ||
| 1 | +#!/usr/bin/env python3 | |
| 2 | +"""Chapitre 40 — figures (DONNÉES SYNTHÉTIQUES : cas 3 et cas 4, graine 20261031). | |
| 3 | +Produit assets/figures/ch40/ch40-fig-{distribution,convergence,nuage,sobol,rcd,lhs,portefeuille}.pdf | |
| 4 | +Usage (racine) : PYTHONPATH=analysis/python python3 analysis/python/figures/ch40_figures.py [--rapide]""" | |
| 5 | +from __future__ import annotations | |
| 6 | +import sys, pathlib | |
| 7 | +import numpy as np, pandas as pd | |
| 8 | +import matplotlib.pyplot as plt | |
| 9 | +from matplotlib.ticker import FuncFormatter, PercentFormatter | |
| 10 | +sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1])) | |
| 11 | +sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1] / "cases")) | |
| 12 | +from traite.style import setup, palette, savefig, LARGEUR, fmt_dollars | |
| 13 | +from traite import finance as fin | |
| 14 | +import ch40_montecarlo as m | |
| 15 | + | |
| 16 | +ETIQ = {"inoccupation_pertes": "Inoccupation et pertes", "croissance_loyers_marche": "Croissance loyers de marché", "rotation_annuelle": "Rotation annuelle", | |
| 17 | + "croissance_depenses": "Croissance des dépenses", "taux_actualisation": "Taux d'actualisation r", "ecart_terminal": "Écart terminal r − R_T", "facteur_capex": "Facteur CAPEX", "taux_terminal": "Taux terminal R_T"} | |
| 18 | +MS = FuncFormatter(lambda x, p: f"{x/1e6:.1f}".replace(".", ",") + " M$") | |
| 19 | +fr = lambda v, d=2: f"{v:.{d}f}".replace(".", ",") | |
| 20 | + | |
| 21 | +def fig_distribution(D): | |
| 22 | + V = D["mc"]["valeur"]; st = D["mc"]["stats"] | |
| 23 | + fig, (a1, a2) = plt.subplots(1, 2, figsize=(LARGEUR, 2.9), gridspec_kw={"wspace": 0.28}) | |
| 24 | + a1.hist(V / 1e6, bins=70, range=(1.2, 3.8), density=True, color=palette.cat[0], alpha=0.85, edgecolor="white", linewidth=0.3) | |
| 25 | + ymax = a1.get_ylim()[1] | |
| 26 | + for q, lab in [(st["q05"], "q₅"), (st["mediane"], "médiane"), (st["q95"], "q₉₅")]: | |
| 27 | + a1.axvline(q / 1e6, color=palette.ink, lw=0.9, ls="--"); a1.text(q / 1e6, ymax * 0.98, f"{lab}\n{fr(q/1e6)}", ha="center", va="top", fontsize=7) | |
| 28 | + a1.axvline(D["dcf"]["valeur"] / 1e6, color=palette.cat[1], lw=1.5); a1.text(D["dcf"]["valeur"] / 1e6 + 0.04, ymax * 0.45, "DCF\ndéterministe", fontsize=7, color=palette.ink) | |
| 29 | + a1.set_xlabel("Valeur simulée (millions de dollars)"); a1.set_ylabel("Densité"); a1.set_title("Histogramme (10 000 tirages)", loc="left") | |
| 30 | + Vs = np.sort(V); F = np.arange(1, len(Vs) + 1) / len(Vs) | |
| 31 | + a2.plot(Vs / 1e6, F, color=palette.cat[0], lw=1.6) | |
| 32 | + q05 = st["q05"]; a2.fill_between(Vs[Vs <= q05] / 1e6, 0, F[Vs <= q05], color=palette.cat[2], alpha=0.35, lw=0) | |
| 33 | + a2.axhline(0.05, color=palette.ink2, lw=0.7, ls=":"); a2.axvline(q05 / 1e6, color=palette.ink2, lw=0.7, ls=":") | |
| 34 | + a2.annotate(f"q₅ = {fr(q05/1e6)} M$\nVaR₅ = {fmt_dollars(D['dcf']['valeur']-q05)}", xy=(q05 / 1e6, 0.05), xytext=(1.25, 0.42), fontsize=7, arrowprops=dict(arrowstyle="-", color=palette.ink2, lw=0.6)) | |
| 35 | + a2.axvline(D["valeur_capitalisation"] / 1e6, color=palette.cat[1], lw=1.2); a2.text(D["valeur_capitalisation"] / 1e6 + 0.03, 0.9, f"capitalisation\ndirecte : F = {fr(D['mc']['probabilites']['V_sous_cap'],3)}", fontsize=7) | |
| 36 | + a2.set_xlim(1.2, 3.8); a2.set_ylim(0, 1); a2.set_xlabel("Valeur simulée (millions de dollars)"); a2.set_ylabel("Fonction de répartition empirique"); a2.set_title("Répartition et VaR de valeur", loc="left") | |
| 37 | + savefig(fig, "ch40/ch40-fig-distribution.pdf") | |
| 38 | + | |
| 39 | +def fig_convergence(D): | |
| 40 | + c = D["convergence"]; s = D["se_N"] | |
| 41 | + fig, (a1, a2) = plt.subplots(1, 2, figsize=(LARGEUR, 2.9), gridspec_kw={"wspace": 0.3}) | |
| 42 | + for col, lab, k in [("q95", "quantile 95 %", 2), ("mediane", "médiane", 0), ("q05", "quantile 5 %", 1)]: | |
| 43 | + a1.plot(c["N"], c[col] / 1e6, marker="o", ms=3, color=palette.cat[k], label=lab) | |
| 44 | + a1.axhline(c[col].iloc[-1] / 1e6, color=palette.cat[k], lw=0.6, ls=":") | |
| 45 | + st = D["mc"]["stats"] | |
| 46 | + for col, k in [("q95", 2), ("mediane", 0), ("q05", 1)]: | |
| 47 | + a1.scatter([10_000], [st[col] / 1e6], marker="D", s=22, color=palette.cat[k], edgecolor=palette.ink, linewidth=0.5, zorder=5) | |
| 48 | + a1.text(10_000, st["mediane"] / 1e6 - 0.07, "graine\n20261031", ha="center", va="top", fontsize=6.8, color=palette.ink2) | |
| 49 | + a1.set_xscale("log"); a1.set_xlabel("Nombre de tirages N (échelle log)"); a1.set_ylabel("Quantile cumulé (M$)"); a1.legend(loc="center right", fontsize=7); a1.set_title("Quantiles cumulés d'une suite de 100 000 tirages", loc="left") | |
| 50 | + a1.yaxis.set_major_formatter(FuncFormatter(lambda x, p: fr(x, 1))) | |
| 51 | + for col, lab, k in [("se_mediane", "médiane", 0), ("se_q05", "quantile 5 %", 1), ("se_q95", "quantile 95 %", 2), ("se_moyenne", "moyenne", 3)]: | |
| 52 | + a2.plot(s["N"], s[col], marker="o", ms=3, color=palette.cat[k], label=lab) | |
| 53 | + ref = s["se_mediane"].iloc[0] * np.sqrt(s["N"].iloc[0] / s["N"]) | |
| 54 | + a2.plot(s["N"], ref, color=palette.ink2, lw=0.8, ls="--", label="pente −1/2") | |
| 55 | + a2.set_xscale("log"); a2.set_yscale("log"); a2.set_xlabel("Nombre de tirages N (échelle log)"); a2.set_ylabel("Écart-type empirique de l'estimateur ($)"); a2.legend(fontsize=7, ncol=2); a2.set_title("Erreur Monte-Carlo (40 répétitions par N)", loc="left") | |
| 56 | + a2.yaxis.set_major_formatter(FuncFormatter(lambda x, p: f"{x:,.0f}".replace(",", " "))) | |
| 57 | + savefig(fig, "ch40/ch40-fig-convergence.pdf") | |
| 58 | + | |
| 59 | +def fig_nuage(D): | |
| 60 | + x, V = D["mc"]["tirages"], D["mc"]["valeur"]; rng = np.random.default_rng(1); idx = rng.choice(len(V), 3000, replace=False) | |
| 61 | + fig, axes = plt.subplots(1, 2, figsize=(LARGEUR, 2.9), gridspec_kw={"wspace": 0.25}) | |
| 62 | + for ax, nom, xl in zip(axes, ["taux_actualisation", "taux_terminal"], ["Taux d'actualisation r (%)", "Taux terminal R_T (%)"]): | |
| 63 | + xv = x[nom]; ax.scatter(xv[idx] * 100, V[idx] / 1e6, s=4, color=palette.cat[0], alpha=0.35, lw=0) | |
| 64 | + bins = np.quantile(xv, np.linspace(0, 1, 21)); mids, meds = [], [] | |
| 65 | + for lo, hi in zip(bins[:-1], bins[1:]): | |
| 66 | + sel = (xv >= lo) & (xv < hi); mids.append(xv[sel].mean() * 100); meds.append(np.median(V[sel]) / 1e6) | |
| 67 | + ax.plot(mids, meds, color=palette.cat[1], lw=1.8, label="médiane conditionnelle") | |
| 68 | + rho = D["rangs"].set_index("intrant").loc[nom, "spearman"] | |
| 69 | + ax.text(0.03, 0.05, f"ρ de Spearman = {fr(rho)}", transform=ax.transAxes, fontsize=7.5) | |
| 70 | + ax.set_xlabel(xl); ax.set_ylabel("Valeur simulée (M$)"); ax.yaxis.set_major_formatter(FuncFormatter(lambda v, p: fr(v, 1))) | |
| 71 | + axes[0].legend(loc="upper right", fontsize=7); axes[0].set_title("Valeur et taux d'actualisation", loc="left"); axes[1].set_title("Valeur et taux terminal", loc="left") | |
| 72 | + savefig(fig, "ch40/ch40-fig-nuage.pdf") | |
| 73 | + | |
| 74 | +def fig_sobol(D): | |
| 75 | + so = D["sobol"].set_index("intrant"); rg = D["rangs"].set_index("intrant"); to = D["tornade"].set_index("intrant") | |
| 76 | + ordre = so["ST"].sort_values().index.tolist(); y = np.arange(len(ordre)); h = 0.26 | |
| 77 | + part_tornade = (to["amplitude"] ** 2 / (to["amplitude"] ** 2).sum()) | |
| 78 | + fig, ax = plt.subplots(figsize=(LARGEUR, 3.0)) | |
| 79 | + ax.barh(y + h, so.loc[ordre, "ST"], h, color=palette.cat[0], label="indice total S_T (Sobol)") | |
| 80 | + ax.barh(y, so.loc[ordre, "S1"], h, color=palette.cat[1], label="indice de premier ordre S₁ (Sobol)") | |
| 81 | + ax.barh(y - h, rg.loc[ordre, "spearman2"], h, color=palette.cat[2], label="ρ² de Spearman (corrélation de rang)") | |
| 82 | + ax.scatter(part_tornade.loc[ordre], y, marker="|", s=120, color=palette.ink, zorder=4, label="part de la tornade (amplitude² normalisée)") | |
| 83 | + ax.set_yticks(y); ax.set_yticklabels([ETIQ[n] for n in ordre]); ax.set_xlabel("Part de la variance de la valeur expliquée"); ax.xaxis.set_major_formatter(PercentFormatter(1.0, decimals=0)) | |
| 84 | + ax.legend(loc="lower right", fontsize=7); ax.set_xlim(0, 0.62) | |
| 85 | + for yi, n in zip(y, ordre): ax.text(so.loc[n, "ST"] + 0.008, yi + h, fr(so.loc[n, "ST"]), va="center", fontsize=6.8, color=palette.ink) | |
| 86 | + savefig(fig, "ch40/ch40-fig-sobol.pdf") | |
| 87 | + | |
| 88 | +def fig_rcd(D): | |
| 89 | + mc = D["mc"]; rne = mc["rne"]; f = D["financement"]; h = f["hypotheque"]; terme = D["hyp"]["financement"]["terme_ans"] | |
| 90 | + h6 = fin.Hypotheque(h.solde(terme * 12), h.taux_nominal + 0.02, h.amortissement_mois - terme * 12) | |
| 91 | + sd = np.where(np.arange(1, 11) <= terme, f["service_dette"], h6.service_dette_annuel) | |
| 92 | + rcd = rne[:, :10] / sd; annees = np.arange(1, 11) | |
| 93 | + fig, ax = plt.subplots(figsize=(LARGEUR, 3.0)) | |
| 94 | + for lo, hi, a in [(0.05, 0.95, 0.18), (0.25, 0.75, 0.32)]: | |
| 95 | + ax.fill_between(annees, np.quantile(rcd, lo, axis=0), np.quantile(rcd, hi, axis=0), color=palette.cat[0], alpha=a, lw=0, label=f"bande {int((hi-lo)*100)} %") | |
| 96 | + ax.plot(annees, np.median(rcd, axis=0), color=palette.cat[0], lw=1.8, label="médiane") | |
| 97 | + rng = np.random.default_rng(3) | |
| 98 | + for i in rng.choice(len(rcd), 12, replace=False): ax.plot(annees, rcd[i], color=palette.ink2, lw=0.5, alpha=0.6) | |
| 99 | + ax.axhline(1.20, color=palette.cat[1], lw=1.0, ls="--"); ax.text(10.1, 1.20, "covenant 1,20", va="center", fontsize=7, color=palette.ink) | |
| 100 | + ax.axhline(1.00, color=palette.cat[3], lw=1.0, ls="--"); ax.text(10.1, 1.00, "défaut 1,00", va="center", fontsize=7, color=palette.ink) | |
| 101 | + ax.axvline(terme + 0.5, color=palette.ink2, lw=0.7, ls=":"); ax.text(terme + 0.6, ax.get_ylim()[1] * 0.98 if ax.get_ylim()[1] > 1 else 2.2, "renouvellement\nà +200 pb", fontsize=7, va="top", color=palette.ink2) | |
| 102 | + ax.set_xlabel("Année d'exploitation"); ax.set_ylabel("Ratio de couverture de la dette"); ax.set_xticks(annees); ax.set_xlim(0.8, 11.2); ax.legend(loc="upper left", fontsize=7) | |
| 103 | + ax.yaxis.set_major_formatter(FuncFormatter(lambda v, p: fr(v, 2))) | |
| 104 | + savefig(fig, "ch40/ch40-fig-rcd.pdf") | |
| 105 | + | |
| 106 | +def fig_lhs(D): | |
| 107 | + rng = np.random.default_rng(5); n = 12 | |
| 108 | + fig, (a1, a2, a3) = plt.subplots(1, 3, figsize=(LARGEUR, 2.6), gridspec_kw={"wspace": 0.35, "width_ratios": [1, 1, 1.6]}) | |
| 109 | + U1 = rng.random((n, 2)); U2 = m.hypercube_latin(n, 2, rng) | |
| 110 | + for ax, U, t in [(a1, U1, "Échantillonnage simple"), (a2, U2, "Hypercube latin")]: | |
| 111 | + for g in np.arange(0, 1.0001, 1 / n): ax.axhline(g, color=palette.grid, lw=0.5); ax.axvline(g, color=palette.grid, lw=0.5) | |
| 112 | + ax.scatter(U[:, 0], U[:, 1], s=18, color=palette.cat[0], zorder=3); ax.set_xlim(0, 1); ax.set_ylim(0, 1); ax.set_aspect("equal"); ax.set_title(t + f" (n = {n})", loc="left", fontsize=8) | |
| 113 | + ax.set_xticks([0, 0.5, 1]); ax.set_yticks([0, 0.5, 1]); ax.grid(False); ax.set_xlabel("u₁"); ax.set_ylabel("u₂") | |
| 114 | + l = D["lhs"].set_index("statistique"); noms = {"moyenne": "moyenne", "ecart_type": "écart-type", "mediane": "médiane", "q05": "quantile 5 %", "q95": "quantile 95 %", "p_V_2M": "P(V < 2 M$)"} | |
| 115 | + ordre = ["moyenne", "ecart_type", "mediane", "p_V_2M", "q95", "q05"]; vals = [min(l.loc[k, "ratio_variance"], 300) for k in ordre] | |
| 116 | + a3.barh(range(len(ordre)), vals, color=palette.cat[1]); a3.set_yticks(range(len(ordre))); a3.set_yticklabels([noms[k] for k in ordre]); a3.set_xscale("log") | |
| 117 | + a3.axvline(1, color=palette.ink, lw=0.8); a3.set_xlabel("Variance simple / variance hypercube latin (log)") | |
| 118 | + for i, k in enumerate(ordre): a3.text(vals[i] * 1.12, i, f"× {l.loc[k, 'ratio_variance']:.1f}".replace(".", ","), va="center", fontsize=7) | |
| 119 | + a3.set_xlim(0.5, 1000); a3.set_title("Gain de variance, N = 1 000, 100 répétitions", loc="left", fontsize=8) | |
| 120 | + savefig(fig, "ch40/ch40-fig-lhs.pdf") | |
| 121 | + | |
| 122 | +def fig_portefeuille(D): | |
| 123 | + pf, cr = D["cas4"]["portefeuille"], D["cas4_croissance"] | |
| 124 | + fig, ax = plt.subplots(figsize=(LARGEUR, 2.9)) | |
| 125 | + for (nom, rp, rr), k in zip([("indépendance", 0.0, 0.0), ("corrélation régionale (0,30 ; 0,60)", 0.30, 0.60), ("corrélation parfaite", 1.0, 1.0)], range(3)): | |
| 126 | + VP = m.simuler_portefeuille(pf, 10_000, m.GRAINE, rho_rmr=rr, rho_prov=rp, croissance=cr)["valeur_portefeuille"] | |
| 127 | + Vs = np.sort(VP); ax.plot(Vs / 1e6, np.arange(1, len(Vs) + 1) / len(Vs), color=palette.cat[k], lw=1.6, label=nom) | |
| 128 | + det = D["portefeuille"]["stats"]["valeur_deterministe"] | |
| 129 | + ax.axvline(det / 1e6, color=palette.ink, lw=0.9, ls="--"); ax.text(det / 1e6 + 0.8, 0.08, f"valeur déterministe\nà un an : {fr(det/1e6,1)} M$", fontsize=7) | |
| 130 | + ax.axhline(0.05, color=palette.ink2, lw=0.6, ls=":") | |
| 131 | + ax.set_xlabel("Valeur du portefeuille de 48 immeubles à un an (millions de dollars)"); ax.set_ylabel("Fonction de répartition empirique"); ax.legend(loc="upper left", fontsize=7) | |
| 132 | + ax.xaxis.set_major_formatter(FuncFormatter(lambda v, p: f"{v:.0f}")) | |
| 133 | + savefig(fig, "ch40/ch40-fig-portefeuille.pdf") | |
| 134 | + | |
| 135 | +if __name__ == "__main__": | |
| 136 | + setup(); rapide = "--rapide" in sys.argv | |
| 137 | + D = m.tout(rapide=rapide) | |
| 138 | + for f in [fig_distribution, fig_convergence, fig_nuage, fig_sobol, fig_rcd, fig_lhs, fig_portefeuille]: | |
| 139 | + f(D); print("OK", f.__name__) | |
added
analysis/python/figures/ch42_bloc_piece.py
+38 −0
@@ -0,0 +1,38 @@ | ||
| 1 | +#!/usr/bin/env python3 | |
| 2 | +"""Figure ch42 — valeur de conversion (somme des fractions nette des coûts, des frais, du portage et des délais) selon la rotation annuelle | |
| 3 | +des locataires, pour trois décotes de vente occupée, comparée à la valeur en bloc (DONNÉES SYNTHÉTIQUES, immeuble fictif de 8 logements). | |
| 4 | +Sortie : assets/figures/ch42/ch42-fig-bloc-piece.pdf""" | |
| 5 | +import sys, pathlib | |
| 6 | +ROOT = pathlib.Path(__file__).resolve().parents[3] | |
| 7 | +sys.path.insert(0, str(ROOT / "analysis/python")); sys.path.insert(0, str(ROOT / "analysis/python/cases")) | |
| 8 | +import numpy as np, matplotlib.pyplot as plt | |
| 9 | +from traite.style import setup, palette, savefig, LARGEUR, fmt_dollars | |
| 10 | +import ch42_copro as cas | |
| 11 | + | |
| 12 | +def main(): | |
| 13 | + setup() | |
| 14 | + rhos = np.linspace(0.02, 0.40, 39) | |
| 15 | + v_bloc = cas.valeur_bloc()["valeur"] | |
| 16 | + fig, ax = plt.subplots(figsize=(LARGEUR, 3.7)) | |
| 17 | + for i, d in enumerate((0.10, 0.20, 0.30)): | |
| 18 | + v = [cas.faisabilite(rho, d)["v_conv"] for rho in rhos] | |
| 19 | + ax.plot(rhos * 100, v, color=palette.cat[i], lw=1.8, label=f"Attente des départs ; reliquat vendu occupé à l'an 6 avec décote {d:.0%}") | |
| 20 | + vo = cas.faisabilite(cas.ROTATION, d, vendre_occupe=True)["v_conv"] | |
| 21 | + ax.scatter([cas.ROTATION * 100], [vo], color=palette.cat[i], marker="D", s=22, zorder=5) | |
| 22 | + ax.annotate(f"vente occupée dès l'an 1 (δ = {d:.0%})", xy=(cas.ROTATION * 100, vo), xytext=(cas.ROTATION * 100 + 1.5, vo + 6000), fontsize=7, color=palette.ink2) | |
| 23 | + ax.axhline(v_bloc, color=palette.ink2, lw=1.2, ls="--") | |
| 24 | + ax.text(40, v_bloc + 8000, f"Valeur en bloc (RNE / 5,25 %) : {v_bloc:,.0f} $".replace(",", " "), ha="right", fontsize=7.8, color=palette.ink) | |
| 25 | + base = cas.faisabilite() | |
| 26 | + ax.scatter([cas.ROTATION * 100], [base["v_conv"]], color=palette.ink, s=26, zorder=6) | |
| 27 | + ax.annotate(f"cas de base : ρ = 12 %, δ = 20 % → {base['v_conv']:,.0f} $".replace(",", " "), xy=(cas.ROTATION * 100, base["v_conv"]), | |
| 28 | + xytext=(cas.ROTATION * 100 + 1.5, base["v_conv"] - 55000), fontsize=7.2, color=palette.ink, | |
| 29 | + arrowprops=dict(arrowstyle="-", color=palette.ink2, lw=0.7)) | |
| 30 | + ax.set_xlabel("Rotation annuelle des locataires ρ (% des logements occupés libérés par année)") | |
| 31 | + ax.set_ylabel("Valeur actualisée à 10 % (dollars de 2026)") | |
| 32 | + ax.yaxis.set_major_formatter(plt.FuncFormatter(fmt_dollars)) | |
| 33 | + ax.set_xlim(0, 41) | |
| 34 | + ax.legend(loc="upper center", bbox_to_anchor=(0.45, -0.2), ncol=1, fontsize=7.3) | |
| 35 | + out = savefig(fig, "ch42/ch42-fig-bloc-piece.pdf"); print("OK", out) | |
| 36 | + | |
| 37 | +if __name__ == "__main__": | |
| 38 | + main() | |
added
analysis/python/figures/ch42_cotisation_speciale.py
+36 −0
@@ -0,0 +1,36 @@ | ||
| 1 | +#!/usr/bin/env python3 | |
| 2 | +"""Figure ch42 — charges annuelles normalisées des huit fractions (ch. 7) et part de chacune dans une cotisation spéciale égale au déficit | |
| 3 | +de capitalisation du fonds de prévoyance (DONNÉES SYNTHÉTIQUES, immeuble fictif du ch. 7). | |
| 4 | +Sortie : assets/figures/ch42/ch42-fig-cotisation-speciale.pdf""" | |
| 5 | +import sys, pathlib | |
| 6 | +ROOT = pathlib.Path(__file__).resolve().parents[3] | |
| 7 | +sys.path.insert(0, str(ROOT / "analysis/python")); sys.path.insert(0, str(ROOT / "analysis/python/cases")) | |
| 8 | +import numpy as np, matplotlib.pyplot as plt | |
| 9 | +from traite.style import setup, palette, savefig, LARGEUR, fmt_dollars | |
| 10 | +import ch42_copro as cas | |
| 11 | + | |
| 12 | +def main(): | |
| 13 | + setup() | |
| 14 | + cs = cas.cotisation_speciale(); rep = cs["rep"]; qp = cas.QP | |
| 15 | + noms = [f"{n} ({s:.0f} m², q = {q:.4f})".replace(".", ",") for n, s, q in zip(qp["noms"], qp["superficies"], qp["q"])] | |
| 16 | + y = np.arange(len(noms)); h = 0.36 | |
| 17 | + fig, ax = plt.subplots(figsize=(LARGEUR, 3.9)) | |
| 18 | + gauche = np.zeros(len(noms)) | |
| 19 | + blocs = [("Charges courantes", rep["par_fraction_courantes"], palette.cat[0]), ("Fonds de prévoyance (A*)", rep["par_fraction_fonds"], palette.cat[1]), | |
| 20 | + ("Fonds d'auto assurance", rep["par_fraction_auto"], palette.cat[2]), ("Stationnement (usage restreint)", rep["par_fraction_restreint"], palette.cat[3])] | |
| 21 | + for lab, vals, col in blocs: | |
| 22 | + ax.barh(y - h / 2, vals, left=gauche, height=h, color=col, label=lab + " — annuel"); gauche += vals | |
| 23 | + ax.barh(y + h / 2, cs["par_fraction"], height=h, color=palette.surface, edgecolor=palette.cat[4], hatch="////", lw=0.8, | |
| 24 | + label=f"Cotisation spéciale unique = q_i × {cs['montant']:,.0f} $ (déficit du fonds)".replace(",", " ")) | |
| 25 | + for yi, tot, part, ratio in zip(y, rep["par_fraction_total"], cs["par_fraction"], cs["ratio_annees"]): | |
| 26 | + ax.text(tot + 300, yi - h / 2, f"{tot:,.0f} $/an".replace(",", " "), va="center", fontsize=7.2, color=palette.ink) | |
| 27 | + ax.text(part + 300, yi + h / 2, f"{part:,.0f} $ (= {ratio:.1f} années de charges)".replace(",", " ").replace(".", ","), va="center", fontsize=7.2, color=palette.ink) | |
| 28 | + ax.set_yticks(y); ax.set_yticklabels(noms); ax.invert_yaxis() | |
| 29 | + ax.xaxis.set_major_formatter(plt.FuncFormatter(fmt_dollars)); ax.set_xlim(0, cs["par_fraction"].max() * 1.75) | |
| 30 | + ax.set_xlabel("Dollars de 2026 (données synthétiques, immeuble fictif de huit fractions)") | |
| 31 | + ax.grid(axis="y", visible=False) | |
| 32 | + ax.legend(loc="upper center", bbox_to_anchor=(0.45, -0.17), ncol=2, fontsize=7.2) | |
| 33 | + out = savefig(fig, "ch42/ch42-fig-cotisation-speciale.pdf"); print("OK", out) | |
| 34 | + | |
| 35 | +if __name__ == "__main__": | |
| 36 | + main() | |
added
analysis/python/figures/ch42_grille.py
+41 −0
@@ -0,0 +1,41 @@ | ||
| 1 | +#!/usr/bin/env python3 | |
| 2 | +"""Figure ch42 — grille de comparaison de la fraction F4 : prix observé, prix ajusté et décomposition des ajustements des cinq comparables | |
| 3 | +fictifs ; valeur pondérée (DONNÉES SYNTHÉTIQUES). | |
| 4 | +Sortie : assets/figures/ch42/ch42-fig-grille-fraction.pdf""" | |
| 5 | +import sys, pathlib | |
| 6 | +ROOT = pathlib.Path(__file__).resolve().parents[3] | |
| 7 | +sys.path.insert(0, str(ROOT / "analysis/python")); sys.path.insert(0, str(ROOT / "analysis/python/cases")) | |
| 8 | +import numpy as np, matplotlib.pyplot as plt | |
| 9 | +from traite.style import setup, palette, savefig, LARGEUR, fmt_dollars | |
| 10 | +import ch42_copro as cas | |
| 11 | + | |
| 12 | +ORDRE = ["marché", "étage", "orientation", "stationnement", "superficie", "charges", "syndicat"] | |
| 13 | +COUL = {k: c for k, c in zip(ORDRE, palette.cat[:6] + [palette.ink2])} | |
| 14 | + | |
| 15 | +def main(): | |
| 16 | + setup() | |
| 17 | + g = cas.grille_fraction(); comps, aj, w = g["comparables"], g["ajustes"], g["poids"]; rec = g["reconciliation"] | |
| 18 | + y = np.arange(len(aj)) | |
| 19 | + fig, ax = plt.subplots(figsize=(LARGEUR, 3.6)) | |
| 20 | + for yi, a, c in zip(y, aj, comps): | |
| 21 | + pos = a.prix; neg = a.prix | |
| 22 | + for k in ORDRE: | |
| 23 | + v = a.ajustements[k] | |
| 24 | + if v >= 0: | |
| 25 | + ax.barh(yi, v, left=pos, height=0.5, color=COUL[k], label=k if yi == 0 else None); pos += v | |
| 26 | + else: | |
| 27 | + neg += v; ax.barh(yi, -v, left=neg, height=0.5, color=COUL[k], alpha=0.55, label=None) | |
| 28 | + ax.plot([a.prix], [yi], marker="o", color=palette.ink, ms=5, zorder=5) | |
| 29 | + ax.plot([a.prix_ajuste], [yi], marker="D", color=palette.ink, ms=5, zorder=5) | |
| 30 | + ax.text(max(pos, a.prix_ajuste) + 3000, yi, f"{a.prix_ajuste:,.0f} $ (w = {wi:.2f})".replace(",", " ").replace("0.", "0,") if (wi := w[yi]) is not None else "", va="center", fontsize=7.2) | |
| 31 | + ax.axvline(rec["valeur_ponderee"], color=palette.ink2, ls="--", lw=1.1) | |
| 32 | + ax.text(rec["valeur_ponderee"], -0.75, f"valeur pondérée {rec['valeur_ponderee']:,.0f} $".replace(",", " "), ha="center", fontsize=7.6, color=palette.ink) | |
| 33 | + ax.set_yticks(y); ax.set_yticklabels([f"{c['nom']} — {c['immeuble']}" for c in comps]); ax.invert_yaxis() | |
| 34 | + ax.xaxis.set_major_formatter(plt.FuncFormatter(fmt_dollars)) | |
| 35 | + ax.set_xlim(300_000, 450_000); ax.set_xlabel("Prix observé (cercle) → prix ajusté (losange) ; ajustements positifs en couleur pleine, négatifs en teinte pâle") | |
| 36 | + ax.grid(axis="y", visible=False) | |
| 37 | + ax.legend(loc="upper center", bbox_to_anchor=(0.45, -0.22), ncol=4, fontsize=7.2, title="Ajustements (du point de vue du sujet)", title_fontsize=7.4) | |
| 38 | + out = savefig(fig, "ch42/ch42-fig-grille-fraction.pdf"); print("OK", out) | |
| 39 | + | |
| 40 | +if __name__ == "__main__": | |
| 41 | + main() | |
added
analysis/python/figures/ch42_medianes.py
+42 −0
@@ -0,0 +1,42 @@ | ||
| 1 | +#!/usr/bin/env python3 | |
| 2 | +"""Figure ch42 — prix médians Centris des copropriétés et des unifamiliales par territoire, T3 2026 (DONNÉES RÉELLES, citées). | |
| 3 | +Source : APCIQ, Statistiques résidentielles Centris, 3e trimestre 2026 (communiqué du 14 octobre 2026 ; sources/notes-E.md, tableau ch. 4) ; | |
| 4 | +les chiffres sont reproduits tels que publiés (valeurs ponctuelles avec attribution ; aucun tableau APCIQ n'est reproduit). | |
| 5 | +Sortie : assets/figures/ch42/ch42-fig-medianes-rmr.pdf""" | |
| 6 | +import sys, pathlib | |
| 7 | +ROOT = pathlib.Path(__file__).resolve().parents[3] | |
| 8 | +sys.path.insert(0, str(ROOT / "analysis/python")) | |
| 9 | +import numpy as np, matplotlib.pyplot as plt | |
| 10 | +from traite.style import setup, palette, savefig, LARGEUR, fmt_dollars | |
| 11 | + | |
| 12 | +# (territoire, médiane copropriété $, variation a/a %, médiane unifamiliale $, variation a/a %) — APCIQ T3 2026 | |
| 13 | +DONNEES = [ | |
| 14 | + ("Province", 400_000, 0, 500_000, 2), | |
| 15 | + ("RMR Montréal", 435_000, 2, 645_500, 2), | |
| 16 | + ("Île de Montréal", 477_500, 1, 811_000, 2), | |
| 17 | + ("RMR Québec", 334_900, 6, 463_500, 2), | |
| 18 | + ("RMR Gatineau", 297_450, -5, 480_000, -3), | |
| 19 | + ("RMR Sherbrooke", 327_000, 4, 490_750, 7), | |
| 20 | + ("RMR Trois-Rivières", 287_000, None, 395_000, 8), | |
| 21 | +] | |
| 22 | + | |
| 23 | +def main(): | |
| 24 | + setup() | |
| 25 | + noms = [d[0] for d in DONNEES]; copro = np.array([d[1] for d in DONNEES], float); unif = np.array([d[3] for d in DONNEES], float) | |
| 26 | + y = np.arange(len(noms)); h = 0.38 | |
| 27 | + fig, ax = plt.subplots(figsize=(LARGEUR, 3.6)) | |
| 28 | + ax.barh(y - h / 2, copro, height=h, color=palette.cat[0], label="Copropriété (médiane Centris, T3 2026)") | |
| 29 | + ax.barh(y + h / 2, unif, height=h, color=palette.cat[1], label="Unifamiliale (médiane Centris, T3 2026)") | |
| 30 | + for yi, c, u, d in zip(y, copro, unif, DONNEES): | |
| 31 | + var = "" if d[2] is None else f" ({d[2]:+d} % a/a)".replace("+", "+").replace("-", "−") | |
| 32 | + ax.text(c + 8000, yi - h / 2, f"{c:,.0f} $".replace(",", " ") + var, va="center", fontsize=7.3, color=palette.ink) | |
| 33 | + ax.text(u + 8000, yi + h / 2, f"{u:,.0f} $".replace(",", " ") + f" (ratio {c / u:.0%})", va="center", fontsize=7.3, color=palette.ink2) | |
| 34 | + ax.set_yticks(y); ax.set_yticklabels(noms); ax.invert_yaxis() | |
| 35 | + ax.xaxis.set_major_formatter(plt.FuncFormatter(fmt_dollars)); ax.set_xlim(0, unif.max() * 1.32) | |
| 36 | + ax.set_xlabel("Prix médian des ventes Centris, 3e trimestre 2026 (dollars courants)") | |
| 37 | + ax.grid(axis="y", visible=False) | |
| 38 | + ax.legend(loc="upper center", bbox_to_anchor=(0.45, -0.18), ncol=2, fontsize=7.5) | |
| 39 | + out = savefig(fig, "ch42/ch42-fig-medianes-rmr.pdf"); print("OK", out) | |
| 40 | + | |
| 41 | +if __name__ == "__main__": | |
| 42 | + main() | |
added
analysis/python/figures/ch43_carte_cas4.py
+38 −0
@@ -0,0 +1,38 @@ | ||
| 1 | +#!/usr/bin/env python3 | |
| 2 | +"""Figure ch43 — carte schématique des 48 immeubles du cas 4 (DONNÉES SYNTHÉTIQUES, coordonnées fictives autour des six RMR) : | |
| 3 | +taille ∝ valeur 2026, immeubles en zone inondable et terrains contaminés mis en évidence ; aucun fond de carte. | |
| 4 | +Sortie : assets/figures/ch43/ch43-fig-carte-cas4.pdf""" | |
| 5 | +import sys, pathlib | |
| 6 | +ROOT = pathlib.Path(__file__).resolve().parents[3] | |
| 7 | +sys.path.insert(0, str(ROOT / "analysis/python")); sys.path.insert(0, str(ROOT / "analysis/python/cases")) | |
| 8 | +import numpy as np, matplotlib.pyplot as plt | |
| 9 | +from traite.style import setup, palette, savefig, LARGEUR | |
| 10 | +from traite import cas4 | |
| 11 | + | |
| 12 | + | |
| 13 | +def main(): | |
| 14 | + setup() | |
| 15 | + pf = cas4.charger()["portefeuille"] | |
| 16 | + fig, ax = plt.subplots(figsize=(LARGEUR, 3.9)) | |
| 17 | + s = np.sqrt(pf["valeur_2026"] / 1e6) * 38 | |
| 18 | + base = pf[(pf["zone_inondable"] == 0) & (pf["contamination_sol"] == 0)] | |
| 19 | + ax.scatter(base["lon"], base["lat"], s=s[base.index], color=palette.cat[0], alpha=0.55, linewidths=0, label="immeuble sans risque déclaré") | |
| 20 | + fl = pf[pf["zone_inondable"] == 1] | |
| 21 | + ax.scatter(fl["lon"], fl["lat"], s=s[fl.index], facecolor="none", edgecolor=palette.cat[1], linewidths=1.8, label="zone inondable (3)", zorder=4) | |
| 22 | + so = pf[pf["contamination_sol"] == 1] | |
| 23 | + ax.scatter(so["lon"], so["lat"], s=s[so.index], facecolor="none", edgecolor=palette.cat[2], linewidths=1.8, linestyle="--", label="contamination du sol (2)", zorder=4) | |
| 24 | + for _, r in pf[(pf["zone_inondable"] == 1) | (pf["contamination_sol"] == 1)].iterrows(): | |
| 25 | + ax.annotate(f"{r['id_immeuble']} · {r['valeur_2026']/1e6:.1f} M$", (r["lon"], r["lat"]), xytext=(7, 5), textcoords="offset points", fontsize=7, color=palette.ink) | |
| 26 | + centres = {"Montréal": (45.52, -73.60), "Québec": (46.81, -71.22), "Gatineau": (45.47, -75.70), "Sherbrooke": (45.40, -71.89), "Trois-Rivières": (46.35, -72.55), "Saguenay": (48.43, -71.07)} | |
| 27 | + for nom, (la, lo) in centres.items(): | |
| 28 | + g = pf[pf["rmr"] == nom] | |
| 29 | + ax.text(lo, la + 0.22, f"{nom}\n{len(g)} imm. · {g['valeur_2026'].sum()/1e6:.0f} M$", ha="center", fontsize=7.5, color=palette.ink2) | |
| 30 | + ax.set_xlabel("Longitude (°, coordonnées fictives)"); ax.set_ylabel("Latitude (°)") | |
| 31 | + ax.set_xlim(-76.4, -70.4); ax.set_ylim(44.9, 49.1); ax.set_aspect(1.35) | |
| 32 | + ax.legend(loc="upper left", fontsize=7.5, markerscale=0.6) | |
| 33 | + fig.tight_layout() | |
| 34 | + savefig(fig, "ch43/ch43-fig-carte-cas4.pdf") | |
| 35 | + | |
| 36 | + | |
| 37 | +if __name__ == "__main__": | |
| 38 | + main() | |
added
analysis/python/figures/ch43_carte_riverain.py
+52 −0
@@ -0,0 +1,52 @@ | ||
| 1 | +#!/usr/bin/env python3 | |
| 2 | +"""Figure ch43 — municipalité fictive de Rivière-Fictive (DONNÉES SYNTHÉTIQUES) : 625 lots, classes d'exposition le long de la | |
| 3 | +rivière, lots riverains du lac (aménité sans exposition) ; (b) prix par m² habitable des ventes selon la distance à la rivière, | |
| 4 | +avant et après l'inondation de 2019 (moyennes par bande de 40 m). | |
| 5 | +Sortie : assets/figures/ch43/ch43-fig-carte-riverain.pdf""" | |
| 6 | +import sys, pathlib | |
| 7 | +ROOT = pathlib.Path(__file__).resolve().parents[3] | |
| 8 | +sys.path.insert(0, str(ROOT / "analysis/python")); sys.path.insert(0, str(ROOT / "analysis/python/cases")) | |
| 9 | +import numpy as np, pandas as pd, matplotlib.pyplot as plt | |
| 10 | +from matplotlib.patches import Rectangle | |
| 11 | +from traite.style import setup, palette, savefig, LARGEUR | |
| 12 | +import ch43_risques as cas | |
| 13 | + | |
| 14 | + | |
| 15 | +def main(): | |
| 16 | + setup() | |
| 17 | + lots, ventes = cas.charger_ou_generer(ecrire=False) | |
| 18 | + fig, (a, b) = plt.subplots(1, 2, figsize=(LARGEUR, 3.4), gridspec_kw={"width_ratios": [1.15, 1]}) | |
| 19 | + couleurs = {"très élevée": palette.seq[-1], "élevée": palette.seq[-2], "modérée": palette.seq[-3], "faible": palette.seq[-4], "hors zone": "#e9e7e2"} | |
| 20 | + for _, L in lots.iterrows(): | |
| 21 | + a.add_patch(Rectangle((L["x"] * cas.PAS, L["y"] * cas.PAS), cas.PAS, cas.PAS, facecolor=couleurs[L["classe"]], edgecolor="white", linewidth=0.3)) | |
| 22 | + if L["rive"] and L["classe"] == "hors zone": | |
| 23 | + a.add_patch(Rectangle((L["x"] * cas.PAS + 6, L["y"] * cas.PAS + 6), cas.PAS - 12, cas.PAS - 12, facecolor="none", edgecolor=palette.cat[1], linewidth=0.7)) | |
| 24 | + a.add_patch(Rectangle((0, -60), cas.NCOL * cas.PAS, 60, facecolor=palette.seq[2], edgecolor="none")) | |
| 25 | + a.add_patch(Rectangle((cas.NCOL * cas.PAS, -60), 70, cas.NLIG * cas.PAS + 60, facecolor=palette.seq[2], edgecolor="none")) | |
| 26 | + a.text(cas.NCOL * cas.PAS / 2, -32, "rivière", ha="center", va="center", fontsize=7.5, color="white") | |
| 27 | + a.text(cas.NCOL * cas.PAS + 35, cas.NLIG * cas.PAS / 2, "lac", rotation=90, ha="center", va="center", fontsize=7.5, color="white") | |
| 28 | + a.set_xlim(-10, cas.NCOL * cas.PAS + 80); a.set_ylim(-70, cas.NLIG * cas.PAS + 10); a.set_aspect("equal"); a.grid(False) | |
| 29 | + a.set_xticks([]); a.set_yticks([]); a.set_title("(a) Lots et classes d'exposition", loc="left") | |
| 30 | + for i, (c, col) in enumerate(couleurs.items()): | |
| 31 | + a.add_patch(Rectangle((cas.NCOL * cas.PAS + 90 + 0, 0), 0, 0, facecolor=col)) | |
| 32 | + handles = [Rectangle((0, 0), 1, 1, facecolor=couleurs[c], edgecolor="white") for c in couleurs] + [Rectangle((0, 0), 1, 1, facecolor="none", edgecolor=palette.cat[1])] | |
| 33 | + a.legend(handles, list(couleurs) + ["vue sur l'eau hors zone (lac)"], loc="upper left", fontsize=6.8, ncol=2, frameon=True, framealpha=0.9, bbox_to_anchor=(0.0, 1.0)) | |
| 34 | + # (b) prix/m² par bande de distance, avant / après, lots non riverains du lac seulement | |
| 35 | + v = ventes[~ventes["id_lot"].str[-2:].astype(int).isin(cas.COLS_LAC)].copy() | |
| 36 | + v["pm2"] = v["prix_vente"] / v["sup_habitable_m2"] | |
| 37 | + t = (pd.to_datetime(v["date_vente"]) - pd.Timestamp(cas.DATE_REF)).dt.days / 365.25 | |
| 38 | + v["pm2_t"] = v["pm2"] / (1 + cas.TAUX_MARCHE) ** t # ramené à la date de référence | |
| 39 | + v["bande"] = (v["dist_riviere_m"] // 40).clip(upper=12) | |
| 40 | + for post, col, lab in [(0, palette.cat[2], "avant mai 2019"), (1, palette.cat[0], "après mai 2019")]: | |
| 41 | + g = v[v["post"] == post].groupby("bande")["pm2_t"].mean() | |
| 42 | + b.plot(g.index * 40 + 20, g.values, marker="o", markersize=3.5, color=col, linewidth=1.3, label=lab) | |
| 43 | + for xb, lab in [(80, "TE | E"), (160, "E | M"), (240, "M | F"), (280, "F | hors zone")]: | |
| 44 | + b.axvline(xb if xb != 280 else 280, color=palette.ink2, linewidth=0.6, linestyle=":") | |
| 45 | + b.set_xlabel("Distance à la rivière (m) — bandes de 40 m, lots du lac exclus"); b.set_ylabel("Prix par m² habitable, à la date de référence ($/m²)") | |
| 46 | + b.set_title("(b) Prix unitaire et distance à la rivière", loc="left"); b.legend(loc="lower right", fontsize=7) | |
| 47 | + fig.tight_layout() | |
| 48 | + savefig(fig, "ch43/ch43-fig-carte-riverain.pdf") | |
| 49 | + | |
| 50 | + | |
| 51 | +if __name__ == "__main__": | |
| 52 | + main() | |
added
analysis/python/figures/ch43_did_cas4.py
+46 −0
@@ -0,0 +1,46 @@ | ||
| 1 | +#!/usr/bin/env python3 | |
| 2 | +"""Figure ch43 — doubles différences sur les séries de RNE du cas 4 avec choc d'inondation injecté en 2019 (DONNÉES SYNTHÉTIQUES). | |
| 3 | +(a) ln RNE moyen des trois immeubles traités et des témoins (centré sur 2019T1), vérité sans choc en tireté ; | |
| 4 | +(b) étude d'événement (effets fixes immeuble + RMR × trimestre), IC à 95 % en grappes, choc déclaré en marches. | |
| 5 | +Sortie : assets/figures/ch43/ch43-fig-did-cas4.pdf""" | |
| 6 | +import sys, pathlib | |
| 7 | +ROOT = pathlib.Path(__file__).resolve().parents[3] | |
| 8 | +sys.path.insert(0, str(ROOT / "analysis/python")); sys.path.insert(0, str(ROOT / "analysis/python/cases")) | |
| 9 | +import numpy as np, matplotlib.pyplot as plt | |
| 10 | +from traite.style import setup, palette, savefig, LARGEUR | |
| 11 | +import ch43_risques as cas | |
| 12 | + | |
| 13 | + | |
| 14 | +def main(): | |
| 15 | + setup() | |
| 16 | + pan = cas.panel_choc(); dd = cas.did_cas4(pan) | |
| 17 | + idx = dd["idx"]; t_ev = idx[cas.T_EVENEMENT]; t_ref = t_ev - 1 | |
| 18 | + fig, (a, b) = plt.subplots(1, 2, figsize=(LARGEUR, 3.3)) | |
| 19 | + # (a) trajectoires centrées sur 2019T1 | |
| 20 | + for tr, col, lab in [(0, palette.cat[2], "témoins (45 immeubles)"), (1, palette.cat[0], "traités (3 immeubles en zone inondable)")]: | |
| 21 | + g = pan[pan["traite"] == tr].groupby("t")["ln_rne"].mean(); g = g - g.loc[t_ref] | |
| 22 | + a.plot(g.index, g.values, color=col, linewidth=1.5, label=lab) | |
| 23 | + g0 = pan[pan["traite"] == 1].groupby("t")["ln_rne_sans_choc"].mean(); g0 = g0 - g0.loc[t_ref] | |
| 24 | + a.plot(g0.index, g0.values, color=palette.cat[0], linewidth=1.0, linestyle="--", label="traités sans le choc (vérité)") | |
| 25 | + a.axvline(t_ev - 0.5, color=palette.ink2, linewidth=0.8, linestyle=":") | |
| 26 | + a.text(t_ev - 0.2, a.get_ylim()[0] if False else -0.26, "inondation\n2019T2", fontsize=7, color=palette.ink2, va="bottom") | |
| 27 | + ticks = [idx[q] for q in ["2016Q1", "2018Q1", "2020Q1", "2022Q1", "2024Q1", "2026Q1"]] | |
| 28 | + a.set_xticks(ticks); a.set_xticklabels(["2016", "2018", "2020", "2022", "2024", "2026"]) | |
| 29 | + a.set_ylabel("ln RNE moyen, écart à 2019T1"); a.set_title("(a) Trajectoires", loc="left"); a.legend(loc="upper left", fontsize=7) | |
| 30 | + # (b) étude d'événement | |
| 31 | + es = dd["event_study"] | |
| 32 | + b.fill_between(es.index, es["ic_bas"], es["ic_haut"], color=palette.cat[0], alpha=0.15, linewidth=0) | |
| 33 | + b.plot(es.index, es["coef"], marker="o", markersize=3.5, color=palette.cat[0], linewidth=1.2, label="coefficient estimé (réf. k = −1)") | |
| 34 | + kk = np.arange(-8, 13); verite = np.zeros(len(kk), float) | |
| 35 | + for q, c in cas.CHOC_2019.items(): verite[kk == idx[q] - t_ev] = c | |
| 36 | + verite[kk >= idx["2020Q1"] - t_ev] = cas.CHOC_PERMANENT | |
| 37 | + b.step(kk, verite, where="mid", color=palette.ink, linewidth=1.0, linestyle="--", label="choc déclaré") | |
| 38 | + b.axhline(0, color=palette.ink2, linewidth=0.8); b.axvline(-0.5, color=palette.ink2, linewidth=0.8, linestyle=":") | |
| 39 | + b.set_xlabel("Trimestres depuis l'inondation (k)"); b.set_ylabel("Effet sur ln RNE"); b.set_title("(b) Étude d'événement", loc="left") | |
| 40 | + b.legend(loc="lower right", fontsize=7) | |
| 41 | + fig.tight_layout() | |
| 42 | + savefig(fig, "ch43/ch43-fig-did-cas4.pdf") | |
| 43 | + | |
| 44 | + | |
| 45 | +if __name__ == "__main__": | |
| 46 | + main() | |
added
analysis/python/figures/ch43_effets_methodes.py
+50 −0
@@ -0,0 +1,50 @@ | ||
| 1 | +#!/usr/bin/env python3 | |
| 2 | +"""Figure ch43 — décote d'une zone inondable mesurée par quatre méthodes sur les ventes de Rivière-Fictive (DONNÉES SYNTHÉTIQUES). | |
| 3 | +(a) estimation (ln P) et IC à 95 % par méthode, avec la vérité du générateur pour la cible de chaque méthode ; | |
| 4 | +(b) décote par classe d'exposition avant l'inondation et stigmate (classe × après), estimées vs déclarées. | |
| 5 | +Sortie : assets/figures/ch43/ch43-fig-effets-methodes.pdf""" | |
| 6 | +import sys, pathlib | |
| 7 | +ROOT = pathlib.Path(__file__).resolve().parents[3] | |
| 8 | +sys.path.insert(0, str(ROOT / "analysis/python")); sys.path.insert(0, str(ROOT / "analysis/python/cases")) | |
| 9 | +import numpy as np, matplotlib.pyplot as plt | |
| 10 | +from traite.style import setup, palette, savefig, LARGEUR | |
| 11 | +import ch43_risques as cas | |
| 12 | + | |
| 13 | + | |
| 14 | +def main(): | |
| 15 | + setup() | |
| 16 | + lots, ventes = cas.charger_ou_generer(ecrire=False) | |
| 17 | + paires = cas.apparier(ventes); t = cas.effets_par_methode(ventes, paires); he = cas.hedoniques(ventes) | |
| 18 | + fig, (a, b) = plt.subplots(1, 2, figsize=(LARGEUR, 3.6), gridspec_kw={"width_ratios": [1.35, 1]}) | |
| 19 | + # (a) méthodes | |
| 20 | + etiq = ["Paires\n(après 2019)", "Hédonique\nexposé", "Hédonique\nexposé + vue", "DiD rég. :\nexposé (avant)", "DiD rég. :\nexposé × après", "DiD rég. :\nsomme (après)", "DiD 2 × 2\n(stigmate)"] | |
| 21 | + y = np.arange(len(t))[::-1] | |
| 22 | + for i, (_, r) in enumerate(t.iterrows()): | |
| 23 | + c = palette.cat[0] if np.isfinite(r["se"]) else palette.cat[2] | |
| 24 | + if np.isfinite(r["se"]): | |
| 25 | + a.plot([r["ic_bas"] * 100, r["ic_haut"] * 100], [y[i], y[i]], color=c, linewidth=1.4) | |
| 26 | + a.plot(r["coef"] * 100, y[i], "o", color=c, markersize=5.5, zorder=3) | |
| 27 | + a.plot(r["verite"] * 100, y[i], marker="|", color=palette.ink, markersize=11, markeredgewidth=1.6, linestyle="none", zorder=4) | |
| 28 | + a.set_yticks(y); a.set_yticklabels(etiq, fontsize=7.5); a.axvline(0, color=palette.ink2, linewidth=0.8) | |
| 29 | + a.set_xlabel("Effet sur ln P (points de pourcentage)"); a.set_title("(a) Sept estimations, quatre méthodes", loc="left") | |
| 30 | + a.plot([], [], "o", color=palette.cat[0], label="estimation et IC à 95 %"); a.plot([], [], "o", color=palette.cat[2], label="estimation sans erreur-type") | |
| 31 | + a.plot([], [], marker="|", color=palette.ink, linestyle="none", markersize=10, markeredgewidth=1.6, label="vérité du générateur (cible)") | |
| 32 | + a.legend(loc="lower left", fontsize=7) | |
| 33 | + # (b) classes | |
| 34 | + cl = ["très élevée", "élevée", "modérée", "faible"]; cc = he["c_classes"] | |
| 35 | + x = np.arange(len(cl)); w = 0.36 | |
| 36 | + av = cc.loc[cl]; ap = cc.loc[[f"{c} × après" for c in cl]] | |
| 37 | + b.bar(x - w / 2, av["coef"] * 100, w, color=palette.cat[0], label="avant 2019 (estimé)") | |
| 38 | + b.bar(x + w / 2, ap["coef"] * 100, w, color=palette.cat[1], label="stigmate × après (estimé)") | |
| 39 | + b.errorbar(x - w / 2, av["coef"] * 100, yerr=1.96 * av["se"] * 100, fmt="none", ecolor=palette.ink2, elinewidth=0.9, capsize=2) | |
| 40 | + b.errorbar(x + w / 2, ap["coef"] * 100, yerr=1.96 * ap["se"] * 100, fmt="none", ecolor=palette.ink2, elinewidth=0.9, capsize=2) | |
| 41 | + b.plot(x - w / 2, av["verite"] * 100, marker="_", color=palette.ink, markersize=12, markeredgewidth=1.8, linestyle="none", label="déclaré", zorder=4) | |
| 42 | + b.plot(x + w / 2, ap["verite"] * 100, marker="_", color=palette.ink, markersize=12, markeredgewidth=1.8, linestyle="none", zorder=4) | |
| 43 | + b.set_xticks(x); b.set_xticklabels([c.replace(" ", "\n") for c in cl], fontsize=7.5); b.axhline(0, color=palette.ink2, linewidth=0.8) | |
| 44 | + b.set_ylabel("Effet sur ln P (points de pourcentage)"); b.set_title("(b) Par classe d'exposition", loc="left"); b.legend(loc="lower right", fontsize=7) | |
| 45 | + fig.tight_layout() | |
| 46 | + savefig(fig, "ch43/ch43-fig-effets-methodes.pdf") | |
| 47 | + | |
| 48 | + | |
| 49 | +if __name__ == "__main__": | |
| 50 | + main() | |
added
analysis/python/figures/ch43_placebos.py
+32 −0
@@ -0,0 +1,32 @@ | ||
| 1 | +#!/usr/bin/env python3 | |
| 2 | +"""Figure ch43 — inférence de randomisation exacte pour le DiD du cas 4 (DONNÉES SYNTHÉTIQUES) : distribution des 17 296 | |
| 3 | +estimateurs placebo obtenus en affectant le « traitement » à chacune des combinaisons de 3 immeubles parmi 48, estimateur | |
| 4 | +observé et vérité ; (a) estimateur 2 × 2 naïf, (b) estimateur par strate de RMR. | |
| 5 | +Sortie : assets/figures/ch43/ch43-fig-placebos.pdf""" | |
| 6 | +import sys, pathlib | |
| 7 | +ROOT = pathlib.Path(__file__).resolve().parents[3] | |
| 8 | +sys.path.insert(0, str(ROOT / "analysis/python")); sys.path.insert(0, str(ROOT / "analysis/python/cases")) | |
| 9 | +import numpy as np, matplotlib.pyplot as plt | |
| 10 | +from traite.style import setup, palette, savefig, LARGEUR | |
| 11 | +import ch43_risques as cas | |
| 12 | + | |
| 13 | + | |
| 14 | +def main(): | |
| 15 | + setup() | |
| 16 | + pan = cas.panel_choc(); dd = cas.did_cas4(pan) | |
| 17 | + fig, axes = plt.subplots(1, 2, figsize=(LARGEUR, 2.9), sharey=True) | |
| 18 | + for ax, dist, obs, p, titre in [(axes[0], dd["placebo_dist"], dd["estimateur_2x2"], dd["p_ri"], "(a) DiD 2 × 2 naïf"), | |
| 19 | + (axes[1], dd["placebo_dist_strate"], dd["estimateur_strate"], dd["p_ri_strate"], "(b) DiD par strate de RMR")]: | |
| 20 | + ax.hist(dist, bins=60, color=palette.cat[0], alpha=0.75, linewidth=0, density=True) | |
| 21 | + ax.axvline(obs, color=palette.cat[1], linewidth=1.6); ax.axvline(-obs, color=palette.cat[1], linewidth=1.0, linestyle=":") | |
| 22 | + ax.axvline(cas.CHOC_PERMANENT, color=palette.ink, linewidth=1.0, linestyle="--") | |
| 23 | + ax.text(obs - 0.004, ax.get_ylim()[1] * 0.92, f"observé {obs:.3f}\np = {p:.3f}", ha="right", fontsize=7.5, color=palette.ink) | |
| 24 | + ax.text(cas.CHOC_PERMANENT + 0.004, ax.get_ylim()[1] * 0.70, f"vérité {cas.CHOC_PERMANENT:.2f}", fontsize=7.5, color=palette.ink) | |
| 25 | + ax.set_xlabel("Estimateur placebo (ln RNE)"); ax.set_title(titre, loc="left") | |
| 26 | + axes[0].set_ylabel("Densité") | |
| 27 | + fig.tight_layout() | |
| 28 | + savefig(fig, "ch43/ch43-fig-placebos.pdf") | |
| 29 | + | |
| 30 | + | |
| 31 | +if __name__ == "__main__": | |
| 32 | + main() | |
modified
analysis/python/tests/test_ch20.py
+45 −27
@@ -78,22 +78,36 @@ def test_elargissement(r): | ||
| 78 | 78 | assert el.loc[16, "residu_moyen_pct"] == pytest.approx(-0.0384, abs=0.0005) |
| 79 | 79 | assert el.loc[10, "valeur_ponderee"] == pytest.approx(592737, abs=1) |
| 80 | 80 | |
| 81 | −# ---------------------------------------------------------------- coût | |
| 81 | +# ---------------------------------------------------------------- coût (chapitre 24) | |
| 82 | 82 | def test_cout(r): |
| 83 | 83 | co = r["cout"]; CN = co["CN"] |
| 84 | − assert co["V_L"] == pytest.approx(0.30 * r["base"]["V_comp"], abs=1e-6) and co["V_L"] == pytest.approx(172669, abs=1) | |
| 85 | − assert co["D"] == pytest.approx(CN * 28 / 60, abs=1e-6) and co["D"] == pytest.approx(185531, abs=1) | |
| 86 | − assert co["valeur"] == pytest.approx(co["V_L"] + CN - co["D"], abs=1e-6) and co["valeur"] == pytest.approx(384705, abs=1) | |
| 87 | − assert co["cout_neuf_plus_terrain"] == pytest.approx(570237, abs=1) | |
| 88 | − assert co["extraction_hed"]["depreciation"] == pytest.approx(-67716, abs=1) and co["extraction_comp"]["depreciation"] == pytest.approx(-5328, abs=1) | |
| 89 | − assert co["min_grille"] == pytest.approx(290043, abs=1) and co["max_grille"] == pytest.approx(479368, abs=1) | |
| 90 | − assert co["sd_grille"] == pytest.approx(50737, abs=1) | |
| 91 | − assert co["age_effectif_variantes"][20] == pytest.approx(437714, abs=1) | |
| 92 | − assert co["V_L_pour_hed"] == pytest.approx(425917, abs=1) | |
| 84 | + assert CN == pytest.approx(397567.33, abs=0.01) | |
| 85 | + assert co["V_L"] == 277000 and co["V_L_mediane"] == pytest.approx(276954, abs=1) and co["n_terrain"] == 13 | |
| 86 | + assert co["D_ext"] == pytest.approx(87461.05, abs=0.05) and co["taux_ext"] == pytest.approx(0.2200, abs=0.0001) | |
| 87 | + assert co["D_fonc"] == pytest.approx(7605.31, abs=0.01) | |
| 88 | + assert co["D_tot"] == pytest.approx(95066.36, abs=0.05) and co["taux_tot"] == pytest.approx(0.2391, abs=0.0001) | |
| 89 | + assert co["valeur"] == pytest.approx(co["V_L"] + CN - co["D_tot"], abs=1e-6) and co["valeur"] == pytest.approx(579500.97, abs=0.05) | |
| 90 | + assert co["arrondie"] == 580000 and co["ventilee"] == pytest.approx(445872.14, abs=0.05) | |
| 91 | + assert co["cout_neuf_plus_terrain"] == pytest.approx(674567, abs=1) | |
| 92 | + assert co["extraction_hed"]["depreciation"] == pytest.approx(36614, abs=1) and co["extraction_hed"]["taux"] == pytest.approx(0.0921, abs=0.0001) | |
| 93 | + assert co["extraction_comp"]["depreciation"] == pytest.approx(99002, abs=1) and co["extraction_comp"]["taux"] == pytest.approx(0.2490, abs=0.0001) | |
| 94 | + # contre-exemple : hypothèses d'usage (allocation 30 %, âge-vie 60 ans) | |
| 95 | + u = co["usage"] | |
| 96 | + assert u["V_L"] == pytest.approx(172669, abs=1) and u["D"] == pytest.approx(185531, abs=1) and u["valeur"] == pytest.approx(384705, abs=1) | |
| 97 | + assert u["extraction_hed"]["depreciation"] == pytest.approx(-67716, abs=1) | |
| 98 | + assert co["sd_spec"] == pytest.approx(2377, abs=2) | |
| 93 | 99 | # identité de la bibliothèque : valeur_par_cout = V_L + C_N − D |
| 94 | 100 | v = ct.valeur_par_cout(100000, 400000, 150000) |
| 95 | 101 | assert v["valeur"] == 350000 and v["taux_depreciation"] == pytest.approx(0.375) |
| 96 | 102 | |
| 103 | +def test_bootstrap_cout(r): | |
| 104 | + bk = r["bootstrap_cout"]; rk = bk["resume"] | |
| 105 | + assert len(bk["VK"]) == 2000 and not np.isnan(bk["VK"]).any() | |
| 106 | + assert rk["ecart_type"] == pytest.approx(23575, abs=60) | |
| 107 | + assert rk["q05"] == pytest.approx(552142, abs=150) and rk["q95"] == pytest.approx(628078, abs=150) | |
| 108 | + assert bk["rho_cout_pred"] == pytest.approx(0.225, abs=0.01) and bk["rho_cout_comp"] == pytest.approx(0.018, abs=0.01) | |
| 109 | + assert bk["sd_VL"] == pytest.approx(29803, abs=80) and bk["sd_T28"] == pytest.approx(0.0419, abs=0.0005) | |
| 110 | + | |
| 97 | 111 | # ---------------------------------------------------------------- combinaison |
| 98 | 112 | def test_poids_optimaux_et_mcg(r): |
| 99 | 113 | cb = r["combinaison"]; S = cb["Sigma"] |
@@ -114,18 +128,21 @@ def test_poids_optimaux_et_mcg(r): | ||
| 114 | 128 | |
| 115 | 129 | def test_combinaison_chiffres(r): |
| 116 | 130 | cb = r["combinaison"]; t = cb["tableau"].set_index("schema") |
| 117 | − assert cb["V"] == pytest.approx([575565, 637953, 384705], abs=1) | |
| 118 | − assert cb["s"] == pytest.approx([18385, 11462, 50737], abs=30) | |
| 131 | + assert cb["V"] == pytest.approx([575565, 637953, 579501], abs=1) | |
| 132 | + assert cb["s"] == pytest.approx([18385, 11462, 23695], abs=60) | |
| 119 | 133 | assert cb["sd_spec_hed"] == pytest.approx(9420, abs=1) and cb["sd_pred_boot"] == pytest.approx(6531, abs=15) |
| 120 | − assert t.loc["égale (un tiers chacune)", "valeur"] == pytest.approx(532741, abs=1) | |
| 121 | − assert t.loc["inverse de la variance (indépendance)", "valeur"] == pytest.approx(612132, abs=150) | |
| 122 | − assert t.loc["inverse de la variance (indépendance)", ["w_comp", "w_hed", "w_cout"]].values == pytest.approx([0.270, 0.695, 0.035], abs=0.005) | |
| 123 | − assert t.loc["variance minimale avec corrélation (MCG)", "valeur"] == pytest.approx(612640, abs=200) | |
| 124 | − assert t.loc["raisonnée (retenue)", "valeur"] == pytest.approx(600335, abs=1) | |
| 125 | − assert t.loc["raisonnée (retenue)", "ecart_type"] == pytest.approx(10411, abs=30) | |
| 126 | − assert cb["conclusion"] == 600000 and cb["fourchette90"] == (585000, 620000) and cb["fourchette_conclue"] == (575000, 640000) | |
| 127 | − assert cb["q"]["q05"] == pytest.approx(587949, abs=100) and cb["q"]["q95"] == pytest.approx(616418, abs=100) | |
| 128 | − assert cb["ecart_conclusion_comp"] == pytest.approx(0.0425, abs=0.0005) and cb["ecart_conclusion_hed"] == pytest.approx(-0.0595, abs=0.0005) | |
| 134 | + assert cb["R"][0, 1] == pytest.approx(0.088, abs=0.005) and cb["R"][1, 2] == pytest.approx(0.225, abs=0.01) and cb["R"][0, 2] == pytest.approx(0.018, abs=0.01) | |
| 135 | + assert t.loc["égale (un tiers chacune)", "valeur"] == pytest.approx(597673, abs=1) | |
| 136 | + assert cb["moyenne_simple_usage"] == pytest.approx(532741, abs=1) | |
| 137 | + assert t.loc["inverse de la variance (indépendance)", "valeur"] == pytest.approx(614579, abs=200) | |
| 138 | + assert t.loc["inverse de la variance (indépendance)", ["w_comp", "w_hed", "w_cout"]].values == pytest.approx([0.240, 0.616, 0.144], abs=0.005) | |
| 139 | + assert t.loc["variance minimale avec corrélation (MCG)", "valeur"] == pytest.approx(616823, abs=250) | |
| 140 | + assert t.loc["variance minimale avec corrélation (MCG)", ["w_comp", "w_hed", "w_cout"]].values == pytest.approx([0.248, 0.655, 0.097], abs=0.006) | |
| 141 | + assert t.loc["raisonnée (retenue)", "valeur"] == pytest.approx(604230, abs=1) | |
| 142 | + assert t.loc["raisonnée (retenue)", "ecart_type"] == pytest.approx(10450, abs=40) | |
| 143 | + assert cb["conclusion"] == 605000 and cb["fourchette90"] == (590000, 625000) and cb["fourchette_conclue"] == (575000, 640000) | |
| 144 | + assert cb["q"]["q05"] == pytest.approx(592310, abs=150) and cb["q"]["q95"] == pytest.approx(622009, abs=150) | |
| 145 | + assert cb["ecart_conclusion_comp"] == pytest.approx(0.0511, abs=0.0005) and cb["ecart_conclusion_hed"] == pytest.approx(-0.0517, abs=0.0005) | |
| 129 | 146 | assert cb["w_opt_deux"][0.0] == pytest.approx(0.280, abs=0.002) and cb["w_opt_deux"][0.6] == pytest.approx(0.023, abs=0.002) |
| 130 | 147 | |
| 131 | 148 | def test_verite_synthetique(r): |
@@ -133,7 +150,7 @@ def test_verite_synthetique(r): | ||
| 133 | 150 | assert vt["V_true"] == pytest.approx(641159, abs=5) |
| 134 | 151 | assert vt["se_c"] == pytest.approx(0.0034, abs=0.0002) and vt["sigma_e"] == pytest.approx(0.0827, abs=0.001) |
| 135 | 152 | assert abs(vt["ecart_hed"]) < 0.01 and vt["ecart_comp"] == pytest.approx(-0.1023, abs=0.001) |
| 136 | − assert r["combinaison"]["conclusion"] / vt["V_true"] - 1 == pytest.approx(-0.0642, abs=0.001) | |
| 153 | + assert r["combinaison"]["conclusion"] / vt["V_true"] - 1 == pytest.approx(-0.0564, abs=0.001) | |
| 137 | 154 | # la prédiction hédonique est dans l'intervalle de la vérité ± 2 erreurs-types de la constante |
| 138 | 155 | assert vt["V_true_bas"] <= b["V_hed"] * 1.01 and b["V_hed"] * 0.99 <= vt["V_true_haut"] |
| 139 | 156 | |
@@ -145,13 +162,14 @@ def test_solutions_exercices(r): | ||
| 145 | 162 | assert e["ex2"]["ic_bas"] == pytest.approx(625586, abs=5) and e["ex2"]["ic_haut"] == pytest.approx(650596, abs=5) |
| 146 | 163 | assert e["ex2"]["ip_bas"] == pytest.approx(539682, abs=60) and e["ex2"]["ip_haut"] == pytest.approx(749087, abs=60) |
| 147 | 164 | assert e["ex2"]["rapport_largeurs"] == pytest.approx(8.36, abs=0.02) |
| 148 | − assert e["ex6"]["V_30_65_05"] == pytest.approx(606574, abs=1) and e["ex6"]["arrondi_30_65_05"] == 605000 | |
| 149 | − assert e["ex6"]["V_k16_raisonnee"] == pytest.approx(611985, abs=1) and e["ex6"]["arrondi_k16"] == 610000 | |
| 150 | − assert e["ex6"]["V_60_35_05"] == pytest.approx(587858, abs=1) | |
| 165 | + assert e["ex6"]["V_30_55_15"] == pytest.approx(610469, abs=1) and e["ex6"]["arrondi_30_55_15"] == 610000 | |
| 166 | + assert e["ex6"]["V_k16_raisonnee"] == pytest.approx(615880, abs=1) and e["ex6"]["arrondi_k16"] == 615000 | |
| 167 | + assert e["ex6"]["V_55_30_15"] == pytest.approx(594872, abs=1) and e["ex6"]["arrondi_55_30_15"] == 595000 | |
| 151 | 168 | assert e["ex3"][0.3]["w1"] == pytest.approx(0.0435, abs=0.0001) and e["ex3"][0.5]["w1"] == pytest.approx(-0.0526, abs=0.0001) |
| 152 | 169 | assert e["ex3"][0.3]["sd"] == pytest.approx(11935, abs=1) |
| 153 | 170 | assert e["ex4"]["moyenne"] == 586150 and e["ex4"]["ecart_type"] == pytest.approx(14954, abs=1) |
| 154 | 171 | assert e["ex4"]["q05"] == pytest.approx(567650, abs=1) and e["ex4"]["q95"] == pytest.approx(611650, abs=1) |
| 155 | 172 | assert e["ex5"]["a"] == pytest.approx(453240, abs=1) and e["ex5"]["b"] == pytest.approx(450590, abs=1) |
| 156 | − assert e["ex5"]["extraction"]["depreciation"] == pytest.approx(-38938, abs=1) | |
| 157 | − assert e["ex7"]["delta"] == 14000 and e["ex7"]["esperance"] == 595800 | |
| 173 | + assert e["ex5"]["extraction_hed"]["depreciation"] == pytest.approx(36614, abs=1) and e["ex5"]["extraction_comp"]["depreciation"] == pytest.approx(99002, abs=1) | |
| 174 | + assert e["ex5"]["V_L_pour_hed"] == pytest.approx(335452, abs=1) | |
| 175 | + assert e["ex7"]["delta"] == 14000 and e["ex7"]["esperance"] == 600800 and e["ex7"]["V_H"] == 605000 | |
added
analysis/python/tests/test_ch42.py
+206 −0
@@ -0,0 +1,206 @@ | ||
| 1 | +"""Tests du chapitre 42 — fraction de copropriété (grille, réconciliation), cotisation spéciale, copropriété locative, conversion, exercices. | |
| 2 | +Tolérances : 1 $ sur les montants cités arrondis au dollar ; 1e-6 sur les identités comptables ; 0,0001 sur les proportions.""" | |
| 3 | +import sys, pathlib, math | |
| 4 | +import numpy as np, pytest | |
| 5 | +sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1] / "cases")) | |
| 6 | +import ch42_copro as cas | |
| 7 | +import ch07_servitudes_copro as c7 | |
| 8 | +from traite import comparables as cp | |
| 9 | +from traite import finance as fin | |
| 10 | + | |
| 11 | + | |
| 12 | +@pytest.fixture(scope="module") | |
| 13 | +def res(): | |
| 14 | + return cas.main() | |
| 15 | + | |
| 16 | + | |
| 17 | +class TestFraction: | |
| 18 | + def test_passif_et_prix_marginal(self, res): | |
| 19 | + assert cas.DEFICIT_IMMEUBLE == pytest.approx(150908, abs=1) # déficit du ch. 7, réutilisé tel quel | |
| 20 | + assert cas.SUJET["passif"] == pytest.approx(21248, abs=1) | |
| 21 | + assert res["grille"]["prix_marginal_m2"] == pytest.approx(2190, abs=0.5) | |
| 22 | + assert cas.prix_marginal_m2() == pytest.approx(cas.ELASTICITE_S * cas.PRIX_M2_REF) | |
| 23 | + | |
| 24 | + def test_prix_observes(self, res): | |
| 25 | + prix = [c["prix"] for c in res["grille"]["comparables"]] | |
| 26 | + assert prix == [373000, 326500, 364000, 373500, 330500] | |
| 27 | + assert all(p % 500 == 0 for p in prix) | |
| 28 | + | |
| 29 | + def test_ajustements_cites(self, res): | |
| 30 | + aj = res["grille"]["ajustes"] | |
| 31 | + a1, a2, a3, a4, a5 = aj | |
| 32 | + assert a1.ajustements["marché"] == pytest.approx(3712, abs=1) and a1.ajustements["étage"] == pytest.approx(15068, abs=1) | |
| 33 | + assert a1.ajustements["syndicat"] == pytest.approx(-845, abs=1) and a1.prix_ajuste == pytest.approx(390935, abs=1) | |
| 34 | + assert a2.ajustements["superficie"] == pytest.approx(28470, abs=1) and a2.ajustements["orientation"] == pytest.approx(9958, abs=1) | |
| 35 | + assert a2.prix_ajuste == pytest.approx(367010, abs=1) | |
| 36 | + assert a3.ajustements["charges"] == pytest.approx(26536, abs=1) and a3.ajustements["syndicat"] == pytest.approx(-21248, abs=1) | |
| 37 | + assert a3.prix_ajuste == pytest.approx(375475, abs=1) | |
| 38 | + assert a4.ajustements["stationnement"] == 28000 and a4.ajustements["étage"] == pytest.approx(-7519, abs=1) | |
| 39 | + assert a4.ajustements["syndicat"] == pytest.approx(-15848, abs=1) and a4.prix_ajuste == pytest.approx(382191, abs=1) | |
| 40 | + assert a5.ajustements["syndicat"] == pytest.approx(9952, abs=1) and a5.prix_ajuste == pytest.approx(373416, abs=1) | |
| 41 | + # identité : prix ajusté = prix + Σ ajustements (traite.comparables) | |
| 42 | + for a in aj: | |
| 43 | + assert a.prix_ajuste == pytest.approx(cp.prix_ajuste_additif(a.prix, a.ajustements), abs=1e-9) | |
| 44 | + # indicateurs brut / net cités | |
| 45 | + assert a1.brut_pct == pytest.approx(0.053, abs=0.001) and a4.brut_pct == pytest.approx(0.200, abs=0.001) | |
| 46 | + assert a5.net_pct == pytest.approx(0.130, abs=0.001) | |
| 47 | + | |
| 48 | + def test_signe_syndicat_et_charges(self, res): | |
| 49 | + # même immeuble : le passif par fraction diffère seulement par la quote-part (q_j − q_s) × déficit | |
| 50 | + a1 = res["grille"]["ajustes"][0] | |
| 51 | + assert a1.ajustements["syndicat"] == pytest.approx(cas.KAPPA * (cas.Q_F2 - cas.Q_F4) * cas.DEFICIT_IMMEUBLE, abs=1e-6) | |
| 52 | + # charges : (c_j − c_s) × S_s / R_c, positif si le comparable paie plus (sujet supérieur) | |
| 53 | + a3 = res["grille"]["ajustes"][2] | |
| 54 | + assert a3.ajustements["charges"] == pytest.approx((78.0 - cas.CHARGES_REF_M2) * 98.0 / 0.08, abs=1e-6) | |
| 55 | + | |
| 56 | + def test_reconciliation(self, res): | |
| 57 | + rec = res["grille"]["reconciliation"]; w = res["grille"]["poids"] | |
| 58 | + assert sum(w) == pytest.approx(1.0, abs=1e-12) and w[0] == pytest.approx(0.419, abs=0.001) | |
| 59 | + assert rec["valeur_ponderee"] == pytest.approx(381052, abs=1) | |
| 60 | + assert rec["moyenne"] == pytest.approx(377805, abs=1) and rec["mediane"] == pytest.approx(375475, abs=1) | |
| 61 | + assert rec["min"] == pytest.approx(367010, abs=1) and rec["max"] == pytest.approx(390935, abs=1) | |
| 62 | + assert rec["etendue_pct"] == pytest.approx(0.063, abs=0.001) | |
| 63 | + # la valeur « vraie » du modèle de génération est dans la fourchette | |
| 64 | + assert rec["min"] < res["grille"]["valeur_modele_sujet"] < rec["max"] | |
| 65 | + assert res["grille"]["valeur_modele_sujet"] == pytest.approx(375183, abs=1) | |
| 66 | + | |
| 67 | + def test_sensibilite(self, res): | |
| 68 | + s = res["sens_fraction"]; g = s["grille"] | |
| 69 | + assert g[1.0][2] == pytest.approx(381052, abs=1) | |
| 70 | + assert g[0.0][0] == pytest.approx(386889, abs=1) and g[1.0][4] == pytest.approx(379437, abs=1) | |
| 71 | + for k in s["kappas"]: | |
| 72 | + assert all(np.diff(g[k]) < 0) # la valeur baisse quand R_c monte (ajustement de charges net positif → plus petit) | |
| 73 | + for j in range(len(s["r_charges"])): | |
| 74 | + assert g[0.0][j] > g[0.5][j] > g[1.0][j] | |
| 75 | + | |
| 76 | + | |
| 77 | +class TestCotisationSpeciale: | |
| 78 | + def test_repartition(self, res): | |
| 79 | + cs = res["cotisation"] | |
| 80 | + assert cs["montant"] == pytest.approx(150908, abs=1) | |
| 81 | + assert cs["par_fraction"].sum() == pytest.approx(cs["montant"], abs=1e-6) | |
| 82 | + assert cs["par_fraction"][3] == pytest.approx(21248, abs=1) and cs["par_fraction"][5] == pytest.approx(24809, abs=1) | |
| 83 | + assert cs["par_fraction"][0] == pytest.approx(14110, abs=1) | |
| 84 | + assert cs["ratio_annees"][3] == pytest.approx(2.08, abs=0.01) and cs["ratio_annees"][0] == pytest.approx(2.26, abs=0.01) | |
| 85 | + assert cs["charges_annuelles"][3] == pytest.approx(10218, abs=1) | |
| 86 | + | |
| 87 | + | |
| 88 | +class TestLocative: | |
| 89 | + def test_rne_et_taux(self, res): | |
| 90 | + cl = res["locative"] | |
| 91 | + assert cl["rbp"] == 20400 and cl["rbe"] == pytest.approx(19972, abs=1) | |
| 92 | + assert cl["frais_copro"] == pytest.approx(10218, abs=1) and cl["depenses"] == pytest.approx(15117, abs=1) | |
| 93 | + assert cl["noi"] == pytest.approx(4855, abs=1) and cl["noi_sans_reserve"] == pytest.approx(7937, abs=1) | |
| 94 | + assert cl["taux_implicite"] == pytest.approx(0.0127, abs=0.0001) and cl["taux_implicite_sans_reserve"] == pytest.approx(0.0208, abs=0.0001) | |
| 95 | + assert cl["valeur_investissement_5_25"] == pytest.approx(92472, abs=1) and cl["mrb"] == pytest.approx(18.7, abs=0.05) | |
| 96 | + assert cl["ratio_depenses"] == pytest.approx(0.757, abs=0.001) | |
| 97 | + # identités (traite.finance) | |
| 98 | + assert cl["rbe"] == pytest.approx(fin.revenu_brut_effectif(20400, 0.021)) | |
| 99 | + assert cl["noi"] == pytest.approx(cl["rbe"] - cl["depenses"]) | |
| 100 | + | |
| 101 | + | |
| 102 | +class TestConversion: | |
| 103 | + def test_bloc(self, res): | |
| 104 | + b = res["faisabilite"]["bloc"] | |
| 105 | + assert b["rbp"] == 146700 and b["rbe"] == pytest.approx(142299, abs=1) and b["noi"] == pytest.approx(82533, abs=1) | |
| 106 | + assert b["valeur"] == pytest.approx(1572065, abs=1) and b["valeur"] == pytest.approx(b["noi"] / 0.0525) | |
| 107 | + assert b["mrb"] == pytest.approx(10.7, abs=0.05) | |
| 108 | + | |
| 109 | + def test_valeurs_libres(self, res): | |
| 110 | + cal = res["faisabilite"]["calendrier"]; v = cal["valeurs"] | |
| 111 | + assert v["F4"] == pytest.approx(396431, abs=1) and v["F1"] == pytest.approx(276481, abs=1) and v["F6"] == pytest.approx(430539, abs=1) | |
| 112 | + assert cal["somme_libre"] == pytest.approx(2802191, abs=1) | |
| 113 | + # F4 libre de passif = F4 du sujet + part du déficit (cohérence des deux blocs) | |
| 114 | + assert v["F4"] - cas.SUJET["passif"] == pytest.approx(res["grille"]["valeur_modele_sujet"], abs=1e-6) | |
| 115 | + assert cal["n_libres_an1"] == 4 and cal["n_occupes"] == 4 and cal["V_occ"] == pytest.approx(1246418, abs=1) | |
| 116 | + | |
| 117 | + def test_calendrier_identite(self, res): | |
| 118 | + fb = res["faisabilite"]; cal = fb["calendrier"]; H = cas.HORIZON_CONV; rho, d = cas.ROTATION, cas.DECOTE_OCCUPE | |
| 119 | + libres = cal["somme_libre"] - cal["V_occ"] | |
| 120 | + attendu = libres + cal["V_occ"] * (1 - (1 - rho) ** (H - 1)) + cal["V_occ"] * (1 - rho) ** (H - 1) * (1 - d) | |
| 121 | + assert cal["produit"].sum() == pytest.approx(attendu, abs=1e-6) | |
| 122 | + assert cal["produit"][1] == pytest.approx(1705343, abs=1) and cal["produit"][6] == pytest.approx(526219, abs=1) | |
| 123 | + | |
| 124 | + def test_faisabilite(self, res): | |
| 125 | + fb = res["faisabilite"] | |
| 126 | + assert fb["couts"] == 305000 and fb["flux"][0] == -305000 | |
| 127 | + assert fb["flux"][1] == pytest.approx(1595097, abs=1) and fb["flux"][6] == pytest.approx(495304, abs=1) | |
| 128 | + assert fb["v_conv"] == pytest.approx(1692804, abs=1) and fb["prime"] == pytest.approx(120739, abs=1) | |
| 129 | + assert fb["marge_sur_bloc"] == pytest.approx(0.077, abs=0.001) | |
| 130 | + assert fb["frais_vente_total"] == pytest.approx(106825, abs=1) and fb["portage_total"] == pytest.approx(106784, abs=1) | |
| 131 | + assert fb["somme_nette_non_actualisee"] == pytest.approx(2152027, abs=1) | |
| 132 | + # V_conv est la VAN des flux au taux du promoteur (traite.finance.van) | |
| 133 | + assert fb["v_conv"] == pytest.approx(fin.van(list(fb["flux"]), 0.10), abs=1e-6) | |
| 134 | + assert res["faisabilite_occ"]["v_conv"] == pytest.approx(1884780, abs=1) | |
| 135 | + | |
| 136 | + def test_sensibilite_conversion(self, res): | |
| 137 | + sc = res["sens_conv"]; g = sc["grille"] | |
| 138 | + assert g[0.20][2] == pytest.approx(1692804, abs=1) | |
| 139 | + assert g[0.10][0] == pytest.approx(1636219, abs=1) and g[0.30][4] == pytest.approx(1871145, abs=1) | |
| 140 | + assert sc["vente_occupee_an1"][0.20] == pytest.approx(1884780, abs=1) | |
| 141 | + for d in sc["decotes"]: | |
| 142 | + assert all(np.diff(g[d]) > 0) # plus la rotation est rapide, plus la conversion vaut | |
| 143 | + for j in range(len(sc["rotations"])): | |
| 144 | + assert g[0.10][j] > g[0.20][j] > g[0.30][j] | |
| 145 | + assert res["seuil_prix"] == pytest.approx(0.937, abs=0.001) | |
| 146 | + assert cas.faisabilite(r=0.08)["v_conv"] == pytest.approx(1768600, abs=1) | |
| 147 | + assert cas.faisabilite(r=0.12)["v_conv"] == pytest.approx(1623172, abs=1) | |
| 148 | + | |
| 149 | + | |
| 150 | +class TestExercices: | |
| 151 | + def test_ex1_charges(self): | |
| 152 | + s = np.array([60, 75, 75, 90, 90, 110.0]); q = s / s.sum() | |
| 153 | + assert s.sum() == 500 and q[3] == pytest.approx(0.18) | |
| 154 | + total = 36000 + 15000 + 4000 | |
| 155 | + assert q[3] * total == pytest.approx(9900) and q[3] * total / 12 == pytest.approx(825) | |
| 156 | + assert q[3] * 15000 == pytest.approx(2700) and q[3] * 36000 == pytest.approx(6480) | |
| 157 | + | |
| 158 | + def test_ex2_attestation(self): | |
| 159 | + q = 0.085; deficit = 180000 - 62000; part_speciale = q * 40000; part_deficit = q * deficit | |
| 160 | + assert deficit == 118000 and part_speciale == pytest.approx(3400) and part_deficit == pytest.approx(10030) | |
| 161 | + assert part_speciale + part_deficit == pytest.approx(13430) | |
| 162 | + assert q * (19500 - 9000) == pytest.approx(892.5) | |
| 163 | + | |
| 164 | + def test_ex3_capitalisation_charges(self): | |
| 165 | + dC = (62 - 48) * 90 | |
| 166 | + assert dC == 1260 | |
| 167 | + assert dC / 0.08 == pytest.approx(15750) and dC / 0.06 == pytest.approx(21000) and dC / 0.12 == pytest.approx(10500) | |
| 168 | + assert dC / (0.05 - 0.025) == pytest.approx(50400) | |
| 169 | + | |
| 170 | + def test_ex4_grille(self): | |
| 171 | + p_t = 342000 * 1.02 ** (5 / 12) | |
| 172 | + assert p_t == pytest.approx(344830, abs=1) | |
| 173 | + A = {"marché": p_t - 342000, "étage": p_t * 0.02, "orientation": p_t * (-0.03 - 0.03), "stationnement": -28000.0, "superficie": -4 * 2190.0} | |
| 174 | + pa = cp.prix_ajuste_additif(342000, A) | |
| 175 | + assert A["étage"] == pytest.approx(6897, abs=1) and A["orientation"] == pytest.approx(-20690, abs=1) and A["superficie"] == -8760 | |
| 176 | + assert pa == pytest.approx(294277, abs=1) | |
| 177 | + bn = cp.ajustement_brut_net(A, 342000) | |
| 178 | + assert bn["brut_pct"] == pytest.approx(0.196, abs=0.001) | |
| 179 | + | |
| 180 | + def test_ex5_locative(self): | |
| 181 | + v = 380000.0; noi_cible = 0.04 * v | |
| 182 | + assert noi_cible == 15200 | |
| 183 | + rbe = (noi_cible + 3300 + 600 + 10218) / 0.95 | |
| 184 | + assert rbe == pytest.approx(30861, abs=1) | |
| 185 | + rbp = rbe / (1 - 0.021) | |
| 186 | + assert rbp == pytest.approx(31523, abs=1) and rbp / 12 == pytest.approx(2627, abs=1) | |
| 187 | + # au loyer de 1 700 $ : RNE et taux comme dans l'exemple, sur 380 000 $ | |
| 188 | + cl = cas.copro_locative(v) | |
| 189 | + assert cl["noi"] == pytest.approx(4855, abs=1) and cl["taux_implicite"] == pytest.approx(0.0128, abs=0.0001) | |
| 190 | + | |
| 191 | + def test_ex6_conversion_script(self): | |
| 192 | + # taux de capitalisation 4,75 % : la valeur en bloc monte et la prime devient négative | |
| 193 | + v_bloc_475 = 82533 / 0.0475 | |
| 194 | + assert cas.valeur_bloc(taux=0.0475)["valeur"] == pytest.approx(v_bloc_475, abs=1) and v_bloc_475 == pytest.approx(1737537, abs=1) | |
| 195 | + assert cas.faisabilite()["v_conv"] - v_bloc_475 == pytest.approx(-44733, abs=1) | |
| 196 | + # rotation de 30 % et décote de 10 % | |
| 197 | + assert cas.faisabilite(0.30, 0.10)["v_conv"] == pytest.approx(1893849, abs=1) | |
| 198 | + # sans coûts d'individualisation (−120 000 $ à t = 0) : la VAN monte exactement de 120 000 $ | |
| 199 | + couts = dict(cas.COUTS_CONVERSION); couts.pop("Individualisation des services et mise aux normes (8 logements)") | |
| 200 | + assert cas.faisabilite(couts=couts)["v_conv"] - cas.faisabilite()["v_conv"] == pytest.approx(120000, abs=1e-6) | |
| 201 | + | |
| 202 | + def test_ex7_indivision(self): | |
| 203 | + V = 1050000.0; parts = 3 * (V / 3) * (1 - 0.12) | |
| 204 | + assert parts == pytest.approx(924000) | |
| 205 | + fractions = 3 * 370000.0; couts = 60000.0 | |
| 206 | + assert fractions - parts - couts == pytest.approx(126000) and (fractions - parts - couts) / 3 == pytest.approx(42000) | |
modified
assets/figures/ch20/ch20-fig-bootstrap-densite.pdf
+0 −0
Binary file not shown.
modified
assets/figures/ch20/ch20-fig-fourchettes-methodes.pdf
+0 −0
Binary file not shown.
modified
assets/figures/ch20/ch20-fig-residus-elargissement.pdf
+0 −0
Binary file not shown.
modified
assets/figures/ch20/ch20-fig-sensibilite-poids.pdf
+0 −0
Binary file not shown.
modified
assets/figures/ch38/ch38-fig-carte-cas1.pdf
+0 −0
Binary file not shown.
modified
assets/figures/ch38/ch38-fig-cas4.pdf
+0 −0
Binary file not shown.
modified
assets/figures/ch38/ch38-fig-choroplethe.pdf
+0 −0
Binary file not shown.
modified
assets/figures/ch38/ch38-fig-faux-positifs.pdf
+0 −0
Binary file not shown.
modified
assets/figures/ch38/ch38-fig-tampon.pdf
+0 −0
Binary file not shown.
added
assets/figures/ch39/ch39-fig-posterieures.pdf
+0 −0
Binary file not shown.
added
assets/figures/ch39/ch39-fig-predictive.pdf
+0 −0
Binary file not shown.
added
assets/figures/ch39/ch39-fig-prior-informatif.pdf
+0 −0
Binary file not shown.
added
assets/figures/ch39/ch39-fig-retrecissement.pdf
+0 −0
Binary file not shown.
added
assets/figures/ch39/ch39-fig-traces.pdf
+0 −0
Binary file not shown.
added
assets/figures/ch40/ch40-fig-convergence.pdf
+0 −0
Binary file not shown.
added
assets/figures/ch40/ch40-fig-distribution.pdf
+0 −0
Binary file not shown.
added
assets/figures/ch40/ch40-fig-lhs.pdf
+0 −0
Binary file not shown.
added
assets/figures/ch40/ch40-fig-nuage.pdf
+0 −0
Binary file not shown.
added
assets/figures/ch40/ch40-fig-portefeuille.pdf
+0 −0
Binary file not shown.
added
assets/figures/ch40/ch40-fig-rcd.pdf
+0 −0
Binary file not shown.
added
assets/figures/ch40/ch40-fig-sobol.pdf
+0 −0
Binary file not shown.
added
assets/figures/ch42/ch42-fig-bloc-piece.pdf
+0 −0
Binary file not shown.
added
assets/figures/ch42/ch42-fig-cotisation-speciale.pdf
+0 −0
Binary file not shown.
added
assets/figures/ch42/ch42-fig-grille-fraction.pdf
+0 −0
Binary file not shown.
added
assets/figures/ch42/ch42-fig-medianes-rmr.pdf
+0 −0
Binary file not shown.
added
assets/figures/ch43/ch43-fig-carte-cas4.pdf
+0 −0
Binary file not shown.
added
assets/figures/ch43/ch43-fig-carte-riverain.pdf
+0 −0
Binary file not shown.
added
assets/figures/ch43/ch43-fig-did-cas4.pdf
+0 −0
Binary file not shown.
added
assets/figures/ch43/ch43-fig-effets-methodes.pdf
+0 −0
Binary file not shown.
added
assets/figures/ch43/ch43-fig-placebos.pdf
+0 −0
Binary file not shown.
added
data/synthetic/ch43_riverain_lots.csv
+626 −0
@@ -0,0 +1,626 @@ | ||
| 1 | +id_lot,x,y,x_m,y_m,dist_riviere_m,classe,expose,rive | |
| 2 | +RF0000,0,0,20.0,20.0,17.7,très élevée,1,1 | |
| 3 | +RF0001,1,0,60.0,20.0,21.1,très élevée,1,1 | |
| 4 | +RF0002,2,0,100.0,20.0,14.0,très élevée,1,1 | |
| 5 | +RF0003,3,0,140.0,20.0,23.0,très élevée,1,1 | |
| 6 | +RF0004,4,0,180.0,20.0,26.2,très élevée,1,1 | |
| 7 | +RF0005,5,0,220.0,20.0,23.0,très élevée,1,1 | |
| 8 | +RF0006,6,0,260.0,20.0,19.1,très élevée,1,1 | |
| 9 | +RF0007,7,0,300.0,20.0,23.0,très élevée,1,1 | |
| 10 | +RF0008,8,0,340.0,20.0,25.0,très élevée,1,1 | |
| 11 | +RF0009,9,0,380.0,20.0,23.4,très élevée,1,1 | |
| 12 | +RF0010,10,0,420.0,20.0,25.4,très élevée,1,1 | |
| 13 | +RF0011,11,0,460.0,20.0,25.9,très élevée,1,1 | |
| 14 | +RF0012,12,0,500.0,20.0,13.9,très élevée,1,1 | |
| 15 | +RF0013,13,0,540.0,20.0,20.2,très élevée,1,1 | |
| 16 | +RF0014,14,0,580.0,20.0,14.2,très élevée,1,1 | |
| 17 | +RF0015,15,0,620.0,20.0,17.3,très élevée,1,1 | |
| 18 | +RF0016,16,0,660.0,20.0,27.8,très élevée,1,1 | |
| 19 | +RF0017,17,0,700.0,20.0,27.7,très élevée,1,1 | |
| 20 | +RF0018,18,0,740.0,20.0,14.6,très élevée,1,1 | |
| 21 | +RF0019,19,0,780.0,20.0,12.2,très élevée,1,1 | |
| 22 | +RF0020,20,0,820.0,20.0,24.7,très élevée,1,1 | |
| 23 | +RF0021,21,0,860.0,20.0,21.4,très élevée,1,1 | |
| 24 | +RF0022,22,0,900.0,20.0,24.9,très élevée,1,1 | |
| 25 | +RF0023,23,0,940.0,20.0,12.7,très élevée,1,1 | |
| 26 | +RF0024,24,0,980.0,20.0,20.3,très élevée,1,1 | |
| 27 | +RF0100,0,1,20.0,60.0,66.9,élevée,1,1 | |
| 28 | +RF0101,1,1,60.0,60.0,58.7,élevée,1,1 | |
| 29 | +RF0102,2,1,100.0,60.0,61.1,élevée,1,1 | |
| 30 | +RF0103,3,1,140.0,60.0,64.9,élevée,1,1 | |
| 31 | +RF0104,4,1,180.0,60.0,61.6,élevée,1,1 | |
| 32 | +RF0105,5,1,220.0,60.0,54.7,élevée,1,1 | |
| 33 | +RF0106,6,1,260.0,60.0,61.6,élevée,1,1 | |
| 34 | +RF0107,7,1,300.0,60.0,58.4,élevée,1,1 | |
| 35 | +RF0108,8,1,340.0,60.0,62.1,élevée,1,1 | |
| 36 | +RF0109,9,1,380.0,60.0,59.2,élevée,1,1 | |
| 37 | +RF0110,10,1,420.0,60.0,64.2,élevée,1,1 | |
| 38 | +RF0111,11,1,460.0,60.0,55.7,élevée,1,1 | |
| 39 | +RF0112,12,1,500.0,60.0,52.0,élevée,1,1 | |
| 40 | +RF0113,13,1,540.0,60.0,57.8,élevée,1,1 | |
| 41 | +RF0114,14,1,580.0,60.0,57.6,élevée,1,1 | |
| 42 | +RF0115,15,1,620.0,60.0,57.6,élevée,1,1 | |
| 43 | +RF0116,16,1,660.0,60.0,62.6,élevée,1,1 | |
| 44 | +RF0117,17,1,700.0,60.0,58.3,élevée,1,1 | |
| 45 | +RF0118,18,1,740.0,60.0,63.0,élevée,1,1 | |
| 46 | +RF0119,19,1,780.0,60.0,58.7,élevée,1,1 | |
| 47 | +RF0120,20,1,820.0,60.0,52.8,élevée,1,1 | |
| 48 | +RF0121,21,1,860.0,60.0,67.3,élevée,1,1 | |
| 49 | +RF0122,22,1,900.0,60.0,60.1,élevée,1,1 | |
| 50 | +RF0123,23,1,940.0,60.0,65.5,élevée,1,1 | |
| 51 | +RF0124,24,1,980.0,60.0,55.8,élevée,1,1 | |
| 52 | +RF0200,0,2,20.0,100.0,95.3,élevée,1,1 | |
| 53 | +RF0201,1,2,60.0,100.0,107.2,élevée,1,1 | |
| 54 | +RF0202,2,2,100.0,100.0,98.0,élevée,1,1 | |
| 55 | +RF0203,3,2,140.0,100.0,93.8,élevée,1,1 | |
| 56 | +RF0204,4,2,180.0,100.0,94.3,élevée,1,1 | |
| 57 | +RF0205,5,2,220.0,100.0,96.3,élevée,1,1 | |
| 58 | +RF0206,6,2,260.0,100.0,101.0,élevée,1,1 | |
| 59 | +RF0207,7,2,300.0,100.0,93.2,élevée,1,1 | |
| 60 | +RF0208,8,2,340.0,100.0,96.8,élevée,1,1 | |
| 61 | +RF0209,9,2,380.0,100.0,103.4,élevée,1,1 | |
| 62 | +RF0210,10,2,420.0,100.0,96.6,élevée,1,1 | |
| 63 | +RF0211,11,2,460.0,100.0,95.3,élevée,1,1 | |
| 64 | +RF0212,12,2,500.0,100.0,104.1,élevée,1,1 | |
| 65 | +RF0213,13,2,540.0,100.0,95.7,élevée,1,1 | |
| 66 | +RF0214,14,2,580.0,100.0,101.6,élevée,1,1 | |
| 67 | +RF0215,15,2,620.0,100.0,107.7,élevée,1,1 | |
| 68 | +RF0216,16,2,660.0,100.0,102.9,élevée,1,1 | |
| 69 | +RF0217,17,2,700.0,100.0,107.4,élevée,1,1 | |
| 70 | +RF0218,18,2,740.0,100.0,97.1,élevée,1,1 | |
| 71 | +RF0219,19,2,780.0,100.0,100.2,élevée,1,1 | |
| 72 | +RF0220,20,2,820.0,100.0,107.8,élevée,1,1 | |
| 73 | +RF0221,21,2,860.0,100.0,99.3,élevée,1,1 | |
| 74 | +RF0222,22,2,900.0,100.0,98.2,élevée,1,1 | |
| 75 | +RF0223,23,2,940.0,100.0,94.2,élevée,1,1 | |
| 76 | +RF0224,24,2,980.0,100.0,100.5,élevée,1,1 | |
| 77 | +RF0300,0,3,20.0,140.0,144.3,modérée,1,0 | |
| 78 | +RF0301,1,3,60.0,140.0,147.7,modérée,1,0 | |
| 79 | +RF0302,2,3,100.0,140.0,140.7,modérée,1,0 | |
| 80 | +RF0303,3,3,140.0,140.0,135.1,modérée,1,0 | |
| 81 | +RF0304,4,3,180.0,140.0,134.6,modérée,1,0 | |
| 82 | +RF0305,5,3,220.0,140.0,136.8,modérée,1,0 | |
| 83 | +RF0306,6,3,260.0,140.0,144.0,modérée,1,0 | |
| 84 | +RF0307,7,3,300.0,140.0,146.7,modérée,1,0 | |
| 85 | +RF0308,8,3,340.0,140.0,140.3,modérée,1,0 | |
| 86 | +RF0309,9,3,380.0,140.0,141.2,modérée,1,0 | |
| 87 | +RF0310,10,3,420.0,140.0,147.1,modérée,1,0 | |
| 88 | +RF0311,11,3,460.0,140.0,137.6,modérée,1,0 | |
| 89 | +RF0312,12,3,500.0,140.0,141.8,modérée,1,0 | |
| 90 | +RF0313,13,3,540.0,140.0,138.1,modérée,1,0 | |
| 91 | +RF0314,14,3,580.0,140.0,147.4,modérée,1,0 | |
| 92 | +RF0315,15,3,620.0,140.0,137.1,modérée,1,0 | |
| 93 | +RF0316,16,3,660.0,140.0,143.6,modérée,1,0 | |
| 94 | +RF0317,17,3,700.0,140.0,142.3,modérée,1,0 | |
| 95 | +RF0318,18,3,740.0,140.0,147.6,modérée,1,0 | |
| 96 | +RF0319,19,3,780.0,140.0,136.4,modérée,1,0 | |
| 97 | +RF0320,20,3,820.0,140.0,133.4,modérée,1,0 | |
| 98 | +RF0321,21,3,860.0,140.0,140.2,modérée,1,0 | |
| 99 | +RF0322,22,3,900.0,140.0,147.3,modérée,1,0 | |
| 100 | +RF0323,23,3,940.0,140.0,143.8,modérée,1,0 | |
| 101 | +RF0324,24,3,980.0,140.0,134.5,modérée,1,0 | |
| 102 | +RF0400,0,4,20.0,180.0,179.5,modérée,1,0 | |
| 103 | +RF0401,1,4,60.0,180.0,178.4,modérée,1,0 | |
| 104 | +RF0402,2,4,100.0,180.0,179.1,modérée,1,0 | |
| 105 | +RF0403,3,4,140.0,180.0,180.8,modérée,1,0 | |
| 106 | +RF0404,4,4,180.0,180.0,173.3,modérée,1,0 | |
| 107 | +RF0405,5,4,220.0,180.0,186.4,modérée,1,0 | |
| 108 | +RF0406,6,4,260.0,180.0,184.4,modérée,1,0 | |
| 109 | +RF0407,7,4,300.0,180.0,184.9,modérée,1,0 | |
| 110 | +RF0408,8,4,340.0,180.0,186.0,modérée,1,0 | |
| 111 | +RF0409,9,4,380.0,180.0,179.8,modérée,1,0 | |
| 112 | +RF0410,10,4,420.0,180.0,175.0,modérée,1,0 | |
| 113 | +RF0411,11,4,460.0,180.0,182.3,modérée,1,0 | |
| 114 | +RF0412,12,4,500.0,180.0,179.6,modérée,1,0 | |
| 115 | +RF0413,13,4,540.0,180.0,177.2,modérée,1,0 | |
| 116 | +RF0414,14,4,580.0,180.0,184.7,modérée,1,0 | |
| 117 | +RF0415,15,4,620.0,180.0,181.4,modérée,1,0 | |
| 118 | +RF0416,16,4,660.0,180.0,174.9,modérée,1,0 | |
| 119 | +RF0417,17,4,700.0,180.0,183.4,modérée,1,0 | |
| 120 | +RF0418,18,4,740.0,180.0,187.5,modérée,1,0 | |
| 121 | +RF0419,19,4,780.0,180.0,183.5,modérée,1,0 | |
| 122 | +RF0420,20,4,820.0,180.0,174.7,modérée,1,0 | |
| 123 | +RF0421,21,4,860.0,180.0,186.8,modérée,1,0 | |
| 124 | +RF0422,22,4,900.0,180.0,187.5,modérée,1,0 | |
| 125 | +RF0423,23,4,940.0,180.0,178.8,modérée,1,0 | |
| 126 | +RF0424,24,4,980.0,180.0,185.1,modérée,1,0 | |
| 127 | +RF0500,0,5,20.0,220.0,221.3,faible,1,0 | |
| 128 | +RF0501,1,5,60.0,220.0,222.2,faible,1,0 | |
| 129 | +RF0502,2,5,100.0,220.0,226.9,faible,1,0 | |
| 130 | +RF0503,3,5,140.0,220.0,223.1,faible,1,0 | |
| 131 | +RF0504,4,5,180.0,220.0,216.8,faible,1,0 | |
| 132 | +RF0505,5,5,220.0,220.0,227.4,faible,1,0 | |
| 133 | +RF0506,6,5,260.0,220.0,214.0,faible,1,0 | |
| 134 | +RF0507,7,5,300.0,220.0,213.4,faible,1,0 | |
| 135 | +RF0508,8,5,340.0,220.0,214.6,faible,1,0 | |
| 136 | +RF0509,9,5,380.0,220.0,223.1,faible,1,0 | |
| 137 | +RF0510,10,5,420.0,220.0,213.8,faible,1,0 | |
| 138 | +RF0511,11,5,460.0,220.0,225.8,faible,1,0 | |
| 139 | +RF0512,12,5,500.0,220.0,219.1,faible,1,0 | |
| 140 | +RF0513,13,5,540.0,220.0,214.6,faible,1,0 | |
| 141 | +RF0514,14,5,580.0,220.0,225.4,faible,1,0 | |
| 142 | +RF0515,15,5,620.0,220.0,216.5,faible,1,0 | |
| 143 | +RF0516,16,5,660.0,220.0,214.0,faible,1,0 | |
| 144 | +RF0517,17,5,700.0,220.0,212.0,faible,1,0 | |
| 145 | +RF0518,18,5,740.0,220.0,213.6,faible,1,0 | |
| 146 | +RF0519,19,5,780.0,220.0,219.3,faible,1,0 | |
| 147 | +RF0520,20,5,820.0,220.0,213.0,faible,1,0 | |
| 148 | +RF0521,21,5,860.0,220.0,217.6,faible,1,0 | |
| 149 | +RF0522,22,5,900.0,220.0,217.2,faible,1,0 | |
| 150 | +RF0523,23,5,940.0,220.0,213.1,faible,1,0 | |
| 151 | +RF0524,24,5,980.0,220.0,224.7,faible,1,0 | |
| 152 | +RF0600,0,6,20.0,260.0,258.0,faible,1,0 | |
| 153 | +RF0601,1,6,60.0,260.0,259.6,faible,1,0 | |
| 154 | +RF0602,2,6,100.0,260.0,258.6,faible,1,0 | |
| 155 | +RF0603,3,6,140.0,260.0,261.7,faible,1,0 | |
| 156 | +RF0604,4,6,180.0,260.0,257.9,faible,1,0 | |
| 157 | +RF0605,5,6,220.0,260.0,255.2,faible,1,0 | |
| 158 | +RF0606,6,6,260.0,260.0,252.6,faible,1,0 | |
| 159 | +RF0607,7,6,300.0,260.0,252.3,faible,1,0 | |
| 160 | +RF0608,8,6,340.0,260.0,260.3,faible,1,0 | |
| 161 | +RF0609,9,6,380.0,260.0,257.9,faible,1,0 | |
| 162 | +RF0610,10,6,420.0,260.0,252.9,faible,1,0 | |
| 163 | +RF0611,11,6,460.0,260.0,263.6,faible,1,0 | |
| 164 | +RF0612,12,6,500.0,260.0,260.7,faible,1,0 | |
| 165 | +RF0613,13,6,540.0,260.0,253.2,faible,1,0 | |
| 166 | +RF0614,14,6,580.0,260.0,256.6,faible,1,0 | |
| 167 | +RF0615,15,6,620.0,260.0,262.1,faible,1,0 | |
| 168 | +RF0616,16,6,660.0,260.0,262.0,faible,1,0 | |
| 169 | +RF0617,17,6,700.0,260.0,267.7,faible,1,0 | |
| 170 | +RF0618,18,6,740.0,260.0,260.2,faible,1,0 | |
| 171 | +RF0619,19,6,780.0,260.0,258.9,faible,1,0 | |
| 172 | +RF0620,20,6,820.0,260.0,253.3,faible,1,0 | |
| 173 | +RF0621,21,6,860.0,260.0,264.1,faible,1,0 | |
| 174 | +RF0622,22,6,900.0,260.0,256.0,faible,1,0 | |
| 175 | +RF0623,23,6,940.0,260.0,257.8,faible,1,0 | |
| 176 | +RF0624,24,6,980.0,260.0,255.1,faible,1,0 | |
| 177 | +RF0700,0,7,20.0,300.0,298.2,hors zone,0,0 | |
| 178 | +RF0701,1,7,60.0,300.0,293.4,hors zone,0,0 | |
| 179 | +RF0702,2,7,100.0,300.0,301.8,hors zone,0,0 | |
| 180 | +RF0703,3,7,140.0,300.0,298.2,hors zone,0,0 | |
| 181 | +RF0704,4,7,180.0,300.0,306.9,hors zone,0,0 | |
| 182 | +RF0705,5,7,220.0,300.0,304.9,hors zone,0,0 | |
| 183 | +RF0706,6,7,260.0,300.0,308.0,hors zone,0,0 | |
| 184 | +RF0707,7,7,300.0,300.0,300.7,hors zone,0,0 | |
| 185 | +RF0708,8,7,340.0,300.0,303.2,hors zone,0,0 | |
| 186 | +RF0709,9,7,380.0,300.0,305.3,hors zone,0,0 | |
| 187 | +RF0710,10,7,420.0,300.0,303.3,hors zone,0,0 | |
| 188 | +RF0711,11,7,460.0,300.0,306.3,hors zone,0,0 | |
| 189 | +RF0712,12,7,500.0,300.0,294.5,hors zone,0,0 | |
| 190 | +RF0713,13,7,540.0,300.0,294.2,hors zone,0,0 | |
| 191 | +RF0714,14,7,580.0,300.0,301.9,hors zone,0,0 | |
| 192 | +RF0715,15,7,620.0,300.0,300.2,hors zone,0,0 | |
| 193 | +RF0716,16,7,660.0,300.0,306.5,hors zone,0,0 | |
| 194 | +RF0717,17,7,700.0,300.0,301.4,hors zone,0,0 | |
| 195 | +RF0718,18,7,740.0,300.0,295.5,hors zone,0,0 | |
| 196 | +RF0719,19,7,780.0,300.0,296.0,hors zone,0,0 | |
| 197 | +RF0720,20,7,820.0,300.0,294.5,hors zone,0,0 | |
| 198 | +RF0721,21,7,860.0,300.0,300.9,hors zone,0,0 | |
| 199 | +RF0722,22,7,900.0,300.0,306.0,hors zone,0,0 | |
| 200 | +RF0723,23,7,940.0,300.0,296.6,hors zone,0,0 | |
| 201 | +RF0724,24,7,980.0,300.0,293.8,hors zone,0,0 | |
| 202 | +RF0800,0,8,20.0,340.0,342.9,hors zone,0,0 | |
| 203 | +RF0801,1,8,60.0,340.0,339.4,hors zone,0,0 | |
| 204 | +RF0802,2,8,100.0,340.0,334.0,hors zone,0,0 | |
| 205 | +RF0803,3,8,140.0,340.0,337.0,hors zone,0,0 | |
| 206 | +RF0804,4,8,180.0,340.0,334.6,hors zone,0,0 | |
| 207 | +RF0805,5,8,220.0,340.0,338.1,hors zone,0,0 | |
| 208 | +RF0806,6,8,260.0,340.0,332.2,hors zone,0,0 | |
| 209 | +RF0807,7,8,300.0,340.0,337.5,hors zone,0,0 | |
| 210 | +RF0808,8,8,340.0,340.0,340.2,hors zone,0,0 | |
| 211 | +RF0809,9,8,380.0,340.0,340.1,hors zone,0,0 | |
| 212 | +RF0810,10,8,420.0,340.0,334.0,hors zone,0,0 | |
| 213 | +RF0811,11,8,460.0,340.0,345.5,hors zone,0,0 | |
| 214 | +RF0812,12,8,500.0,340.0,333.4,hors zone,0,0 | |
| 215 | +RF0813,13,8,540.0,340.0,339.6,hors zone,0,0 | |
| 216 | +RF0814,14,8,580.0,340.0,343.1,hors zone,0,0 | |
| 217 | +RF0815,15,8,620.0,340.0,344.6,hors zone,0,0 | |
| 218 | +RF0816,16,8,660.0,340.0,335.8,hors zone,0,0 | |
| 219 | +RF0817,17,8,700.0,340.0,347.6,hors zone,0,0 | |
| 220 | +RF0818,18,8,740.0,340.0,334.2,hors zone,0,0 | |
| 221 | +RF0819,19,8,780.0,340.0,345.3,hors zone,0,0 | |
| 222 | +RF0820,20,8,820.0,340.0,343.1,hors zone,0,0 | |
| 223 | +RF0821,21,8,860.0,340.0,343.8,hors zone,0,0 | |
| 224 | +RF0822,22,8,900.0,340.0,341.2,hors zone,0,0 | |
| 225 | +RF0823,23,8,940.0,340.0,334.9,hors zone,0,0 | |
| 226 | +RF0824,24,8,980.0,340.0,344.6,hors zone,0,0 | |
| 227 | +RF0900,0,9,20.0,380.0,373.6,hors zone,0,0 | |
| 228 | +RF0901,1,9,60.0,380.0,374.7,hors zone,0,0 | |
| 229 | +RF0902,2,9,100.0,380.0,375.0,hors zone,0,0 | |
| 230 | +RF0903,3,9,140.0,380.0,382.0,hors zone,0,0 | |
| 231 | +RF0904,4,9,180.0,380.0,378.2,hors zone,0,0 | |
| 232 | +RF0905,5,9,220.0,380.0,381.8,hors zone,0,0 | |
| 233 | +RF0906,6,9,260.0,380.0,385.6,hors zone,0,0 | |
| 234 | +RF0907,7,9,300.0,380.0,387.1,hors zone,0,0 | |
| 235 | +RF0908,8,9,340.0,380.0,383.5,hors zone,0,0 | |
| 236 | +RF0909,9,9,380.0,380.0,382.2,hors zone,0,0 | |
| 237 | +RF0910,10,9,420.0,380.0,384.0,hors zone,0,0 | |
| 238 | +RF0911,11,9,460.0,380.0,374.7,hors zone,0,0 | |
| 239 | +RF0912,12,9,500.0,380.0,385.7,hors zone,0,0 | |
| 240 | +RF0913,13,9,540.0,380.0,380.2,hors zone,0,0 | |
| 241 | +RF0914,14,9,580.0,380.0,373.3,hors zone,0,0 | |
| 242 | +RF0915,15,9,620.0,380.0,387.3,hors zone,0,0 | |
| 243 | +RF0916,16,9,660.0,380.0,377.9,hors zone,0,0 | |
| 244 | +RF0917,17,9,700.0,380.0,383.9,hors zone,0,0 | |
| 245 | +RF0918,18,9,740.0,380.0,376.8,hors zone,0,0 | |
| 246 | +RF0919,19,9,780.0,380.0,372.6,hors zone,0,0 | |
| 247 | +RF0920,20,9,820.0,380.0,386.8,hors zone,0,0 | |
| 248 | +RF0921,21,9,860.0,380.0,374.5,hors zone,0,0 | |
| 249 | +RF0922,22,9,900.0,380.0,375.8,hors zone,0,0 | |
| 250 | +RF0923,23,9,940.0,380.0,377.4,hors zone,0,0 | |
| 251 | +RF0924,24,9,980.0,380.0,382.2,hors zone,0,0 | |
| 252 | +RF1000,0,10,20.0,420.0,417.4,hors zone,0,0 | |
| 253 | +RF1001,1,10,60.0,420.0,418.6,hors zone,0,0 | |
| 254 | +RF1002,2,10,100.0,420.0,417.1,hors zone,0,0 | |
| 255 | +RF1003,3,10,140.0,420.0,413.8,hors zone,0,0 | |
| 256 | +RF1004,4,10,180.0,420.0,412.8,hors zone,0,0 | |
| 257 | +RF1005,5,10,220.0,420.0,424.3,hors zone,0,0 | |
| 258 | +RF1006,6,10,260.0,420.0,421.5,hors zone,0,0 | |
| 259 | +RF1007,7,10,300.0,420.0,416.4,hors zone,0,0 | |
| 260 | +RF1008,8,10,340.0,420.0,412.4,hors zone,0,0 | |
| 261 | +RF1009,9,10,380.0,420.0,420.6,hors zone,0,0 | |
| 262 | +RF1010,10,10,420.0,420.0,415.6,hors zone,0,0 | |
| 263 | +RF1011,11,10,460.0,420.0,424.6,hors zone,0,0 | |
| 264 | +RF1012,12,10,500.0,420.0,416.4,hors zone,0,0 | |
| 265 | +RF1013,13,10,540.0,420.0,413.3,hors zone,0,0 | |
| 266 | +RF1014,14,10,580.0,420.0,420.9,hors zone,0,0 | |
| 267 | +RF1015,15,10,620.0,420.0,425.1,hors zone,0,0 | |
| 268 | +RF1016,16,10,660.0,420.0,426.1,hors zone,0,0 | |
| 269 | +RF1017,17,10,700.0,420.0,425.9,hors zone,0,0 | |
| 270 | +RF1018,18,10,740.0,420.0,427.8,hors zone,0,0 | |
| 271 | +RF1019,19,10,780.0,420.0,427.8,hors zone,0,0 | |
| 272 | +RF1020,20,10,820.0,420.0,422.0,hors zone,0,0 | |
| 273 | +RF1021,21,10,860.0,420.0,420.8,hors zone,0,0 | |
| 274 | +RF1022,22,10,900.0,420.0,415.7,hors zone,0,0 | |
| 275 | +RF1023,23,10,940.0,420.0,427.0,hors zone,0,0 | |
| 276 | +RF1024,24,10,980.0,420.0,425.0,hors zone,0,0 | |
| 277 | +RF1100,0,11,20.0,460.0,460.0,hors zone,0,0 | |
| 278 | +RF1101,1,11,60.0,460.0,459.4,hors zone,0,0 | |
| 279 | +RF1102,2,11,100.0,460.0,453.8,hors zone,0,0 | |
| 280 | +RF1103,3,11,140.0,460.0,463.9,hors zone,0,0 | |
| 281 | +RF1104,4,11,180.0,460.0,454.7,hors zone,0,0 | |
| 282 | +RF1105,5,11,220.0,460.0,457.4,hors zone,0,0 | |
| 283 | +RF1106,6,11,260.0,460.0,456.3,hors zone,0,0 | |
| 284 | +RF1107,7,11,300.0,460.0,456.3,hors zone,0,0 | |
| 285 | +RF1108,8,11,340.0,460.0,467.6,hors zone,0,0 | |
| 286 | +RF1109,9,11,380.0,460.0,466.7,hors zone,0,0 | |
| 287 | +RF1110,10,11,420.0,460.0,462.8,hors zone,0,0 | |
| 288 | +RF1111,11,11,460.0,460.0,452.2,hors zone,0,0 | |
| 289 | +RF1112,12,11,500.0,460.0,459.4,hors zone,0,0 | |
| 290 | +RF1113,13,11,540.0,460.0,467.3,hors zone,0,0 | |
| 291 | +RF1114,14,11,580.0,460.0,462.9,hors zone,0,0 | |
| 292 | +RF1115,15,11,620.0,460.0,459.8,hors zone,0,0 | |
| 293 | +RF1116,16,11,660.0,460.0,454.8,hors zone,0,0 | |
| 294 | +RF1117,17,11,700.0,460.0,456.8,hors zone,0,0 | |
| 295 | +RF1118,18,11,740.0,460.0,458.8,hors zone,0,0 | |
| 296 | +RF1119,19,11,780.0,460.0,453.3,hors zone,0,0 | |
| 297 | +RF1120,20,11,820.0,460.0,466.2,hors zone,0,0 | |
| 298 | +RF1121,21,11,860.0,460.0,452.7,hors zone,0,0 | |
| 299 | +RF1122,22,11,900.0,460.0,452.6,hors zone,0,0 | |
| 300 | +RF1123,23,11,940.0,460.0,467.5,hors zone,0,0 | |
| 301 | +RF1124,24,11,980.0,460.0,452.7,hors zone,0,0 | |
| 302 | +RF1200,0,12,20.0,500.0,500.4,hors zone,0,0 | |
| 303 | +RF1201,1,12,60.0,500.0,499.1,hors zone,0,0 | |
| 304 | +RF1202,2,12,100.0,500.0,496.0,hors zone,0,0 | |
| 305 | +RF1203,3,12,140.0,500.0,501.5,hors zone,0,0 | |
| 306 | +RF1204,4,12,180.0,500.0,493.0,hors zone,0,0 | |
| 307 | +RF1205,5,12,220.0,500.0,501.4,hors zone,0,0 | |
| 308 | +RF1206,6,12,260.0,500.0,500.2,hors zone,0,0 | |
| 309 | +RF1207,7,12,300.0,500.0,503.5,hors zone,0,0 | |
| 310 | +RF1208,8,12,340.0,500.0,507.2,hors zone,0,0 | |
| 311 | +RF1209,9,12,380.0,500.0,495.1,hors zone,0,0 | |
| 312 | +RF1210,10,12,420.0,500.0,497.3,hors zone,0,0 | |
| 313 | +RF1211,11,12,460.0,500.0,495.0,hors zone,0,0 | |
| 314 | +RF1212,12,12,500.0,500.0,493.7,hors zone,0,0 | |
| 315 | +RF1213,13,12,540.0,500.0,500.0,hors zone,0,0 | |
| 316 | +RF1214,14,12,580.0,500.0,504.8,hors zone,0,0 | |
| 317 | +RF1215,15,12,620.0,500.0,503.1,hors zone,0,0 | |
| 318 | +RF1216,16,12,660.0,500.0,493.9,hors zone,0,0 | |
| 319 | +RF1217,17,12,700.0,500.0,502.2,hors zone,0,0 | |
| 320 | +RF1218,18,12,740.0,500.0,498.7,hors zone,0,0 | |
| 321 | +RF1219,19,12,780.0,500.0,503.5,hors zone,0,0 | |
| 322 | +RF1220,20,12,820.0,500.0,493.3,hors zone,0,0 | |
| 323 | +RF1221,21,12,860.0,500.0,504.1,hors zone,0,0 | |
| 324 | +RF1222,22,12,900.0,500.0,505.5,hors zone,0,0 | |
| 325 | +RF1223,23,12,940.0,500.0,497.6,hors zone,0,0 | |
| 326 | +RF1224,24,12,980.0,500.0,495.2,hors zone,0,0 | |
| 327 | +RF1300,0,13,20.0,540.0,541.1,hors zone,0,0 | |
| 328 | +RF1301,1,13,60.0,540.0,532.2,hors zone,0,0 | |
| 329 | +RF1302,2,13,100.0,540.0,543.8,hors zone,0,0 | |
| 330 | +RF1303,3,13,140.0,540.0,544.7,hors zone,0,0 | |
| 331 | +RF1304,4,13,180.0,540.0,533.5,hors zone,0,0 | |
| 332 | +RF1305,5,13,220.0,540.0,540.4,hors zone,0,0 | |
| 333 | +RF1306,6,13,260.0,540.0,542.2,hors zone,0,0 | |
| 334 | +RF1307,7,13,300.0,540.0,541.4,hors zone,0,0 | |
| 335 | +RF1308,8,13,340.0,540.0,536.3,hors zone,0,0 | |
| 336 | +RF1309,9,13,380.0,540.0,543.8,hors zone,0,0 | |
| 337 | +RF1310,10,13,420.0,540.0,532.2,hors zone,0,0 | |
| 338 | +RF1311,11,13,460.0,540.0,546.1,hors zone,0,0 | |
| 339 | +RF1312,12,13,500.0,540.0,534.8,hors zone,0,0 | |
| 340 | +RF1313,13,13,540.0,540.0,532.5,hors zone,0,0 | |
| 341 | +RF1314,14,13,580.0,540.0,537.7,hors zone,0,0 | |
| 342 | +RF1315,15,13,620.0,540.0,539.3,hors zone,0,0 | |
| 343 | +RF1316,16,13,660.0,540.0,535.0,hors zone,0,0 | |
| 344 | +RF1317,17,13,700.0,540.0,534.0,hors zone,0,0 | |
| 345 | +RF1318,18,13,740.0,540.0,547.9,hors zone,0,0 | |
| 346 | +RF1319,19,13,780.0,540.0,545.3,hors zone,0,0 | |
| 347 | +RF1320,20,13,820.0,540.0,541.6,hors zone,0,0 | |
| 348 | +RF1321,21,13,860.0,540.0,532.0,hors zone,0,0 | |
| 349 | +RF1322,22,13,900.0,540.0,547.8,hors zone,0,0 | |
| 350 | +RF1323,23,13,940.0,540.0,534.9,hors zone,0,0 | |
| 351 | +RF1324,24,13,980.0,540.0,532.6,hors zone,0,0 | |
| 352 | +RF1400,0,14,20.0,580.0,576.4,hors zone,0,0 | |
| 353 | +RF1401,1,14,60.0,580.0,579.1,hors zone,0,0 | |
| 354 | +RF1402,2,14,100.0,580.0,581.0,hors zone,0,0 | |
| 355 | +RF1403,3,14,140.0,580.0,583.1,hors zone,0,0 | |
| 356 | +RF1404,4,14,180.0,580.0,579.0,hors zone,0,0 | |
| 357 | +RF1405,5,14,220.0,580.0,582.4,hors zone,0,0 | |
| 358 | +RF1406,6,14,260.0,580.0,579.5,hors zone,0,0 | |
| 359 | +RF1407,7,14,300.0,580.0,580.3,hors zone,0,0 | |
| 360 | +RF1408,8,14,340.0,580.0,587.2,hors zone,0,0 | |
| 361 | +RF1409,9,14,380.0,580.0,587.6,hors zone,0,0 | |
| 362 | +RF1410,10,14,420.0,580.0,581.9,hors zone,0,0 | |
| 363 | +RF1411,11,14,460.0,580.0,586.7,hors zone,0,0 | |
| 364 | +RF1412,12,14,500.0,580.0,584.0,hors zone,0,0 | |
| 365 | +RF1413,13,14,540.0,580.0,580.9,hors zone,0,0 | |
| 366 | +RF1414,14,14,580.0,580.0,576.2,hors zone,0,0 | |
| 367 | +RF1415,15,14,620.0,580.0,585.0,hors zone,0,0 | |
| 368 | +RF1416,16,14,660.0,580.0,577.2,hors zone,0,0 | |
| 369 | +RF1417,17,14,700.0,580.0,578.1,hors zone,0,0 | |
| 370 | +RF1418,18,14,740.0,580.0,575.2,hors zone,0,0 | |
| 371 | +RF1419,19,14,780.0,580.0,576.0,hors zone,0,0 | |
| 372 | +RF1420,20,14,820.0,580.0,580.3,hors zone,0,0 | |
| 373 | +RF1421,21,14,860.0,580.0,581.1,hors zone,0,0 | |
| 374 | +RF1422,22,14,900.0,580.0,573.0,hors zone,0,0 | |
| 375 | +RF1423,23,14,940.0,580.0,585.9,hors zone,0,0 | |
| 376 | +RF1424,24,14,980.0,580.0,577.1,hors zone,0,0 | |
| 377 | +RF1500,0,15,20.0,620.0,622.3,hors zone,0,0 | |
| 378 | +RF1501,1,15,60.0,620.0,621.9,hors zone,0,0 | |
| 379 | +RF1502,2,15,100.0,620.0,616.8,hors zone,0,0 | |
| 380 | +RF1503,3,15,140.0,620.0,627.3,hors zone,0,0 | |
| 381 | +RF1504,4,15,180.0,620.0,622.8,hors zone,0,0 | |
| 382 | +RF1505,5,15,220.0,620.0,612.7,hors zone,0,0 | |
| 383 | +RF1506,6,15,260.0,620.0,613.1,hors zone,0,0 | |
| 384 | +RF1507,7,15,300.0,620.0,615.8,hors zone,0,0 | |
| 385 | +RF1508,8,15,340.0,620.0,624.2,hors zone,0,0 | |
| 386 | +RF1509,9,15,380.0,620.0,617.5,hors zone,0,0 | |
| 387 | +RF1510,10,15,420.0,620.0,617.9,hors zone,0,0 | |
| 388 | +RF1511,11,15,460.0,620.0,616.2,hors zone,0,0 | |
| 389 | +RF1512,12,15,500.0,620.0,614.9,hors zone,0,0 | |
| 390 | +RF1513,13,15,540.0,620.0,623.9,hors zone,0,0 | |
| 391 | +RF1514,14,15,580.0,620.0,621.3,hors zone,0,0 | |
| 392 | +RF1515,15,15,620.0,620.0,622.3,hors zone,0,0 | |
| 393 | +RF1516,16,15,660.0,620.0,625.8,hors zone,0,0 | |
| 394 | +RF1517,17,15,700.0,620.0,624.1,hors zone,0,0 | |
| 395 | +RF1518,18,15,740.0,620.0,620.4,hors zone,0,0 | |
| 396 | +RF1519,19,15,780.0,620.0,613.3,hors zone,0,0 | |
| 397 | +RF1520,20,15,820.0,620.0,618.8,hors zone,0,0 | |
| 398 | +RF1521,21,15,860.0,620.0,617.9,hors zone,0,0 | |
| 399 | +RF1522,22,15,900.0,620.0,620.8,hors zone,0,0 | |
| 400 | +RF1523,23,15,940.0,620.0,626.0,hors zone,0,0 | |
| 401 | +RF1524,24,15,980.0,620.0,623.6,hors zone,0,0 | |
| 402 | +RF1600,0,16,20.0,660.0,652.0,hors zone,0,0 | |
| 403 | +RF1601,1,16,60.0,660.0,658.2,hors zone,0,0 | |
| 404 | +RF1602,2,16,100.0,660.0,661.5,hors zone,0,0 | |
| 405 | +RF1603,3,16,140.0,660.0,653.7,hors zone,0,0 | |
| 406 | +RF1604,4,16,180.0,660.0,655.4,hors zone,0,0 | |
| 407 | +RF1605,5,16,220.0,660.0,653.1,hors zone,0,0 | |
| 408 | +RF1606,6,16,260.0,660.0,666.8,hors zone,0,0 | |
| 409 | +RF1607,7,16,300.0,660.0,659.0,hors zone,0,0 | |
| 410 | +RF1608,8,16,340.0,660.0,660.3,hors zone,0,0 | |
| 411 | +RF1609,9,16,380.0,660.0,654.7,hors zone,0,0 | |
| 412 | +RF1610,10,16,420.0,660.0,666.3,hors zone,0,0 | |
| 413 | +RF1611,11,16,460.0,660.0,660.0,hors zone,0,0 | |
| 414 | +RF1612,12,16,500.0,660.0,659.9,hors zone,0,0 | |
| 415 | +RF1613,13,16,540.0,660.0,665.3,hors zone,0,0 | |
| 416 | +RF1614,14,16,580.0,660.0,658.6,hors zone,0,0 | |
| 417 | +RF1615,15,16,620.0,660.0,657.7,hors zone,0,0 | |
| 418 | +RF1616,16,16,660.0,660.0,654.8,hors zone,0,0 | |
| 419 | +RF1617,17,16,700.0,660.0,666.7,hors zone,0,0 | |
| 420 | +RF1618,18,16,740.0,660.0,653.5,hors zone,0,0 | |
| 421 | +RF1619,19,16,780.0,660.0,664.0,hors zone,0,0 | |
| 422 | +RF1620,20,16,820.0,660.0,665.3,hors zone,0,0 | |
| 423 | +RF1621,21,16,860.0,660.0,658.6,hors zone,0,0 | |
| 424 | +RF1622,22,16,900.0,660.0,664.8,hors zone,0,0 | |
| 425 | +RF1623,23,16,940.0,660.0,652.2,hors zone,0,0 | |
| 426 | +RF1624,24,16,980.0,660.0,660.3,hors zone,0,0 | |
| 427 | +RF1700,0,17,20.0,700.0,702.5,hors zone,0,0 | |
| 428 | +RF1701,1,17,60.0,700.0,701.9,hors zone,0,0 | |
| 429 | +RF1702,2,17,100.0,700.0,696.8,hors zone,0,0 | |
| 430 | +RF1703,3,17,140.0,700.0,699.5,hors zone,0,0 | |
| 431 | +RF1704,4,17,180.0,700.0,694.2,hors zone,0,0 | |
| 432 | +RF1705,5,17,220.0,700.0,696.4,hors zone,0,0 | |
| 433 | +RF1706,6,17,260.0,700.0,706.3,hors zone,0,0 | |
| 434 | +RF1707,7,17,300.0,700.0,697.1,hors zone,0,0 | |
| 435 | +RF1708,8,17,340.0,700.0,704.6,hors zone,0,0 | |
| 436 | +RF1709,9,17,380.0,700.0,695.2,hors zone,0,0 | |
| 437 | +RF1710,10,17,420.0,700.0,693.9,hors zone,0,0 | |
| 438 | +RF1711,11,17,460.0,700.0,694.8,hors zone,0,0 | |
| 439 | +RF1712,12,17,500.0,700.0,693.6,hors zone,0,0 | |
| 440 | +RF1713,13,17,540.0,700.0,695.6,hors zone,0,0 | |
| 441 | +RF1714,14,17,580.0,700.0,696.7,hors zone,0,0 | |
| 442 | +RF1715,15,17,620.0,700.0,700.2,hors zone,0,0 | |
| 443 | +RF1716,16,17,660.0,700.0,704.5,hors zone,0,0 | |
| 444 | +RF1717,17,17,700.0,700.0,695.4,hors zone,0,0 | |
| 445 | +RF1718,18,17,740.0,700.0,695.5,hors zone,0,0 | |
| 446 | +RF1719,19,17,780.0,700.0,706.9,hors zone,0,0 | |
| 447 | +RF1720,20,17,820.0,700.0,694.0,hors zone,0,0 | |
| 448 | +RF1721,21,17,860.0,700.0,703.2,hors zone,0,0 | |
| 449 | +RF1722,22,17,900.0,700.0,698.3,hors zone,0,0 | |
| 450 | +RF1723,23,17,940.0,700.0,694.2,hors zone,0,0 | |
| 451 | +RF1724,24,17,980.0,700.0,701.5,hors zone,0,0 | |
| 452 | +RF1800,0,18,20.0,740.0,742.1,hors zone,0,0 | |
| 453 | +RF1801,1,18,60.0,740.0,742.4,hors zone,0,0 | |
| 454 | +RF1802,2,18,100.0,740.0,747.3,hors zone,0,0 | |
| 455 | +RF1803,3,18,140.0,740.0,735.2,hors zone,0,0 | |
| 456 | +RF1804,4,18,180.0,740.0,740.5,hors zone,0,0 | |
| 457 | +RF1805,5,18,220.0,740.0,735.1,hors zone,0,0 | |
| 458 | +RF1806,6,18,260.0,740.0,747.1,hors zone,0,0 | |
| 459 | +RF1807,7,18,300.0,740.0,736.0,hors zone,0,0 | |
| 460 | +RF1808,8,18,340.0,740.0,739.5,hors zone,0,0 | |
| 461 | +RF1809,9,18,380.0,740.0,733.1,hors zone,0,0 | |
| 462 | +RF1810,10,18,420.0,740.0,737.7,hors zone,0,0 | |
| 463 | +RF1811,11,18,460.0,740.0,733.4,hors zone,0,0 | |
| 464 | +RF1812,12,18,500.0,740.0,740.3,hors zone,0,0 | |
| 465 | +RF1813,13,18,540.0,740.0,742.2,hors zone,0,0 | |
| 466 | +RF1814,14,18,580.0,740.0,746.5,hors zone,0,0 | |
| 467 | +RF1815,15,18,620.0,740.0,737.1,hors zone,0,0 | |
| 468 | +RF1816,16,18,660.0,740.0,735.8,hors zone,0,0 | |
| 469 | +RF1817,17,18,700.0,740.0,732.9,hors zone,0,0 | |
| 470 | +RF1818,18,18,740.0,740.0,741.2,hors zone,0,0 | |
| 471 | +RF1819,19,18,780.0,740.0,738.9,hors zone,0,0 | |
| 472 | +RF1820,20,18,820.0,740.0,744.0,hors zone,0,0 | |
| 473 | +RF1821,21,18,860.0,740.0,743.6,hors zone,0,0 | |
| 474 | +RF1822,22,18,900.0,740.0,739.9,hors zone,0,0 | |
| 475 | +RF1823,23,18,940.0,740.0,733.5,hors zone,0,0 | |
| 476 | +RF1824,24,18,980.0,740.0,740.5,hors zone,0,0 | |
| 477 | +RF1900,0,19,20.0,780.0,787.6,hors zone,0,0 | |
| 478 | +RF1901,1,19,60.0,780.0,774.4,hors zone,0,0 | |
| 479 | +RF1902,2,19,100.0,780.0,780.1,hors zone,0,0 | |
| 480 | +RF1903,3,19,140.0,780.0,772.6,hors zone,0,0 | |
| 481 | +RF1904,4,19,180.0,780.0,780.3,hors zone,0,0 | |
| 482 | +RF1905,5,19,220.0,780.0,778.2,hors zone,0,0 | |
| 483 | +RF1906,6,19,260.0,780.0,783.4,hors zone,0,0 | |
| 484 | +RF1907,7,19,300.0,780.0,781.0,hors zone,0,0 | |
| 485 | +RF1908,8,19,340.0,780.0,779.2,hors zone,0,0 | |
| 486 | +RF1909,9,19,380.0,780.0,785.9,hors zone,0,0 | |
| 487 | +RF1910,10,19,420.0,780.0,784.5,hors zone,0,0 | |
| 488 | +RF1911,11,19,460.0,780.0,784.4,hors zone,0,0 | |
| 489 | +RF1912,12,19,500.0,780.0,773.0,hors zone,0,0 | |
| 490 | +RF1913,13,19,540.0,780.0,777.2,hors zone,0,0 | |
| 491 | +RF1914,14,19,580.0,780.0,776.4,hors zone,0,0 | |
| 492 | +RF1915,15,19,620.0,780.0,773.3,hors zone,0,0 | |
| 493 | +RF1916,16,19,660.0,780.0,781.0,hors zone,0,0 | |
| 494 | +RF1917,17,19,700.0,780.0,785.2,hors zone,0,0 | |
| 495 | +RF1918,18,19,740.0,780.0,784.0,hors zone,0,0 | |
| 496 | +RF1919,19,19,780.0,780.0,774.8,hors zone,0,0 | |
| 497 | +RF1920,20,19,820.0,780.0,782.2,hors zone,0,0 | |
| 498 | +RF1921,21,19,860.0,780.0,777.5,hors zone,0,0 | |
| 499 | +RF1922,22,19,900.0,780.0,775.8,hors zone,0,0 | |
| 500 | +RF1923,23,19,940.0,780.0,786.1,hors zone,0,0 | |
| 501 | +RF1924,24,19,980.0,780.0,774.5,hors zone,0,0 | |
| 502 | +RF2000,0,20,20.0,820.0,813.8,hors zone,0,0 | |
| 503 | +RF2001,1,20,60.0,820.0,817.8,hors zone,0,0 | |
| 504 | +RF2002,2,20,100.0,820.0,817.0,hors zone,0,0 | |
| 505 | +RF2003,3,20,140.0,820.0,815.1,hors zone,0,0 | |
| 506 | +RF2004,4,20,180.0,820.0,822.7,hors zone,0,0 | |
| 507 | +RF2005,5,20,220.0,820.0,818.9,hors zone,0,0 | |
| 508 | +RF2006,6,20,260.0,820.0,820.6,hors zone,0,0 | |
| 509 | +RF2007,7,20,300.0,820.0,819.3,hors zone,0,0 | |
| 510 | +RF2008,8,20,340.0,820.0,817.1,hors zone,0,0 | |
| 511 | +RF2009,9,20,380.0,820.0,815.7,hors zone,0,0 | |
| 512 | +RF2010,10,20,420.0,820.0,822.7,hors zone,0,0 | |
| 513 | +RF2011,11,20,460.0,820.0,825.1,hors zone,0,0 | |
| 514 | +RF2012,12,20,500.0,820.0,827.6,hors zone,0,0 | |
| 515 | +RF2013,13,20,540.0,820.0,823.1,hors zone,0,0 | |
| 516 | +RF2014,14,20,580.0,820.0,820.5,hors zone,0,0 | |
| 517 | +RF2015,15,20,620.0,820.0,821.4,hors zone,0,0 | |
| 518 | +RF2016,16,20,660.0,820.0,818.5,hors zone,0,0 | |
| 519 | +RF2017,17,20,700.0,820.0,824.1,hors zone,0,0 | |
| 520 | +RF2018,18,20,740.0,820.0,823.2,hors zone,0,0 | |
| 521 | +RF2019,19,20,780.0,820.0,815.6,hors zone,0,0 | |
| 522 | +RF2020,20,20,820.0,820.0,812.5,hors zone,0,0 | |
| 523 | +RF2021,21,20,860.0,820.0,823.7,hors zone,0,0 | |
| 524 | +RF2022,22,20,900.0,820.0,822.8,hors zone,0,0 | |
| 525 | +RF2023,23,20,940.0,820.0,818.5,hors zone,0,0 | |
| 526 | +RF2024,24,20,980.0,820.0,817.8,hors zone,0,0 | |
| 527 | +RF2100,0,21,20.0,860.0,857.4,hors zone,0,0 | |
| 528 | +RF2101,1,21,60.0,860.0,858.3,hors zone,0,0 | |
| 529 | +RF2102,2,21,100.0,860.0,862.1,hors zone,0,0 | |
| 530 | +RF2103,3,21,140.0,860.0,860.1,hors zone,0,0 | |
| 531 | +RF2104,4,21,180.0,860.0,859.4,hors zone,0,0 | |
| 532 | +RF2105,5,21,220.0,860.0,855.8,hors zone,0,0 | |
| 533 | +RF2106,6,21,260.0,860.0,865.3,hors zone,0,0 | |
| 534 | +RF2107,7,21,300.0,860.0,867.4,hors zone,0,0 | |
| 535 | +RF2108,8,21,340.0,860.0,867.6,hors zone,0,0 | |
| 536 | +RF2109,9,21,380.0,860.0,864.4,hors zone,0,0 | |
| 537 | +RF2110,10,21,420.0,860.0,855.5,hors zone,0,0 | |
| 538 | +RF2111,11,21,460.0,860.0,858.9,hors zone,0,0 | |
| 539 | +RF2112,12,21,500.0,860.0,861.3,hors zone,0,0 | |
| 540 | +RF2113,13,21,540.0,860.0,858.9,hors zone,0,0 | |
| 541 | +RF2114,14,21,580.0,860.0,853.6,hors zone,0,0 | |
| 542 | +RF2115,15,21,620.0,860.0,866.5,hors zone,0,0 | |
| 543 | +RF2116,16,21,660.0,860.0,862.2,hors zone,0,0 | |
| 544 | +RF2117,17,21,700.0,860.0,860.0,hors zone,0,0 | |
| 545 | +RF2118,18,21,740.0,860.0,863.6,hors zone,0,0 | |
| 546 | +RF2119,19,21,780.0,860.0,865.3,hors zone,0,0 | |
| 547 | +RF2120,20,21,820.0,860.0,862.4,hors zone,0,0 | |
| 548 | +RF2121,21,21,860.0,860.0,853.7,hors zone,0,0 | |
| 549 | +RF2122,22,21,900.0,860.0,857.5,hors zone,0,0 | |
| 550 | +RF2123,23,21,940.0,860.0,861.6,hors zone,0,0 | |
| 551 | +RF2124,24,21,980.0,860.0,858.1,hors zone,0,0 | |
| 552 | +RF2200,0,22,20.0,900.0,901.0,hors zone,0,0 | |
| 553 | +RF2201,1,22,60.0,900.0,893.0,hors zone,0,0 | |
| 554 | +RF2202,2,22,100.0,900.0,901.1,hors zone,0,0 | |
| 555 | +RF2203,3,22,140.0,900.0,905.1,hors zone,0,0 | |
| 556 | +RF2204,4,22,180.0,900.0,903.2,hors zone,0,0 | |
| 557 | +RF2205,5,22,220.0,900.0,898.0,hors zone,0,0 | |
| 558 | +RF2206,6,22,260.0,900.0,892.9,hors zone,0,0 | |
| 559 | +RF2207,7,22,300.0,900.0,897.3,hors zone,0,0 | |
| 560 | +RF2208,8,22,340.0,900.0,896.3,hors zone,0,0 | |
| 561 | +RF2209,9,22,380.0,900.0,900.0,hors zone,0,0 | |
| 562 | +RF2210,10,22,420.0,900.0,892.9,hors zone,0,0 | |
| 563 | +RF2211,11,22,460.0,900.0,905.1,hors zone,0,0 | |
| 564 | +RF2212,12,22,500.0,900.0,902.1,hors zone,0,0 | |
| 565 | +RF2213,13,22,540.0,900.0,892.1,hors zone,0,0 | |
| 566 | +RF2214,14,22,580.0,900.0,900.0,hors zone,0,0 | |
| 567 | +RF2215,15,22,620.0,900.0,896.8,hors zone,0,0 | |
| 568 | +RF2216,16,22,660.0,900.0,892.2,hors zone,0,0 | |
| 569 | +RF2217,17,22,700.0,900.0,907.6,hors zone,0,0 | |
| 570 | +RF2218,18,22,740.0,900.0,896.7,hors zone,0,0 | |
| 571 | +RF2219,19,22,780.0,900.0,897.3,hors zone,0,0 | |
| 572 | +RF2220,20,22,820.0,900.0,895.1,hors zone,0,0 | |
| 573 | +RF2221,21,22,860.0,900.0,901.7,hors zone,0,0 | |
| 574 | +RF2222,22,22,900.0,900.0,903.8,hors zone,0,0 | |
| 575 | +RF2223,23,22,940.0,900.0,896.7,hors zone,0,0 | |
| 576 | +RF2224,24,22,980.0,900.0,895.6,hors zone,0,0 | |
| 577 | +RF2300,0,23,20.0,940.0,936.6,hors zone,0,0 | |
| 578 | +RF2301,1,23,60.0,940.0,943.7,hors zone,0,0 | |
| 579 | +RF2302,2,23,100.0,940.0,943.5,hors zone,0,0 | |
| 580 | +RF2303,3,23,140.0,940.0,935.9,hors zone,0,0 | |
| 581 | +RF2304,4,23,180.0,940.0,936.0,hors zone,0,0 | |
| 582 | +RF2305,5,23,220.0,940.0,935.0,hors zone,0,0 | |
| 583 | +RF2306,6,23,260.0,940.0,944.7,hors zone,0,0 | |
| 584 | +RF2307,7,23,300.0,940.0,945.6,hors zone,0,0 | |
| 585 | +RF2308,8,23,340.0,940.0,934.0,hors zone,0,0 | |
| 586 | +RF2309,9,23,380.0,940.0,936.1,hors zone,0,0 | |
| 587 | +RF2310,10,23,420.0,940.0,944.8,hors zone,0,0 | |
| 588 | +RF2311,11,23,460.0,940.0,942.6,hors zone,0,0 | |
| 589 | +RF2312,12,23,500.0,940.0,941.8,hors zone,0,0 | |
| 590 | +RF2313,13,23,540.0,940.0,946.6,hors zone,0,0 | |
| 591 | +RF2314,14,23,580.0,940.0,942.3,hors zone,0,0 | |
| 592 | +RF2315,15,23,620.0,940.0,942.3,hors zone,0,0 | |
| 593 | +RF2316,16,23,660.0,940.0,943.3,hors zone,0,0 | |
| 594 | +RF2317,17,23,700.0,940.0,945.1,hors zone,0,0 | |
| 595 | +RF2318,18,23,740.0,940.0,940.5,hors zone,0,0 | |
| 596 | +RF2319,19,23,780.0,940.0,941.8,hors zone,0,0 | |
| 597 | +RF2320,20,23,820.0,940.0,936.1,hors zone,0,0 | |
| 598 | +RF2321,21,23,860.0,940.0,937.3,hors zone,0,0 | |
| 599 | +RF2322,22,23,900.0,940.0,939.5,hors zone,0,0 | |
| 600 | +RF2323,23,23,940.0,940.0,944.2,hors zone,0,0 | |
| 601 | +RF2324,24,23,980.0,940.0,946.6,hors zone,0,0 | |
| 602 | +RF2400,0,24,20.0,980.0,977.3,hors zone,0,0 | |
| 603 | +RF2401,1,24,60.0,980.0,986.6,hors zone,0,0 | |
| 604 | +RF2402,2,24,100.0,980.0,980.0,hors zone,0,0 | |
| 605 | +RF2403,3,24,140.0,980.0,985.4,hors zone,0,0 | |
| 606 | +RF2404,4,24,180.0,980.0,981.7,hors zone,0,0 | |
| 607 | +RF2405,5,24,220.0,980.0,976.9,hors zone,0,0 | |
| 608 | +RF2406,6,24,260.0,980.0,975.6,hors zone,0,0 | |
| 609 | +RF2407,7,24,300.0,980.0,973.7,hors zone,0,0 | |
| 610 | +RF2408,8,24,340.0,980.0,972.6,hors zone,0,0 | |
| 611 | +RF2409,9,24,380.0,980.0,973.3,hors zone,0,0 | |
| 612 | +RF2410,10,24,420.0,980.0,975.4,hors zone,0,0 | |
| 613 | +RF2411,11,24,460.0,980.0,977.6,hors zone,0,0 | |
| 614 | +RF2412,12,24,500.0,980.0,980.3,hors zone,0,0 | |
| 615 | +RF2413,13,24,540.0,980.0,983.0,hors zone,0,0 | |
| 616 | +RF2414,14,24,580.0,980.0,979.1,hors zone,0,0 | |
| 617 | +RF2415,15,24,620.0,980.0,984.4,hors zone,0,0 | |
| 618 | +RF2416,16,24,660.0,980.0,986.3,hors zone,0,0 | |
| 619 | +RF2417,17,24,700.0,980.0,977.4,hors zone,0,0 | |
| 620 | +RF2418,18,24,740.0,980.0,973.0,hors zone,0,0 | |
| 621 | +RF2419,19,24,780.0,980.0,979.1,hors zone,0,0 | |
| 622 | +RF2420,20,24,820.0,980.0,977.0,hors zone,0,0 | |
| 623 | +RF2421,21,24,860.0,980.0,983.2,hors zone,0,0 | |
| 624 | +RF2422,22,24,900.0,980.0,981.1,hors zone,0,0 | |
| 625 | +RF2423,23,24,940.0,980.0,972.4,hors zone,0,0 | |
| 626 | +RF2424,24,24,980.0,980.0,976.8,hors zone,0,0 | |
added
data/synthetic/ch43_riverain_ventes.csv
+765 −0
@@ -0,0 +1,765 @@ | ||
| 1 | +id_vente,id_lot,date_vente,prix_vente,sup_habitable_m2,sup_terrain_m2,annee_construction,age,qualite,garage,classe,expose,rive,dist_riviere_m,post,decote_declaree | |
| 2 | +V0165,RF0514,2016-01-08,320500,155.9,362,1993,23,3,1,faible,1,0,225.4,0,-0.015 | |
| 3 | +V0636,RF2101,2016-01-10,261500,103.6,743,1987,29,4,1,hors zone,0,0,858.3,0,0.0 | |
| 4 | +V0289,RF0917,2016-01-21,248000,143.2,436,1980,36,3,0,hors zone,0,0,383.9,0,0.0 | |
| 5 | +V0257,RF0820,2016-02-07,242500,114.2,482,1983,33,3,1,hors zone,0,0,343.1,0,0.0 | |
| 6 | +V0446,RF1410,2016-02-23,353000,199.5,390,1989,27,3,1,hors zone,0,0,581.9,0,0.0 | |
| 7 | +V0748,RF2409,2016-02-28,228500,124.9,541,1980,36,2,1,hors zone,0,0,973.3,0,0.0 | |
| 8 | +V0110,RF0315,2016-03-02,284500,125.3,496,1973,43,4,1,modérée,1,0,137.1,0,-0.04 | |
| 9 | +V0198,RF0616,2016-03-04,305000,107.8,632,1998,18,3,1,faible,1,0,262.0,0,-0.015 | |
| 10 | +V0416,RF1317,2016-03-04,307000,126.8,825,1986,30,4,0,hors zone,0,0,534.0,0,0.0 | |
| 11 | +V0466,RF1504,2016-03-06,293000,146.7,552,1993,23,2,1,hors zone,0,0,622.8,0,0.0 | |
| 12 | +V0435,RF1402,2016-03-11,245500,183.7,471,1976,40,3,1,hors zone,0,0,581.0,0,0.0 | |
| 13 | +V0373,RF1209,2016-03-18,238500,147.3,326,1975,41,2,0,hors zone,0,0,495.1,0,0.0 | |
| 14 | +V0497,RF1524,2016-03-23,496000,221.8,607,2016,0,4,1,hors zone,0,0,623.6,0,0.0 | |
| 15 | +V0240,RF0809,2016-03-31,287000,122.2,626,1976,40,4,1,hors zone,0,0,340.1,0,0.0 | |
| 16 | +V0477,RF1511,2016-04-06,291000,150.9,632,1954,62,4,1,hors zone,0,0,616.2,0,0.0 | |
| 17 | +V0293,RF0921,2016-04-16,277500,132.6,415,1975,41,3,1,hors zone,0,0,374.5,0,0.0 | |
| 18 | +V0075,RF0202,2016-04-18,200500,118.7,519,1976,40,1,0,élevée,1,1,98.0,0,-0.07 | |
| 19 | +V0282,RF0914,2016-04-18,359000,163.3,610,2016,0,4,0,hors zone,0,0,373.3,0,0.0 | |
| 20 | +V0003,RF0003,2016-04-20,251500,155.9,328,2009,7,2,0,très élevée,1,1,23.0,0,-0.1 | |
| 21 | +V0353,RF1117,2016-04-23,282500,138.5,451,1984,32,2,1,hors zone,0,0,456.8,0,0.0 | |
| 22 | +V0400,RF1302,2016-05-02,210500,129.5,660,1959,57,2,0,hors zone,0,0,543.8,0,0.0 | |
| 23 | +V0114,RF0317,2016-05-03,222000,162.5,509,1954,62,3,1,modérée,1,0,142.3,0,-0.04 | |
| 24 | +V0715,RF2307,2016-05-04,253500,162.4,328,1980,36,2,1,hors zone,0,0,945.6,0,0.0 | |
| 25 | +V0160,RF0511,2016-05-06,269000,111.6,455,1989,27,3,1,faible,1,0,225.8,0,-0.015 | |
| 26 | +V0651,RF2109,2016-05-13,292000,126.8,691,2000,16,3,0,hors zone,0,0,864.4,0,0.0 | |
| 27 | +V0159,RF0510,2016-05-20,281000,137.1,512,1976,40,4,0,faible,1,0,213.8,0,-0.015 | |
| 28 | +V0392,RF1222,2016-05-20,242500,108.8,551,1976,40,3,0,hors zone,0,0,505.5,0,0.0 | |
| 29 | +V0667,RF2121,2016-05-21,282500,103.9,695,1979,37,4,1,hors zone,0,0,853.7,0,0.0 | |
| 30 | +V0732,RF2321,2016-06-12,290000,157.7,675,1971,45,4,0,hors zone,0,0,937.3,0,0.0 | |
| 31 | +V0591,RF1905,2016-06-14,259000,131.9,602,1975,41,2,1,hors zone,0,0,778.2,0,0.0 | |
| 32 | +V0733,RF2322,2016-06-17,310000,131.2,603,2000,16,3,0,hors zone,0,0,939.5,0,0.0 | |
| 33 | +V0673,RF2203,2016-06-20,331000,139.4,545,2000,16,3,1,hors zone,0,0,905.1,0,0.0 | |
| 34 | +V0032,RF0103,2016-06-22,198500,115.7,690,1944,72,2,0,élevée,1,1,64.9,0,-0.07 | |
| 35 | +V0524,RF1622,2016-07-03,246500,103.9,663,2001,15,3,0,hors zone,0,0,664.8,0,0.0 | |
| 36 | +V0139,RF0420,2016-07-07,249000,120.5,640,1978,38,3,1,modérée,1,0,174.7,0,-0.04 | |
| 37 | +V0015,RF0014,2016-07-11,258500,139.5,533,1992,24,1,0,très élevée,1,1,14.2,0,-0.1 | |
| 38 | +V0507,RF1608,2016-07-11,291000,134.8,343,1986,30,3,0,hors zone,0,0,660.3,0,0.0 | |
| 39 | +V0417,RF1317,2016-07-15,327000,126.8,825,1986,30,4,0,hors zone,0,0,534.0,0,0.0 | |
| 40 | +V0258,RF0821,2016-07-27,263500,183.6,560,1956,60,2,0,hors zone,0,0,343.8,0,0.0 | |
| 41 | +V0265,RF0904,2016-07-29,274000,151.8,557,1970,46,3,1,hors zone,0,0,378.2,0,0.0 | |
| 42 | +V0306,RF1003,2016-07-29,295000,122.9,873,2001,15,3,1,hors zone,0,0,413.8,0,0.0 | |
| 43 | +V0694,RF2222,2016-07-29,270500,138.5,439,1995,21,3,1,hors zone,0,0,903.8,0,0.0 | |
| 44 | +V0074,RF0202,2016-08-02,215000,118.7,519,1976,40,1,0,élevée,1,1,98.0,0,-0.07 | |
| 45 | +V0623,RF2015,2016-08-04,274000,122.7,688,1995,21,3,0,hors zone,0,0,821.4,0,0.0 | |
| 46 | +V0393,RF1223,2016-08-05,298500,159.0,529,1974,42,4,1,hors zone,0,0,497.6,0,0.0 | |
| 47 | +V0688,RF2218,2016-08-09,250500,121.7,682,1993,23,2,0,hors zone,0,0,896.7,0,0.0 | |
| 48 | +V0712,RF2305,2016-08-13,214500,115.6,452,1974,42,2,1,hors zone,0,0,935.0,0,0.0 | |
| 49 | +V0224,RF0716,2016-08-14,191500,116.2,434,1959,57,2,0,hors zone,0,0,306.5,0,0.0 | |
| 50 | +V0467,RF1504,2016-08-21,262000,146.7,552,1993,23,2,1,hors zone,0,0,622.8,0,0.0 | |
| 51 | +V0403,RF1303,2016-08-24,216000,102.3,763,1966,50,2,0,hors zone,0,0,544.7,0,0.0 | |
| 52 | +V0528,RF1701,2016-09-01,331000,171.3,618,1975,41,4,1,hors zone,0,0,701.9,0,0.0 | |
| 53 | +V0753,RF2414,2016-09-01,353000,151.0,599,1995,21,4,1,hors zone,0,0,979.1,0,0.0 | |
| 54 | +V0199,RF0616,2016-09-21,275500,107.8,632,1998,18,3,1,faible,1,0,262.0,0,-0.015 | |
| 55 | +V0126,RF0409,2016-09-25,311500,181.7,419,1986,30,3,0,modérée,1,0,179.8,0,-0.04 | |
| 56 | +V0378,RF1212,2016-09-25,252000,112.9,630,1970,46,2,0,hors zone,0,0,493.7,0,0.0 | |
| 57 | +V0686,RF2214,2016-09-29,239000,125.6,641,1950,66,3,0,hors zone,0,0,900.0,0,0.0 | |
| 58 | +V0099,RF0303,2016-10-01,441500,228.6,381,2016,0,3,1,modérée,1,0,135.1,0,-0.04 | |
| 59 | +V0141,RF0423,2016-10-06,268000,99.4,666,1985,31,3,1,modérée,1,0,178.8,0,-0.04 | |
| 60 | +V0211,RF0706,2016-10-07,346000,185.3,514,1957,59,3,1,hors zone,0,0,308.0,0,0.0 | |
| 61 | +V0744,RF2409,2016-10-07,273000,124.9,541,1980,36,2,1,hors zone,0,0,973.3,0,0.0 | |
| 62 | +V0163,RF0514,2016-10-22,302000,155.9,362,1993,23,3,1,faible,1,0,225.4,0,-0.015 | |
| 63 | +V0494,RF1523,2016-10-28,331500,208.6,379,1966,50,3,1,hors zone,0,0,626.0,0,0.0 | |
| 64 | +V0731,RF2320,2016-11-01,235000,147.9,456,1971,45,2,1,hors zone,0,0,936.1,0,0.0 | |
| 65 | +V0020,RF0018,2016-11-05,268500,132.4,581,1981,35,3,1,très élevée,1,1,14.6,0,-0.1 | |
| 66 | +V0619,RF2012,2016-11-17,327500,203.0,713,1995,21,3,0,hors zone,0,0,827.6,0,0.0 | |
| 67 | +V0620,RF2013,2016-11-23,269000,115.6,554,1975,41,4,0,hors zone,0,0,823.1,0,0.0 | |
| 68 | +V0027,RF0101,2016-11-25,278500,145.0,552,1983,33,3,0,élevée,1,1,58.7,0,-0.07 | |
| 69 | +V0469,RF1506,2016-12-10,310500,123.5,590,2012,4,3,0,hors zone,0,0,613.1,0,0.0 | |
| 70 | +V0746,RF2409,2016-12-13,271000,124.9,541,1980,36,2,1,hors zone,0,0,973.3,0,0.0 | |
| 71 | +V0049,RF0114,2016-12-14,249000,96.2,675,1994,22,4,0,élevée,1,1,57.6,0,-0.07 | |
| 72 | +V0360,RF1122,2016-12-15,222000,108.5,573,1946,70,2,1,hors zone,0,0,452.6,0,0.0 | |
| 73 | +V0685,RF2214,2016-12-21,232500,125.6,641,1950,66,3,0,hors zone,0,0,900.0,0,0.0 | |
| 74 | +V0628,RF2020,2016-12-23,365000,160.8,732,1971,45,4,1,hors zone,0,0,812.5,0,0.0 | |
| 75 | +V0598,RF1912,2016-12-24,320000,148.7,667,1971,45,4,0,hors zone,0,0,773.0,0,0.0 | |
| 76 | +V0094,RF0220,2016-12-28,388000,218.2,493,1978,38,3,1,élevée,1,1,107.8,0,-0.07 | |
| 77 | +V0579,RF1823,2017-01-08,217500,113.2,503,1967,50,2,0,hors zone,0,0,733.5,0,0.0 | |
| 78 | +V0422,RF1320,2017-01-21,228000,125.2,300,1977,40,3,0,hors zone,0,0,541.6,0,0.0 | |
| 79 | +V0440,RF1404,2017-02-05,322000,152.4,561,1978,39,3,0,hors zone,0,0,579.0,0,0.0 | |
| 80 | +V0254,RF0817,2017-02-13,314500,117.2,573,2015,2,2,1,hors zone,0,0,347.6,0,0.0 | |
| 81 | +V0585,RF1902,2017-02-16,287000,113.0,466,1975,42,4,1,hors zone,0,0,780.1,0,0.0 | |
| 82 | +V0145,RF0500,2017-03-03,237500,129.9,408,1965,52,3,1,faible,1,0,221.3,0,-0.015 | |
| 83 | +V0090,RF0219,2017-03-21,252500,138.0,570,1964,53,3,1,élevée,1,1,100.2,0,-0.07 | |
| 84 | +V0111,RF0315,2017-03-22,255500,125.3,496,1973,44,4,1,modérée,1,0,137.1,0,-0.04 | |
| 85 | +V0707,RF2302,2017-03-23,381000,197.8,374,2000,17,3,1,hors zone,0,0,943.5,0,0.0 | |
| 86 | +V0010,RF0009,2017-04-04,237000,100.2,628,2014,3,2,0,très élevée,1,1,23.4,0,-0.1 | |
| 87 | +V0297,RF0922,2017-04-04,266000,126.9,629,1961,56,4,0,hors zone,0,0,375.8,0,0.0 | |
| 88 | +V0406,RF1305,2017-04-15,253500,96.7,732,2000,17,3,0,hors zone,0,0,540.4,0,0.0 | |
| 89 | +V0348,RF1115,2017-04-17,343500,149.1,593,1966,51,3,1,hors zone,0,0,459.8,0,0.0 | |
| 90 | +V0328,RF1020,2017-04-18,301500,114.3,492,2016,1,3,1,hors zone,0,0,422.0,0,0.0 | |
| 91 | +V0073,RF0201,2017-05-03,282500,109.4,699,1994,23,4,1,élevée,1,1,107.2,0,-0.07 | |
| 92 | +V0091,RF0219,2017-05-05,284500,138.0,570,1964,53,3,1,élevée,1,1,100.2,0,-0.07 | |
| 93 | +V0358,RF1120,2017-05-05,331000,158.0,714,1998,19,3,0,hors zone,0,0,466.2,0,0.0 | |
| 94 | +V0454,RF1419,2017-05-11,366000,201.4,563,1983,34,3,0,hors zone,0,0,576.0,0,0.0 | |
| 95 | +V0186,RF0601,2017-05-19,162500,89.3,495,1959,58,3,0,faible,1,0,259.6,0,-0.015 | |
| 96 | +V0167,RF0515,2017-05-21,342500,143.7,811,2003,14,2,0,faible,1,0,216.5,0,-0.015 | |
| 97 | +V0286,RF0915,2017-05-28,321500,146.7,581,1989,28,4,0,hors zone,0,0,387.3,0,0.0 | |
| 98 | +V0230,RF0724,2017-05-30,397000,201.5,662,1987,30,2,0,hors zone,0,0,293.8,0,0.0 | |
| 99 | +V0142,RF0424,2017-06-08,335500,137.4,529,2006,11,5,0,modérée,1,0,185.1,0,-0.04 | |
| 100 | +V0171,RF0516,2017-06-09,261500,122.2,464,1994,23,2,0,faible,1,0,214.0,0,-0.015 | |
| 101 | +V0295,RF0921,2017-06-09,267500,132.6,415,1975,42,3,1,hors zone,0,0,374.5,0,0.0 | |
| 102 | +V0550,RF1723,2017-06-09,244500,100.2,682,1968,49,3,1,hors zone,0,0,694.2,0,0.0 | |
| 103 | +V0443,RF1406,2017-06-13,343000,178.3,607,1976,41,4,0,hors zone,0,0,579.5,0,0.0 | |
| 104 | +V0290,RF0917,2017-06-17,284000,143.2,436,1980,37,3,0,hors zone,0,0,383.9,0,0.0 | |
| 105 | +V0496,RF1523,2017-06-20,359500,208.6,379,1966,51,3,1,hors zone,0,0,626.0,0,0.0 | |
| 106 | +V0272,RF0909,2017-06-21,208000,97.3,489,1993,24,2,0,hors zone,0,0,382.2,0,0.0 | |
| 107 | +V0389,RF1221,2017-06-21,412000,196.2,754,2007,10,3,1,hors zone,0,0,504.1,0,0.0 | |
| 108 | +V0734,RF2323,2017-06-26,310500,136.8,300,1994,23,3,0,hors zone,0,0,944.2,0,0.0 | |
| 109 | +V0616,RF2008,2017-06-27,301000,178.1,514,1984,33,2,0,hors zone,0,0,817.1,0,0.0 | |
| 110 | +V0170,RF0515,2017-06-28,315500,143.7,811,2003,14,2,0,faible,1,0,216.5,0,-0.015 | |
| 111 | +V0380,RF1215,2017-06-28,297500,151.8,566,1987,30,2,1,hors zone,0,0,503.1,0,0.0 | |
| 112 | +V0149,RF0503,2017-07-04,311000,135.9,516,1990,27,3,0,faible,1,0,223.1,0,-0.015 | |
| 113 | +V0461,RF1500,2017-07-14,291000,154.7,631,1985,32,2,1,hors zone,0,0,622.3,0,0.0 | |
| 114 | +V0601,RF1917,2017-07-20,353000,223.3,680,1950,67,4,1,hors zone,0,0,785.2,0,0.0 | |
| 115 | +V0122,RF0405,2017-07-21,328500,127.2,578,2016,1,2,1,modérée,1,0,186.4,0,-0.04 | |
| 116 | +V0180,RF0524,2017-07-26,268500,116.8,613,1963,54,4,0,faible,1,0,224.7,0,-0.015 | |
| 117 | +V0520,RF1619,2017-07-30,307000,116.7,857,1991,26,3,1,hors zone,0,0,664.0,0,0.0 | |
| 118 | +V0676,RF2205,2017-07-30,331500,159.4,467,1985,32,4,0,hors zone,0,0,898.0,0,0.0 | |
| 119 | +V0244,RF0811,2017-08-02,264000,118.1,345,2012,5,2,0,hors zone,0,0,345.5,0,0.0 | |
| 120 | +V0594,RF1908,2017-08-03,279000,148.8,662,1956,61,2,1,hors zone,0,0,779.2,0,0.0 | |
| 121 | +V0571,RF1814,2017-08-10,259500,148.8,448,1966,51,3,0,hors zone,0,0,746.5,0,0.0 | |
| 122 | +V0451,RF1414,2017-08-13,217500,113.3,539,1983,34,1,0,hors zone,0,0,576.2,0,0.0 | |
| 123 | +V0610,RF2001,2017-08-15,339500,175.3,511,1973,44,4,1,hors zone,0,0,817.8,0,0.0 | |
| 124 | +V0547,RF1719,2017-08-20,250000,177.4,331,1985,32,2,0,hors zone,0,0,706.9,0,0.0 | |
| 125 | +V0522,RF1621,2017-08-26,328500,150.4,489,1995,22,2,0,hors zone,0,0,658.6,0,0.0 | |
| 126 | +V0336,RF1101,2017-08-27,367500,154.5,657,2003,14,4,0,hors zone,0,0,459.4,0,0.0 | |
| 127 | +V0098,RF0300,2017-09-02,263000,112.9,347,2013,4,3,1,modérée,1,0,144.3,0,-0.04 | |
| 128 | +V0516,RF1615,2017-09-06,303000,144.1,451,1975,42,2,1,hors zone,0,0,657.7,0,0.0 | |
| 129 | +V0475,RF1509,2017-09-15,332500,128.0,708,1993,24,4,0,hors zone,0,0,617.5,0,0.0 | |
| 130 | +V0695,RF2222,2017-09-23,300500,138.5,439,1995,22,3,1,hors zone,0,0,903.8,0,0.0 | |
| 131 | +V0217,RF0707,2017-09-26,342500,140.4,458,1997,20,3,1,hors zone,0,0,300.7,0,0.0 | |
| 132 | +V0218,RF0708,2017-10-10,236000,116.9,395,1957,60,2,1,hors zone,0,0,303.2,0,0.0 | |
| 133 | +V0600,RF1917,2017-10-10,398500,223.3,680,1950,67,4,1,hors zone,0,0,785.2,0,0.0 | |
| 134 | +V0001,RF0000,2017-10-19,298500,181.1,558,1977,40,2,1,très élevée,1,1,17.7,0,-0.1 | |
| 135 | +V0043,RF0108,2017-10-19,348000,140.3,534,1995,22,5,0,élevée,1,1,62.1,0,-0.07 | |
| 136 | +V0514,RF1614,2017-10-25,364000,192.0,362,1965,52,4,1,hors zone,0,0,658.6,0,0.0 | |
| 137 | +V0004,RF0004,2017-11-12,251500,192.0,355,1968,49,1,0,très élevée,1,1,26.2,0,-0.1 | |
| 138 | +V0207,RF0624,2017-12-10,358500,192.8,682,1984,33,3,1,faible,1,0,255.1,0,-0.015 | |
| 139 | +V0355,RF1118,2017-12-11,322000,116.1,515,2000,17,4,1,hors zone,0,0,458.8,0,0.0 | |
| 140 | +V0624,RF2015,2017-12-13,274500,122.7,688,1995,22,3,0,hors zone,0,0,821.4,0,0.0 | |
| 141 | +V0287,RF0916,2017-12-18,297000,114.2,621,2016,1,3,1,hors zone,0,0,377.9,0,0.0 | |
| 142 | +V0344,RF1110,2017-12-21,428000,200.2,619,2008,9,4,1,hors zone,0,0,462.8,0,0.0 | |
| 143 | +V0692,RF2220,2017-12-21,316000,130.1,670,1976,41,4,1,hors zone,0,0,895.1,0,0.0 | |
| 144 | +V0567,RF1811,2017-12-24,308500,119.7,780,1986,31,4,0,hors zone,0,0,733.4,0,0.0 | |
| 145 | +V0502,RF1605,2017-12-26,241500,153.3,396,1969,48,1,1,hors zone,0,0,653.1,0,0.0 | |
| 146 | +V0298,RF0922,2017-12-28,263000,126.9,629,1961,56,4,0,hors zone,0,0,375.8,0,0.0 | |
| 147 | +V0530,RF1705,2017-12-30,381500,139.8,504,2016,1,4,1,hors zone,0,0,696.4,0,0.0 | |
| 148 | +V0718,RF2311,2018-01-04,240000,109.1,629,1982,36,2,1,hors zone,0,0,942.6,0,0.0 | |
| 149 | +V0493,RF1523,2018-01-10,300500,208.6,379,1966,52,3,1,hors zone,0,0,626.0,0,0.0 | |
| 150 | +V0284,RF0914,2018-01-13,386500,163.3,610,2016,2,4,0,hors zone,0,0,373.3,0,0.0 | |
| 151 | +V0135,RF0416,2018-01-17,231500,121.5,448,1998,20,3,0,modérée,1,0,174.9,0,-0.04 | |
| 152 | +V0374,RF1210,2018-01-19,231500,100.1,630,1981,37,4,0,hors zone,0,0,497.3,0,0.0 | |
| 153 | +V0500,RF1604,2018-01-19,289000,197.8,562,1943,75,3,1,hors zone,0,0,655.4,0,0.0 | |
| 154 | +V0457,RF1421,2018-01-22,343500,192.0,532,1965,53,3,1,hors zone,0,0,581.1,0,0.0 | |
| 155 | +V0605,RF1920,2018-02-05,346500,180.4,707,1979,39,3,0,hors zone,0,0,782.2,0,0.0 | |
| 156 | +V0197,RF0615,2018-02-06,209000,114.8,471,1983,35,2,0,faible,1,0,262.1,0,-0.015 | |
| 157 | +V0409,RF1310,2018-02-14,286500,120.6,739,1980,38,3,0,hors zone,0,0,532.2,0,0.0 | |
| 158 | +V0319,RF1011,2018-02-18,339500,141.7,531,1993,25,3,1,hors zone,0,0,424.6,0,0.0 | |
| 159 | +V0678,RF2209,2018-02-20,302000,157.6,611,1974,44,2,0,hors zone,0,0,900.0,0,0.0 | |
| 160 | +V0376,RF1211,2018-02-21,279500,130.3,403,1994,24,3,1,hors zone,0,0,495.0,0,0.0 | |
| 161 | +V0592,RF1906,2018-02-25,355000,118.2,683,1996,22,3,1,hors zone,0,0,783.4,0,0.0 | |
| 162 | +V0604,RF1919,2018-03-04,273000,130.7,469,1968,50,3,0,hors zone,0,0,774.8,0,0.0 | |
| 163 | +V0459,RF1421,2018-03-06,367000,192.0,532,1965,53,3,1,hors zone,0,0,581.1,0,0.0 | |
| 164 | +V0251,RF0816,2018-03-26,243000,101.5,722,1956,62,3,1,hors zone,0,0,335.8,0,0.0 | |
| 165 | +V0512,RF1611,2018-04-03,321000,180.0,536,1985,33,3,0,hors zone,0,0,660.0,0,0.0 | |
| 166 | +V0178,RF0522,2018-04-08,268000,108.7,414,2006,12,3,0,faible,1,0,217.2,0,-0.015 | |
| 167 | +V0216,RF0707,2018-04-17,315000,140.4,458,1997,21,3,1,hors zone,0,0,300.7,0,0.0 | |
| 168 | +V0666,RF2121,2018-04-24,283000,103.9,695,1979,39,4,1,hors zone,0,0,853.7,0,0.0 | |
| 169 | +V0490,RF1518,2018-05-06,396500,201.9,730,1982,36,4,1,hors zone,0,0,620.4,0,0.0 | |
| 170 | +V0065,RF0123,2018-05-14,319500,170.7,664,1971,47,3,0,élevée,1,1,65.5,0,-0.07 | |
| 171 | +V0521,RF1621,2018-05-21,317500,150.4,489,1995,23,2,0,hors zone,0,0,658.6,0,0.0 | |
| 172 | +V0408,RF1307,2018-05-29,251000,110.2,657,1975,43,3,1,hors zone,0,0,541.4,0,0.0 | |
| 173 | +V0132,RF0413,2018-06-10,258500,148.7,378,1930,88,2,1,modérée,1,0,177.2,0,-0.04 | |
| 174 | +V0478,RF1511,2018-06-12,341000,150.9,632,1954,64,4,1,hors zone,0,0,616.2,0,0.0 | |
| 175 | +V0372,RF1208,2018-06-26,274000,108.5,636,1992,26,3,1,hors zone,0,0,507.2,0,0.0 | |
| 176 | +V0559,RF1804,2018-06-27,353000,123.7,594,2000,18,4,1,hors zone,0,0,740.5,0,0.0 | |
| 177 | +V0583,RF1901,2018-06-27,290500,127.0,504,1983,35,3,1,hors zone,0,0,774.4,0,0.0 | |
| 178 | +V0201,RF0618,2018-06-30,269000,154.8,429,1946,72,2,0,faible,1,0,260.2,0,-0.015 | |
| 179 | +V0383,RF1217,2018-06-30,261000,123.9,442,1985,33,3,0,hors zone,0,0,502.2,0,0.0 | |
| 180 | +V0511,RF1611,2018-07-03,378000,180.0,536,1985,33,3,0,hors zone,0,0,660.0,0,0.0 | |
| 181 | +V0370,RF1207,2018-07-04,262500,124.4,778,1977,41,3,0,hors zone,0,0,503.5,0,0.0 | |
| 182 | +V0615,RF2007,2018-07-08,277000,132.9,540,1968,50,3,1,hors zone,0,0,819.3,0,0.0 | |
| 183 | +V0005,RF0006,2018-07-11,305500,141.7,424,1987,31,3,0,très élevée,1,1,19.1,0,-0.1 | |
| 184 | +V0130,RF0413,2018-07-14,197000,148.7,378,1930,88,2,1,modérée,1,0,177.2,0,-0.04 | |
| 185 | +V0206,RF0621,2018-07-17,343500,147.7,644,1991,27,4,0,faible,1,0,264.1,0,-0.015 | |
| 186 | +V0680,RF2211,2018-07-20,243000,92.6,654,1969,49,2,1,hors zone,0,0,905.1,0,0.0 | |
| 187 | +V0243,RF0811,2018-08-08,272500,118.1,345,2012,6,2,0,hors zone,0,0,345.5,0,0.0 | |
| 188 | +V0093,RF0220,2018-08-09,369500,218.2,493,1978,40,3,1,élevée,1,1,107.8,0,-0.07 | |
| 189 | +V0492,RF1521,2018-08-16,234500,127.7,569,1952,66,3,0,hors zone,0,0,617.9,0,0.0 | |
| 190 | +V0398,RF1300,2018-08-19,321500,140.4,468,2000,18,3,0,hors zone,0,0,541.1,0,0.0 | |
| 191 | +V0158,RF0510,2018-08-24,309500,137.1,512,1976,42,4,0,faible,1,0,213.8,0,-0.015 | |
| 192 | +V0740,RF2403,2018-09-02,286000,162.2,617,1943,75,3,0,hors zone,0,0,985.4,0,0.0 | |
| 193 | +V0464,RF1500,2018-09-05,309000,154.7,631,1985,33,2,1,hors zone,0,0,622.3,0,0.0 | |
| 194 | +V0756,RF2416,2018-09-15,330500,132.9,394,1974,44,5,1,hors zone,0,0,986.3,0,0.0 | |
| 195 | +V0219,RF0709,2018-09-22,316500,166.3,558,1976,42,2,1,hors zone,0,0,305.3,0,0.0 | |
| 196 | +V0671,RF2201,2018-09-23,288000,125.5,597,1989,29,3,0,hors zone,0,0,893.0,0,0.0 | |
| 197 | +V0146,RF0501,2018-10-01,318500,132.6,472,2016,2,1,0,faible,1,0,222.2,0,-0.015 | |
| 198 | +V0245,RF0812,2018-10-11,345000,127.6,790,2003,15,3,0,hors zone,0,0,333.4,0,0.0 | |
| 199 | +V0629,RF2020,2018-10-11,352000,160.8,732,1971,47,4,1,hors zone,0,0,812.5,0,0.0 | |
| 200 | +V0310,RF1005,2018-10-20,253000,151.3,449,1965,53,2,1,hors zone,0,0,424.3,0,0.0 | |
| 201 | +V0471,RF1506,2018-10-24,363500,123.5,590,2012,6,3,0,hors zone,0,0,613.1,0,0.0 | |
| 202 | +V0659,RF2116,2018-10-26,326500,169.2,485,1984,34,3,1,hors zone,0,0,862.2,0,0.0 | |
| 203 | +V0177,RF0521,2018-10-30,441500,177.2,628,1986,32,5,1,faible,1,0,217.6,0,-0.015 | |
| 204 | +V0011,RF0009,2018-11-04,280000,100.2,628,2014,4,2,0,très élevée,1,1,23.4,0,-0.1 | |
| 205 | +V0719,RF2313,2018-11-11,298000,109.1,634,1981,37,3,1,hors zone,0,0,946.6,0,0.0 | |
| 206 | +V0222,RF0714,2018-11-15,331500,227.2,473,1970,48,3,0,hors zone,0,0,301.9,0,0.0 | |
| 207 | +V0352,RF1117,2018-11-16,277000,138.5,451,1984,34,2,1,hors zone,0,0,456.8,0,0.0 | |
| 208 | +V0129,RF0412,2018-11-17,219000,95.1,582,1983,35,3,0,modérée,1,0,179.6,0,-0.04 | |
| 209 | +V0638,RF2101,2018-11-21,310500,103.6,743,1987,31,4,1,hors zone,0,0,858.3,0,0.0 | |
| 210 | +V0316,RF1009,2018-11-28,307500,147.6,711,2002,16,2,0,hors zone,0,0,420.6,0,0.0 | |
| 211 | +V0698,RF2222,2018-11-28,312000,138.5,439,1995,23,3,1,hors zone,0,0,903.8,0,0.0 | |
| 212 | +V0473,RF1507,2018-11-29,319500,122.6,597,1981,37,3,1,hors zone,0,0,615.8,0,0.0 | |
| 213 | +V0196,RF0615,2018-12-04,261000,114.8,471,1983,35,2,0,faible,1,0,262.1,0,-0.015 | |
| 214 | +V0179,RF0523,2018-12-07,312000,143.2,490,2007,11,2,1,faible,1,0,213.1,0,-0.015 | |
| 215 | +V0541,RF1710,2018-12-07,322500,124.1,506,2008,10,3,1,hors zone,0,0,693.9,0,0.0 | |
| 216 | +V0102,RF0307,2018-12-11,336000,165.1,420,2002,16,4,0,modérée,1,0,146.7,0,-0.04 | |
| 217 | +V0711,RF2304,2018-12-12,318500,200.2,634,1970,48,2,0,hors zone,0,0,936.0,0,0.0 | |
| 218 | +V0728,RF2319,2018-12-12,402500,150.3,693,2006,12,4,0,hors zone,0,0,941.8,0,0.0 | |
| 219 | +V0200,RF0618,2018-12-27,246000,154.8,429,1946,72,2,0,faible,1,0,260.2,0,-0.015 | |
| 220 | +V0570,RF1813,2018-12-28,332500,154.8,576,1971,47,3,1,hors zone,0,0,742.2,0,0.0 | |
| 221 | +V0513,RF1612,2019-01-04,246500,104.1,586,1992,27,2,0,hors zone,0,0,659.9,0,0.0 | |
| 222 | +V0189,RF0605,2019-01-05,249000,106.5,617,1985,34,3,1,faible,1,0,255.2,0,-0.015 | |
| 223 | +V0329,RF1021,2019-01-09,308500,132.2,635,1986,33,3,1,hors zone,0,0,420.8,0,0.0 | |
| 224 | +V0051,RF0114,2019-01-11,277500,96.2,675,1994,25,4,0,élevée,1,1,57.6,0,-0.07 | |
| 225 | +V0381,RF1215,2019-01-20,312000,151.8,566,1987,32,2,1,hors zone,0,0,503.1,0,0.0 | |
| 226 | +V0097,RF0223,2019-01-27,281500,106.6,618,1999,20,3,1,élevée,1,1,94.2,0,-0.07 | |
| 227 | +V0563,RF1808,2019-01-28,220000,108.1,530,1960,59,3,0,hors zone,0,0,739.5,0,0.0 | |
| 228 | +V0627,RF2017,2019-02-06,332500,120.2,583,1991,28,3,1,hors zone,0,0,824.1,0,0.0 | |
| 229 | +V0115,RF0318,2019-02-07,358000,190.6,484,2000,19,3,0,modérée,1,0,147.6,0,-0.04 | |
| 230 | +V0745,RF2409,2019-02-24,270500,124.9,541,1980,39,2,1,hors zone,0,0,973.3,0,0.0 | |
| 231 | +V0565,RF1809,2019-03-09,300000,94.4,520,1991,28,4,1,hors zone,0,0,733.1,0,0.0 | |
| 232 | +V0256,RF0817,2019-03-16,347000,117.2,573,2015,4,2,1,hors zone,0,0,347.6,0,0.0 | |
| 233 | +V0315,RF1008,2019-03-18,294000,112.8,382,1987,32,4,1,hors zone,0,0,412.4,0,0.0 | |
| 234 | +V0649,RF2109,2019-03-20,365500,126.8,691,2000,19,3,0,hors zone,0,0,864.4,0,0.0 | |
| 235 | +V0441,RF1405,2019-03-26,343500,136.5,706,1971,48,4,1,hors zone,0,0,582.4,0,0.0 | |
| 236 | +V0542,RF1717,2019-03-29,311500,101.7,570,1994,25,3,1,hors zone,0,0,695.4,0,0.0 | |
| 237 | +V0665,RF2121,2019-03-31,293000,103.9,695,1979,40,4,1,hors zone,0,0,853.7,0,0.0 | |
| 238 | +V0632,RF2022,2019-04-05,259000,114.3,712,1985,34,3,0,hors zone,0,0,822.8,0,0.0 | |
| 239 | +V0575,RF1821,2019-04-17,334000,154.3,439,1996,23,3,0,hors zone,0,0,743.6,0,0.0 | |
| 240 | +V0007,RF0007,2019-04-28,238000,138.1,560,1962,57,3,0,très élevée,1,1,23.0,0,-0.1 | |
| 241 | +V0026,RF0023,2019-05-02,233500,116.2,730,1973,46,3,1,très élevée,1,1,12.7,1,-0.15 | |
| 242 | +V0742,RF2406,2019-05-05,253500,136.5,657,1977,42,2,0,hors zone,0,0,975.6,1,0.0 | |
| 243 | +V0437,RF1402,2019-05-09,348000,183.7,471,1976,43,3,1,hors zone,0,0,581.0,1,0.0 | |
| 244 | +V0232,RF0800,2019-05-12,359000,173.8,735,1989,30,2,0,hors zone,0,0,342.9,1,0.0 | |
| 245 | +V0710,RF2304,2019-05-12,308500,200.2,634,1970,49,2,0,hors zone,0,0,936.0,1,0.0 | |
| 246 | +V0252,RF0816,2019-05-17,241500,101.5,722,1956,63,3,1,hors zone,0,0,335.8,1,0.0 | |
| 247 | +V0709,RF2304,2019-05-17,320000,200.2,634,1970,49,2,0,hors zone,0,0,936.0,1,0.0 | |
| 248 | +V0123,RF0406,2019-05-23,302000,153.1,505,1974,45,3,1,modérée,1,0,184.4,1,-0.06 | |
| 249 | +V0275,RF0910,2019-05-23,304000,148.4,616,1978,41,3,0,hors zone,0,0,384.0,1,0.0 | |
| 250 | +V0668,RF2122,2019-05-28,290500,123.6,501,1978,41,2,1,hors zone,0,0,857.5,1,0.0 | |
| 251 | +V0161,RF0511,2019-05-31,275000,111.6,455,1989,30,3,1,faible,1,0,225.8,1,-0.0225 | |
| 252 | +V0621,RF2014,2019-06-10,329000,165.7,535,1977,42,3,0,hors zone,0,0,820.5,1,0.0 | |
| 253 | +V0184,RF0601,2019-06-16,197500,89.3,495,1959,60,3,0,faible,1,0,259.6,1,-0.0225 | |
| 254 | +V0081,RF0205,2019-06-26,267500,134.2,713,1946,73,3,1,élevée,1,1,96.3,1,-0.105 | |
| 255 | +V0689,RF2218,2019-06-30,283500,121.7,682,1993,26,2,0,hors zone,0,0,896.7,1,0.0 | |
| 256 | +V0033,RF0103,2019-07-01,221000,115.7,690,1944,75,2,0,élevée,1,1,64.9,1,-0.105 | |
| 257 | +V0363,RF1124,2019-07-04,294000,148.7,590,1995,24,3,0,hors zone,0,0,452.7,1,0.0 | |
| 258 | +V0449,RF1413,2019-07-08,393500,174.9,404,2002,17,3,1,hors zone,0,0,580.9,1,0.0 | |
| 259 | +V0283,RF0914,2019-07-15,462500,163.3,610,2016,3,4,0,hors zone,0,0,373.3,1,0.0 | |
| 260 | +V0193,RF0614,2019-07-16,257000,158.8,489,1946,73,4,0,faible,1,0,256.6,1,-0.0225 | |
| 261 | +V0482,RF1514,2019-07-17,349500,144.3,648,2003,16,2,0,hors zone,0,0,621.3,1,0.0 | |
| 262 | +V0488,RF1517,2019-07-22,310000,127.7,442,2001,18,3,1,hors zone,0,0,624.1,1,0.0 | |
| 263 | +V0501,RF1605,2019-07-24,295000,153.3,396,1969,50,1,1,hors zone,0,0,653.1,1,0.0 | |
| 264 | +V0726,RF2319,2019-07-25,341000,150.3,693,2006,13,4,0,hors zone,0,0,941.8,1,0.0 | |
| 265 | +V0269,RF0906,2019-08-01,353500,157.3,835,1997,22,3,0,hors zone,0,0,385.6,1,0.0 | |
| 266 | +V0606,RF1921,2019-08-03,352500,137.8,368,1991,28,4,0,hors zone,0,0,777.5,1,0.0 | |
| 267 | +V0335,RF1101,2019-08-05,385000,154.5,657,2003,16,4,0,hors zone,0,0,459.4,1,0.0 | |
| 268 | +V0059,RF0119,2019-08-15,326000,140.5,440,1983,36,4,1,élevée,1,1,58.7,1,-0.105 | |
| 269 | +V0365,RF1201,2019-08-25,318000,165.4,662,1983,36,3,0,hors zone,0,0,499.1,1,0.0 | |
| 270 | +V0276,RF0911,2019-09-01,377000,154.3,445,1995,24,3,1,hors zone,0,0,374.7,1,0.0 | |
| 271 | +V0331,RF1022,2019-09-04,328000,119.1,565,1980,39,3,1,hors zone,0,0,415.7,1,0.0 | |
| 272 | +V0278,RF0911,2019-09-19,345500,154.3,445,1995,24,3,1,hors zone,0,0,374.7,1,0.0 | |
| 273 | +V0242,RF0810,2019-09-25,240500,133.7,629,1965,54,3,0,hors zone,0,0,334.0,1,0.0 | |
| 274 | +V0210,RF0702,2019-09-28,268500,134.0,425,1971,48,3,0,hors zone,0,0,301.8,1,0.0 | |
| 275 | +V0532,RF1707,2019-10-01,301500,117.2,688,1991,28,3,1,hors zone,0,0,697.1,1,0.0 | |
| 276 | +V0362,RF1123,2019-10-07,231500,124.1,505,1961,58,2,0,hors zone,0,0,467.5,1,0.0 | |
| 277 | +V0387,RF1219,2019-10-09,405500,197.0,659,1979,40,3,0,hors zone,0,0,503.5,1,0.0 | |
| 278 | +V0030,RF0102,2019-10-15,270000,112.5,554,1965,54,3,1,élevée,1,1,61.1,1,-0.105 | |
| 279 | +V0195,RF0615,2019-11-05,263000,114.8,471,1983,36,2,0,faible,1,0,262.1,1,-0.0225 | |
| 280 | +V0019,RF0018,2019-11-07,275000,132.4,581,1981,38,3,1,très élevée,1,1,14.6,1,-0.15 | |
| 281 | +V0056,RF0118,2019-11-12,267000,129.0,634,1985,34,4,0,élevée,1,1,63.0,1,-0.105 | |
| 282 | +V0696,RF2222,2019-11-13,313000,138.5,439,1995,24,3,1,hors zone,0,0,903.8,1,0.0 | |
| 283 | +V0296,RF0921,2019-11-27,299000,132.6,415,1975,44,3,1,hors zone,0,0,374.5,1,0.0 | |
| 284 | +V0164,RF0514,2019-12-02,345500,155.9,362,1993,26,3,1,faible,1,0,225.4,1,-0.0225 | |
| 285 | +V0239,RF0804,2019-12-02,291500,114.5,451,2005,14,2,0,hors zone,0,0,334.6,1,0.0 | |
| 286 | +V0581,RF1900,2019-12-09,312500,132.9,500,1989,30,3,0,hors zone,0,0,787.6,1,0.0 | |
| 287 | +V0077,RF0203,2019-12-16,215000,102.9,774,1974,45,2,0,élevée,1,1,93.8,1,-0.105 | |
| 288 | +V0762,RF2422,2019-12-21,327000,121.8,572,2014,5,4,0,hors zone,0,0,981.1,1,0.0 | |
| 289 | +V0016,RF0014,2020-01-02,283500,139.5,533,1992,28,1,0,très élevée,1,1,14.2,1,-0.15 | |
| 290 | +V0724,RF2315,2020-01-07,333000,199.5,502,1957,63,2,1,hors zone,0,0,942.3,1,0.0 | |
| 291 | +V0462,RF1500,2020-01-14,321000,154.7,631,1985,35,2,1,hors zone,0,0,622.3,1,0.0 | |
| 292 | +V0369,RF1206,2020-01-18,328500,156.5,628,1964,56,3,0,hors zone,0,0,500.2,1,0.0 | |
| 293 | +V0658,RF2115,2020-01-22,271000,104.1,514,1972,48,2,1,hors zone,0,0,866.5,1,0.0 | |
| 294 | +V0759,RF2420,2020-01-26,287500,109.5,698,1971,49,3,1,hors zone,0,0,977.0,1,0.0 | |
| 295 | +V0639,RF2102,2020-01-29,410500,153.0,553,2002,18,4,0,hors zone,0,0,862.1,1,0.0 | |
| 296 | +V0035,RF0105,2020-02-03,400500,163.1,598,2001,19,2,1,élevée,1,1,54.7,1,-0.105 | |
| 297 | +V0423,RF1321,2020-02-03,347500,137.1,533,2016,4,3,0,hors zone,0,0,532.0,1,0.0 | |
| 298 | +V0687,RF2215,2020-02-03,347500,102.6,505,2016,4,3,0,hors zone,0,0,896.8,1,0.0 | |
| 299 | +V0304,RF1002,2020-02-12,272500,135.0,526,1963,57,4,0,hors zone,0,0,417.1,1,0.0 | |
| 300 | +V0429,RF1400,2020-02-15,326500,103.5,650,2006,14,4,0,hors zone,0,0,576.4,1,0.0 | |
| 301 | +V0657,RF2115,2020-02-16,243500,104.1,514,1972,48,2,1,hors zone,0,0,866.5,1,0.0 | |
| 302 | +V0192,RF0612,2020-03-01,286500,121.4,656,1997,23,1,0,faible,1,0,260.7,1,-0.0225 | |
| 303 | +V0741,RF2405,2020-03-02,361500,122.8,531,1999,21,3,1,hors zone,0,0,976.9,1,0.0 | |
| 304 | +V0220,RF0710,2020-03-07,385000,142.2,848,2001,19,3,1,hors zone,0,0,303.3,1,0.0 | |
| 305 | +V0611,RF2003,2020-03-10,426000,236.2,554,1999,21,2,0,hors zone,0,0,815.1,1,0.0 | |
| 306 | +V0303,RF1000,2020-03-19,295000,124.3,447,1988,32,3,1,hors zone,0,0,417.4,1,0.0 | |
| 307 | +V0693,RF2220,2020-03-20,296500,130.1,670,1976,44,4,1,hors zone,0,0,895.1,1,0.0 | |
| 308 | +V0379,RF1215,2020-03-30,362500,151.8,566,1987,33,2,1,hors zone,0,0,503.1,1,0.0 | |
| 309 | +V0472,RF1506,2020-04-11,366500,123.5,590,2012,8,3,0,hors zone,0,0,613.1,1,0.0 | |
| 310 | +V0577,RF1822,2020-04-11,335500,144.8,529,1964,56,4,0,hors zone,0,0,739.9,1,0.0 | |
| 311 | +V0061,RF0121,2020-04-15,245500,160.4,380,1981,39,2,0,élevée,1,1,67.3,1,-0.105 | |
| 312 | +V0529,RF1704,2020-04-15,258000,111.6,327,1977,43,3,0,hors zone,0,0,694.2,1,0.0 | |
| 313 | +V0578,RF1822,2020-04-17,310500,144.8,529,1964,56,4,0,hors zone,0,0,739.9,1,0.0 | |
| 314 | +V0138,RF0420,2020-04-25,252500,120.5,640,1978,42,3,1,modérée,1,0,174.7,1,-0.06 | |
| 315 | +V0483,RF1514,2020-04-25,322500,144.3,648,2003,17,2,0,hors zone,0,0,621.3,1,0.0 | |
| 316 | +V0255,RF0817,2020-04-28,312000,117.2,573,2015,5,2,1,hors zone,0,0,347.6,1,0.0 | |
| 317 | +V0082,RF0205,2020-05-03,254500,134.2,713,1946,74,3,1,élevée,1,1,96.3,1,-0.105 | |
| 318 | +V0053,RF0116,2020-05-04,292500,103.4,479,1976,44,4,1,élevée,1,1,62.6,1,-0.105 | |
| 319 | +V0439,RF1404,2020-05-04,348000,152.4,561,1978,42,3,0,hors zone,0,0,579.0,1,0.0 | |
| 320 | +V0444,RF1407,2020-05-08,391500,194.9,300,1989,31,3,0,hors zone,0,0,580.3,1,0.0 | |
| 321 | +V0484,RF1514,2020-05-10,394500,144.3,648,2003,17,2,0,hors zone,0,0,621.3,1,0.0 | |
| 322 | +V0181,RF0600,2020-05-20,420500,243.0,588,1978,42,3,0,faible,1,0,258.0,1,-0.0225 | |
| 323 | +V0264,RF0903,2020-05-20,305500,111.0,637,1994,26,3,1,hors zone,0,0,382.0,1,0.0 | |
| 324 | +V0404,RF1304,2020-06-05,397000,227.2,387,1983,37,3,1,hors zone,0,0,533.5,1,0.0 | |
| 325 | +V0708,RF2304,2020-06-06,332500,200.2,634,1970,50,2,0,hors zone,0,0,936.0,1,0.0 | |
| 326 | +V0281,RF0912,2020-06-15,243000,125.5,377,1959,61,3,0,hors zone,0,0,385.7,1,0.0 | |
| 327 | +V0385,RF1217,2020-06-17,287500,123.9,442,1985,35,3,0,hors zone,0,0,502.2,1,0.0 | |
| 328 | +V0587,RF1902,2020-06-28,302000,113.0,466,1975,45,4,1,hors zone,0,0,780.1,1,0.0 | |
| 329 | +V0046,RF0109,2020-06-29,348500,147.4,499,1984,36,4,0,élevée,1,1,59.2,1,-0.105 | |
| 330 | +V0556,RF1802,2020-07-06,324000,128.9,513,1993,27,4,1,hors zone,0,0,747.3,1,0.0 | |
| 331 | +V0034,RF0104,2020-07-09,219000,148.3,453,1959,61,1,0,élevée,1,1,61.6,1,-0.105 | |
| 332 | +V0247,RF0813,2020-07-25,301000,110.5,492,1985,35,4,0,hors zone,0,0,339.6,1,0.0 | |
| 333 | +V0432,RF1400,2020-07-26,352500,103.5,650,2006,14,4,0,hors zone,0,0,576.4,1,0.0 | |
| 334 | +V0722,RF2315,2020-08-04,312000,199.5,502,1957,63,2,1,hors zone,0,0,942.3,1,0.0 | |
| 335 | +V0212,RF0706,2020-08-13,332000,185.3,514,1957,63,3,1,hors zone,0,0,308.0,1,0.0 | |
| 336 | +V0086,RF0214,2020-08-22,313500,182.7,711,1962,58,3,0,élevée,1,1,101.6,1,-0.105 | |
| 337 | +V0213,RF0706,2020-08-31,330000,185.3,514,1957,63,3,1,hors zone,0,0,308.0,1,0.0 | |
| 338 | +V0202,RF0619,2020-09-15,260000,114.5,777,1984,36,2,0,faible,1,0,258.9,1,-0.0225 | |
| 339 | +V0025,RF0023,2020-09-18,279000,116.2,730,1973,47,3,1,très élevée,1,1,12.7,1,-0.15 | |
| 340 | +V0338,RF1105,2020-09-20,388000,165.2,488,1991,29,4,1,hors zone,0,0,457.4,1,0.0 | |
| 341 | +V0050,RF0114,2020-09-25,252500,96.2,675,1994,26,4,0,élevée,1,1,57.6,1,-0.105 | |
| 342 | +V0599,RF1915,2020-10-01,347000,135.5,595,1975,45,4,0,hors zone,0,0,773.3,1,0.0 | |
| 343 | +V0561,RF1807,2020-10-22,314500,161.7,300,2002,18,3,0,hors zone,0,0,736.0,1,0.0 | |
| 344 | +V0340,RF1106,2020-10-24,217500,81.6,606,1974,46,2,0,hors zone,0,0,456.3,1,0.0 | |
| 345 | +V0100,RF0303,2020-11-05,472000,228.6,381,2016,4,3,1,modérée,1,0,135.1,1,-0.06 | |
| 346 | +V0412,RF1311,2020-11-11,320500,86.0,579,2016,4,3,0,hors zone,0,0,546.1,1,0.0 | |
| 347 | +V0664,RF2120,2020-11-21,413000,217.2,582,1985,35,2,1,hors zone,0,0,862.4,1,0.0 | |
| 348 | +V0700,RF2223,2020-11-21,303000,122.5,359,1979,41,4,1,hors zone,0,0,896.7,1,0.0 | |
| 349 | +V0208,RF0624,2020-11-30,402500,192.8,682,1984,36,3,1,faible,1,0,255.1,1,-0.0225 | |
| 350 | +V0613,RF2004,2020-12-02,247500,111.4,361,1958,62,3,0,hors zone,0,0,822.7,1,0.0 | |
| 351 | +V0590,RF1905,2020-12-13,288000,131.9,602,1975,45,2,1,hors zone,0,0,778.2,1,0.0 | |
| 352 | +V0334,RF1100,2020-12-16,327000,140.0,571,1981,39,3,0,hors zone,0,0,460.0,1,0.0 | |
| 353 | +V0569,RF1812,2020-12-24,294500,121.9,527,1983,37,3,0,hors zone,0,0,740.3,1,0.0 | |
| 354 | +V0721,RF2315,2020-12-25,301500,199.5,502,1957,63,2,1,hors zone,0,0,942.3,1,0.0 | |
| 355 | +V0428,RF1324,2021-01-08,313000,122.6,485,1996,25,2,0,hors zone,0,0,532.6,1,0.0 | |
| 356 | +V0054,RF0116,2021-01-14,278500,103.4,479,1976,45,4,1,élevée,1,1,62.6,1,-0.105 | |
| 357 | +V0663,RF2120,2021-01-18,419000,217.2,582,1985,36,2,1,hors zone,0,0,862.4,1,0.0 | |
| 358 | +V0233,RF0800,2021-01-29,391000,173.8,735,1989,32,2,0,hors zone,0,0,342.9,1,0.0 | |
| 359 | +V0346,RF1113,2021-01-30,321500,99.2,515,2010,11,3,0,hors zone,0,0,467.3,1,0.0 | |
| 360 | +V0752,RF2414,2021-02-05,400500,151.0,599,1995,26,4,1,hors zone,0,0,979.1,1,0.0 | |
| 361 | +V0411,RF1311,2021-02-09,276000,86.0,579,2016,5,3,0,hors zone,0,0,546.1,1,0.0 | |
| 362 | +V0140,RF0421,2021-02-16,366000,168.3,475,1984,37,3,0,modérée,1,0,186.8,1,-0.06 | |
| 363 | +V0566,RF1810,2021-02-25,256000,114.4,476,1967,54,2,0,hors zone,0,0,737.7,1,0.0 | |
| 364 | +V0118,RF0324,2021-02-28,255000,117.5,383,1989,32,3,0,modérée,1,0,134.5,1,-0.06 | |
| 365 | +V0253,RF0817,2021-03-06,360500,117.2,573,2015,6,2,1,hors zone,0,0,347.6,1,0.0 | |
| 366 | +V0683,RF2214,2021-03-11,240500,125.6,641,1950,71,3,0,hors zone,0,0,900.0,1,0.0 | |
| 367 | +V0173,RF0518,2021-03-15,315000,140.3,629,1979,42,3,1,faible,1,0,213.6,1,-0.0225 | |
| 368 | +V0436,RF1402,2021-03-23,351000,183.7,471,1976,45,3,1,hors zone,0,0,581.0,1,0.0 | |
| 369 | +V0526,RF1623,2021-03-27,347500,196.9,624,1959,62,3,0,hors zone,0,0,652.2,1,0.0 | |
| 370 | +V0552,RF1800,2021-03-27,367500,128.6,457,1988,33,4,1,hors zone,0,0,742.1,1,0.0 | |
| 371 | +V0134,RF0416,2021-03-28,258500,121.5,448,1998,23,3,0,modérée,1,0,174.9,1,-0.06 | |
| 372 | +V0554,RF1801,2021-04-05,426500,205.0,362,1996,25,4,0,hors zone,0,0,742.4,1,0.0 | |
| 373 | +V0560,RF1805,2021-04-06,322000,141.6,455,1963,58,3,1,hors zone,0,0,735.1,1,0.0 | |
| 374 | +V0382,RF1216,2021-04-08,283500,116.3,491,1985,36,3,0,hors zone,0,0,493.9,1,0.0 | |
| 375 | +V0209,RF0701,2021-04-12,386000,219.0,621,1978,43,4,0,hors zone,0,0,293.4,1,0.0 | |
| 376 | +V0350,RF1116,2021-04-14,309500,146.8,633,1982,39,2,0,hors zone,0,0,454.8,1,0.0 | |
| 377 | +V0474,RF1508,2021-04-27,459000,185.4,512,2016,5,3,0,hors zone,0,0,624.2,1,0.0 | |
| 378 | +V0080,RF0205,2021-05-04,242000,134.2,713,1946,75,3,1,élevée,1,1,96.3,1,-0.105 | |
| 379 | +V0749,RF2410,2021-05-04,283000,115.5,515,1965,56,4,1,hors zone,0,0,975.4,1,0.0 | |
| 380 | +V0697,RF2222,2021-05-11,315000,138.5,439,1995,26,3,1,hors zone,0,0,903.8,1,0.0 | |
| 381 | +V0397,RF1224,2021-05-14,348500,116.1,454,2009,12,3,1,hors zone,0,0,495.2,1,0.0 | |
| 382 | +V0339,RF1105,2021-05-18,414500,165.2,488,1991,30,4,1,hors zone,0,0,457.4,1,0.0 | |
| 383 | +V0401,RF1302,2021-05-28,263000,129.5,660,1959,62,2,0,hors zone,0,0,543.8,1,0.0 | |
| 384 | +V0699,RF2223,2021-06-04,326000,122.5,359,1979,42,4,1,hors zone,0,0,896.7,1,0.0 | |
| 385 | +V0052,RF0116,2021-06-05,278000,103.4,479,1976,45,4,1,élevée,1,1,62.6,1,-0.105 | |
| 386 | +V0391,RF1222,2021-06-07,297000,108.8,551,1976,45,3,0,hors zone,0,0,505.5,1,0.0 | |
| 387 | +V0562,RF1807,2021-06-07,373000,161.7,300,2002,19,3,0,hors zone,0,0,736.0,1,0.0 | |
| 388 | +V0157,RF0509,2021-06-16,350500,217.1,346,1971,50,3,1,faible,1,0,223.1,1,-0.0225 | |
| 389 | +V0375,RF1211,2021-06-26,314500,130.3,403,1994,27,3,1,hors zone,0,0,495.0,1,0.0 | |
| 390 | +V0607,RF1923,2021-06-28,319500,142.6,602,1979,42,1,1,hors zone,0,0,786.1,1,0.0 | |
| 391 | +V0246,RF0812,2021-07-04,367000,127.6,790,2003,18,3,0,hors zone,0,0,333.4,1,0.0 | |
| 392 | +V0618,RF2010,2021-07-22,328000,158.6,857,1952,69,2,1,hors zone,0,0,822.7,1,0.0 | |
| 393 | +V0727,RF2319,2021-07-23,363000,150.3,693,2006,15,4,0,hors zone,0,0,941.8,1,0.0 | |
| 394 | +V0739,RF2403,2021-08-11,284500,162.2,617,1943,78,3,0,hors zone,0,0,985.4,1,0.0 | |
| 395 | +V0323,RF1017,2021-08-13,300500,135.5,763,1966,55,3,0,hors zone,0,0,425.9,1,0.0 | |
| 396 | +V0682,RF2213,2021-08-21,317000,162.3,499,1971,50,2,1,hors zone,0,0,892.1,1,0.0 | |
| 397 | +V0564,RF1808,2021-09-03,251500,108.1,530,1960,61,3,0,hors zone,0,0,739.5,1,0.0 | |
| 398 | +V0083,RF0208,2021-09-09,391000,159.3,587,1989,32,4,1,élevée,1,1,96.8,1,-0.105 | |
| 399 | +V0704,RF2300,2021-09-15,293000,174.7,492,1966,55,2,0,hors zone,0,0,936.6,1,0.0 | |
| 400 | +V0361,RF1122,2021-09-21,199500,108.5,573,1946,75,2,1,hors zone,0,0,452.6,1,0.0 | |
| 401 | +V0593,RF1907,2021-09-26,425500,158.1,640,1991,30,4,1,hors zone,0,0,781.0,1,0.0 | |
| 402 | +V0078,RF0204,2021-10-10,424500,130.0,679,2016,5,4,1,élevée,1,1,94.3,1,-0.105 | |
| 403 | +V0609,RF1924,2021-10-11,298500,128.2,334,1997,24,2,0,hors zone,0,0,774.5,1,0.0 | |
| 404 | +V0510,RF1610,2021-10-12,269000,100.0,483,1985,36,3,1,hors zone,0,0,666.3,1,0.0 | |
| 405 | +V0377,RF1211,2021-10-16,339000,130.3,403,1994,27,3,1,hors zone,0,0,495.0,1,0.0 | |
| 406 | +V0079,RF0204,2021-10-18,365000,130.0,679,2016,5,4,1,élevée,1,1,94.3,1,-0.105 | |
| 407 | +V0279,RF0911,2021-10-20,340000,154.3,445,1995,26,3,1,hors zone,0,0,374.7,1,0.0 | |
| 408 | +V0351,RF1116,2021-10-29,331000,146.8,633,1982,39,2,0,hors zone,0,0,454.8,1,0.0 | |
| 409 | +V0625,RF2015,2021-10-29,365500,122.7,688,1995,26,3,0,hors zone,0,0,821.4,1,0.0 | |
| 410 | +V0144,RF0424,2021-11-04,398500,137.4,529,2006,15,5,0,modérée,1,0,185.1,1,-0.06 | |
| 411 | +V0549,RF1723,2021-11-05,266500,100.2,682,1968,53,3,1,hors zone,0,0,694.2,1,0.0 | |
| 412 | +V0456,RF1420,2021-11-06,379000,153.2,436,1983,38,3,1,hors zone,0,0,580.3,1,0.0 | |
| 413 | +V0612,RF2003,2021-11-10,470500,236.2,554,1999,22,2,0,hors zone,0,0,815.1,1,0.0 | |
| 414 | +V0626,RF2016,2021-11-14,313000,147.2,559,1981,40,2,0,hors zone,0,0,818.5,1,0.0 | |
| 415 | +V0263,RF0903,2021-11-15,376500,111.0,637,1994,27,3,1,hors zone,0,0,382.0,1,0.0 | |
| 416 | +V0647,RF2108,2021-11-15,363500,153.9,639,1974,47,2,1,hors zone,0,0,867.6,1,0.0 | |
| 417 | +V0489,RF1518,2021-11-23,469000,201.9,730,1982,39,4,1,hors zone,0,0,620.4,1,0.0 | |
| 418 | +V0602,RF1917,2021-11-27,437000,223.3,680,1950,71,4,1,hors zone,0,0,785.2,1,0.0 | |
| 419 | +V0447,RF1411,2021-11-29,351000,127.4,344,1984,37,4,1,hors zone,0,0,586.7,1,0.0 | |
| 420 | +V0238,RF0804,2021-12-01,313000,114.5,451,2005,16,2,0,hors zone,0,0,334.6,1,0.0 | |
| 421 | +V0470,RF1506,2021-12-04,377000,123.5,590,2012,9,3,0,hors zone,0,0,613.1,1,0.0 | |
| 422 | +V0037,RF0105,2021-12-05,378500,163.1,598,2001,20,2,1,élevée,1,1,54.7,1,-0.105 | |
| 423 | +V0047,RF0110,2021-12-07,344500,173.1,709,1979,42,2,0,élevée,1,1,64.2,1,-0.105 | |
| 424 | +V0103,RF0307,2021-12-11,394500,165.1,420,2002,19,4,0,modérée,1,0,146.7,1,-0.06 | |
| 425 | +V0190,RF0607,2021-12-20,382500,151.2,717,1989,32,3,1,faible,1,0,252.3,1,-0.0225 | |
| 426 | +V0418,RF1319,2022-01-01,342500,141.4,611,1983,39,3,1,hors zone,0,0,545.3,1,0.0 | |
| 427 | +V0662,RF2120,2022-01-06,374000,217.2,582,1985,37,2,1,hors zone,0,0,862.4,1,0.0 | |
| 428 | +V0366,RF1203,2022-01-09,389000,182.4,434,1985,37,3,1,hors zone,0,0,501.5,1,0.0 | |
| 429 | +V0320,RF1013,2022-01-10,321500,138.7,510,1989,33,2,1,hors zone,0,0,413.3,1,0.0 | |
| 430 | +V0154,RF0506,2022-01-13,295500,99.9,617,1980,42,4,1,faible,1,0,214.0,1,-0.0225 | |
| 431 | +V0308,RF1003,2022-01-15,370500,122.9,873,2001,21,3,1,hors zone,0,0,413.8,1,0.0 | |
| 432 | +V0326,RF1017,2022-01-15,285000,135.5,763,1966,56,3,0,hors zone,0,0,425.9,1,0.0 | |
| 433 | +V0299,RF0923,2022-01-16,262000,98.5,427,1970,52,4,1,hors zone,0,0,377.4,1,0.0 | |
| 434 | +V0634,RF2100,2022-01-17,347500,103.0,529,2016,6,3,1,hors zone,0,0,857.4,1,0.0 | |
| 435 | +V0519,RF1618,2022-01-18,320500,129.4,541,1992,30,2,0,hors zone,0,0,653.5,1,0.0 | |
| 436 | +V0104,RF0308,2022-01-22,379500,133.4,578,2010,12,4,0,modérée,1,0,140.3,1,-0.06 | |
| 437 | +V0318,RF1011,2022-01-27,352500,141.7,531,1993,29,3,1,hors zone,0,0,424.6,1,0.0 | |
| 438 | +V0499,RF1600,2022-01-27,344000,127.9,667,2003,19,4,1,hors zone,0,0,652.0,1,0.0 | |
| 439 | +V0066,RF0123,2022-02-02,320500,170.7,664,1971,51,3,0,élevée,1,1,65.5,1,-0.105 | |
| 440 | +V0261,RF0900,2022-02-02,323000,104.0,609,1994,28,2,1,hors zone,0,0,373.6,1,0.0 | |
| 441 | +V0395,RF1224,2022-02-12,348000,116.1,454,2009,13,3,1,hors zone,0,0,495.2,1,0.0 | |
| 442 | +V0168,RF0515,2022-02-13,353500,143.7,811,2003,19,2,0,faible,1,0,216.5,1,-0.0225 | |
| 443 | +V0481,RF1513,2022-02-22,417000,158.4,406,1983,39,4,1,hors zone,0,0,623.9,1,0.0 | |
| 444 | +V0147,RF0501,2022-02-26,327000,132.6,472,2016,6,1,0,faible,1,0,222.2,1,-0.0225 | |
| 445 | +V0096,RF0222,2022-03-06,333000,170.9,534,1980,42,2,1,élevée,1,1,98.2,1,-0.105 | |
| 446 | +V0576,RF1821,2022-03-07,400000,154.3,439,1996,26,3,0,hors zone,0,0,743.6,1,0.0 | |
| 447 | +V0491,RF1520,2022-03-09,289500,116.4,513,1993,29,3,0,hors zone,0,0,618.8,1,0.0 | |
| 448 | +V0425,RF1322,2022-03-15,251500,134.2,491,1964,58,3,0,hors zone,0,0,547.8,1,0.0 | |
| 449 | +V0670,RF2124,2022-03-18,361500,145.2,420,1991,31,3,0,hors zone,0,0,858.1,1,0.0 | |
| 450 | +V0153,RF0505,2022-03-22,310500,107.7,543,1963,59,4,0,faible,1,0,227.4,1,-0.0225 | |
| 451 | +V0738,RF2402,2022-03-27,313500,118.8,562,1971,51,3,1,hors zone,0,0,980.0,1,0.0 | |
| 452 | +V0017,RF0014,2022-03-29,234500,139.5,533,1992,30,1,0,très élevée,1,1,14.2,1,-0.15 | |
| 453 | +V0150,RF0504,2022-03-29,254000,122.0,440,1975,47,2,0,faible,1,0,216.8,1,-0.0225 | |
| 454 | +V0413,RF1315,2022-04-06,385000,189.7,532,1975,47,3,1,hors zone,0,0,539.3,1,0.0 | |
| 455 | +V0729,RF2320,2022-04-12,299000,147.9,456,1971,51,2,1,hors zone,0,0,936.1,1,0.0 | |
| 456 | +V0690,RF2219,2022-04-25,363500,151.2,703,1965,57,3,0,hors zone,0,0,897.3,1,0.0 | |
| 457 | +V0675,RF2204,2022-04-29,335000,128.5,429,1979,43,2,1,hors zone,0,0,903.2,1,0.0 | |
| 458 | +V0300,RF0924,2022-04-30,360500,168.3,497,1973,49,2,0,hors zone,0,0,382.2,1,0.0 | |
| 459 | +V0332,RF1024,2022-05-18,384500,148.9,547,1996,26,2,1,hors zone,0,0,425.0,1,0.0 | |
| 460 | +V0014,RF0013,2022-05-20,356500,153.6,447,2016,6,3,1,très élevée,1,1,20.2,1,-0.15 | |
| 461 | +V0476,RF1511,2022-05-23,325000,150.9,632,1954,68,4,1,hors zone,0,0,616.2,1,0.0 | |
| 462 | +V0758,RF2419,2022-05-30,364000,154.8,582,1963,59,4,1,hors zone,0,0,979.1,1,0.0 | |
| 463 | +V0641,RF2104,2022-06-05,310500,132.3,488,1978,44,4,0,hors zone,0,0,859.4,1,0.0 | |
| 464 | +V0089,RF0218,2022-06-10,369500,166.4,527,1980,42,5,0,élevée,1,1,97.1,1,-0.105 | |
| 465 | +V0399,RF1301,2022-06-15,336000,111.3,725,2002,20,4,0,hors zone,0,0,532.2,1,0.0 | |
| 466 | +V0357,RF1119,2022-06-20,372500,163.1,588,1960,62,3,1,hors zone,0,0,453.3,1,0.0 | |
| 467 | +V0327,RF1018,2022-06-24,374500,125.0,550,2010,12,4,1,hors zone,0,0,427.8,1,0.0 | |
| 468 | +V0121,RF0401,2022-06-27,351000,138.4,756,1999,23,3,0,modérée,1,0,178.4,1,-0.06 | |
| 469 | +V0041,RF0107,2022-07-01,280000,161.9,633,1970,52,2,0,élevée,1,1,58.4,1,-0.105 | |
| 470 | +V0535,RF1708,2022-07-11,328500,103.2,636,2001,21,4,0,hors zone,0,0,704.6,1,0.0 | |
| 471 | +V0343,RF1108,2022-07-12,327500,158.0,505,1984,38,2,0,hors zone,0,0,467.6,1,0.0 | |
| 472 | +V0312,RF1006,2022-07-17,252000,86.6,558,1984,38,1,1,hors zone,0,0,421.5,1,0.0 | |
| 473 | +V0717,RF2310,2022-07-19,232500,117.9,671,1968,54,1,1,hors zone,0,0,944.8,1,0.0 | |
| 474 | +V0691,RF2219,2022-08-11,294000,151.2,703,1965,57,3,0,hors zone,0,0,897.3,1,0.0 | |
| 475 | +V0225,RF0717,2022-08-20,379500,166.6,637,1996,26,2,0,hors zone,0,0,301.4,1,0.0 | |
| 476 | +V0294,RF0921,2022-08-22,303000,132.6,415,1975,47,3,1,hors zone,0,0,374.5,1,0.0 | |
| 477 | +V0127,RF0409,2022-08-23,354000,181.7,419,1986,36,3,0,modérée,1,0,179.8,1,-0.06 | |
| 478 | +V0595,RF1909,2022-08-25,302000,122.5,673,1970,52,3,1,hors zone,0,0,785.9,1,0.0 | |
| 479 | +V0036,RF0105,2022-08-28,362500,163.1,598,2001,21,2,1,élevée,1,1,54.7,1,-0.105 | |
| 480 | +V0646,RF2108,2022-09-03,327000,153.9,639,1974,48,2,1,hors zone,0,0,867.6,1,0.0 | |
| 481 | +V0584,RF1902,2022-09-04,313500,113.0,466,1975,47,4,1,hors zone,0,0,780.1,1,0.0 | |
| 482 | +V0119,RF0401,2022-09-19,348500,138.4,756,1999,23,3,0,modérée,1,0,178.4,1,-0.06 | |
| 483 | +V0617,RF2009,2022-09-20,372500,154.8,578,1997,25,3,0,hors zone,0,0,815.7,1,0.0 | |
| 484 | +V0002,RF0003,2022-10-01,342500,155.9,328,2009,13,2,0,très élevée,1,1,23.0,1,-0.15 | |
| 485 | +V0427,RF1324,2022-10-04,263500,122.6,485,1996,26,2,0,hors zone,0,0,532.6,1,0.0 | |
| 486 | +V0354,RF1117,2022-10-06,338500,138.5,451,1984,38,2,1,hors zone,0,0,456.8,1,0.0 | |
| 487 | +V0463,RF1500,2022-10-08,345000,154.7,631,1985,37,2,1,hors zone,0,0,622.3,1,0.0 | |
| 488 | +V0388,RF1219,2022-10-11,412000,197.0,659,1979,43,3,0,hors zone,0,0,503.5,1,0.0 | |
| 489 | +V0648,RF2108,2022-10-31,338500,153.9,639,1974,48,2,1,hors zone,0,0,867.6,1,0.0 | |
| 490 | +V0039,RF0106,2022-11-09,295000,123.8,487,1983,39,2,1,élevée,1,1,61.6,1,-0.105 | |
| 491 | +V0506,RF1608,2022-11-18,309500,134.8,343,1986,36,3,0,hors zone,0,0,660.3,1,0.0 | |
| 492 | +V0106,RF0311,2022-11-22,378000,202.5,609,1993,29,2,0,modérée,1,0,137.6,1,-0.06 | |
| 493 | +V0087,RF0214,2022-11-23,310000,182.7,711,1962,60,3,0,élevée,1,1,101.6,1,-0.105 | |
| 494 | +V0324,RF1017,2022-11-27,294500,135.5,763,1966,56,3,0,hors zone,0,0,425.9,1,0.0 | |
| 495 | +V0048,RF0111,2022-12-02,289500,112.3,334,2001,21,2,0,élevée,1,1,55.7,1,-0.105 | |
| 496 | +V0215,RF0707,2022-12-06,356000,140.4,458,1997,25,3,1,hors zone,0,0,300.7,1,0.0 | |
| 497 | +V0384,RF1217,2022-12-06,340500,123.9,442,1985,37,3,0,hors zone,0,0,502.2,1,0.0 | |
| 498 | +V0191,RF0608,2022-12-10,361500,146.7,374,1989,33,2,1,faible,1,0,260.3,1,-0.0225 | |
| 499 | +V0415,RF1316,2022-12-20,334500,125.1,536,1986,36,3,1,hors zone,0,0,535.0,1,0.0 | |
| 500 | +V0434,RF1401,2022-12-30,455000,193.7,494,1995,27,4,0,hors zone,0,0,579.1,1,0.0 | |
| 501 | +V0236,RF0802,2022-12-31,398000,189.3,611,1974,48,2,1,hors zone,0,0,334.0,1,0.0 | |
| 502 | +V0260,RF0823,2023-01-07,310500,102.1,518,1981,42,3,1,hors zone,0,0,334.9,1,0.0 | |
| 503 | +V0534,RF1707,2023-01-15,370500,117.2,688,1991,32,3,1,hors zone,0,0,697.1,1,0.0 | |
| 504 | +V0705,RF2300,2023-01-16,338500,174.7,492,1966,57,2,0,hors zone,0,0,936.6,1,0.0 | |
| 505 | +V0538,RF1708,2023-01-20,345500,103.2,636,2001,22,4,0,hors zone,0,0,704.6,1,0.0 | |
| 506 | +V0537,RF1708,2023-01-21,365000,103.2,636,2001,22,4,0,hors zone,0,0,704.6,1,0.0 | |
| 507 | +V0042,RF0108,2023-01-29,368500,140.3,534,1995,28,5,0,élevée,1,1,62.1,1,-0.105 | |
| 508 | +V0076,RF0203,2023-01-30,252500,102.9,774,1974,49,2,0,élevée,1,1,93.8,1,-0.105 | |
| 509 | +V0743,RF2408,2023-01-31,457500,169.6,360,2004,19,3,1,hors zone,0,0,972.6,1,0.0 | |
| 510 | +V0706,RF2302,2023-02-02,439500,197.8,374,2000,23,3,1,hors zone,0,0,943.5,1,0.0 | |
| 511 | +V0067,RF0123,2023-02-07,361000,170.7,664,1971,52,3,0,élevée,1,1,65.5,1,-0.105 | |
| 512 | +V0540,RF1710,2023-02-09,394000,124.1,506,2008,15,3,1,hors zone,0,0,693.9,1,0.0 | |
| 513 | +V0274,RF0910,2023-03-05,320500,148.4,616,1978,45,3,0,hors zone,0,0,384.0,1,0.0 | |
| 514 | +V0185,RF0601,2023-03-09,226500,89.3,495,1959,64,3,0,faible,1,0,259.6,1,-0.0225 | |
| 515 | +V0674,RF2203,2023-03-09,407500,139.4,545,2000,23,3,1,hors zone,0,0,905.1,1,0.0 | |
| 516 | +V0347,RF1114,2023-03-18,409000,147.2,485,1996,27,4,1,hors zone,0,0,462.9,1,0.0 | |
| 517 | +V0485,RF1515,2023-03-20,361000,184.3,497,1996,27,2,1,hors zone,0,0,622.3,1,0.0 | |
| 518 | +V0368,RF1206,2023-03-23,365000,156.5,628,1964,59,3,0,hors zone,0,0,500.2,1,0.0 | |
| 519 | +V0544,RF1718,2023-03-29,318000,155.2,571,1977,46,2,0,hors zone,0,0,695.5,1,0.0 | |
| 520 | +V0117,RF0322,2023-04-02,321500,113.1,465,1976,47,4,1,modérée,1,0,147.3,1,-0.06 | |
| 521 | +V0582,RF1900,2023-04-15,337500,132.9,500,1989,34,3,0,hors zone,0,0,787.6,1,0.0 | |
| 522 | +V0237,RF0802,2023-04-16,339000,189.3,611,1974,49,2,1,hors zone,0,0,334.0,1,0.0 | |
| 523 | +V0679,RF2209,2023-04-17,351500,157.6,611,1974,49,2,0,hors zone,0,0,900.0,1,0.0 | |
| 524 | +V0386,RF1217,2023-04-21,359500,123.9,442,1985,38,3,0,hors zone,0,0,502.2,1,0.0 | |
| 525 | +V0445,RF1407,2023-04-22,398500,194.9,300,1989,34,3,0,hors zone,0,0,580.3,1,0.0 | |
| 526 | +V0650,RF2109,2023-04-26,366000,126.8,691,2000,23,3,0,hors zone,0,0,864.4,1,0.0 | |
| 527 | +V0720,RF2314,2023-04-26,363000,161.1,532,1976,47,3,1,hors zone,0,0,942.3,1,0.0 | |
| 528 | +V0390,RF1222,2023-04-28,286000,108.8,551,1976,47,3,0,hors zone,0,0,505.5,1,0.0 | |
| 529 | +V0112,RF0315,2023-04-30,311500,125.3,496,1973,50,4,1,modérée,1,0,137.1,1,-0.06 | |
| 530 | +V0006,RF0006,2023-05-07,292000,141.7,424,1987,36,3,0,très élevée,1,1,19.1,1,-0.15 | |
| 531 | +V0448,RF1411,2023-05-12,335500,127.4,344,1984,39,4,1,hors zone,0,0,586.7,1,0.0 | |
| 532 | +V0509,RF1609,2023-05-12,307000,142.5,483,1955,68,2,1,hors zone,0,0,654.7,1,0.0 | |
| 533 | +V0029,RF0101,2023-05-18,351500,145.0,552,1983,40,3,0,élevée,1,1,58.7,1,-0.105 | |
| 534 | +V0156,RF0509,2023-05-18,350500,217.1,346,1971,52,3,1,faible,1,0,223.1,1,-0.0225 | |
| 535 | +V0267,RF0904,2023-06-21,338500,151.8,557,1970,53,3,1,hors zone,0,0,378.2,1,0.0 | |
| 536 | +V0642,RF2105,2023-06-23,341500,146.9,494,1980,43,2,1,hors zone,0,0,855.8,1,0.0 | |
| 537 | +V0431,RF1400,2023-06-26,353500,103.5,650,2006,17,4,0,hors zone,0,0,576.4,1,0.0 | |
| 538 | +V0107,RF0311,2023-06-30,435000,202.5,609,1993,30,2,0,modérée,1,0,137.6,1,-0.06 | |
| 539 | +V0068,RF0123,2023-07-02,317000,170.7,664,1971,52,3,0,élevée,1,1,65.5,1,-0.105 | |
| 540 | +V0669,RF2123,2023-07-03,420500,160.2,592,1986,37,4,0,hors zone,0,0,861.6,1,0.0 | |
| 541 | +V0301,RF0924,2023-07-10,345000,168.3,497,1973,50,2,0,hors zone,0,0,382.2,1,0.0 | |
| 542 | +V0120,RF0401,2023-07-11,369500,138.4,756,1999,24,3,0,modérée,1,0,178.4,1,-0.06 | |
| 543 | +V0672,RF2201,2023-07-12,365000,125.5,597,1989,34,3,0,hors zone,0,0,893.0,1,0.0 | |
| 544 | +V0131,RF0413,2023-07-17,239500,148.7,378,1930,93,2,1,modérée,1,0,177.2,1,-0.06 | |
| 545 | +V0057,RF0119,2023-07-21,351500,140.5,440,1983,40,4,1,élevée,1,1,58.7,1,-0.105 | |
| 546 | +V0268,RF0905,2023-07-27,323500,138.3,538,1986,37,3,0,hors zone,0,0,381.8,1,0.0 | |
| 547 | +V0271,RF0908,2023-07-27,393500,175.8,474,1994,29,4,0,hors zone,0,0,383.5,1,0.0 | |
| 548 | +V0101,RF0306,2023-07-29,371000,160.5,380,1997,26,3,0,modérée,1,0,144.0,1,-0.06 | |
| 549 | +V0341,RF1106,2023-08-18,206500,81.6,606,1974,49,2,0,hors zone,0,0,456.3,1,0.0 | |
| 550 | +V0761,RF2421,2023-08-21,291000,119.1,463,1969,54,3,1,hors zone,0,0,983.2,1,0.0 | |
| 551 | +V0539,RF1710,2023-09-09,389500,124.1,506,2008,15,3,1,hors zone,0,0,693.9,1,0.0 | |
| 552 | +V0188,RF0604,2023-09-10,332500,132.0,543,1983,40,3,0,faible,1,0,257.9,1,-0.0225 | |
| 553 | +V0543,RF1718,2023-09-13,291000,155.2,571,1977,46,2,0,hors zone,0,0,695.5,1,0.0 | |
| 554 | +V0330,RF1022,2023-09-17,349000,119.1,565,1980,43,3,1,hors zone,0,0,415.7,1,0.0 | |
| 555 | +V0285,RF0914,2023-10-01,451000,163.3,610,2016,7,4,0,hors zone,0,0,373.3,1,0.0 | |
| 556 | +V0653,RF2111,2023-10-01,315500,106.6,449,1968,55,3,1,hors zone,0,0,858.9,1,0.0 | |
| 557 | +V0603,RF1918,2023-10-10,300500,78.7,423,2016,7,4,1,hors zone,0,0,784.0,1,0.0 | |
| 558 | +V0655,RF2113,2023-10-21,365500,103.3,521,2001,22,4,0,hors zone,0,0,858.9,1,0.0 | |
| 559 | +V0008,RF0008,2023-11-07,337500,142.5,472,1974,49,3,0,très élevée,1,1,25.0,1,-0.15 | |
| 560 | +V0249,RF0813,2023-11-08,325000,110.5,492,1985,38,4,0,hors zone,0,0,339.6,1,0.0 | |
| 561 | +V0187,RF0604,2023-11-10,354000,132.0,543,1983,40,3,0,faible,1,0,257.9,1,-0.0225 | |
| 562 | +V0438,RF1403,2023-11-19,275500,121.2,546,1956,67,3,0,hors zone,0,0,583.1,1,0.0 | |
| 563 | +V0241,RF0810,2023-11-30,278500,133.7,629,1965,58,3,0,hors zone,0,0,334.0,1,0.0 | |
| 564 | +V0349,RF1116,2023-11-30,323000,146.8,633,1982,41,2,0,hors zone,0,0,454.8,1,0.0 | |
| 565 | +V0133,RF0413,2023-12-07,283500,148.7,378,1930,93,2,1,modérée,1,0,177.2,1,-0.06 | |
| 566 | +V0356,RF1118,2023-12-16,378000,116.1,515,2000,23,4,1,hors zone,0,0,458.8,1,0.0 | |
| 567 | +V0337,RF1103,2023-12-17,397500,143.1,577,2016,7,3,0,hors zone,0,0,463.9,1,0.0 | |
| 568 | +V0058,RF0119,2023-12-23,382000,140.5,440,1983,40,4,1,élevée,1,1,58.7,1,-0.105 | |
| 569 | +V0040,RF0106,2023-12-24,295500,123.8,487,1983,40,2,1,élevée,1,1,61.6,1,-0.105 | |
| 570 | +V0681,RF2213,2023-12-25,339000,162.3,499,1971,52,2,1,hors zone,0,0,892.1,1,0.0 | |
| 571 | +V0426,RF1322,2023-12-31,343000,134.2,491,1964,59,3,0,hors zone,0,0,547.8,1,0.0 | |
| 572 | +V0498,RF1600,2024-01-11,423500,127.9,667,2003,21,4,1,hors zone,0,0,652.0,1,0.0 | |
| 573 | +V0677,RF2208,2024-01-11,446500,162.2,637,1982,42,4,1,hors zone,0,0,896.3,1,0.0 | |
| 574 | +V0536,RF1708,2024-01-18,384000,103.2,636,2001,23,4,0,hors zone,0,0,704.6,1,0.0 | |
| 575 | +V0227,RF0720,2024-01-21,364000,151.6,760,1981,43,2,1,hors zone,0,0,294.5,1,0.0 | |
| 576 | +V0229,RF0722,2024-01-22,281000,131.9,580,1954,70,2,1,hors zone,0,0,306.0,1,0.0 | |
| 577 | +V0557,RF1803,2024-01-30,387000,135.4,512,1992,32,3,1,hors zone,0,0,735.2,1,0.0 | |
| 578 | +V0009,RF0008,2024-02-07,293500,142.5,472,1974,50,3,0,très élevée,1,1,25.0,1,-0.15 | |
| 579 | +V0652,RF2111,2024-02-10,270500,106.6,449,1968,56,3,1,hors zone,0,0,858.9,1,0.0 | |
| 580 | +V0022,RF0021,2024-02-15,250000,113.7,365,1952,72,3,1,très élevée,1,1,21.4,1,-0.15 | |
| 581 | +V0458,RF1421,2024-02-20,359500,192.0,532,1965,59,3,1,hors zone,0,0,581.1,1,0.0 | |
| 582 | +V0553,RF1800,2024-02-29,406500,128.6,457,1988,36,4,1,hors zone,0,0,742.1,1,0.0 | |
| 583 | +V0723,RF2315,2024-02-29,392500,199.5,502,1957,67,2,1,hors zone,0,0,942.3,1,0.0 | |
| 584 | +V0062,RF0121,2024-03-02,362500,160.4,380,1981,43,2,0,élevée,1,1,67.3,1,-0.105 | |
| 585 | +V0757,RF2417,2024-03-02,344000,147.9,593,1992,32,3,0,hors zone,0,0,977.4,1,0.0 | |
| 586 | +V0234,RF0800,2024-03-07,417500,173.8,735,1989,35,2,0,hors zone,0,0,342.9,1,0.0 | |
| 587 | +V0063,RF0121,2024-03-12,347000,160.4,380,1981,43,2,0,élevée,1,1,67.3,1,-0.105 | |
| 588 | +V0394,RF1223,2024-03-17,409500,159.0,529,1974,50,4,1,hors zone,0,0,497.6,1,0.0 | |
| 589 | +V0116,RF0322,2024-03-18,310500,113.1,465,1976,48,4,1,modérée,1,0,147.3,1,-0.06 | |
| 590 | +V0018,RF0015,2024-03-31,361500,116.0,638,2016,8,3,0,très élevée,1,1,17.3,1,-0.15 | |
| 591 | +V0084,RF0211,2024-03-31,299500,117.3,360,1971,53,4,0,élevée,1,1,95.3,1,-0.105 | |
| 592 | +V0531,RF1707,2024-04-10,382500,117.2,688,1991,33,3,1,hors zone,0,0,697.1,1,0.0 | |
| 593 | +V0321,RF1013,2024-04-23,300000,138.7,510,1989,35,2,1,hors zone,0,0,413.3,1,0.0 | |
| 594 | +V0755,RF2416,2024-05-02,370000,132.9,394,1974,50,5,1,hors zone,0,0,986.3,1,0.0 | |
| 595 | +V0479,RF1512,2024-05-10,357500,166.4,633,1973,51,3,1,hors zone,0,0,614.9,1,0.0 | |
| 596 | +V0143,RF0424,2024-05-13,424500,137.4,529,2006,18,5,0,modérée,1,0,185.1,1,-0.06 | |
| 597 | +V0137,RF0418,2024-05-21,460500,201.1,534,1988,36,3,1,modérée,1,0,187.5,1,-0.06 | |
| 598 | +V0105,RF0309,2024-05-25,442000,192.4,532,1990,34,3,0,modérée,1,0,141.2,1,-0.06 | |
| 599 | +V0342,RF1108,2024-05-28,375500,158.0,505,1984,40,2,0,hors zone,0,0,467.6,1,0.0 | |
| 600 | +V0661,RF2117,2024-06-16,605000,207.3,564,2016,8,5,0,hors zone,0,0,860.0,1,0.0 | |
| 601 | +V0085,RF0213,2024-06-19,305500,140.1,454,1978,46,3,0,élevée,1,1,95.7,1,-0.105 | |
| 602 | +V0568,RF1811,2024-06-21,396500,119.7,780,1986,38,4,0,hors zone,0,0,733.4,1,0.0 | |
| 603 | +V0231,RF0724,2024-06-22,451000,201.5,662,1987,37,2,0,hors zone,0,0,293.8,1,0.0 | |
| 604 | +V0044,RF0108,2024-06-27,406500,140.3,534,1995,29,5,0,élevée,1,1,62.1,1,-0.105 | |
| 605 | +V0508,RF1608,2024-07-03,295500,134.8,343,1986,38,3,0,hors zone,0,0,660.3,1,0.0 | |
| 606 | +V0573,RF1817,2024-07-06,409000,159.6,595,1973,51,3,1,hors zone,0,0,732.9,1,0.0 | |
| 607 | +V0205,RF0620,2024-07-10,419500,181.9,544,1976,48,3,0,faible,1,0,253.3,1,-0.0225 | |
| 608 | +V0175,RF0518,2024-07-13,367500,140.3,629,1979,45,3,1,faible,1,0,213.6,1,-0.0225 | |
| 609 | +V0333,RF1100,2024-07-14,365500,140.0,571,1981,43,3,0,hors zone,0,0,460.0,1,0.0 | |
| 610 | +V0465,RF1502,2024-07-19,528500,164.5,456,1989,35,5,1,hors zone,0,0,616.8,1,0.0 | |
| 611 | +V0656,RF2114,2024-07-20,402500,136.1,565,1969,55,3,0,hors zone,0,0,853.6,1,0.0 | |
| 612 | +V0023,RF0022,2024-07-23,310000,138.9,549,1992,32,3,1,très élevée,1,1,24.9,1,-0.15 | |
| 613 | +V0302,RF0924,2024-07-24,353000,168.3,497,1973,51,2,0,hors zone,0,0,382.2,1,0.0 | |
| 614 | +V0313,RF1006,2024-07-25,245000,86.6,558,1984,40,1,1,hors zone,0,0,421.5,1,0.0 | |
| 615 | +V0072,RF0200,2024-08-14,407500,164.9,479,2016,8,3,1,élevée,1,1,95.3,1,-0.105 | |
| 616 | +V0013,RF0013,2024-08-17,395500,153.6,447,2016,8,3,1,très élevée,1,1,20.2,1,-0.15 | |
| 617 | +V0622,RF2014,2024-08-18,360500,165.7,535,1977,47,3,0,hors zone,0,0,820.5,1,0.0 | |
| 618 | +V0574,RF1820,2024-08-30,446500,200.8,470,1969,55,4,0,hors zone,0,0,744.0,1,0.0 | |
| 619 | +V0750,RF2411,2024-09-11,345500,145.9,676,1952,72,3,0,hors zone,0,0,977.6,1,0.0 | |
| 620 | +V0558,RF1804,2024-09-12,409000,123.7,594,2000,24,4,1,hors zone,0,0,740.5,1,0.0 | |
| 621 | +V0226,RF0718,2024-09-16,409500,197.6,675,1970,54,3,0,hors zone,0,0,295.5,1,0.0 | |
| 622 | +V0125,RF0408,2024-09-22,347500,148.6,544,1982,42,3,1,modérée,1,0,186.0,1,-0.06 | |
| 623 | +V0450,RF1414,2024-09-26,277000,113.3,539,1983,41,1,0,hors zone,0,0,576.2,1,0.0 | |
| 624 | +V0292,RF0920,2024-09-29,318500,127.4,419,1964,60,3,1,hors zone,0,0,386.8,1,0.0 | |
| 625 | +V0580,RF1900,2024-10-13,328500,132.9,500,1989,35,3,0,hors zone,0,0,787.6,1,0.0 | |
| 626 | +V0424,RF1321,2024-10-17,373000,137.1,533,2016,8,3,0,hors zone,0,0,532.0,1,0.0 | |
| 627 | +V0325,RF1017,2024-10-31,318000,135.5,763,1966,58,3,0,hors zone,0,0,425.9,1,0.0 | |
| 628 | +V0021,RF0019,2024-11-02,214000,95.3,485,1970,54,3,0,très élevée,1,1,12.2,1,-0.15 | |
| 629 | +V0214,RF0707,2024-11-02,381000,140.4,458,1997,27,3,1,hors zone,0,0,300.7,1,0.0 | |
| 630 | +V0148,RF0502,2024-11-04,474000,192.0,678,2010,14,3,0,faible,1,0,226.9,1,-0.0225 | |
| 631 | +V0288,RF0917,2024-11-04,373000,143.2,436,1980,44,3,0,hors zone,0,0,383.9,1,0.0 | |
| 632 | +V0545,RF1718,2024-11-24,371500,155.2,571,1977,47,2,0,hors zone,0,0,695.5,1,0.0 | |
| 633 | +V0309,RF1003,2024-11-26,461000,122.9,873,2001,23,3,1,hors zone,0,0,413.8,1,0.0 | |
| 634 | +V0410,RF1310,2024-12-12,306000,120.6,739,1980,44,3,0,hors zone,0,0,532.2,1,0.0 | |
| 635 | +V0345,RF1112,2024-12-21,299000,113.5,548,1987,37,2,1,hors zone,0,0,459.4,1,0.0 | |
| 636 | +V0136,RF0417,2024-12-26,430000,158.2,568,1997,27,4,0,modérée,1,0,183.4,1,-0.06 | |
| 637 | +V0071,RF0200,2024-12-30,431000,164.9,479,2016,8,3,1,élevée,1,1,95.3,1,-0.105 | |
| 638 | +V0420,RF1319,2025-01-03,413000,141.4,611,1983,42,3,1,hors zone,0,0,545.3,1,0.0 | |
| 639 | +V0637,RF2101,2025-01-03,324500,103.6,743,1987,38,4,1,hors zone,0,0,858.3,1,0.0 | |
| 640 | +V0701,RF2224,2025-01-03,425000,136.6,675,1998,27,4,1,hors zone,0,0,895.6,1,0.0 | |
| 641 | +V0703,RF2224,2025-01-03,455000,136.6,675,1998,27,4,1,hors zone,0,0,895.6,1,0.0 | |
| 642 | +V0460,RF1423,2025-01-09,447500,149.6,531,1999,26,4,1,hors zone,0,0,585.9,1,0.0 | |
| 643 | +V0235,RF0801,2025-01-11,413000,140.4,532,1989,36,5,1,hors zone,0,0,339.4,1,0.0 | |
| 644 | +V0359,RF1120,2025-01-24,430500,158.0,714,1998,27,3,0,hors zone,0,0,466.2,1,0.0 | |
| 645 | +V0751,RF2412,2025-01-28,258500,112.8,564,1968,57,1,0,hors zone,0,0,980.3,1,0.0 | |
| 646 | +V0113,RF0316,2025-01-31,338500,125.4,725,2016,9,2,0,modérée,1,0,143.6,1,-0.06 | |
| 647 | +V0221,RF0710,2025-02-01,510000,142.2,848,2001,24,3,1,hors zone,0,0,303.3,1,0.0 | |
| 648 | +V0763,RF2422,2025-02-10,362500,121.8,572,2014,11,4,0,hors zone,0,0,981.1,1,0.0 | |
| 649 | +V0055,RF0117,2025-02-24,358000,120.4,726,1998,27,4,1,élevée,1,1,58.3,1,-0.105 | |
| 650 | +V0259,RF0822,2025-02-24,335000,92.1,523,2003,22,4,1,hors zone,0,0,341.2,1,0.0 | |
| 651 | +V0735,RF2323,2025-02-24,392000,136.8,300,1994,31,3,0,hors zone,0,0,944.2,1,0.0 | |
| 652 | +V0045,RF0109,2025-02-25,384000,147.4,499,1984,41,4,0,élevée,1,1,59.2,1,-0.105 | |
| 653 | +V0092,RF0220,2025-02-28,421500,218.2,493,1978,47,3,1,élevée,1,1,107.8,1,-0.105 | |
| 654 | +V0069,RF0123,2025-03-01,348500,170.7,664,1971,54,3,0,élevée,1,1,65.5,1,-0.105 | |
| 655 | +V0442,RF1406,2025-03-04,434000,178.3,607,1976,49,4,0,hors zone,0,0,579.5,1,0.0 | |
| 656 | +V0713,RF2307,2025-03-05,349500,162.4,328,1980,45,2,1,hors zone,0,0,945.6,1,0.0 | |
| 657 | +V0597,RF1911,2025-03-13,379000,128.0,755,1993,32,3,1,hors zone,0,0,784.4,1,0.0 | |
| 658 | +V0633,RF2023,2025-03-18,371000,140.9,417,1985,40,4,1,hors zone,0,0,818.5,1,0.0 | |
| 659 | +V0468,RF1505,2025-03-19,310000,113.2,525,1964,61,2,1,hors zone,0,0,612.7,1,0.0 | |
| 660 | +V0546,RF1719,2025-03-25,376500,177.4,331,1985,40,2,0,hors zone,0,0,706.9,1,0.0 | |
| 661 | +V0643,RF2105,2025-03-25,380000,146.9,494,1980,45,2,1,hors zone,0,0,855.8,1,0.0 | |
| 662 | +V0517,RF1615,2025-03-30,341500,144.1,451,1975,50,2,1,hors zone,0,0,657.7,1,0.0 | |
| 663 | +V0684,RF2214,2025-03-30,302500,125.6,641,1950,75,3,0,hors zone,0,0,900.0,1,0.0 | |
| 664 | +V0640,RF2102,2025-04-05,444000,153.0,553,2002,23,4,0,hors zone,0,0,862.1,1,0.0 | |
| 665 | +V0551,RF1724,2025-04-12,447500,188.1,570,1996,29,2,1,hors zone,0,0,701.5,1,0.0 | |
| 666 | +V0277,RF0911,2025-04-16,412000,154.3,445,1995,30,3,1,hors zone,0,0,374.7,1,0.0 | |
| 667 | +V0182,RF0601,2025-04-17,248500,89.3,495,1959,66,3,0,faible,1,0,259.6,1,-0.0225 | |
| 668 | +V0503,RF1606,2025-05-16,368000,145.4,501,2012,13,2,0,hors zone,0,0,666.8,1,0.0 | |
| 669 | +V0024,RF0022,2025-05-19,364500,138.9,549,1992,33,3,1,très élevée,1,1,24.9,1,-0.15 | |
| 670 | +V0430,RF1400,2025-05-22,416000,103.5,650,2006,19,4,0,hors zone,0,0,576.4,1,0.0 | |
| 671 | +V0262,RF0902,2025-05-28,505500,217.2,639,1988,37,3,0,hors zone,0,0,375.0,1,0.0 | |
| 672 | +V0172,RF0516,2025-06-01,264000,122.2,464,1994,31,2,0,faible,1,0,214.0,1,-0.0225 | |
| 673 | +V0088,RF0216,2025-06-03,344500,122.5,604,1986,39,3,0,élevée,1,1,102.9,1,-0.105 | |
| 674 | +V0518,RF1617,2025-06-03,431000,142.4,652,1976,49,4,1,hors zone,0,0,666.7,1,0.0 | |
| 675 | +V0407,RF1306,2025-06-06,411500,128.8,492,2007,18,2,1,hors zone,0,0,542.2,1,0.0 | |
| 676 | +V0533,RF1707,2025-06-07,361000,117.2,688,1991,34,3,1,hors zone,0,0,697.1,1,0.0 | |
| 677 | +V0396,RF1224,2025-06-10,370500,116.1,454,2009,16,3,1,hors zone,0,0,495.2,1,0.0 | |
| 678 | +V0152,RF0505,2025-06-19,316000,107.7,543,1963,62,4,0,faible,1,0,227.4,1,-0.0225 | |
| 679 | +V0204,RF0619,2025-06-19,303500,114.5,777,1984,41,2,0,faible,1,0,258.9,1,-0.0225 | |
| 680 | +V0367,RF1205,2025-07-01,468500,231.3,531,1977,48,3,1,hors zone,0,0,501.4,1,0.0 | |
| 681 | +V0128,RF0411,2025-07-22,398000,148.5,502,1985,40,4,1,modérée,1,0,182.3,1,-0.06 | |
| 682 | +V0486,RF1516,2025-07-22,347500,149.6,560,1967,58,4,0,hors zone,0,0,625.8,1,0.0 | |
| 683 | +V0714,RF2307,2025-07-25,431500,162.4,328,1980,45,2,1,hors zone,0,0,945.6,1,0.0 | |
| 684 | +V0203,RF0619,2025-07-27,354500,114.5,777,1984,41,2,0,faible,1,0,258.9,1,-0.0225 | |
| 685 | +V0270,RF0907,2025-07-27,441000,145.6,500,1972,53,4,1,hors zone,0,0,387.1,1,0.0 | |
| 686 | +V0630,RF2021,2025-08-07,374500,163.3,444,1997,28,2,0,hors zone,0,0,823.7,1,0.0 | |
| 687 | +V0280,RF0912,2025-08-08,322000,125.5,377,1959,66,3,0,hors zone,0,0,385.7,1,0.0 | |
| 688 | +V0124,RF0408,2025-08-09,373500,148.6,544,1982,43,3,1,modérée,1,0,186.0,1,-0.06 | |
| 689 | +V0183,RF0601,2025-08-09,237500,89.3,495,1959,66,3,0,faible,1,0,259.6,1,-0.0225 | |
| 690 | +V0223,RF0714,2025-08-09,471500,227.2,473,1970,55,3,0,hors zone,0,0,301.9,1,0.0 | |
| 691 | +V0736,RF2324,2025-08-12,515000,202.0,483,1993,32,4,0,hors zone,0,0,946.6,1,0.0 | |
| 692 | +V0548,RF1722,2025-08-13,358500,106.7,749,2012,13,2,1,hors zone,0,0,698.3,1,0.0 | |
| 693 | +V0421,RF1320,2025-08-14,284000,125.2,300,1977,48,3,0,hors zone,0,0,541.6,1,0.0 | |
| 694 | +V0455,RF1420,2025-08-19,398000,153.2,436,1983,42,3,1,hors zone,0,0,580.3,1,0.0 | |
| 695 | +V0586,RF1902,2025-08-28,422000,113.0,466,1975,50,4,1,hors zone,0,0,780.1,1,0.0 | |
| 696 | +V0523,RF1622,2025-09-01,317000,103.9,663,2001,24,3,0,hors zone,0,0,664.8,1,0.0 | |
| 697 | +V0060,RF0120,2025-09-15,392000,158.6,347,1998,27,4,0,élevée,1,1,52.8,1,-0.105 | |
| 698 | +V0505,RF1608,2025-09-17,325000,134.8,343,1986,39,3,0,hors zone,0,0,660.3,1,0.0 | |
| 699 | +V0527,RF1624,2025-10-08,356500,152.3,438,1973,52,2,0,hors zone,0,0,660.3,1,0.0 | |
| 700 | +V0169,RF0515,2025-10-14,415500,143.7,811,2003,22,2,0,faible,1,0,216.5,1,-0.0225 | |
| 701 | +V0764,RF2423,2025-10-16,467000,155.4,424,2005,20,4,1,hors zone,0,0,972.4,1,0.0 | |
| 702 | +V0504,RF1606,2025-10-24,392000,145.4,501,2012,13,2,0,hors zone,0,0,666.8,1,0.0 | |
| 703 | +V0730,RF2320,2025-10-29,326000,147.9,456,1971,54,2,1,hors zone,0,0,936.1,1,0.0 | |
| 704 | +V0480,RF1513,2025-11-01,453000,158.4,406,1983,42,4,1,hors zone,0,0,623.9,1,0.0 | |
| 705 | +V0525,RF1623,2025-11-06,413500,196.9,624,1959,66,3,0,hors zone,0,0,652.2,1,0.0 | |
| 706 | +V0635,RF2101,2025-11-11,329000,103.6,743,1987,38,4,1,hors zone,0,0,858.3,1,0.0 | |
| 707 | +V0070,RF0124,2025-11-16,378500,138.9,602,1987,38,2,1,élevée,1,1,55.8,1,-0.105 | |
| 708 | +V0596,RF1909,2025-11-18,359500,122.5,673,1970,55,3,1,hors zone,0,0,785.9,1,0.0 | |
| 709 | +V0495,RF1523,2025-11-23,452000,208.6,379,1966,59,3,1,hors zone,0,0,626.0,1,0.0 | |
| 710 | +V0273,RF0909,2025-11-28,295500,97.3,489,1993,32,2,0,hors zone,0,0,382.2,1,0.0 | |
| 711 | +V0702,RF2224,2025-11-30,429000,136.6,675,1998,27,4,1,hors zone,0,0,895.6,1,0.0 | |
| 712 | +V0371,RF1207,2025-12-01,325500,124.4,778,1977,48,3,0,hors zone,0,0,503.5,1,0.0 | |
| 713 | +V0095,RF0221,2025-12-06,350500,170.0,460,1985,40,3,0,élevée,1,1,99.3,1,-0.105 | |
| 714 | +V0038,RF0105,2025-12-28,467000,163.1,598,2001,24,2,1,élevée,1,1,54.7,1,-0.105 | |
| 715 | +V0174,RF0518,2026-01-02,435500,140.3,629,1979,47,3,1,faible,1,0,213.6,1,-0.0225 | |
| 716 | +V0305,RF1002,2026-01-03,376000,135.0,526,1963,63,4,0,hors zone,0,0,417.1,1,0.0 | |
| 717 | +V0228,RF0720,2026-01-04,363000,151.6,760,1981,45,2,1,hors zone,0,0,294.5,1,0.0 | |
| 718 | +V0760,RF2421,2026-01-18,332000,119.1,463,1969,57,3,1,hors zone,0,0,983.2,1,0.0 | |
| 719 | +V0419,RF1319,2026-01-24,411000,141.4,611,1983,43,3,1,hors zone,0,0,545.3,1,0.0 | |
| 720 | +V0364,RF1200,2026-01-27,398000,123.2,476,1972,54,4,1,hors zone,0,0,500.4,1,0.0 | |
| 721 | +V0012,RF0012,2026-01-28,319000,95.3,551,1975,51,4,1,très élevée,1,1,13.9,1,-0.15 | |
| 722 | +V0402,RF1302,2026-01-30,322500,129.5,660,1959,67,2,0,hors zone,0,0,543.8,1,0.0 | |
| 723 | +V0555,RF1802,2026-01-31,382500,128.9,513,1993,33,4,1,hors zone,0,0,747.3,1,0.0 | |
| 724 | +V0572,RF1817,2026-02-02,390000,159.6,595,1973,53,3,1,hors zone,0,0,732.9,1,0.0 | |
| 725 | +V0031,RF0102,2026-02-23,318500,112.5,554,1965,61,3,1,élevée,1,1,61.1,1,-0.105 | |
| 726 | +V0162,RF0513,2026-02-26,306500,116.4,446,1968,58,3,0,faible,1,0,214.6,1,-0.0225 | |
| 727 | +V0250,RF0815,2026-03-03,398500,128.4,479,1975,51,4,0,hors zone,0,0,344.6,1,0.0 | |
| 728 | +V0405,RF1305,2026-03-03,338500,96.7,732,2000,26,3,0,hors zone,0,0,540.4,1,0.0 | |
| 729 | +V0307,RF1003,2026-03-11,407000,122.9,873,2001,25,3,1,hors zone,0,0,413.8,1,0.0 | |
| 730 | +V0614,RF2006,2026-03-12,369000,133.2,568,2011,15,3,0,hors zone,0,0,820.6,1,0.0 | |
| 731 | +V0716,RF2308,2026-03-15,387500,171.5,557,1975,51,2,1,hors zone,0,0,934.0,1,0.0 | |
| 732 | +V0515,RF1614,2026-03-20,412500,192.0,362,1965,61,4,1,hors zone,0,0,658.6,1,0.0 | |
| 733 | +V0737,RF2400,2026-03-29,371000,103.5,582,1995,31,4,0,hors zone,0,0,977.3,1,0.0 | |
| 734 | +V0194,RF0614,2026-04-05,383500,158.8,489,1946,80,4,0,faible,1,0,256.6,1,-0.0225 | |
| 735 | +V0317,RF1010,2026-04-05,579000,215.2,637,2007,19,3,1,hors zone,0,0,415.6,1,0.0 | |
| 736 | +V0645,RF2108,2026-04-09,348500,153.9,639,1974,52,2,1,hors zone,0,0,867.6,1,0.0 | |
| 737 | +V0322,RF1016,2026-04-13,272500,143.6,561,1943,83,2,0,hors zone,0,0,426.1,1,0.0 | |
| 738 | +V0433,RF1400,2026-04-16,361500,103.5,650,2006,20,4,0,hors zone,0,0,576.4,1,0.0 | |
| 739 | +V0155,RF0508,2026-04-23,384000,146.8,428,2000,26,3,1,faible,1,0,214.6,1,-0.0225 | |
| 740 | +V0414,RF1316,2026-04-23,400000,125.1,536,1986,40,3,1,hors zone,0,0,535.0,1,0.0 | |
| 741 | +V0176,RF0521,2026-04-25,567000,177.2,628,1986,40,5,1,faible,1,0,217.6,1,-0.0225 | |
| 742 | +V0589,RF1904,2026-04-26,376500,121.8,716,1977,49,4,0,hors zone,0,0,780.3,1,0.0 | |
| 743 | +V0453,RF1415,2026-05-06,384000,145.4,571,1973,53,4,1,hors zone,0,0,585.0,1,0.0 | |
| 744 | +V0151,RF0504,2026-05-12,280500,122.0,440,1975,51,2,0,faible,1,0,216.8,1,-0.0225 | |
| 745 | +V0631,RF2021,2026-05-18,396500,163.3,444,1997,29,2,0,hors zone,0,0,823.7,1,0.0 | |
| 746 | +V0108,RF0313,2026-05-25,322000,112.9,630,1974,52,3,0,modérée,1,0,138.1,1,-0.06 | |
| 747 | +V0588,RF1903,2026-05-26,340000,129.8,715,1974,52,3,0,hors zone,0,0,772.6,1,0.0 | |
| 748 | +V0028,RF0101,2026-05-27,398500,145.0,552,1983,43,3,0,élevée,1,1,58.7,1,-0.105 | |
| 749 | +V0314,RF1007,2026-05-29,393500,146.9,872,1964,62,5,0,hors zone,0,0,416.4,1,0.0 | |
| 750 | +V0248,RF0813,2026-05-30,386500,110.5,492,1985,41,4,0,hors zone,0,0,339.6,1,0.0 | |
| 751 | +V0660,RF2117,2026-06-01,734000,207.3,564,2016,10,5,0,hors zone,0,0,860.0,1,0.0 | |
| 752 | +V0725,RF2316,2026-06-10,443000,193.2,715,1958,68,2,1,hors zone,0,0,943.3,1,0.0 | |
| 753 | +V0747,RF2409,2026-06-21,393500,124.9,541,1980,46,2,1,hors zone,0,0,973.3,1,0.0 | |
| 754 | +V0608,RF1924,2026-06-22,345500,128.2,334,1997,29,2,0,hors zone,0,0,774.5,1,0.0 | |
| 755 | +V0109,RF0314,2026-06-24,485500,145.5,571,2016,10,3,1,modérée,1,0,147.4,1,-0.06 | |
| 756 | +V0644,RF2106,2026-06-28,446500,120.0,660,2011,15,4,0,hors zone,0,0,865.3,1,0.0 | |
| 757 | +V0291,RF0919,2026-07-15,407500,138.7,472,1991,35,4,1,hors zone,0,0,372.6,1,0.0 | |
| 758 | +V0487,RF1517,2026-07-22,386000,127.7,442,2001,25,3,1,hors zone,0,0,624.1,1,0.0 | |
| 759 | +V0754,RF2415,2026-07-28,421500,114.6,562,2006,20,3,1,hors zone,0,0,984.4,1,0.0 | |
| 760 | +V0064,RF0123,2026-08-13,394500,170.7,664,1971,55,3,0,élevée,1,1,65.5,1,-0.105 | |
| 761 | +V0166,RF0514,2026-08-18,396500,155.9,362,1993,33,3,1,faible,1,0,225.4,1,-0.0225 | |
| 762 | +V0311,RF1005,2026-08-19,357500,151.3,449,1965,61,2,1,hors zone,0,0,424.3,1,0.0 | |
| 763 | +V0654,RF2112,2026-08-20,358000,128.4,523,2007,19,2,1,hors zone,0,0,861.3,1,0.0 | |
| 764 | +V0452,RF1415,2026-08-23,404500,145.4,571,1973,53,4,1,hors zone,0,0,585.0,1,0.0 | |
| 765 | +V0266,RF0904,2026-08-30,296000,151.8,557,1970,56,3,1,hors zone,0,0,378.2,1,0.0 | |
modified
manuscript/chapters/ch34.tex
+12 −12
@@ -42,7 +42,7 @@ Le Règlement sur les critères de fixation de loyer a été profondément reman | ||
| 42 | 42 | \begin{table}[htbp] |
| 43 | 43 | \caption{Les six critères de l'article 3 du Règlement sur les critères de fixation de loyer (version en vigueur le 1\textsuperscript{er} janvier 2026) et leur règle d'application}\label{tab:34:criteres} |
| 44 | 44 | \centering\small |
| 45 | −\begin{tabularx}{\textwidth}{clYY} | |
| 45 | +\begin{tabularx}{\textwidth}{c>{\raggedright\arraybackslash}p{36mm}YY} | |
| 46 | 46 | \toprule |
| 47 | 47 | N\textsuperscript{o} & Critère & Mesure & Application au logement \\ |
| 48 | 48 | \midrule |
@@ -73,7 +73,7 @@ où $A$ est la moyenne, pour la période de référence, de l'indice des prix à | ||
| 73 | 73 | |
| 74 | 74 | \begin{table}[htbp] |
| 75 | 75 | \caption{Reconstitution du pourcentage de base des fixations de 2025 et de 2026 à partir des moyennes annuelles de l'IPC du Québec (ensemble, 2002 = 100, non désaisonnalisé)}\label{tab:34:ipc} |
| 76 | −\centering\small | |
| 76 | +\centering\footnotesize\setlength{\tabcolsep}{3.2pt} | |
| 77 | 77 | \begin{tabular}{lS[table-format=3.3]S[table-format=3.3]S[table-format=3.3]S[table-format=3.3]S[table-format=1.2]S[table-format=1.2]S[table-format=1.2]S[table-format=1.3]S[table-format=1.1]} |
| 78 | 78 | \toprule |
| 79 | 79 | Fixation & {$A$} & {$B$} & {$C$} & {$D$} & {$\frac{A-B}{B}$ (\%)} & {$\frac{B-C}{C}$ (\%)} & {$\frac{C-D}{D}$ (\%)} & {Formule (\%)} & {TAL (\%)} \\ |
@@ -163,20 +163,20 @@ La part attribuable au logement se calcule sur les revenus de l'immeuble, soit \ | ||
| 163 | 163 | |
| 164 | 164 | \begin{table}[htbp] |
| 165 | 165 | \caption{Calcul pas à pas de l'augmentation mensuelle du logement 103 du cas 3 selon la méthode en vigueur depuis 2026, pourcentages de 2026 (données synthétiques ; reconstitution pédagogique, le formulaire officiel fait foi)}\label{tab:34:fixation} |
| 166 | −\centering\small | |
| 167 | −\begin{tabular}{lS[table-format=-2.1]S[table-format=1.1]S[table-format=2.2]S[table-format=-2.1]S[table-format=1.1]S[table-format=2.2]} | |
| 166 | +\centering\footnotesize\setlength{\tabcolsep}{3pt} | |
| 167 | +\begin{tabular}{p{56mm}S[table-format=-2.1]S[table-format=1.1]S[table-format=3.2]S[table-format=-2.1]S[table-format=1.1]S[table-format=3.2]} | |
| 168 | 168 | \toprule |
| 169 | 169 | & \multicolumn{3}{c}{Variante A (dossier)} & \multicolumn{3}{c}{Variante B (illustrative)} \\ |
| 170 | 170 | \cmidrule(lr){2-4}\cmidrule(lr){5-7} |
| 171 | −Étape & {Variation (\%)} & {Net (\%)} & {\$/mois} & {Variation (\%)} & {Net (\%)} & {\$/mois} \\ | |
| 171 | +Étape & {\makecell{Variation\\(\%)}} & {\makecell{Net\\(\%)}} & {\$/mois} & {\makecell{Variation\\(\%)}} & {\makecell{Net\\(\%)}} & {\$/mois} \\ | |
| 172 | 172 | \midrule |
| 173 | 173 | Loyer au terme du bail (\$/mois) & & & 728.00 & & & 728.00 \\ |
| 174 | 174 | Part attribuable $s_{103}$ (\%) & & & 4.995 & & & 4.995 \\ |
| 175 | 175 | 1\textsuperscript{o} Pourcentage de base ($728 \times 3{,}1\,\%$) & & 3.1 & 22.57 & & 3.1 & 22.57 \\ |
| 176 | −2\textsuperscript{o} Taxes municipales (10 631 \$ $\to$ 10 188 \$ ; variante B : +8 \%) & -4.2 & 0.0 & 0.00 & 8.0 & 4.9 & 2.17 \\ | |
| 176 | +2\textsuperscript{o} Taxes municipales (10 631 \$ $\to$ 10 188 \$ ; B : +8 \%) & -4.2 & 0.0 & 0.00 & 8.0 & 4.9 & 2.17 \\ | |
| 177 | 177 | 3\textsuperscript{o} Taxes scolaires (1 712 \$ $\to$ 1 744 \$) & 1.9 & 0.0 & 0.00 & 1.9 & 0.0 & 0.00 \\ |
| 178 | −4\textsuperscript{o} Assurances (12 614 \$ $\to$ 11 060 \$ ; variante B : +10 \%) & -12.3 & 0.0 & 0.00 & 10.0 & 6.9 & 3.62 \\ | |
| 179 | −5\textsuperscript{o} Dépenses d'immobilisation (0 \$ ; variante B : 85 000 \$ × 5 \%) & & & 0.00 & & 5.0 & 17.69 \\ | |
| 178 | +4\textsuperscript{o} Assurances (12 614 \$ $\to$ 11 060 \$ ; B : +10 \%) & -12.3 & 0.0 & 0.00 & 10.0 & 6.9 & 3.62 \\ | |
| 179 | +5\textsuperscript{o} Dépenses d'immobilisation (0 \$ ; B : 85 000 \$ $\times$ 5 \%) & & & 0.00 & & 5.0 & 17.69 \\ | |
| 180 | 180 | 6\textsuperscript{o} Nouveau service & & & 0.00 & & & 0.00 \\ |
| 181 | 181 | \midrule |
| 182 | 182 | Augmentation avant arrondi & & & 22.57 & & & 46.05 \\ |
@@ -207,7 +207,7 @@ Si la fixation ne résorbe pas l'écart, quels mécanismes le peuvent ? La répo | ||
| 207 | 207 | \begin{table}[htbp] |
| 208 | 208 | \caption{Mécanismes juridiques qui gouvernent le rattrapage des loyers et leur traduction en hypothèses de projection}\label{tab:34:droit} |
| 209 | 209 | \centering\small |
| 210 | −\begin{tabularx}{\textwidth}{lYY} | |
| 210 | +\begin{tabularx}{\textwidth}{>{\raggedright\arraybackslash}p{40mm}YY} | |
| 211 | 211 | \toprule |
| 212 | 212 | Mécanisme (disposition) & Règle & Conséquence pour la projection \\ |
| 213 | 213 | \midrule |
@@ -304,7 +304,7 @@ L'espérance du revenu brut potentiel de l'immeuble s'obtient en appliquant \eqr | ||
| 304 | 304 | \begin{equation}\label{eq:34:rbp} |
| 305 | 305 | \E[RBP_t] = 12 \sum_{i=1}^{n} M_{i,0}\,(1+g_m)^{t}\; \E[q_{i,t}] . |
| 306 | 306 | \end{equation} |
| 307 | −La projection du module commun \texttt{traite.cas3.projection\_dcf}, utilisée aux \cref{ch:30,ch:47}, procède autrement : elle reloue chaque année un nombre entier de logements, $k = \mathrm{round}(\rho n)$, soit deux logements sur douze pour $\rho = \pct{15}$, en choisissant \emph{les plus sous le marché}. Cette hypothèse d'ordre n'est pas neutre. Dans le cas 3, le rapport de loyer de l'immeuble part de \pct{89.0} ; après dix ans, il atteint \pct{99.0} sous l'hypothèse déterministe « les plus sous le marché d'abord », mais seulement \pct{96.2} en espérance sous départs indépendants, et \pct{85.6} sans relocation. L'écart de \dollars{7523} sur le revenu brut de la onzième année se traduit, après capitalisation dans la valeur terminale, par une différence de valeur de \dollars{116937}, soit \pct{5.3} : \dollars{2208566} dans le dossier contre \dollars{2091629} en espérance. L'hypothèse du dossier revient à supposer que le propriétaire sait lesquels de ses locataires partiront, et que ce sont ceux dont le départ lui rapporte le plus ; elle est favorable au locateur. L'hypothèse d'indépendance est plus prudente mais ignore qu'un locataire très avantagé déménage moins qu'un autre, ce qui la rend à son tour optimiste. La vérité se situe vraisemblablement en deçà des deux, et c'est pourquoi la \cref{sec:34:scenarios} fait varier la rotation sur une large plage plutôt que de chercher la « bonne » valeur. | |
| 307 | +La projection du module commun du dossier, \texttt{traite.cas3.projection\_dcf}, que les \cref{ch:30,ch:47} utilisent, procède autrement : elle reloue chaque année un nombre entier de logements, $k = \mathrm{round}(\rho n)$, soit deux logements sur douze pour $\rho = \pct{15}$, en choisissant \emph{les plus sous le marché}. Cette hypothèse d'ordre n'est pas neutre. Dans le cas 3, le rapport de loyer de l'immeuble part de \pct{89.0} ; après dix ans, il atteint \pct{99.0} sous l'hypothèse déterministe « les plus sous le marché d'abord », mais seulement \pct{96.2} en espérance sous départs indépendants, et \pct{85.6} sans relocation. L'écart de \dollars{7523} sur le revenu brut de la onzième année se traduit, après capitalisation dans la valeur terminale, par une différence de valeur de \dollars{116937}, soit \pct{5.3} : \dollars{2208566} dans le dossier contre \dollars{2091629} en espérance. L'hypothèse du dossier revient à supposer que le propriétaire sait lesquels de ses locataires partiront, et que ce sont ceux dont le départ lui rapporte le plus ; elle est favorable au locateur. L'hypothèse d'indépendance est plus prudente mais ignore qu'un locataire très avantagé déménage moins qu'un autre, ce qui la rend à son tour optimiste. La vérité se situe vraisemblablement en deçà des deux, et c'est pourquoi la \cref{sec:34:scenarios} fait varier la rotation sur une large plage plutôt que de chercher la « bonne » valeur. | |
| 308 | 308 | |
| 309 | 309 | \subsection{Rattrapage partiel et récupération des dépenses d'immobilisation} |
| 310 | 310 | |
@@ -338,10 +338,10 @@ Le \cref{tab:34:grille} croise trois taux de rotation, \pct{5}, \pct{15} et \pct | ||
| 338 | 338 | |
| 339 | 339 | \begin{table}[htbp] |
| 340 | 340 | \caption{Valeur DCF du cas 3 selon la rotation annuelle et le pourcentage de base de la fixation (croissance du marché \pct{3.5}, relocation intégrale au marché, autres hypothèses du dossier ; mode déterministe)}\label{tab:34:grille} |
| 341 | −\centering\small | |
| 341 | +\centering\footnotesize\setlength{\tabcolsep}{4pt} | |
| 342 | 342 | \begin{tabular}{S[table-format=2.0]S[table-format=1.1]S[table-format=6.0]S[table-format=-1.1]S[table-format=6.0]S[table-format=7.0]S[table-format=-2.1]} |
| 343 | 343 | \toprule |
| 344 | −{Rotation (\%)} & {Base (\%)} & {$RBP_{11}$ (\$)} & {Écart au marché en $t = 11$ (\%)} & {$\mathit{VA}_{\text{écart}}$ (\$)} & {Valeur $V_0$ (\$)} & {Écart à la base (\%)} \\ | |
| 344 | +{\makecell{Rotation\\(\%)}} & {\makecell{Base\\(\%)}} & {\makecell{$RBP_{11}$\\(\$)}} & {\makecell{Écart au marché\\en $t = 11$ (\%)}} & {\makecell{$\mathit{VA}_{\text{écart}}$\\(\$)}} & {\makecell{Valeur $V_0$\\(\$)}} & {\makecell{Écart à la\\base (\%)}} \\ | |
| 345 | 345 | \midrule |
| 346 | 346 | 5 & 2.0 & 243052 & 7.4 & 135975 & 1974822 & -10.6 \\ |
| 347 | 347 | 5 & 3.1 & 256471 & 2.3 & 93090 & 2140916 & -3.1 \\ |
modified
manuscript/chapters/ch35.tex
+30 −27
@@ -18,7 +18,7 @@ Les chapitres précédents ont supposé, chaque fois qu'un comparable vendu il y | ||
| 18 | 18 | |
| 19 | 19 | \subsection{Trois usages, une même exigence} |
| 20 | 20 | |
| 21 | −L'évaluateur rencontre les indices de prix dans trois situations. La première est l'\motcle{ajustement de conditions du marché} d'un comparable : le \cref{ch:17} a montré que le prix d'une vente ancienne doit être multiplié par le rapport de l'indice à la date d'évaluation à l'indice à la date de la vente, et que cet ajustement est souvent le plus important de la grille lorsque les prix évoluent de plusieurs points par année. La deuxième est le \emph{suivi du marché} : l'étude de marché du \cref{ch:15}, le rapport d'évaluation, l'analyse du cycle du \cref{ch:03} et la mesure du risque de portefeuille du \cref{ch:48} ont besoin d'une mesure de la variation des prix dégagée des accidents de composition. La troisième, moins familière au praticien québécois, est la construction de \emph{produits financiers} adossés aux prix des maisons : indexation des contrats, assurance de la valeur, contrats à terme et options, que \textcite{shiller2015} a longtemps défendus comme instruments de partage du risque immobilier des ménages et qui exigent un indice publié, reproductible et peu révisé. Les trois usages posent la même exigence : l'indice doit mesurer la variation du prix d'un logement de caractéristiques \emph{constantes}, et non la variation du prix des logements qui se trouvent avoir été vendus. | |
| 21 | +L'évaluateur rencontre les indices de prix dans trois situations. La première situation est celle de l'\motcle{ajustement de conditions du marché} d'un comparable : le \cref{ch:17} a montré que le prix d'une vente ancienne doit être multiplié par le rapport de l'indice à la date d'évaluation à l'indice à la date de la vente, et que cet ajustement est souvent le plus important de la grille lorsque les prix évoluent de plusieurs points par année. La deuxième est le \emph{suivi du marché} : l'étude de marché du \cref{ch:15}, le rapport d'évaluation, l'analyse du cycle du \cref{ch:03} et la mesure du risque de portefeuille du \cref{ch:48} ont besoin d'une mesure de la variation des prix dégagée des accidents de composition. La troisième, moins familière au praticien québécois, est la construction de \emph{produits financiers} adossés aux prix des maisons : indexation des contrats, assurance de la valeur, contrats à terme et options, que \textcite{shiller2015} a longtemps défendus comme instruments de partage du risque immobilier des ménages et qui exigent un indice publié, reproductible et peu révisé. Les trois usages posent la même exigence : l'indice doit mesurer la variation du prix d'un logement de caractéristiques \emph{constantes}, et non la variation du prix des logements qui se trouvent avoir été vendus. | |
| 22 | 22 | |
| 23 | 23 | \begin{definition}{Indice de prix à qualité constante}{indice-qualite-constante} |
| 24 | 24 | Soit $\E[P \mid \mathbf{x}, t]$ le prix espéré à la date $t$ d'un logement de caractéristiques $\mathbf{x}$, au sens de l'\cref{eq:16:esperance}. L'\motcle{indice de prix à qualité constante} entre la période de base $0$ et la période $t$ est le rapport |
@@ -59,9 +59,10 @@ Trois précisions évitent des confusions courantes. D'abord, un indice de prix | ||
| 59 | 59 | |
| 60 | 60 | Le \motcle{prix médian} est la statistique la plus diffusée, parce qu'elle est simple, robuste aux ventes extrêmes et calculable sans aucune information sur les caractéristiques des biens vendus. C'est précisément cette dernière propriété qui en fait un mauvais indice : si la médiane ne voit pas les caractéristiques, elle ne peut pas les tenir constantes. Supposons que la fonction de prix soit celle du modèle de référence, $\ln P_i = \alpha + \mathbf{x}_i\T\boldsymbol\beta + \tau_{t(i)} + \varepsilon_i$. La moyenne des logarithmes des prix de la période $t$, c'est-à-dire le logarithme de leur moyenne géométrique $\bar P^{\,g}_t$, s'écrit |
| 61 | 61 | \begin{equation}\label{eq:35:decomposition} |
| 62 | −\ln \bar P^{\,g}_t = \alpha + \bar{\mathbf{x}}_t\T\boldsymbol\beta + \tau_t + \bar\varepsilon_t , | |
| 63 | −\qquad\text{d'où}\qquad | |
| 64 | −\ln \frac{\bar P^{\,g}_t}{\bar P^{\,g}_0} = \underbrace{(\tau_t - \tau_0)}_{\text{variation pure}} + \underbrace{(\bar{\mathbf{x}}_t - \bar{\mathbf{x}}_0)\T\boldsymbol\beta}_{\text{effet de composition}} + (\bar\varepsilon_t - \bar\varepsilon_0), | |
| 62 | +\begin{aligned} | |
| 63 | +\ln \bar P^{\,g}_t &= \alpha + \bar{\mathbf{x}}_t\T\boldsymbol\beta + \tau_t + \bar\varepsilon_t , \\ | |
| 64 | +\ln \frac{\bar P^{\,g}_t}{\bar P^{\,g}_0} &= \underbrace{(\tau_t - \tau_0)}_{\text{variation pure}} + \underbrace{(\bar{\mathbf{x}}_t - \bar{\mathbf{x}}_0)\T\boldsymbol\beta}_{\text{effet de composition}} + (\bar\varepsilon_t - \bar\varepsilon_0), | |
| 65 | +\end{aligned} | |
| 65 | 66 | \end{equation} |
| 66 | 67 | où $\bar{\mathbf{x}}_t$ est le vecteur des caractéristiques moyennes des ventes de la période $t$. La variation de la moyenne géométrique est la somme de la variation pure des prix, qui est ce que l'indice doit mesurer, et de l'effet de composition, qui est la valeur, aux prix implicites du modèle, du déplacement des caractéristiques moyennes des biens vendus. Si les ventes de la période $t$ comptent plus de maisons grandes, récentes ou situées dans un secteur cher que celles de la période $0$, la moyenne monte sans qu'aucun prix n'ait bougé. La médiane obéit à la même logique, de façon moins algébrique parce qu'elle n'est pas une moyenne, mais avec une sensibilité comparable à la composition. Lorsque les indicatrices temporelles sont estimées par les moindres carrés avec les mêmes données, les résidus somment à zéro dans chaque période et l'\cref{eq:35:decomposition} est une identité exacte : la moyenne géométrique corrigée de la composition, $\exp(\ln \bar P^{\,g}_t - \bar{\mathbf{x}}_t\T\hat{\boldsymbol\beta})$, reproduit exactement l'indice hédonique. C'est la façon la plus directe de voir qu'un indice hédonique \emph{est} une moyenne de prix corrigée de la composition. |
| 67 | 68 | |
@@ -99,9 +100,11 @@ et enchaîne les variations. Les prix implicites $\boldsymbol\beta_t$ sont libre | ||
| 99 | 100 | |
| 100 | 101 | La troisième construction renoue avec la théorie classique des indices. On estime un modèle hédonique \emph{par période}, puis l'on impute à chaque logement vendu à la période de base le prix qu'il aurait obtenu à la période $t$, ou réciproquement. En logarithmes, avec $\hat p_s(\mathbf{x}) = \hat\alpha_s + \mathbf{x}\T\hat{\boldsymbol\beta}_s$ le prix prédit à la période $s$ pour les caractéristiques $\mathbf{x}$, l'indice d'\motcle{imputation} géométrique de Laspeyres (panier de la période de base) et celui de Paasche (panier de la période courante) sont |
| 101 | 102 | \begin{equation}\label{eq:35:imputation} |
| 102 | −\ln \hat I^{\,L}_t = \frac{1}{n_0}\sum_{i \in 0}\left[\hat p_t(\mathbf{x}_i) - \hat p_0(\mathbf{x}_i)\right], \qquad | |
| 103 | −\ln \hat I^{\,P}_t = \frac{1}{n_t}\sum_{i \in t}\left[\hat p_t(\mathbf{x}_i) - \hat p_0(\mathbf{x}_i)\right], \qquad | |
| 104 | −\ln \hat I^{\,F}_t = \tfrac{1}{2}\left(\ln \hat I^{\,L}_t + \ln \hat I^{\,P}_t\right), | |
| 103 | +\begin{aligned} | |
| 104 | +\ln \hat I^{\,L}_t &= \frac{1}{n_0}\sum_{i \in 0}\left[\hat p_t(\mathbf{x}_i) - \hat p_0(\mathbf{x}_i)\right], \qquad | |
| 105 | +\ln \hat I^{\,P}_t = \frac{1}{n_t}\sum_{i \in t}\left[\hat p_t(\mathbf{x}_i) - \hat p_0(\mathbf{x}_i)\right], \\ | |
| 106 | +\ln \hat I^{\,F}_t &= \tfrac{1}{2}\left(\ln \hat I^{\,L}_t + \ln \hat I^{\,P}_t\right), | |
| 107 | +\end{aligned} | |
| 105 | 108 | \end{equation} |
| 106 | 109 | l'indice de Fisher étant la moyenne géométrique des deux. La version dite à \emph{double imputation} remplace, pour le panier de base, le prix observé par le prix prédit dans la période de base aussi, ce que l'\cref{eq:35:imputation} fait déjà en utilisant $\hat p_0(\mathbf{x}_i)$ plutôt que $\ln P_i$ : les résidus des ventes individuelles n'entrent pas dans l'indice. Le manuel international recommande cette famille, et en particulier l'indice de Fisher à double imputation, parce qu'elle laisse les prix implicites varier librement d'une période à l'autre et qu'elle se rattache aux indices économiques classiques \parencite{eurostat2013,dehaanDiewert2013}. Son exigence pratique est lourde : il faut assez de ventes dans \emph{chaque} période pour estimer un modèle complet, ce qui exclut les marchés minces et les périodes courtes, sauf à réduire le modèle. |
| 107 | 110 | |
@@ -241,14 +244,14 @@ L'exemple découpe les 600 ventes en quinze trimestres, de 2023T1 à 2026T3, qui | ||
| 241 | 244 | |
| 242 | 245 | \begin{table}[htbp] |
| 243 | 246 | \caption{Jeu de reventes synthétique dérivé des 600 ventes du cas 1 : caractéristiques des 220 paires}\label{tab:35:reventes} |
| 244 | −\centering\small | |
| 245 | −\begin{tabular}{@{}l S[table-format=3.0] S[table-format=3.0] S[table-format=2.1] S[table-format=2.1] S[table-format=1.4] S[table-format=1.4]@{}} | |
| 247 | +\centering\footnotesize\setlength{\tabcolsep}{2.4pt} | |
| 248 | +\begin{tabular}{@{}l S[table-format=3.0] S[table-format=2.0] S[table-format=2.1] S[table-format=2.1] S[table-format=1.4] S[table-format=1.4]@{}} | |
| 246 | 249 | \toprule |
| 247 | −Ensemble & {Paires} & {Rénovées} & {Intervalle médian (mois)} & {Intervalle moyen (mois)} & {Moy. $\ln(P_2/P_1)$} & {É.-t. $\ln(P_2/P_1)$} \\ | |
| 250 | +Ensemble & {Paires} & {Rénovées} & {\shortstack[c]{Intervalle\\médian (mois)}} & {\shortstack[c]{Intervalle\\moyen (mois)}} & {\shortstack[c]{Moyenne\\$\ln(P_2/P_1)$}} & {\shortstack[c]{Écart-type\\$\ln(P_2/P_1)$}} \\ | |
| 248 | 251 | \midrule |
| 249 | 252 | Toutes les paires & 220 & 47 & 10.0 & 12.8 & 0.0547 & 0.1441 \\ |
| 250 | 253 | Reventes ordinaires & 173 & 0 & {---} & {---} & 0.0436 & 0.1416 \\ |
| 251 | −Reventes rapides rénovées (3 à 9 mois) & 47 & 47 & {---} & {---} & 0.0957 & 0.1476 \\ | |
| 254 | +Reventes rapides rénovées (3--9 mois) & 47 & 47 & {---} & {---} & 0.0957 & 0.1476 \\ | |
| 252 | 255 | Intervalle d'au moins 12 mois & 97 & 0 & {---} & {---} & {---} & {---} \\ |
| 253 | 256 | Intervalle d'au moins 18 mois & 45 & 0 & {---} & {---} & {---} & {---} \\ |
| 254 | 257 | \bottomrule |
@@ -258,7 +261,7 @@ Intervalle d'au moins 18 mois & 45 & 0 & {---} & {---} & {---} & {---} \\ | ||
| 258 | 261 | |
| 259 | 262 | \begin{table}[htbp] |
| 260 | 263 | \caption{Six indices de prix du cas 1 par trimestre, base 2023T1 = 100, et vérité de génération (données synthétiques)}\label{tab:35:indices} |
| 261 | −\centering\footnotesize\setlength{\tabcolsep}{2.6pt} | |
| 264 | +\centering\footnotesize\setlength{\tabcolsep}{2.1pt} | |
| 262 | 265 | \begin{tabular}{@{}l S[table-format=2.0] S[table-format=3.2] S[table-format=3.2] S[table-format=3.2] c S[table-format=3.2] S[table-format=3.2] c S[table-format=3.2] S[table-format=3.2]@{}} |
| 263 | 266 | \toprule |
| 264 | 267 | Trim. & {$n$} & {Vérité} & {Médiane} & {Hédon.} & IC 95 \% hédon. & {Chaîné} & {BMN} & IC 95 \% BMN & {Case--Shiller} & {SPAR} \\ |
@@ -285,15 +288,15 @@ Trim. & {$n$} & {Vérité} & {Médiane} & {Hédon.} & IC 95 \% hédon. & {Chaîn | ||
| 285 | 288 | |
| 286 | 289 | \begin{table}[htbp] |
| 287 | 290 | \caption{Précision des indices par rapport à la vérité de génération, 2023T1--2026T3 (données synthétiques)}\label{tab:35:precision} |
| 288 | −\centering\small\setlength{\tabcolsep}{3.5pt} | |
| 291 | +\centering\footnotesize\setlength{\tabcolsep}{3pt} | |
| 289 | 292 | \begin{tabular}{@{}l S[table-format=2.2] S[table-format=-2.2] S[table-format=2.2] S[table-format=2.2] S[table-format=2.2] S[table-format=1.2] S[table-format=2.2]@{}} |
| 290 | 293 | \toprule |
| 291 | −Indice & {Hausse totale (\%)} & {Écart final (\%)} & {EAM (pts)} & {RMSE (pts)} & {Écart max (\%)} & {É.-t. des var. trim. (pts)} & {Largeur IC finale (\%)} \\ | |
| 294 | +Indice & {\shortstack[c]{Hausse\\totale (\%)}} & {\shortstack[c]{Écart\\final (\%)}} & {\shortstack[c]{EAM\\(pts)}} & {\shortstack[c]{RMSE\\(pts)}} & {\shortstack[c]{Écart\\max (\%)}} & {\shortstack[c]{É.-t. var.\\trim. (pts)}} & {\shortstack[c]{Largeur IC\\finale (\%)}} \\ | |
| 292 | 295 | \midrule |
| 293 | 296 | Médiane des prix & 28.63 & 10.54 & 10.97 & 11.76 & 19.06 & 5.50 & {---} \\ |
| 294 | 297 | Moyenne géométrique & 27.78 & 9.81 & 7.93 & 8.54 & 13.49 & 4.02 & {---} \\ |
| 295 | −Hédonique, indicatrices trimestrielles & 22.04 & 4.87 & 2.81 & 3.04 & 5.03 & 1.77 & 7.47 \\ | |
| 296 | −Hédonique chaîné (périodes adjacentes) & 21.69 & 4.57 & 4.01 & 4.15 & 6.66 & 1.88 & {---} \\ | |
| 298 | +Hédonique, indicatrices trim. & 22.04 & 4.87 & 2.81 & 3.04 & 5.03 & 1.77 & 7.47 \\ | |
| 299 | +Hédonique chaîné & 21.69 & 4.57 & 4.01 & 4.15 & 6.66 & 1.88 & {---} \\ | |
| 297 | 300 | Ventes répétées, BMN & 20.68 & 3.70 & 3.02 & 3.51 & 6.42 & 3.07 & 20.83 \\ |
| 298 | 301 | Ventes répétées, Case--Shiller & 20.48 & 3.53 & 2.90 & 3.40 & 6.33 & 3.10 & 20.62 \\ |
| 299 | 302 | SPAR géométrique & 15.06 & -1.13 & 1.26 & 1.64 & 3.83 & 1.71 & 8.18 \\ |
@@ -321,14 +324,14 @@ Vérité de génération & 16.37 & 0.00 & 0.00 & 0.00 & 0.00 & 1.09 & {---} \\ | ||
| 321 | 324 | |
| 322 | 325 | \begin{table}[htbp] |
| 323 | 326 | \caption{Deuxième étape de Case--Shiller : variance des résidus BMN selon l'intervalle et poids relatifs (données synthétiques)}\label{tab:35:cs} |
| 324 | −\centering\small | |
| 327 | +\centering\footnotesize\setlength{\tabcolsep}{3.5pt} | |
| 325 | 328 | \begin{tabular}{@{}l S[table-format=1.5] S[table-format=-1.5] S[table-format=1.3] S[table-format=1.3] S[table-format=1.3]@{}} |
| 326 | 329 | \toprule |
| 327 | −Ensemble de paires & {$\hat a_0$ ($\approx 2\sigma^2$)} & {$\hat a_1$ ($\approx \delta^2$ par trim.)} & {Poids relatif, 4 trim.} & {Poids relatif, 8 trim.} & {Poids relatif, 12 trim.} \\ | |
| 330 | +Ensemble de paires & {$\hat a_0$ ($\approx 2\sigma^2$)} & {$\hat a_1$ ($\approx \delta^2$/trim.)} & {\shortstack[c]{Poids relatif\\4 trim.}} & {\shortstack[c]{Poids relatif\\8 trim.}} & {\shortstack[c]{Poids relatif\\12 trim.}} \\ | |
| 328 | 331 | \midrule |
| 329 | 332 | Toutes les paires (220) & 0.02079 & -0.00023 & 1.007 & 1.027 & 1.050 \\ |
| 330 | 333 | Sans les reventes rénovées (173) & 0.01574 & 0.00053 & 0.955 & 0.903 & 0.859 \\ |
| 331 | −Loi de génération ($2\sigma^2 = 0{,}01445$ ; $\delta^2 = 0{,}0012$) & 0.01445 & 0.00120 & 0.902 & 0.807 & 0.737 \\ | |
| 334 | +Loi de génération & 0.01445 & 0.00120 & 0.902 & 0.807 & 0.737 \\ | |
| 332 | 335 | \bottomrule |
| 333 | 336 | \end{tabular} |
| 334 | 337 | \tabsource{\synth{\texttt{analysis/python/cases/ch35\_indices.py}}. Poids relatif : $\sqrt{(\hat a_0 + \hat a_1)/(\hat a_0 + \hat a_1\Delta)}$, rapporté à une paire d'un trimestre.} |
@@ -343,13 +346,13 @@ Loi de génération ($2\sigma^2 = 0{,}01445$ ; $\delta^2 = 0{,}0012$) & 0.01445 | ||
| 343 | 346 | |
| 344 | 347 | \begin{table}[htbp] |
| 345 | 348 | \caption{Ajustement de conditions du marché du comparable G0404 (vendu \dollars{506500} en 2025T2, évalué en 2026T3, 15,2 mois) selon l'indice retenu (données synthétiques)}\label{tab:35:ajustement} |
| 346 | −\centering\small | |
| 349 | +\centering\footnotesize | |
| 347 | 350 | \begin{tabular}{@{}l S[table-format=3.2] S[table-format=3.2] S[table-format=1.2] S[table-format=5.0]@{}} |
| 348 | 351 | \toprule |
| 349 | 352 | Indice & {$I_{2025\mathrm{T}2}$} & {$I_{2026\mathrm{T}3}$} & {Variation (\%)} & {Ajustement (\$)} \\ |
| 350 | 353 | \midrule |
| 351 | −Vérité de génération (tendance 4,43 \%/an, 15,2 mois) & {---} & {---} & 5.64 & 28558 \\ | |
| 352 | −Tendance linéaire du modèle de référence (0,364 \%/mois) & {---} & {---} & 5.67 & 28733 \\ | |
| 354 | +Vérité de génération (4,43 \%/an, 15,2 mois) & {---} & {---} & 5.64 & 28558 \\ | |
| 355 | +Tendance du modèle de référence (0,364 \%/mois) & {---} & {---} & 5.67 & 28733 \\ | |
| 353 | 356 | Hédonique, indicatrices trimestrielles & 112.13 & 122.04 & 8.84 & 44760 \\ |
| 354 | 357 | Hédonique chaîné & 114.68 & 121.69 & 6.12 & 30987 \\ |
| 355 | 358 | Ventes répétées, BMN & 113.42 & 120.68 & 6.40 & 32406 \\ |
@@ -372,24 +375,24 @@ Quatre décisions gouvernent un indice de ventes répétées ou hédonique : la | ||
| 372 | 375 | \toprule |
| 373 | 376 | Variante & {Paires ou ventes} & {Valeur ou hausse} & {EAM (pts)} & {Note} \\ |
| 374 | 377 | \midrule |
| 375 | −\multicolumn{5}{@{}l}{\emph{Révision de l'indice BMN de 2025T2 selon le millésime (paires dont la seconde vente est connue à la date de publication)}} \\ | |
| 378 | +\multicolumn{5}{@{}l}{\emph{Révision de l'indice BMN de 2025T2 selon le millésime de publication}} \\ | |
| 376 | 379 | Publié en 2025T2 & 110 & 116.39 & {---} & {---} \\ |
| 377 | 380 | Publié en 2025T4 & 158 & 114.69 & {---} & -1.70 \\ |
| 378 | 381 | Publié en 2026T3 & 220 & 113.42 & {---} & -2.97 \\ |
| 379 | −Pour mémoire, 2024T4 : publié en 2025T2 puis en 2026T3 & {---} & {114,45 → 109,99} & {---} & -4.46 \\ | |
| 382 | +Indice de 2024T4 : 114,45 publié en 2025T2 ; publié en 2026T3 & 220 & 109.99 & {---} & -4.46 \\ | |
| 380 | 383 | \midrule |
| 381 | −\multicolumn{5}{@{}l}{\emph{Filtre des reventes rapides (hausse totale BMN 2023T1--2026T3, en \% ; vérité 16,37)}} \\ | |
| 384 | +\multicolumn{5}{@{}l}{\emph{Filtre des reventes rapides (hausse totale BMN en \% ; vérité 16,37)}} \\ | |
| 382 | 385 | Toutes les paires & 220 & 20.68 & 3.02 & {---} \\ |
| 383 | 386 | Intervalle d'au moins 12 mois & 97 & 17.98 & 2.76 & {---} \\ |
| 384 | 387 | Intervalle d'au moins 18 mois & 45 & 19.41 & 6.56 & {---} \\ |
| 385 | 388 | Sans les reventes rénovées (oracle, inobservable en pratique) & 173 & 18.39 & 2.01 & {---} \\ |
| 386 | 389 | \midrule |
| 387 | −\multicolumn{5}{@{}l}{\emph{Taille de l'échantillon de paires (BMN ; moyenne de 200 sous-échantillons aléatoires)}} \\ | |
| 390 | +\multicolumn{5}{@{}l}{\emph{Taille de l'échantillon de paires (BMN ; 200 sous-échantillons aléatoires)}} \\ | |
| 388 | 391 | Toutes les paires & 220 & {---} & 3.02 & {---} \\ |
| 389 | 392 | Moitié des paires & 110 & {---} & 4.26 & 4.77 \\ |
| 390 | 393 | Quart des paires & 55 & {---} & 6.46 & 8.96 \\ |
| 391 | 394 | \midrule |
| 392 | −\multicolumn{5}{@{}l}{\emph{Fenêtre d'estimation de l'indice hédonique (huit derniers trimestres, rechaîné sur 2024T4)}} \\ | |
| 395 | +\multicolumn{5}{@{}l}{\emph{Fenêtre d'estimation de l'indice hédonique (rechaîné sur 2024T4)}} \\ | |
| 393 | 396 | Fenêtre complète (15 trimestres) & 600 & 122.04 & {---} & {---} \\ |
| 394 | 397 | Huit derniers trimestres & 295 & 122.38 & {---} & 0.37 \\ |
| 395 | 398 | \bottomrule |
added
references/lib-ch34.bib
+16 −0
@@ -0,0 +1,16 @@ | ||
| 1 | +% ============================================================================== | |
| 2 | +% lib-ch34.bib — Chapitre 34 (TAL, encadrement locatif et scénarios) | |
| 3 | +% Aucune nouvelle entrée : le chapitre ne cite que des clés déjà présentes dans | |
| 4 | +% references/library.bib : | |
| 5 | +% droit et TAL : ccq, loiTAL, reglCriteresFixation, decret1455_2025, reglFormulairesBail, | |
| 6 | +% loi31_2024, loi65_2024, talIndices2026, talCommunique2026, talCommunique2025, | |
| 7 | +% talOutilCalcul2026, talFormulaireRN2026, talAugmentationLoyer, talResumesDecisions, | |
| 8 | +% talformulaire2021, quebecNouvelleMethodeLoyers2025, oeaqNormes ; | |
| 9 | +% données : statcanipc, schlLocatif2025, schlrmltables2025 ; | |
| 10 | +% littérature : arnott1995, glaeserLuttmer2003, sims2007, autorPalmerPathak2014, | |
| 11 | +% diamondMcquadeQian2019, kholodilin2024, smith1988, fallisSmith1985. | |
| 12 | +% Besoin signalé (non ajouté, faute de source arbitrée identifiée) : étude empirique du régime | |
| 13 | +% québécois de fixation des loyers — voir verification/chapters/ch34-claims.csv CLM-34-21. | |
| 14 | +% Suggestion pour library.bib : préciser dans la note de talIndices2026 que la page a été revérifiée | |
| 15 | +% le 2026-10-31 et que les pourcentages 2027 n'étaient pas publiés. | |
| 16 | +% ============================================================================== | |
added
references/lib-ch35.bib
+23 −0
@@ -0,0 +1,23 @@ | ||
| 1 | +% Nouvelles références du chapitre 35 — à fusionner dans library.bib par l'orchestrateur. | |
| 2 | +% Vérifiées le 2026-10-31 par consultation directe des pages (curl ; housepriceindex.ca a un certificat TLS invalide, page lue en mode non vérifié). | |
| 3 | +% Non citées par \parencite dans ch35.tex (règle de collision : seules les clés déjà dans library.bib y sont citées) ; le chapitre les | |
| 4 | +% donne en toutes lettres dans deux notes de bas de page. Après fusion, remplacer ces notes par \parencite{teranetMethodologie2026} | |
| 5 | +% et \parencite{statcaniplnimdb2310}. | |
| 6 | + | |
| 7 | +@online{teranetMethodologie2026, | |
| 8 | + author = {{Teranet} and {Banque Nationale du Canada}}, | |
| 9 | + title = {Our Methodology --- Teranet--National Bank House Price Index}, | |
| 10 | + year = {2026}, | |
| 11 | + url = {https://housepriceindex.ca/about/our-methodology/}, | |
| 12 | + urldate = {2026-10-31}, | |
| 13 | + note = {Méthode des ventes répétées (« properties with at least two sales ») ; exclusions : ventes non sincères, changement de type de propriété (rénovations), erreurs de données, rotation « biannual or higher » ; exemple simplifié à trois maisons ; la description complète annoncée comme téléchargeable n'était pas accessible depuis la page. Certificat TLS du site invalide à la date de consultation} | |
| 14 | +} | |
| 15 | + | |
| 16 | +@online{statcaniplnimdb2310, | |
| 17 | + author = {{Statistique Canada}}, | |
| 18 | + title = {Indice des prix des logements neufs (IPLN) --- fiche de l'enquête n\textsuperscript{o} 2310, Base de métadonnées intégrée}, | |
| 19 | + year = {2026}, | |
| 20 | + url = {https://www23.statcan.gc.ca/imdb/p2SV_f.pl?Function=getSurvey&SDDS=2310}, | |
| 21 | + urldate = {2026-10-31}, | |
| 22 | + note = {Mensuel ; prix de vente des maisons neuves établis par les entrepreneurs « lorsque les spécifications détaillées de chaque maison demeurent les mêmes pendant deux périodes consécutives » ; ajustement de qualité ; « n'est pas révisé et n'est pas désaisonnalisé » ; base décembre 2016 = 100 ; renvoie à la méthodologie détaillée 62F0014M2022003 (non consultée)} | |
| 23 | +} | |
modified
references/library.bib
+18 −0
@@ -5148,3 +5148,21 @@ | ||
| 5148 | 5148 | urldate = {2026-10-31}, |
| 5149 | 5149 | note = {Frais d'adhésion de 556,48~\$ taxes comprises (non remboursables) ; cautionnement selon la cote qualité et le nombre d'unités (garanties minimales 55~000, 70~000 et 100~000~\$) ; frais d'enregistrement des bâtiments non chiffrés sur la page} |
| 5150 | 5150 | } |
| 5151 | + | |
| 5152 | +@online{teranetMethodologie2026, | |
| 5153 | + author = {{Teranet} and {Banque Nationale du Canada}}, | |
| 5154 | + title = {Our Methodology --- Teranet--National Bank House Price Index}, | |
| 5155 | + year = {2026}, | |
| 5156 | + url = {https://housepriceindex.ca/about/our-methodology/}, | |
| 5157 | + urldate = {2026-10-31}, | |
| 5158 | + note = {Méthode des ventes répétées (« properties with at least two sales ») ; exclusions : ventes non sincères, changement de type de propriété (rénovations), erreurs de données, rotation « biannual or higher » ; exemple simplifié à trois maisons ; la description complète annoncée comme téléchargeable n'était pas accessible depuis la page. Certificat TLS du site invalide à la date de consultation} | |
| 5159 | +} | |
| 5160 | + | |
| 5161 | +@online{statcaniplnimdb2310, | |
| 5162 | + author = {{Statistique Canada}}, | |
| 5163 | + title = {Indice des prix des logements neufs (IPLN) --- fiche de l'enquête n\textsuperscript{o} 2310, Base de métadonnées intégrée}, | |
| 5164 | + year = {2026}, | |
| 5165 | + url = {https://www23.statcan.gc.ca/imdb/p2SV_f.pl?Function=getSurvey&SDDS=2310}, | |
| 5166 | + urldate = {2026-10-31}, | |
| 5167 | + note = {Mensuel ; prix de vente des maisons neuves établis par les entrepreneurs « lorsque les spécifications détaillées de chaque maison demeurent les mêmes pendant deux périodes consécutives » ; ajustement de qualité ; « n'est pas révisé et n'est pas désaisonnalisé » ; base décembre 2016 = 100 ; renvoie à la méthodologie détaillée 62F0014M2022003 (non consultée)} | |
| 5168 | +} | |
modified
solutions/ch35.tex
+38 −0
@@ -1 +1,39 @@ | ||
| 1 | 1 | % Solutions du chapitre 35 — Indices immobiliers et ventes répétées |
| 2 | +% Chiffres recalculés par analysis/python/cases/ch35_indices.py (fonction exercices) et vérifiés par analysis/python/tests/test_ch35.py. | |
| 3 | + | |
| 4 | +\begin{solution}{ex:35:1}{Médiane et effet de composition} | |
| 5 | +La médiane varie de $480\,000/450\,000 - 1 = 6{,}67$ \%. L'effet de composition attribuable à la superficie est, avec la forme log-log du modèle de référence, $(150/140)^{0{,}539} - 1 = \exp(0{,}539 \ln 1{,}0714) - 1 = \exp(0{,}03720) - 1 = 3{,}79$ \% : les maisons vendues la seconde année valaient, à prix constants, 3,79 \% de plus par leur seule taille. La variation pure est le rapport des deux facteurs, $1{,}0667 / 1{,}0379 - 1 = 2{,}77$ \%, soit moins de la moitié de la hausse affichée par la médiane. La correction est incomplète : il faudrait connaître, pour les deux années, la répartition des ventes par secteur (la prime d'Aylmer sur Hull vaut 8,9 \% dans le modèle de référence), l'âge médian, la qualité, le type de bâtiment et les équipements ; c'est exactement ce que la décomposition \eqref{eq:35:decomposition} fait pour toutes les caractéristiques à la fois, et ce que la stratification ne fait que pour les strates observées. | |
| 6 | +\end{solution} | |
| 7 | + | |
| 8 | +\begin{solution}{ex:35:2}{Lire un indice à indicatrices temporelles} | |
| 9 | +Les indices sont $100\exp(\hat\tau_t)$ : 100 ; $100\exp(0{,}031) = 103{,}15$ ; $100\exp(0{,}058) = 105{,}97$ ; $100\exp(0{,}102) = 110{,}74$. La variation entre le deuxième et le troisième trimestre est $\exp(0{,}102 - 0{,}058) - 1 = \exp(0{,}044) - 1 = 4{,}50$ \%, ou de façon équivalente $110{,}74/105{,}97 - 1$. L'intervalle de confiance à 95 \% de l'indice du troisième trimestre est $100\exp(0{,}102 \pm 1{,}96 \times 0{,}021) = [100\exp(0{,}0608) ; 100\exp(0{,}1432)] = [106{,}27 ; 115{,}39]$, soit environ $\pm 4{,}1$ \% autour de 110,74 : avec une erreur-type de 0,021, typique d'un trimestre de 25 à 30 ventes, la hausse du trimestre n'est pas distinguable de zéro au seuil usuel. La différence des $\hat\tau$ donne directement la variation entre deux périodes quelconques parce que le modèle est additif en logarithme : $\ln \E[P \mid \mathbf{x}, 3] - \ln \E[P \mid \mathbf{x}, 2] = \tau_3 - \tau_2$ quel que soit $\mathbf{x}$, de sorte que l'indice est défini sans panier (\cref{def:indice-qualite-constante}). | |
| 10 | +\end{solution} | |
| 11 | + | |
| 12 | +\begin{solution}{ex:35:3}{L'estimateur BMN à la main} | |
| 13 | +Les variations logarithmiques sont $y_A = \ln(462/430) = 0{,}0718$, $y_B = \ln(512/470) = 0{,}0856$, $y_C = \ln(540/495) = 0{,}0870$ et $y_D = \ln(455/436) = 0{,}0427$. La période 0 étant omise, $\mathbf{D}$ a deux colonnes (périodes 1 et 2) : A (0 → 1) donne $(1, 0)$ ; B (1 → 2) donne $(-1, 1)$ ; C (0 → 2) donne $(0, 1)$ ; D (0 → 1) donne $(1, 0)$. Alors | |
| 14 | +\[ | |
| 15 | +\mathbf{D}\T\mathbf{D} = \begin{pmatrix} 3 & -1 \\ -1 & 2 \end{pmatrix}, \qquad | |
| 16 | +\mathbf{D}\T\mathbf{y} = \begin{pmatrix} 0{,}0718 - 0{,}0856 + 0{,}0427 \\ 0{,}0856 + 0{,}0870 \end{pmatrix} = \begin{pmatrix} 0{,}0288 \\ 0{,}1726 \end{pmatrix}. | |
| 17 | +\] | |
| 18 | +La diagonale compte les paires qui touchent chaque période (trois pour la période 1, deux pour la période 2) et l'élément hors diagonale est l'opposé du nombre de paires reliant 1 et 2 (une, la paire B). L'inverse est $\frac{1}{5}\begin{pmatrix} 2 & 1 \\ 1 & 3 \end{pmatrix}$, d'où $\hat\tau_1 = (2 \times 0{,}0288 + 0{,}1726)/5 = 0{,}0461$ et $\hat\tau_2 = (0{,}0288 + 3 \times 0{,}1726)/5 = 0{,}1093$ ; l'indice vaut $100\exp(0{,}0461) = 104{,}71$ en période 1 et $100\exp(0{,}1093) = 111{,}55$ en période 2. Les résidus sont $y - \mathbf{D}\hat{\boldsymbol\tau}$ : $0{,}0257$ (A), $0{,}0223$ (B), $-0{,}0223$ (C), $-0{,}0034$ (D) ; leur produit avec la première colonne de $\mathbf{D}$ donne $0{,}0257 - 0{,}0223 - 0{,}0034 = 0$ et avec la seconde $0{,}0223 - 0{,}0223 = 0$, comme l'exigent les équations normales. Sans la paire B, aucune paire ne relierait la période 1 à la période 2 : $\mathbf{D}\T\mathbf{D}$ deviendrait diagonale, $\hat\tau_1$ serait la moyenne de $y_A$ et $y_D$ (0,0573) et $\hat\tau_2$ vaudrait $y_C$ (0,0870) ; les deux périodes seraient estimées indépendamment l'une de l'autre, chacune par rapport à la période 0 seulement, et la précision de la période 2, fondée sur une seule paire, serait nulle. Le graphe des périodes resterait connexe grâce à la période 0, mais à peine. | |
| 19 | +\end{solution} | |
| 20 | + | |
| 21 | +\begin{solution}{ex:35:4}{Variance et poids de Case--Shiller} | |
| 22 | +La variance $\hat a_0 + \hat a_1\Delta$ vaut 0,0157 (1 trimestre), 0,0193 (4), 0,0241 (8) et 0,0289 (12), soit des écarts-types de 12,53 \%, 13,89 \%, 15,52 \% et 17,00 \%. Les poids $\hat w_j = 1/\sqrt{\text{var}}$ sont 7,98 ; 7,20 ; 6,44 ; 5,88, et les poids relatifs à une paire d'un trimestre $\sqrt{0{,}0157/\text{var}}$ : 1 ; 0,902 ; 0,807 ; 0,737. Une paire de douze trimestres compte donc pour les trois quarts d'une paire d'un trimestre dans la régression pondérée. Les paramètres structurels sont $\hat\sigma = \sqrt{\hat a_0 / 2} = \sqrt{0{,}00725} = 0{,}0851$, le bruit de transaction par vente, et $\hat\delta = \sqrt{\hat a_1} = \sqrt{0{,}0012} = 0{,}0346$ par racine de trimestre, la dérive idiosyncrasique, soit un écart-type de $0{,}0346\sqrt{12} = 12$ \% de la valeur d'un immeuble par rapport au marché après trois ans. Si l'échantillon contenait de nombreuses reventes rénovées à court intervalle, leurs variations anormalement grandes gonfleraient les carrés des résidus aux petits $\Delta$ et abaisseraient la pente, qui pourrait devenir négative, comme dans l'exemple intégral ($\hat a_1 = -0{,}00023$ sur toutes les paires, $+0{,}00053$ sans les reventes rénovées) ; une pente négative est un signal de contamination des courts intervalles, non une propriété du marché. | |
| 23 | +\end{solution} | |
| 24 | + | |
| 25 | +\begin{solution}{ex:35:5}{Un indice SPAR sur dix ventes} | |
| 26 | +La somme des prix est $5\,094\,000$ \$ et celle des valeurs au rôle $4\,716\,000$ \$, d'où un rapport arithmétique de $1{,}0802$. Les rapports individuels sont 1,0894 ; 1,0365 ; 1,1028 ; 1,0471 ; 1,1042 ; 1,0417 ; 1,1275 ; 1,0341 ; 1,0945 ; 1,1083, et la moyenne de leurs logarithmes vaut 0,0752, d'où un rapport géométrique de $\exp(0{,}0752) = 1{,}0781$ ; la différence avec le rapport arithmétique tient à ce que celui-ci pondère chaque immeuble par sa valeur. L'indice SPAR du trimestre est $100 \times 1{,}0802 / 1{,}045 = 103{,}36$ en version arithmétique et $100 \times 1{,}0781 / 1{,}045 = 103{,}16$ en version géométrique : les prix ont monté d'environ 3,2 à 3,4 \% depuis le trimestre de base, à qualité constante au sens du rôle. Avant de s'y fier, il faut vérifier l'uniformité du rôle par les statistiques de ratio du \cref{ch:18} : un COD raisonnable (dispersion des rapports) et surtout un PRD proche de 1 et un PRB proche de 0, faute de quoi le rapport prix/valeur dépend du niveau de valeur et la composition des ventes par gamme de prix réapparaît dans l'indice. | |
| 27 | +\end{solution} | |
| 28 | + | |
| 29 | +\begin{solution}{ex:35:6}{L'ajustement de conditions du marché selon l'indice} | |
| 30 | +Avec $A = P\,(I_{2026\mathrm{T}3}/I_{2025\mathrm{T}2} - 1)$ et les valeurs du \cref{tab:35:indices} : hédonique, $506\,500 \times (122{,}04/112{,}13 - 1) = \dollars{44760}$ ($+8{,}84$ \%) ; BMN, $506\,500 \times (120{,}68/113{,}42 - 1) = \dollars{32406}$ ($+6{,}40$ \%) ; Case--Shiller, $506\,500 \times (120{,}48/113{,}25 - 1) = \dollars{32359}$ ($+6{,}39$ \%) ; médiane, $506\,500 \times (128{,}63/118{,}35 - 1) = \dollars{44009}$ ($+8{,}69$ \%) ; tendance, $506\,500 \times [\exp(0{,}00364 \times 15{,}2) - 1] = 506\,500 \times 0{,}0568 = \dollars{28774}$ ($+5{,}68$ \%), la vérité de génération étant de \dollars{28558}. Le classement va de la tendance (la plus basse et la plus proche de la vérité) aux ventes répétées, puis à la médiane et à l'indice hédonique. L'écart de \dollars{16000} entre l'indice hédonique et la tendance estimée sur les mêmes 600 ventes vient entièrement du dernier trimestre : 2026T3 ne compte que 22 ventes, son indicatrice a une erreur-type de 0,018 et sa valeur, 122,04, se situe 5 \% au-dessus de la tendance, à l'intérieur de son propre intervalle de confiance $[117{,}72 ; 126{,}52]$, qui contient aussi la tendance (116,5). Rien, statistiquement, ne distingue les deux lectures ; la tendance emprunte sa précision à l'ensemble de la période, l'indicatrice n'a que 22 ventes. Paragraphe type : « L'ajustement de conditions du marché est fondé sur la variation des prix des unifamiliales du secteur entre mai 2025 et septembre 2026, estimée à 5,7 \% par la tendance d'un modèle hédonique estimé sur 600 ventes de janvier 2023 à août 2026 (0,364 \% par mois). Les indices trimestriels construits sur les mêmes ventes (indicatrices : 8,8 \% ; ventes répétées : 6,4 \%) et la médiane de l'APCIQ pour le secteur encadrent ce taux ; la dispersion de ces mesures, de 5 à 9 \%, est intégrée à la fourchette de valeur. Le dernier trimestre repose sur 22 ventes et sa valeur est susceptible de révision. » | |
| 31 | +\end{solution} | |
| 32 | + | |
| 33 | +\begin{solution}{ex:35:7}{Révision et filtres : expérience sur le script} | |
| 34 | +(a) Avec \texttt{PART\_FLIP} $= 0$, aucune paire n'est rénovée : la hausse totale BMN se rapproche de celle de l'oracle de l'exemple (18,4 \% pour une vérité de 16,4 \% ; l'écart restant est le bruit d'échantillonnage de quelque 170 paires) et la pente $\hat a_1$ redevient positive, de l'ordre de $+0{,}0005$ par trimestre, du signe de la loi de génération ($\delta^2 = 0{,}0012$). Avec 0,30, près d'une paire sur trois est rénovée : la hausse mesurée dépasse la vérité de plusieurs points de plus qu'avec 0,15 (où le biais imputable aux reventes rénovées était de 2,3 points, 20,7 \% contre 18,4 \%), et la pente $\hat a_1$ devient franchement négative, puisque les courts intervalles concentrent les grandes variations. Les valeurs exactes dépendent de la graine et doivent être lues dans la sortie du script. (b) Le filtre à 12 mois élimine, dans cette simulation, toutes les reventes rénovées (intervalles de 3 à 9 mois) et supprime donc le biais quelle que soit leur part, au prix de la perte de plus de la moitié des paires ; le filtre à 18 mois ne laisse qu'une quarantaine de paires et l'écart absolu moyen double (6,56 points contre 2,76 dans l'exemple) : le biais est éliminé mais la variance l'emporte. Le seuil doit être proportionné à l'échantillon. (c) Les millésimes reproduisent la \cref{fig:35:revision} : l'indice de 2025T2 passe de 116,39 (110 paires connues en 2025T2) à 114,69 (158 paires en 2025T4) puis à 113,42 (220 paires en 2026T3). La révision est systématiquement à la baisse parce que les paires disponibles à une date donnée sont celles dont la seconde vente est déjà survenue, donc les intervalles courts, où les reventes rénovées (à prime positive) sont surreprésentées ; les paires ordinaires d'intervalle long arrivent ensuite et corrigent l'indice vers le bas. Note attendue : le producteur devrait publier, pour chaque millésime, le nombre de paires par période, les filtres appliqués (seuils d'intervalle, de variation, d'exclusion), la variance estimée des résidus et sa relation à l'intervalle, l'intervalle de confiance de chaque valeur, et l'historique des révisions des douze derniers mois, afin que l'utilisateur sache quelle confiance accorder à la dernière valeur, celle que l'ajustement de conditions du marché utilise. | |
| 35 | +\end{solution} | |
| 36 | + | |
| 37 | +\begin{solution}{ex:35:8}{Dérivations : imputation, moyenne géométrique et estimateur arithmétique} | |
| 38 | +(a) Si $\hat p_t(\mathbf{x}) - \hat p_0(\mathbf{x}) = \hat\tau_t$ pour tout $\mathbf{x}$, chaque terme des moyennes de l'\cref{eq:35:imputation} vaut $\hat\tau_t$, donc $\ln \hat I^{\,L}_t = \ln \hat I^{\,P}_t = \ln \hat I^{\,F}_t = \hat\tau_t$ et les trois indices valent $100\exp(\hat\tau_t)$. Avec des coefficients propres à chaque période, $\hat p_t(\mathbf{x}) - \hat p_0(\mathbf{x}) = (\hat\alpha_t - \hat\alpha_0) + \mathbf{x}\T(\hat{\boldsymbol\beta}_t - \hat{\boldsymbol\beta}_0)$ dépend de $\mathbf{x}$ ; la moyenne sur le panier de base donne $(\hat\alpha_t - \hat\alpha_0) + \bar{\mathbf{x}}_0\T(\hat{\boldsymbol\beta}_t - \hat{\boldsymbol\beta}_0)$ et celle sur le panier courant remplace $\bar{\mathbf{x}}_0$ par $\bar{\mathbf{x}}_t$ ; leur différence, $(\bar{\mathbf{x}}_t - \bar{\mathbf{x}}_0)\T(\hat{\boldsymbol\beta}_t - \hat{\boldsymbol\beta}_0)$, est l'interaction entre le changement de composition et le changement des prix implicites, nulle si l'un des deux est nul. (b) Avec deux périodes et toutes les paires de 0 vers 1, $\mathbf{D}$ est un vecteur de $n$ uns, $\mathbf{D}\T\mathbf{D} = n$ et $\hat\tau_1 = \frac{1}{n}\sum_j \ln(P_{j2}/P_{j1})$ ; donc $\exp(\hat\tau_1) = \left(\prod_j P_{j2}/P_{j1}\right)^{1/n}$, la moyenne géométrique des rapports. (c) Les rapports sont 1,10 et 1,15. Indice géométrique : $\sqrt{1{,}10 \times 1{,}15} = 1{,}1247$, soit $+12{,}47$ \% ; moyenne arithmétique des rapports : $(1{,}10 + 1{,}15)/2 = 1{,}125$, soit $+12{,}50$ \%, supérieure de 0,03 point par l'inégalité de Jensen (demi-variance des rapports, $0{,}025^2/2 \approx 0{,}0003$) ; indice arithmétique pondéré par la valeur : $(110\,000 + 230\,000)/(100\,000 + 200\,000) = 340/300 = 1{,}1333$, soit $+13{,}33$ \%. Le premier répond à la question « de combien le prix de la maison type a-t-il varié ? » (chaque immeuble compte pour un) ; le second est une moyenne simple des variations individuelles, voisine du premier quand les variations sont peu dispersées ; le troisième répond à « de combien la valeur d'un portefeuille formé de ces maisons a-t-elle varié ? » et donne plus de poids à la maison chère, qui s'est ici le plus appréciée. C'est la question pertinente pour un indice financier, celle de \textcite{shiller1991}, non pour l'ajustement d'un comparable. | |
| 39 | +\end{solution} | |
modified
sources/registry.csv
+6 −0
@@ -380,4 +380,10 @@ SRC-23-007,loi,"Loi sur l'aménagement et l'urbanisme, art. 117.6 (texte)",Légi | ||
| 380 | 380 | SRC-25-001,donnees_synthetiques,ch25_terrains_ventes.csv — 200 ventes synthétiques de terrains vacants (Gatineau fictif),Auteur (analysis/python/cases/ch25_terrain.py),"Généré le 2026-10-31, graine 20261031",data/synthetic/ch25_terrains_ventes.csv,2026-10-31,verifie,25,Lois déclarées dans data/synthetic/README.md (section « Chapitre 25 ») ; prix de référence 225 000 $ = hypothèse pédagogique ; aucune vente réelle utilisée |
| 381 | 381 | SRC-26-001,web,Jour du déménagement au Québec (L'Encyclopédie canadienne),Historica Canada,2024-04-23,https://thecanadianencyclopedia.ca/fr/article/jour-du-demenagement-au-quebec,2026-10-31,partiellement_verifie,ch26,Clé patterson2024demenagement déjà dans library.bib ; source secondaire pour l'historique du 1er juillet |
| 382 | 382 | # Aucune nouvelle source : le chapitre 33 ne cite que des clés déjà présentes dans references/library.bib (voir ch33.md) ; aucune vérification web supplémentaire n'a été nécessaire.,,,,,,,,, |
| 383 | +SRC-34-01,page_web,Pourcentages applicables aux critères de fixation de loyer (revérification des tableaux 2025 et 2026),Tribunal administratif du logement,Page rechargée le 2026-10-31 (curl avec User-Agent navigateur),https://www.tal.gouv.qc.ca/fr/reconduction-du-bail-et-fixation-de-loyer/pourcentages-applicables-aux-criteres-de-fixation-de-loyer,2026-10-31,verifie,34,"Doublon de contrôle de SRC-A-005 : les deux tableaux (avis à compter du / avant le 1er janvier 2026) relus intégralement ; base 4,5 %/3,1 %, services à la personne 6 %/6,7 %, immobilisations 5 %/5 % ; ancienne méthode identique à notes-A ; toujours aucun pourcentage 2027." | |
| 384 | +SRC-34-02,reglement,"Règlement sur les critères de fixation de loyer, RLRQ c. T-15.01, r. 2 — section II (art. 3 à 7), art. 13, 15.1 et annexe I (revérification du texte)",Québec (LégisQuébec),À jour au 1er juillet 2026 ; page rechargée le 2026-10-31,"https://www.legisquebec.gouv.qc.ca/fr/document/rc/T-15.01,%20r.%202",2026-10-31,verifie,34,Doublon de contrôle de SRC-A-003 : texte de l'art. 4 relu pour déterminer l'assiette des pourcentages nets (montant mensuel de la période de référence pour les taxes municipales ; de la période précédente pour le scolaire et les assurances) ; art. 3.1 formule et plancher ; art. 5 part attribuable. | |
| 385 | +SRC-34-03,donnees,"Indice des prix à la consommation, mensuel, non désaisonnalisé, Québec, ensemble (2002 = 100), vecteur v41691783 — tableau 18-10-0004-01",Statistique Canada,Diffusion du 19 octobre 2026 (données jusqu'en septembre 2026) ; fichier brut data/raw/statcan_18100004/18100004.csv (SRC-E-015),https://www150.statcan.gc.ca/t1/tbl1/fr/tv.action?pid=1810000401,2026-10-31,verifie,34,"Extraction de la série Québec vers data/processed/ipc_quebec_mensuel.csv par analysis/python/cases/ch34_tal.py ; moyennes annuelles 2021-2025 : 137,792 ; 146,983 ; 153,583 ; 157,158 ; 161,033. Licence ouverte Statistique Canada." | |
| 386 | +SRC-35-001,page_web,Our Methodology — Teranet–National Bank House Price Index,Teranet / Banque Nationale du Canada,Consultée le 2026-10-31,https://housepriceindex.ca/about/our-methodology/,2026-10-31,verifie,35,"Lue par curl en mode non vérifié (certificat TLS invalide) ; méthode des ventes répétées, exclusions a)-d), exemple simplifié ; document complet annoncé non accessible ; clé teranetMethodologie2026 (lib-ch35.bib)" | |
| 387 | +SRC-35-002,metadonnees,Indice des prix des logements neufs (IPLN) — fiche de l'enquête 2310 (Base de métadonnées intégrée),Statistique Canada,Date de parution 2026-10-22 ; consultée le 2026-10-31,https://www23.statcan.gc.ca/imdb/p2SV_f.pl?Function=getSurvey&SDDS=2310,2026-10-31,verifie,35,"Définition, ajustement de qualité, population cible, collecte, « n'est pas révisé et n'est pas désaisonnalisé » ; renvoie à 62F0014M2022003 (non consulté) ; clé statcaniplnimdb2310 (lib-ch35.bib)" | |
| 388 | +SRC-35-003,page_web,S&P CoreLogic Case-Shiller Home Price Indices — Methodology (PDF),S&P Dow Jones Indices,Tentative le 2026-10-31,https://www.spglobal.com/spdji/en/documents/methodologies/methodology-sp-corelogic-cs-home-price-indices.pdf,2026-10-31,inaccessible,35,HTTP 403 ; non cité dans le chapitre (voir CLM-35-10) | |
| 383 | 389 | # Aucune nouvelle source : le chapitre 47 ne cite que des clés déjà présentes dans references/library.bib (voir ch47.md).,,,,,,,,, |
modified
verification/calculations.csv
+13 −0
@@ -534,3 +534,16 @@ CALC-34-15,34,"Scénarios complémentaires : indexation seule 1 756 414 ; φ = 0 | ||
| 534 | 534 | CALC-34-16,34,Rattrapage partiel réduit le RBP dès l'année 2 ; récupération CAPEX n'agit qu'à partir de l'année 3 (travaux 2027) et ajoute ≈ 5 % × 85 000 = 4 250 $,analysis/python/cases/ch34_tal.py,analysis/python/tests/test_ch34.py::test_rattrapage_partiel_et_recuperation,4 250 $ (±2 %),≈ 4 250,verifie,2026-10-31 |
| 535 | 535 | CALC-34-17,34,"Élasticités (mode espérance, différences centrées) : ≈ 11 900 $ par point de rotation ; ≈ 131 200 $ par point de pourcentage de base",analysis/python/cases/ch34_tal.py,analysis/python/tests/test_ch34.py::test_elasticites,11 877 ; 131 236,11877 ; 131236,verifie,2026-10-31 |
| 536 | 536 | CALC-34-18,34,"Exercices 1 à 7 (3,084 % ; 10,12 $ ; 1 194 $ ; q∞ 0,9664 ; 2 049 073 / 1 829 294 / 2 148 793 $ ; indemnité 13 104 $, 3,8 ans ; dérivées 1,34 et 0,16)",analysis/python/cases/ch34_tal.py,analysis/python/tests/test_ch34.py::test_exercices,voir solutions/ch34.tex,conformes,verifie,2026-10-31 |
| 537 | +CALC-35-01,35,"Vérité de génération : β = ln(1,0443)/12 par mois ; indice au milieu de 2026T3, base 2023T1",analysis/python/cases/ch35_indices.py,analysis/python/tests/test_ch35.py::test_verite_et_donnees,"0,003614 ; 4,43 %/an ; 116,37","0,003614 ; 4,43 ; 116,37",verifie,2026-10-31 | |
| 538 | +CALC-35-02,35,"Indice hédonique à indicatrices trimestrielles (traite.hedonique.indice_dummies_temps) : k, σ, R² aj., valeur 2026T3 et IC 95 %, EAM, largeur IC",analysis/python/cases/ch35_indices.py,analysis/python/tests/test_ch35.py::test_indice_hedonique_identites,"29 ; 0,083 ; 0,849 ; 122,04 [117,72 ; 126,52] ; EAM 2,81 ; 7,47 %",idem,verifie,2026-10-31 | |
| 539 | +CALC-35-03,35,Identité : imputation géométrique (Laspeyres = Paasche = Fisher) = indice à indicatrices quand les coefficients sont communs (prop. imputation-td),analysis/python/cases/ch35_indices.py,analysis/python/tests/test_ch35.py::test_indice_hedonique_identites,écart max < 1e-9,"8,5e-14",verifie,2026-10-31 | |
| 540 | +CALC-35-04,35,Indice chaîné par périodes adjacentes : I_t = I_{t-1} exp(δ_t) ; valeur finale ; EAM,analysis/python/cases/ch35_indices.py,analysis/python/tests/test_ch35.py::test_indice_hedonique_identites,"121,69 ; EAM 4,01","121,69 ; 4,01",verifie,2026-10-31 | |
| 541 | +CALC-35-05,35,"Médiane et moyenne géométrique : indices finaux, maximum, EAM, croissance totale ; décomposition exacte ln(moy. géo.) = composition + τ ; corrélation écart/composition",analysis/python/cases/ch35_indices.py,analysis/python/tests/test_ch35.py::test_mediane_et_composition,"128,63 ; max 133,68 ; moy. géo. 127,78 ; EAM médiane 10,97 ; hausse 28,63 % ; identité exacte ; corr 0,86",idem (identité à 1e-9),verifie,2026-10-31 | |
| 542 | +CALC-35-06,35,"Jeu de reventes synthétique (generer_reventes, graine 20261031) : paires, rénovées, intervalle médian/moyen/min/max, part < 12 mois, é.-t. ln ratio, reproductibilité",analysis/python/cases/ch35_indices.py,analysis/python/tests/test_ch35.py::test_reventes_synthetiques,"220 ; 47 ; 10 ; 12,8 ; 3 ; 38 ; 55,9 % ; 0,144 ; identique à la régénération",idem,verifie,2026-10-31 | |
| 543 | +CALC-35-07,35,BMN (traite.hedonique.ventes_repetees_bmn) : forme fermée (D'D)^{-1}D'y = lstsq ; σ_η ; valeur finale et IC ; e.-t. finale ; EAM ; largeur IC,analysis/python/cases/ch35_indices.py,analysis/python/tests/test_ch35.py::test_bmn_identites,"écart < 1e-10 ; 0,1455 ; 120,68 [109,78 ; 132,65] ; 0,0483 ; 3,02 ; 20,83 %",idem,verifie,2026-10-31 | |
| 544 | +CALC-35-08,35,Case–Shiller (ventes_repetees_case_shiller) : coefficients de la variance (toutes paires ; sans rénovées) vs loi de génération ; poids = 1/√var ; |CS − BMN| ; valeur finale ; EAM,analysis/python/cases/ch35_indices.py,analysis/python/tests/test_ch35.py::test_case_shiller,"0,02079 − 0,00023·Δ ; 0,01574 + 0,00053·Δ ; théorie 0,01445 + 0,0012·Δ ; < 0,5 pt ; 120,48 ; 2,90",idem,verifie,2026-10-31 | |
| 545 | +CALC-35-09,35,"SPAR géométrique et arithmétique (valeur_role_2025) : valeurs finales, EAM, é.-t. ln(P/A), ratio médian, identité du rapport des sommes",analysis/python/cases/ch35_indices.py,analysis/python/tests/test_ch35.py::test_spar,"115,06 ; 114,36 ; 1,26 ; 0,087 ; 1,095",idem,verifie,2026-10-31 | |
| 546 | +CALC-35-10,35,"Ajustement de conditions du marché de G0404 (506 500 $, 2025T2 → 2026T3, 15,2 mois) selon l'indice : vérité, tendance, hédonique, chaîné, BMN, CS, SPAR, médiane",analysis/python/cases/ch35_indices.py,analysis/python/tests/test_ch35.py::test_ajustement_et_sensibilite,28 558 ; 28 733 ; 44 760 ; 30 987 ; 32 406 ; 32 359 ; 26 575 ; 44 009 $,idem,verifie,2026-10-31 | |
| 547 | +CALC-35-11,35,"Sensibilité : révision BMN de 2025T2 (millésimes 2025T2/2025T4/2026T3) et de 2024T4 ; filtres 12/18 mois et oracle ; sous-échantillons 50 %/25 % (EAM, é.-t. finale) ; fenêtre hédonique 8 trimestres",analysis/python/cases/ch35_indices.py,analysis/python/tests/test_ch35.py::test_ajustement_et_sensibilite,"116,39 → 114,69 → 113,42 ; 114,45 → 109,99 ; hausses 20,68/17,98/19,41/18,39 % ; EAM 3,02/2,76/6,56/2,01 ; 4,26 & 4,77 ; 6,46 & 8,96 ; écart max 0,37 %",idem,verifie,2026-10-31 | |
| 548 | +CALC-35-12,35,"Solutions des exercices 35.1 à 35.8 (composition 3,79 % ; indices exp(τ) ; BMN à la main β = (0,0461 ; 0,1093) ; poids CS 1/0,902/0,807/0,737 ; SPAR 103,36/103,16 ; ajustements G0404 ; géométrique 1,1247 vs arithmétique 1,125 vs pondéré 1,1333)",analysis/python/cases/ch35_indices.py,analysis/python/tests/test_ch35.py::test_solutions_exercices,voir solutions/ch35.tex,idem,verifie,2026-10-31 | |
| 549 | +CALC-35-13,35,IPLN (18-10-0205-01) : variation sur douze mois en septembre 2026 par RMR et Canada ; sommets de la variation annuelle,analysis/python/figures/ch35_figures.py,—,"Montréal 1,2 ; Québec 1,9 ; Gatineau 0,3 ; Sherbrooke 1,2 ; Trois-Rivières 3,0 ; Canada −2,0 ; sommets 21,9 (2021-09), 14,3 (2021-05), 6,7 (2020-12), 12,2 (Canada 2021-08)",idem (calcul direct sur data/raw),verifie,2026-10-31 | |
added
verification/chapters/ch34-claims.csv
+24 −0
@@ -0,0 +1,24 @@ | ||
| 1 | +id,chapitre,affirmation,source_id,verification,date,statut,notes | |
| 2 | +CLM-34-01,34,"Règlement sur les critères de fixation de loyer, art. 3 (six critères) et 3.1 (pourcentage de base = moyenne des trois variations annuelles de l'IPC Québec ; négatif réputé nul), remplacés par D. 1455-2025 en vigueur le 2026-01-01",SRC-A-003;SRC-A-004,Texte refondu LégisQuébec relu le 2026-10-31 (section II, art. 3 à 7) ; G.O. 2 du 2025-12-17 (notes-A.md §6.2),2026-10-31,verifie,texte de l'art. 3 et de la formule 3.1 vérifiés mot à mot | |
| 3 | +CLM-34-02,34,"Art. 4 : variation positive moins la valeur absolue du pourcentage de base (sans égard au plancher) ; négatif réputé nul ; % net appliqué au montant mensuel des taxes municipales de la période de référence et au montant mensuel de la période précédente pour le scolaire et les assurances",SRC-A-003,Texte refondu LégisQuébec relu le 2026-10-31 (art. 4, al. 2 et 3),2026-10-31,verifie, | |
| 4 | +CLM-34-03,34,"Art. 5 : part attribuable = loyer au terme du bail / revenus ; proportion sur les logements bénéficiaires si la dépense ne concerne que certains logements ; aucune part pour un local non résidentiel",SRC-A-003,LégisQuébec relu le 2026-10-31,2026-10-31,verifie,le chapitre inclut les autres revenus dans les revenus (approximation déclarée ; formulaire RN fait foi) | |
| 5 | +CLM-34-04,34,"Art. 1 (période de référence : année civile précédant le terme pour un bail se terminant du 1er avril au 31 décembre ; avant-dernière sinon), art. 6 (loyer de faveur → logements comparables), art. 13 (dépense comptée une fois, réduite des aides), art. 15.1 (arrondi au dollar), annexe I (liste de travaux), art. 3 al. 4 (prêt à intérêt réduit)",SRC-A-003,notes-A.md §6.2 et texte refondu (art. 13, 15.1, annexe 1 relus le 2026-10-31),2026-10-31,verifie, | |
| 6 | +CLM-34-05,34,"TAL, pourcentages 2025/2026 : base 4,5 %/3,1 % ; services à la personne 6 %/6,7 % ; immobilisations 5 %/5 % ; ancienne méthode : électricité 2,9/2,9 ; gaz −5,8/13,7 ; mazout −2,9/4,9 ; entretien 6,9/5,7 ; services immeuble 4,5/3,4 ; RPA 5,9/6,3 ; gestion 8,2/7,2 ; revenu net 6,9/5,7 ; immobilisations 4,7/4 ; aucun pourcentage 2027 publié",SRC-A-005,Page TAL rechargée et relue le 2026-10-31 (curl, deux tableaux),2026-10-31,verifie,revérification du jour ; entrée SRC-34-01 du registre du chapitre | |
| 7 | +CLM-34-06,34,"Pourcentage de base 2026 (3,1 %) et 2025 (4,5 %) reproduits par la formule de l'art. 3.1 à partir de l'IPC Québec (StatCan 18-10-0004-01, v41691783) : 3,095 % et 4,496 %",SRC-E-015,Calcul ch34_tal.py sur data/processed/ipc_quebec_mensuel.csv (extrait du brut),2026-10-31,verifie,hypothèse : période de référence = année civile et IPC « ensemble » Québec non désaisonnalisé ; l'avis d'indexation (2026) 158 G.O. 1 146 n'a pas été ouvert | |
| 8 | +CLM-34-07,34,"Communiqué TAL du 21 janvier 2025 : ancienne méthode (composantes IPC ; gestion 5 % des revenus, réels jusqu'à 10 % ; immobilisations au taux CPG 5 ans + 1 %) ; moyenne de 3,8 % d'ajustement accordé 2015-2024 ; 8 650 calculs en 2023-2024",SRC-A-007,PDF lu (notes-A.md §6.2),2026-10-31,verifie,la structure de l'ancien formulaire reconstituée dans eq. 34.5 est schématique (déclaré) | |
| 9 | +CLM-34-08,34,"Loi sur le TAL : art. 28 (compétence exclusive, art. 1941-1964 C.c.Q.), art. 91 (pas d'appel en fixation), art. 56.3 (formulaire RN dans les 90 jours, péremption), art. 108 (habilitation réglementaire), art. 51-54.13 (conversion)",SRC-A-002,LégisQuébec à jour au 2026-10-15 (notes-A.md §6.1),2026-10-31,verifie, | |
| 10 | +CLM-34-09,34,"C.c.Q. art. 1941-1947 (reconduction, avis 3-6 mois, refus dans le mois, fixation dans le mois du refus), 1952-1953 (fixation selon normes, ≤ 12 mois), 1896 (section G loyer le plus bas des 12 mois ; dommages punitifs 2024 c. 2), 1950 (10 jours / 2 mois), 1945 (logement 1955 : refus = départ)",SRC-A-001,LégisQuébec CCQ-1991 à jour au 2026-10-15 (notes-A.md §4-6 ; normes-A NRM-A-075 à 083),2026-10-31,verifie, | |
| 11 | +CLM-34-10,34,"C.c.Q. art. 1978.1-1978.4 (Loi 31) : refus de cession pour motif autre que sérieux → résiliation à la date de cession ; aucune contrepartie exigible par le cédant ; sous-location plafonnée",SRC-A-001;SRC-A-013,LégisQuébec ; L.Q. 2024 c. 2 (notes-A.md §4),2026-10-31,verifie,transitoire : avis avant le 21 février 2024 exclus | |
| 12 | +CLM-34-11,34,"C.c.Q. art. 1957-1958 (reprise réservée au propriétaire personne physique ; indivisaire seulement avec son conjoint), 1970 (relocation après reprise avec autorisation et fixation), 1963-1964 (autorisation TAL, preuve), 1937 (vente n'affecte pas le bail)",SRC-A-001,LégisQuébec (notes-A.md §5 ; NRM-A-078, 086, 090, 092),2026-10-31,verifie, | |
| 13 | +CLM-34-12,34,"Loi 65 (L.Q. 2024 c. 23) : moratoire sur les évictions de l'art. 1959 jusqu'au 6 juin 2027 (art. 1) ; soustraction territoriale possible (art. 2) ; fin anticipée 60 jours après avis si inoccupation SCHL ≥ 3 % dans les centres ≥ 10 000 hab. (art. 11) ; rapport au gouvernement au plus tard le 6 février 2027 (art. 12) ; aucun avis de fin anticipée repéré au 2026-10-31",SRC-A-014,G.O. 2 du 2024-07-10 p. 4917-4921 (notes-A.md §5 ; NRM-A-088),2026-10-31,verifie,état du moratoire à revérifier avant publication | |
| 14 | +CLM-34-13,34,"C.c.Q. art. 1959.1 : protection si 65 ans ou plus, 10 ans d'occupation, revenu ≤ 125 % du seuil HLM (auparavant 70 ans et 100 %) ; art. 1965 : indemnité 1 mois/année, min 3, max 24 mois + déménagement ; art. 1968 : bonne foi, dommages punitifs",SRC-A-001;SRC-A-014,LégisQuébec (NRM-A-087, 091),2026-10-31,verifie, | |
| 15 | +CLM-34-14,34,"C.c.Q. art. 1955 : exclusion de la fixation 5 ans pour immeuble neuf/changement d'affectation ; opposable seulement si au bail et, depuis le 21 février 2024, si loyer maximal indiqué ; coopératives d'habitation depuis le 24 février 2026 (2026 c. 1 a. 152)",SRC-A-001,LégisQuébec (NRM-A-083 ; notes-A.md §5),2026-10-31,verifie, | |
| 16 | +CLM-34-15,34,"L'appellation « clause F » désigne la section F du formulaire de bail (restrictions au droit à la fixation)",,Usage ; annexes du règlement T-15.01 r. 3 non reproduites par LégisQuébec (notes-A.md point 5),2026-10-31,partiellement_verifie,déclaré entre guillemets dans le chapitre et signalé dans limites | |
| 17 | +CLM-34-16,34,"Résumés de décisions TAL : « la valeur marchande locative n'est pas un critère » (Société de financement alouette inc. c. A., 2022-08-17) ; dépense comptée une seule fois, travaux connus au bail initial non récupérables (André-Bélisle c. Audet, 2022-03-17) ; regroupement de dépenses de plusieurs années non permis (Skalli Housseini, 2021-04-20) ; exception 1955 immeuble < 5 ans (OMH Montréal c. Morin, 2024-01-09)",SRC-A-011;SRC-A-038,Résumés officiels lus sur tal.gouv.qc.ca (notes-A.md §6.3) ; références neutres non affichées,2026-10-31,partiellement_verifie,textes intégraux non ouverts (CanLII 403) | |
| 18 | +CLM-34-17,34,"SCHL oct. 2025 : taux de rotation RMR Montréal 9,4 % (11,1 % en 2024) ; loyer 2 ch. +7,2 % à échantillon constant",SRC-E-007,Rapport sur le marché locatif 2025-12-11 (notes-E.md),2026-10-31,verifie, | |
| 19 | +CLM-34-18,34,"Formulaire RN 2026-2027 : revenus de décembre 2025 × 12, autres revenus 2025, taxes municipales 2026/2025, scolaires 2025-2026/2024-2025, assurances au 31 décembre 2025/2024, dépenses d'immobilisation 2025 avec nombre de logements",SRC-A-009,PDF TAL-004-E RN (2025-11) lu (notes-A.md §6.2),2026-10-31,verifie, | |
| 20 | +CLM-34-19,34,"Diamond, McQuade et Qian (2019) : réduction de l'offre locative d'environ 15 % par les locateurs visés et hausse des loyers de l'ordre de 5 % à l'échelle de San Francisco",,Résumé de l'article (AER 109(9), DOI 10.1257/aer.20181289) ; chiffres du résumé publié,2026-10-31,partiellement_verifie,article non relu intégralement dans ce lot ; formulation prudente (« environ », « de l'ordre de ») | |
| 21 | +CLM-34-20,34,"Glaeser et Luttmer (2003), Sims (2007), Autor, Palmer et Pathak (2014), Kholodilin (2024), Arnott (1995), Fallis et Smith (1985), Smith (1988) : résultats résumés qualitativement (mauvaise allocation ; décontrôle → loyers, offre, entretien ; retombées de voisinage ; recension ; première/deuxième génération ; Ontario)",,notes-D.md §ch. 34 (entrées vérifiées par DOI) ; aucun chiffre précis cité sauf CLM-34-19,2026-10-31,verifie, | |
| 22 | +CLM-34-21,34,"Aucune étude arbitrée propre au régime québécois actuel de fixation des loyers n'a été identifiée",,notes-D.md (entrées non vérifiées : « Études arbitrées sur le contrôle des loyers au Québec » — rien trouvé),2026-10-31,non_verifie,constat d'absence déclaré dans limites et dans l'encadré | |
| 23 | +CLM-34-22,34,"NPP OEAQ : hypothèses extraordinaires à divulguer avec leur incidence sur la valeur (règle 2.1 c))",SRC-C-001,notes-C.md §2.1 (repris du ch. 30, CLM-30-12),2026-10-31,verifie, | |
| 24 | +CLM-34-23,34,"Le recours de l'art. 1950 est peu exercé en pratique",,Aucune statistique officielle trouvée (TAL ne publie pas le nombre de demandes de l'art. 1950 dans les sources consultées),2026-10-31,non_verifie,formulé comme observation de pratique ; à documenter ou à nuancer avant publication | |
added
verification/chapters/ch34-registry.csv
+4 −0
@@ -0,0 +1,4 @@ | ||
| 1 | +id,type,titre,organisme,version_ou_date,url,date_consultation,statut,utilise_dans,notes | |
| 2 | +SRC-34-01,page_web,Pourcentages applicables aux critères de fixation de loyer (revérification des tableaux 2025 et 2026),Tribunal administratif du logement,Page rechargée le 2026-10-31 (curl avec User-Agent navigateur),https://www.tal.gouv.qc.ca/fr/reconduction-du-bail-et-fixation-de-loyer/pourcentages-applicables-aux-criteres-de-fixation-de-loyer,2026-10-31,verifie,34,"Doublon de contrôle de SRC-A-005 : les deux tableaux (avis à compter du / avant le 1er janvier 2026) relus intégralement ; base 4,5 %/3,1 %, services à la personne 6 %/6,7 %, immobilisations 5 %/5 % ; ancienne méthode identique à notes-A ; toujours aucun pourcentage 2027." | |
| 3 | +SRC-34-02,reglement,"Règlement sur les critères de fixation de loyer, RLRQ c. T-15.01, r. 2 — section II (art. 3 à 7), art. 13, 15.1 et annexe I (revérification du texte)",Québec (LégisQuébec),À jour au 1er juillet 2026 ; page rechargée le 2026-10-31,"https://www.legisquebec.gouv.qc.ca/fr/document/rc/T-15.01,%20r.%202",2026-10-31,verifie,34,"Doublon de contrôle de SRC-A-003 : texte de l'art. 4 relu pour déterminer l'assiette des pourcentages nets (montant mensuel de la période de référence pour les taxes municipales ; de la période précédente pour le scolaire et les assurances) ; art. 3.1 formule et plancher ; art. 5 part attribuable." | |
| 4 | +SRC-34-03,donnees,"Indice des prix à la consommation, mensuel, non désaisonnalisé, Québec, ensemble (2002 = 100), vecteur v41691783 — tableau 18-10-0004-01",Statistique Canada,Diffusion du 19 octobre 2026 (données jusqu'en septembre 2026) ; fichier brut data/raw/statcan_18100004/18100004.csv (SRC-E-015),https://www150.statcan.gc.ca/t1/tbl1/fr/tv.action?pid=1810000401,2026-10-31,verifie,34,"Extraction de la série Québec vers data/processed/ipc_quebec_mensuel.csv par analysis/python/cases/ch34_tal.py ; moyennes annuelles 2021-2025 : 137,792 ; 146,983 ; 153,583 ; 157,158 ; 161,033. Licence ouverte Statistique Canada." | |
added
verification/chapters/ch34.md
+35 −0
@@ -0,0 +1,35 @@ | ||
| 1 | +# Chapitre 34 — TAL, encadrement locatif et scénarios : bilan de rédaction (2026-10-31) | |
| 2 | + | |
| 3 | +## Livrables | |
| 4 | +- `manuscript/chapters/ch34.tex` : ≈ 9 300 mots de prose hors tableaux/figures/équations (≈ 11 900 jetons avec les tableaux), 7 sections + exercices + « Pour aller plus loin ». | |
| 5 | +- Compteurs : **5 figures** (4 synthétiques cas 3 + 1 réelle Statistique Canada 18-10-0004-01), **8 tableaux** (critères art. 3 ; IPC → pourcentage de base ; pourcentages TAL 2025/2026 deux régimes ; calcul pas à pas logement 103 ; mécanismes juridiques ; trajectoire espérée ; grille rotation × base ; scénarios complémentaires), **13 équations numérotées**, 1 définition, 1 proposition avec démonstration (forme fermée E[q_t] et limite q_∞), 1 exemple intégral, 1 encadré critique (littérature internationale), encadrés `objectifs`, `quebec`, `synthetique`, `erreurs`, `limites`, `synthese`, **7 exercices** (2 × niveau 1, 3 × niveau 2, 2 × niveau 3). | |
| 6 | +- `analysis/python/cases/ch34_tal.py` : extraction de l'IPC Québec (brut 170 Mo → `data/processed/ipc_quebec_mensuel.csv`), formule de l'art. 3.1 (reproduit 4,5 % / 3,1 %), historique 2006-2026, fixation nouvelle méthode (art. 3, 3.1, 4, 5, 15.1) et reconstitution de l'ancienne, forme fermée + Monte-Carlo de la rotation, **fonction locale `projection_scenario`** (reprend `traite.cas3.projection_dcf` avec rotation, base, marché, rattrapage partiel φ, récupération 5 % CAPEX, mode déterministe/espérance — `traite/` non modifié), grille et scénarios via `traite.cas3.valeur_dcf`, élasticités, exercices. | |
| 7 | +- `analysis/python/figures/ch34_{ipc_base,calcul_tal,rotation,trajectoires,valeur_scenarios}.py` → `assets/figures/ch34/ch34-fig-*.pdf` (5/5 via `run_all.py ch34`). | |
| 8 | +- `analysis/python/tests/test_ch34.py` : 18 tests (formule 3.1 vs TAL ; plancher ; art. 4/5/15.1 ; composantes ; valeurs citées logement 103 ; forme fermée = récursion = simulation ; limites ; cas 3 ; identité avec `projection_dcf` ; base 2 208 566 $ et scénario ch. 30 1 912 046 $ ; monotonie de la grille ; scénarios ; récupération CAPEX ; élasticités ; exercices). | |
| 9 | +- `solutions/ch34.tex` : 7 solutions détaillées (≈ 1 450 mots). | |
| 10 | +- `verification/chapters/ch34-figures.csv` (5), `ch34-calculations.csv` (18), `ch34-claims.csv` (23), `ch34-registry.csv` (3 : revérification TAL du jour, relecture LégisQuébec art. 3-5, IPC StatCan), `references/lib-ch34.bib` (aucune nouvelle entrée ; toutes les clés citées existent dans `library.bib`). | |
| 11 | + | |
| 12 | +## Contrôles | |
| 13 | +- `tools/compile_chapter.sh 34` → `OK ch34 : 37 pages, 0 references/citations non definies, 0 overfull hbox`. | |
| 14 | +- `python3 -m pytest -q analysis/python/tests/test_ch34.py` → `18 passed`. | |
| 15 | +- `python3 tools/check_manuscript.py --no-log` → aucun problème signalé pour ch34 (9 problèmes résiduels concernent ch20/ch35). | |
| 16 | +- Inspection visuelle du PDF (pages 1, 4-14, 16, 18-19, 21-24, 26, 28, 33, 36) : tableaux dans la justification après passage en `footnotesize`/`makecell`/`p{}`, figures lisibles, encadrés corrects, solutions en annexe. | |
| 17 | + | |
| 18 | +## Chiffres clés (tous issus de `ch34_tal.py`) | |
| 19 | +Pourcentage de base : formule 3,095 % (2026) et 4,496 % (2025) vs 3,1 %/4,5 % publiés ; IPC Québec moyennes 2022-2025 : 146,983 ; 153,583 ; 157,158 ; 161,033. Logement 103 (728 $ ; marché 1 047 $ ; écart 319 $) : variante A +23 $ → 751 $ (3,16 %) ; variante B (taxes +8 %, assurances +10 %, travaux 85 000 $) +46 $ → 774 $ (6,32 %) ; ancienne méthode reconstituée 755 $ / 786 $. q_∞ = 0,9316 / 0,9786 / 0,9885 pour ρ = 5/15/25 %. Cas 3 : rapport de loyer 0,890 → 0,990 (déterministe) / 0,962 (espérance) / 0,856 (sans relocation) en 2036. Grille : 1 974 822 $ (5 %, 2 %) → 2 340 208 $ (25 %, 4 %) autour de 2 208 566 $ ; indexation seule 1 756 414 $ (−20,5 %) ; encadrement renforcé 1 701 507 $ (−23,0 %) ; φ = 0,5 : 2 056 076 $ (−6,9 %) ; récupération 5 % CAPEX 2 289 503 $ (+3,7 %) ; mode espérance 2 091 629 $ (−5,3 %). Élasticités : ≈ 11 900 $/point de rotation, ≈ 131 200 $/point de base. | |
| 20 | + | |
| 21 | +## Points non vérifiés / à l'attention de l'orchestrateur | |
| 22 | +1. **« Clause F »** : appellation d'usage (section F du bail) non lue dans les annexes du règlement T-15.01, r. 3 → CLM-34-15 `partiellement_verifie` ; mise entre guillemets dans le texte et signalée dans `limites`. | |
| 23 | +2. **Résumés de décisions TAL** cités sans référence neutre (CanLII inaccessible) → CLM-34-16 `partiellement_verifie`. | |
| 24 | +3. **Diamond, McQuade et Qian (2019)** : chiffres « environ 15 % » et « de l'ordre de 5 % » tirés du résumé publié → CLM-34-19 `partiellement_verifie`. | |
| 25 | +4. **Absence d'étude arbitrée sur le régime québécois** (CLM-34-21, `non_verifie`, constat déclaré) ; **recours de l'art. 1950 « peu exercé »** (CLM-34-23, `non_verifie`, observation de pratique à documenter ou nuancer). | |
| 26 | +5. **Pourcentages 2027** non publiés : l'exemple applique ceux de 2026 à des baux relevant de 2027 (déclaré). **Moratoire Loi 65** : état au 31 octobre 2026, à revérifier avant publication (fin le 6 juin 2027 ; avis de fin anticipée possible). | |
| 27 | +6. La reconstitution de l'**ancienne méthode** (éq. 34.5) est schématique et explicitement présentée comme telle ; la part attribuable inclut les autres revenus (approximation déclarée, formulaire RN fait foi). | |
| 28 | +7. Le modèle ne plafonne pas les loyers indexés au marché (scénarios base 4 % = bornes supérieures) et ne lie pas l'inoccupation à la rotation : déclaré dans `limites`. | |
| 29 | + | |
| 30 | +## Suggestions d'harmonisation | |
| 31 | +- `traite.cas3.projection_dcf` pourrait accueillir, sans changer ses résultats par défaut, les paramètres `fraction_rattrapage`, `recuperation_capex` et `mode="esperance"` de `ch34_tal.projection_scenario` (ch. 40 pourrait en tirer des lois d'intrants) ; `ch40` devrait faire varier la rotation sur [5 %, 25 %] et φ sur [0,5 ; 1]. | |
| 32 | +- Le ch. 26 (stub) doit décrire la mécanique des avis et des délais (art. 1941-1947, 1896, 1950) sans refaire la fixation : le ch. 34 renvoie à `ch:26` au niveau du chapitre seulement (aucune étiquette fine du ch. 26 n'est utilisée). | |
| 33 | +- Le ch. 44 (rénovation) peut réutiliser `fixation_nouvelle(..., immobilisations=I)` et `projection_scenario(recuperation_capex=True)` pour la rentabilité des travaux ; le ch. 47 cite déjà la Loi 65 et l'art. 1965 avec les mêmes références. | |
| 34 | +- `data/processed/ipc_quebec_mensuel.csv` (série v41691783) est réutilisable par les ch. 3, 4, 27, 35 ; penser à l'inscrire dans `data/raw/SOURCES.md`/README processed si l'orchestrateur tient un inventaire des fichiers traités. | |
| 35 | +- `library.bib` : ajouter dans la note de `talIndices2026` la revérification du 2026-10-31 et l'absence de pourcentages 2027. | |
added
verification/chapters/ch35-calculations.csv
+14 −0
@@ -0,0 +1,14 @@ | ||
| 1 | +id,chapitre,description,script,test,resultat_attendu,resultat_obtenu,statut,date | |
| 2 | +CALC-35-01,35,"Vérité de génération : β = ln(1,0443)/12 par mois ; indice au milieu de 2026T3, base 2023T1",analysis/python/cases/ch35_indices.py,analysis/python/tests/test_ch35.py::test_verite_et_donnees,"0,003614 ; 4,43 %/an ; 116,37","0,003614 ; 4,43 ; 116,37",verifie,2026-10-31 | |
| 3 | +CALC-35-02,35,"Indice hédonique à indicatrices trimestrielles (traite.hedonique.indice_dummies_temps) : k, σ, R² aj., valeur 2026T3 et IC 95 %, EAM, largeur IC",analysis/python/cases/ch35_indices.py,analysis/python/tests/test_ch35.py::test_indice_hedonique_identites,"29 ; 0,083 ; 0,849 ; 122,04 [117,72 ; 126,52] ; EAM 2,81 ; 7,47 %",idem,verifie,2026-10-31 | |
| 4 | +CALC-35-03,35,"Identité : imputation géométrique (Laspeyres = Paasche = Fisher) = indice à indicatrices quand les coefficients sont communs (prop. imputation-td)",analysis/python/cases/ch35_indices.py,analysis/python/tests/test_ch35.py::test_indice_hedonique_identites,"écart max < 1e-9","8,5e-14",verifie,2026-10-31 | |
| 5 | +CALC-35-04,35,"Indice chaîné par périodes adjacentes : I_t = I_{t-1} exp(δ_t) ; valeur finale ; EAM",analysis/python/cases/ch35_indices.py,analysis/python/tests/test_ch35.py::test_indice_hedonique_identites,"121,69 ; EAM 4,01","121,69 ; 4,01",verifie,2026-10-31 | |
| 6 | +CALC-35-05,35,"Médiane et moyenne géométrique : indices finaux, maximum, EAM, croissance totale ; décomposition exacte ln(moy. géo.) = composition + τ ; corrélation écart/composition",analysis/python/cases/ch35_indices.py,analysis/python/tests/test_ch35.py::test_mediane_et_composition,"128,63 ; max 133,68 ; moy. géo. 127,78 ; EAM médiane 10,97 ; hausse 28,63 % ; identité exacte ; corr 0,86","idem (identité à 1e-9)",verifie,2026-10-31 | |
| 7 | +CALC-35-06,35,"Jeu de reventes synthétique (generer_reventes, graine 20261031) : paires, rénovées, intervalle médian/moyen/min/max, part < 12 mois, é.-t. ln ratio, reproductibilité",analysis/python/cases/ch35_indices.py,analysis/python/tests/test_ch35.py::test_reventes_synthetiques,"220 ; 47 ; 10 ; 12,8 ; 3 ; 38 ; 55,9 % ; 0,144 ; identique à la régénération",idem,verifie,2026-10-31 | |
| 8 | +CALC-35-07,35,"BMN (traite.hedonique.ventes_repetees_bmn) : forme fermée (D'D)^{-1}D'y = lstsq ; σ_η ; valeur finale et IC ; e.-t. finale ; EAM ; largeur IC",analysis/python/cases/ch35_indices.py,analysis/python/tests/test_ch35.py::test_bmn_identites,"écart < 1e-10 ; 0,1455 ; 120,68 [109,78 ; 132,65] ; 0,0483 ; 3,02 ; 20,83 %",idem,verifie,2026-10-31 | |
| 9 | +CALC-35-08,35,"Case–Shiller (ventes_repetees_case_shiller) : coefficients de la variance (toutes paires ; sans rénovées) vs loi de génération ; poids = 1/√var ; |CS − BMN| ; valeur finale ; EAM",analysis/python/cases/ch35_indices.py,analysis/python/tests/test_ch35.py::test_case_shiller,"0,02079 − 0,00023·Δ ; 0,01574 + 0,00053·Δ ; théorie 0,01445 + 0,0012·Δ ; < 0,5 pt ; 120,48 ; 2,90",idem,verifie,2026-10-31 | |
| 10 | +CALC-35-09,35,"SPAR géométrique et arithmétique (valeur_role_2025) : valeurs finales, EAM, é.-t. ln(P/A), ratio médian, identité du rapport des sommes",analysis/python/cases/ch35_indices.py,analysis/python/tests/test_ch35.py::test_spar,"115,06 ; 114,36 ; 1,26 ; 0,087 ; 1,095",idem,verifie,2026-10-31 | |
| 11 | +CALC-35-10,35,"Ajustement de conditions du marché de G0404 (506 500 $, 2025T2 → 2026T3, 15,2 mois) selon l'indice : vérité, tendance, hédonique, chaîné, BMN, CS, SPAR, médiane",analysis/python/cases/ch35_indices.py,analysis/python/tests/test_ch35.py::test_ajustement_et_sensibilite,"28 558 ; 28 733 ; 44 760 ; 30 987 ; 32 406 ; 32 359 ; 26 575 ; 44 009 $",idem,verifie,2026-10-31 | |
| 12 | +CALC-35-11,35,"Sensibilité : révision BMN de 2025T2 (millésimes 2025T2/2025T4/2026T3) et de 2024T4 ; filtres 12/18 mois et oracle ; sous-échantillons 50 %/25 % (EAM, é.-t. finale) ; fenêtre hédonique 8 trimestres",analysis/python/cases/ch35_indices.py,analysis/python/tests/test_ch35.py::test_ajustement_et_sensibilite,"116,39 → 114,69 → 113,42 ; 114,45 → 109,99 ; hausses 20,68/17,98/19,41/18,39 % ; EAM 3,02/2,76/6,56/2,01 ; 4,26 & 4,77 ; 6,46 & 8,96 ; écart max 0,37 %",idem,verifie,2026-10-31 | |
| 13 | +CALC-35-12,35,"Solutions des exercices 35.1 à 35.8 (composition 3,79 % ; indices exp(τ) ; BMN à la main β = (0,0461 ; 0,1093) ; poids CS 1/0,902/0,807/0,737 ; SPAR 103,36/103,16 ; ajustements G0404 ; géométrique 1,1247 vs arithmétique 1,125 vs pondéré 1,1333)",analysis/python/cases/ch35_indices.py,analysis/python/tests/test_ch35.py::test_solutions_exercices,"voir solutions/ch35.tex",idem,verifie,2026-10-31 | |
| 14 | +CALC-35-13,35,"IPLN (18-10-0205-01) : variation sur douze mois en septembre 2026 par RMR et Canada ; sommets de la variation annuelle",analysis/python/figures/ch35_figures.py,—,"Montréal 1,2 ; Québec 1,9 ; Gatineau 0,3 ; Sherbrooke 1,2 ; Trois-Rivières 3,0 ; Canada −2,0 ; sommets 21,9 (2021-09), 14,3 (2021-05), 6,7 (2020-12), 12,2 (Canada 2021-08)","idem (calcul direct sur data/raw)",verifie,2026-10-31 | |
added
verification/chapters/ch35-claims.csv
+13 −0
@@ -0,0 +1,13 @@ | ||
| 1 | +id,chapitre,affirmation,source_id,verification,date,statut,notes | |
| 2 | +CLM-35-01,35,"Indice Teranet–Banque Nationale : méthode des ventes répétées (« properties with at least two sales ») ; exclusion des ventes non sincères, des changements de type de propriété (rénovations), des erreurs de données et des rotations « biannual or higher »",SRC-35-001,Page « Our Methodology » lue le 2026-10-31 (curl, certificat TLS invalide),2026-10-31,verifie,"La page ne donne ni l'estimateur exact ni les seuils numériques ni la politique de révision ; le document complet annoncé n'était pas accessible" | |
| 3 | +CLM-35-02,35,"Teranet–BN septembre 2026 : composite 294,94 (+0,1 % m/m désaisonnalisé ; −3,5 % a/a ; −8,1 % sous le sommet d'avril 2022) ; Montréal 310,62 (+1,4 % a/a ; −1,8 % sous le sommet de mars 2026) ; Québec 327,56 (+7,7 % a/a ; sommet) ; juin 2005 = 100 ; 11 RMR",SRC-C-060,notes-C.md §4.9,2026-10-31,verifie, | |
| 4 | +CLM-35-03,35,"IPLN : série mensuelle mesurant les variations des prix de vente des maisons neuves établis par les entrepreneurs « lorsque les spécifications détaillées de chaque maison demeurent les mêmes pendant deux périodes consécutives » ; prix ajustés des variations de qualité ; « n'est pas révisé et n'est pas désaisonnalisé » ; base déc. 2016 ; population : maisons individuelles, jumelées, en rangée",SRC-35-002,Fiche IMDB 2310 lue le 2026-10-31,2026-10-31,verifie,"Méthodologie détaillée 62F0014M2022003 non consultée" | |
| 5 | +CLM-35-04,35,"IPLN septembre 2026 (déc. 2016 = 100) : Montréal 159,5 (+1,2 %), Québec 144,2 (+1,9 %), Ottawa-Gatineau partie QC 116,6 (+0,3 %), Sherbrooke +1,2 %, Trois-Rivières +3,0 %, Canada 120,2 (−2,0 %) ; sommets de la variation annuelle : Montréal 21,9 % (sept. 2021), Québec 14,3 % (mai 2021), Gatineau 6,7 % (déc. 2020), Canada 12,2 % (août 2021)",SRC-E-016,Calcul direct sur data/raw/statcan_18100205/18100205.csv (ch35_figures.py),2026-10-31,verifie, | |
| 6 | +CLM-35-05,35,"APCIQ T3 2026 : prix médian unifamiliale 500 000 $ (province), 480 000 $ (RMR de Gatineau) ; médianes Centris = ventes par courtier, date de la promesse ; attribution obligatoire",SRC-C-053;SRC-C-055;SRC-E-001,notes-C.md §4.8 ; notes-E.md,2026-10-31,verifie, | |
| 7 | +CLM-35-06,35,"JLR septembre 2026 : médianes 12 mois province unifamiliale 483 000 $ (+7 %), 2-5 logements 622 000 $ (+9 %) ; transactions du Registre foncier ; seuil 30 ; exclusions (< 5 000 $, ventes liées, reprises) ; pas d'indice formel",SRC-C-059,notes-C.md §4.9,2026-10-31,verifie, | |
| 8 | +CLM-35-07,35,"MEFQ 2026 partie 3A : taux de variation du marché ; Règlement sur la proportion médiane art. 11-12 : rajustement des prix aux conditions du marché à la date de référence ; LFM art. 264 : proportion médiane et facteur comparatif",SRC-B-001,notes-B.md §9 ; LégisQuébec consulté le 2026-10-31 (via ch18),2026-10-31,verifie,"Repris du ch. 18 (CLM-18-03 et suivants)" | |
| 9 | +CLM-35-08,35,"Harding, Rosenthal et Sirmans (2007) : dépréciation nette d'entretien de l'ordre de 2 à 3 % par an (États-Unis), estimée par ventes répétées",SRC-D-035,notes-D.md §ch. 24/35 ; article dans library.bib (DOI 10.1016/j.jue.2006.07.007),2026-10-31,partiellement_verifie,"Ordre de grandeur cité de mémoire de l'article (résumé : ≈ 2,5 %/an brut d'entretien) ; à confirmer à la page avant publication ; non transposable au Québec (dit dans limites)" | |
| 10 | +CLM-35-09,35,"Silverstein (2014) : révisions des indices de prix américains de l'ordre du point de pourcentage sur les variations récentes, de signe systématique lié à la surreprésentation des reventes rapides dans les premières publications",SRC-D-016,notes-D.md §ch. 35 ; rapport FRB Philadelphia dans library.bib,2026-10-31,partiellement_verifie,"Sens général du rapport ; les chiffres exacts n'ont pas été relus" | |
| 11 | +CLM-35-10,35,"Méthodologie publiée de l'indice S&P CoreLogic Case–Shiller (filtres numériques, moyenne mobile) : NON citée",—,Accès refusé (HTTP 403) le 2026-10-31 sur spglobal.com,2026-10-31,non_verifie,"Le chapitre s'en tient aux articles de Case et Shiller (1987, 1989) ; mentionné dans limites" | |
| 12 | +CLM-35-11,35,"Shiller (2015) défend des marchés de dérivés et d'assurance adossés aux indices de prix des maisons comme instruments de partage du risque",SRC-D-016,notes-D.md (Irrational Exuberance, 3e éd., library.bib),2026-10-31,partiellement_verifie,"Thème récurrent de l'ouvrage ; page non citée" | |
| 13 | +CLM-35-12,35,"Hypothèses du jeu de reventes synthétique : p = 0,60 ; 15 % de reventes rapides (3-9 mois) à prime N(0,12 ; 0,04) ; δ² = 0,0004/mois ; σ = 0,085 ; tendance 4,43 %/an ; valeur au rôle = 0,93 × valeur au 2024-07-01, σ 0,07",—,Déclarées dans l'encadré synthetique et dans le script,2026-10-31,non_verifie,"Hypothèses pédagogiques, pas des estimations ; signalées comme telles" | |
added
verification/chapters/ch35-figures.csv
+6 −0
@@ -0,0 +1,6 @@ | ||
| 1 | +fichier,chapitre,script,donnees,type_donnees,legende_courte,statut | |
| 2 | +ch35/ch35-fig-indices-compares.pdf,35,analysis/python/figures/ch35_figures.py,"data/synthetic/cas1_gatineau_ventes.csv;reventes synthétiques générées par analysis/python/cases/ch35_indices.py",synthetique,"Indices hédonique (IC 95 %), BMN, Case–Shiller, SPAR et vérité de génération, base 2023T1 = 100 ; écarts à la vérité [données synthétiques]",verifie | |
| 3 | +ch35/ch35-fig-biais-mediane.pdf,35,analysis/python/figures/ch35_figures.py,data/synthetic/cas1_gatineau_ventes.csv,synthetique,"Indice de la médiane et de la moyenne géométrique vs indice hédonique ; moyenne géométrique corrigée de la composition ; effet de composition et part d'Aylmer par trimestre [données synthétiques]",verifie | |
| 4 | +ch35/ch35-fig-poids-case-shiller.pdf,35,analysis/python/figures/ch35_figures.py,"reventes synthétiques générées par analysis/python/cases/ch35_indices.py (graine 20261031)",synthetique,"Carrés des résidus BMN selon l'intervalle (reventes rapides rénovées distinguées), droites ajustées avec et sans elles, loi de génération, poids relatifs de Case–Shiller [données synthétiques]",verifie | |
| 5 | +ch35/ch35-fig-revision-filtre.pdf,35,analysis/python/figures/ch35_figures.py,"reventes synthétiques générées par analysis/python/cases/ch35_indices.py (graine 20261031)",synthetique,"Indice BMN selon le millésime de publication (2025T2, 2025T4, 2026T3) ; effet du filtre des reventes rapides (12 et 18 mois) et oracle sans reventes rénovées [données synthétiques]",verifie | |
| 6 | +ch35/ch35-fig-ipln-variation.pdf,35,analysis/python/figures/ch35_figures.py,data/raw/statcan_18100205/18100205.csv,reelle,"IPLN total (déc. 2016 = 100), variation sur douze mois, janv. 2011 – sept. 2026 : Montréal, Québec, Gatineau (partie QC), Sherbrooke, Trois-Rivières, Canada (18-10-0205-01, licence ouverte StatCan)",verifie | |
added
verification/chapters/ch35-registry.csv
+4 −0
@@ -0,0 +1,4 @@ | ||
| 1 | +id,type,titre,organisme,version_ou_date,url,date_consultation,statut,utilise_dans,notes | |
| 2 | +SRC-35-001,page_web,"Our Methodology — Teranet–National Bank House Price Index",Teranet / Banque Nationale du Canada,Consultée le 2026-10-31,https://housepriceindex.ca/about/our-methodology/,2026-10-31,verifie,35,"Lue par curl en mode non vérifié (certificat TLS invalide) ; méthode des ventes répétées, exclusions a)-d), exemple simplifié ; document complet annoncé non accessible ; clé teranetMethodologie2026 (lib-ch35.bib)" | |
| 3 | +SRC-35-002,metadonnees,"Indice des prix des logements neufs (IPLN) — fiche de l'enquête 2310 (Base de métadonnées intégrée)",Statistique Canada,Date de parution 2026-10-22 ; consultée le 2026-10-31,https://www23.statcan.gc.ca/imdb/p2SV_f.pl?Function=getSurvey&SDDS=2310,2026-10-31,verifie,35,"Définition, ajustement de qualité, population cible, collecte, « n'est pas révisé et n'est pas désaisonnalisé » ; renvoie à 62F0014M2022003 (non consulté) ; clé statcaniplnimdb2310 (lib-ch35.bib)" | |
| 4 | +SRC-35-003,page_web,"S&P CoreLogic Case-Shiller Home Price Indices — Methodology (PDF)",S&P Dow Jones Indices,Tentative le 2026-10-31,https://www.spglobal.com/spdji/en/documents/methodologies/methodology-sp-corelogic-cs-home-price-indices.pdf,2026-10-31,inaccessible,35,"HTTP 403 ; non cité dans le chapitre (voir CLM-35-10)" | |
modified
verification/claims.csv
+35 −0
@@ -795,3 +795,38 @@ CLM-33-029,33,IPCB Montréal T3 2026 : immeuble < 5 étages +9,2 % ; tour ≥ 5 | ||
| 795 | 795 | CLM-33-030,33,Description qualitative des marchés privé et institutionnel (acteurs tailles liquidité) et typologie walk-up / conciergerie / tour / neuf,—,pratique courante ; aucune statistique publique de liquidité par taille,2026-10-31,non_verifie,déclaré comme pratique courante dans le texte et les limites |
| 796 | 796 | CLM-33-031,33,Effets de taille d'état et de région (régression cas 4) et taux des RMR hors Montréal/Québec,—,propriétés du jeu synthétique (calibration.json : cap_rate_autres_rmr NON_VERIFIE),2026-10-31,non_verifie,données synthétiques déclarées ; encadré synthetique |
| 797 | 797 | CLM-33-032,33,P45 : amortissement 480 mois de la dette (hypothèse de l'auteur) ; RCD 1,15,—,hypothèse déclarée,2026-10-31,non_verifie |
| 798 | +CLM-34-01,34,"Règlement sur les critères de fixation de loyer, art. 3 (six critères) et 3.1 (pourcentage de base = moyenne des trois variations annuelles de l'IPC Québec ; négatif réputé nul), remplacés par D. 1455-2025 en vigueur le 2026-01-01",SRC-A-003;SRC-A-004,Texte refondu LégisQuébec relu le 2026-10-31 (section II,art. 3 à 7) ; G.O. 2 du 2025-12-17 (notes-A.md §6.2),2026-10-31,verifie | |
| 799 | +CLM-34-02,34,Art. 4 : variation positive moins la valeur absolue du pourcentage de base (sans égard au plancher) ; négatif réputé nul ; % net appliqué au montant mensuel des taxes municipales de la période de référence et au montant mensuel de la période précédente pour le scolaire et les assurances,SRC-A-003,Texte refondu LégisQuébec relu le 2026-10-31 (art. 4,al. 2 et 3),2026-10-31,verifie | |
| 800 | +CLM-34-03,34,Art. 5 : part attribuable = loyer au terme du bail / revenus ; proportion sur les logements bénéficiaires si la dépense ne concerne que certains logements ; aucune part pour un local non résidentiel,SRC-A-003,LégisQuébec relu le 2026-10-31,2026-10-31,verifie,le chapitre inclut les autres revenus dans les revenus (approximation déclarée ; formulaire RN fait foi) | |
| 801 | +CLM-34-04,34,"Art. 1 (période de référence : année civile précédant le terme pour un bail se terminant du 1er avril au 31 décembre ; avant-dernière sinon), art. 6 (loyer de faveur → logements comparables), art. 13 (dépense comptée une fois, réduite des aides), art. 15.1 (arrondi au dollar), annexe I (liste de travaux), art. 3 al. 4 (prêt à intérêt réduit)",SRC-A-003,notes-A.md §6.2 et texte refondu (art. 13,15.1,annexe 1 relus le 2026-10-31),2026-10-31 | |
| 802 | +CLM-34-05,34,"TAL, pourcentages 2025/2026 : base 4,5 %/3,1 % ; services à la personne 6 %/6,7 % ; immobilisations 5 %/5 % ; ancienne méthode : électricité 2,9/2,9 ; gaz −5,8/13,7 ; mazout −2,9/4,9 ; entretien 6,9/5,7 ; services immeuble 4,5/3,4 ; RPA 5,9/6,3 ; gestion 8,2/7,2 ; revenu net 6,9/5,7 ; immobilisations 4,7/4 ; aucun pourcentage 2027 publié",SRC-A-005,Page TAL rechargée et relue le 2026-10-31 (curl,deux tableaux),2026-10-31,verifie | |
| 803 | +CLM-34-06,34,"Pourcentage de base 2026 (3,1 %) et 2025 (4,5 %) reproduits par la formule de l'art. 3.1 à partir de l'IPC Québec (StatCan 18-10-0004-01, v41691783) : 3,095 % et 4,496 %",SRC-E-015,Calcul ch34_tal.py sur data/processed/ipc_quebec_mensuel.csv (extrait du brut),2026-10-31,verifie,hypothèse : période de référence = année civile et IPC « ensemble » Québec non désaisonnalisé ; l'avis d'indexation (2026) 158 G.O. 1 146 n'a pas été ouvert | |
| 804 | +CLM-34-07,34,"Communiqué TAL du 21 janvier 2025 : ancienne méthode (composantes IPC ; gestion 5 % des revenus, réels jusqu'à 10 % ; immobilisations au taux CPG 5 ans + 1 %) ; moyenne de 3,8 % d'ajustement accordé 2015-2024 ; 8 650 calculs en 2023-2024",SRC-A-007,PDF lu (notes-A.md §6.2),2026-10-31,verifie,la structure de l'ancien formulaire reconstituée dans eq. 34.5 est schématique (déclaré) | |
| 805 | +CLM-34-08,34,"Loi sur le TAL : art. 28 (compétence exclusive, art. 1941-1964 C.c.Q.), art. 91 (pas d'appel en fixation), art. 56.3 (formulaire RN dans les 90 jours, péremption), art. 108 (habilitation réglementaire), art. 51-54.13 (conversion)",SRC-A-002,LégisQuébec à jour au 2026-10-15 (notes-A.md §6.1),2026-10-31,verifie, | |
| 806 | +CLM-34-09,34,"C.c.Q. art. 1941-1947 (reconduction, avis 3-6 mois, refus dans le mois, fixation dans le mois du refus), 1952-1953 (fixation selon normes, ≤ 12 mois), 1896 (section G loyer le plus bas des 12 mois ; dommages punitifs 2024 c. 2), 1950 (10 jours / 2 mois), 1945 (logement 1955 : refus = départ)",SRC-A-001,LégisQuébec CCQ-1991 à jour au 2026-10-15 (notes-A.md §4-6 ; normes-A NRM-A-075 à 083),2026-10-31,verifie, | |
| 807 | +CLM-34-10,34,C.c.Q. art. 1978.1-1978.4 (Loi 31) : refus de cession pour motif autre que sérieux → résiliation à la date de cession ; aucune contrepartie exigible par le cédant ; sous-location plafonnée,SRC-A-001;SRC-A-013,LégisQuébec ; L.Q. 2024 c. 2 (notes-A.md §4),2026-10-31,verifie,transitoire : avis avant le 21 février 2024 exclus | |
| 808 | +CLM-34-11,34,"C.c.Q. art. 1957-1958 (reprise réservée au propriétaire personne physique ; indivisaire seulement avec son conjoint), 1970 (relocation après reprise avec autorisation et fixation), 1963-1964 (autorisation TAL, preuve), 1937 (vente n'affecte pas le bail)",SRC-A-001,LégisQuébec (notes-A.md §5 ; NRM-A-078,086,090,092) | |
| 809 | +CLM-34-12,34,Loi 65 (L.Q. 2024 c. 23) : moratoire sur les évictions de l'art. 1959 jusqu'au 6 juin 2027 (art. 1) ; soustraction territoriale possible (art. 2) ; fin anticipée 60 jours après avis si inoccupation SCHL ≥ 3 % dans les centres ≥ 10 000 hab. (art. 11) ; rapport au gouvernement au plus tard le 6 février 2027 (art. 12) ; aucun avis de fin anticipée repéré au 2026-10-31,SRC-A-014,G.O. 2 du 2024-07-10 p. 4917-4921 (notes-A.md §5 ; NRM-A-088),2026-10-31,verifie,état du moratoire à revérifier avant publication | |
| 810 | +CLM-34-13,34,"C.c.Q. art. 1959.1 : protection si 65 ans ou plus, 10 ans d'occupation, revenu ≤ 125 % du seuil HLM (auparavant 70 ans et 100 %) ; art. 1965 : indemnité 1 mois/année, min 3, max 24 mois + déménagement ; art. 1968 : bonne foi, dommages punitifs",SRC-A-001;SRC-A-014,LégisQuébec (NRM-A-087,091),2026-10-31,verifie | |
| 811 | +CLM-34-14,34,"C.c.Q. art. 1955 : exclusion de la fixation 5 ans pour immeuble neuf/changement d'affectation ; opposable seulement si au bail et, depuis le 21 février 2024, si loyer maximal indiqué ; coopératives d'habitation depuis le 24 février 2026 (2026 c. 1 a. 152)",SRC-A-001,LégisQuébec (NRM-A-083 ; notes-A.md §5),2026-10-31,verifie, | |
| 812 | +CLM-34-15,34,L'appellation « clause F » désigne la section F du formulaire de bail (restrictions au droit à la fixation),,Usage ; annexes du règlement T-15.01 r. 3 non reproduites par LégisQuébec (notes-A.md point 5),2026-10-31,partiellement_verifie,déclaré entre guillemets dans le chapitre et signalé dans limites | |
| 813 | +CLM-34-16,34,"Résumés de décisions TAL : « la valeur marchande locative n'est pas un critère » (Société de financement alouette inc. c. A., 2022-08-17) ; dépense comptée une seule fois, travaux connus au bail initial non récupérables (André-Bélisle c. Audet, 2022-03-17) ; regroupement de dépenses de plusieurs années non permis (Skalli Housseini, 2021-04-20) ; exception 1955 immeuble < 5 ans (OMH Montréal c. Morin, 2024-01-09)",SRC-A-011;SRC-A-038,Résumés officiels lus sur tal.gouv.qc.ca (notes-A.md §6.3) ; références neutres non affichées,2026-10-31,partiellement_verifie,textes intégraux non ouverts (CanLII 403) | |
| 814 | +CLM-34-17,34,"SCHL oct. 2025 : taux de rotation RMR Montréal 9,4 % (11,1 % en 2024) ; loyer 2 ch. +7,2 % à échantillon constant",SRC-E-007,Rapport sur le marché locatif 2025-12-11 (notes-E.md),2026-10-31,verifie, | |
| 815 | +CLM-34-18,34,"Formulaire RN 2026-2027 : revenus de décembre 2025 × 12, autres revenus 2025, taxes municipales 2026/2025, scolaires 2025-2026/2024-2025, assurances au 31 décembre 2025/2024, dépenses d'immobilisation 2025 avec nombre de logements",SRC-A-009,PDF TAL-004-E RN (2025-11) lu (notes-A.md §6.2),2026-10-31,verifie, | |
| 816 | +CLM-34-19,34,"Diamond, McQuade et Qian (2019) : réduction de l'offre locative d'environ 15 % par les locateurs visés et hausse des loyers de l'ordre de 5 % à l'échelle de San Francisco",,Résumé de l'article (AER 109(9),DOI 10.1257/aer.20181289) ; chiffres du résumé publié,2026-10-31,partiellement_verifie | |
| 817 | +CLM-34-20,34,"Glaeser et Luttmer (2003), Sims (2007), Autor, Palmer et Pathak (2014), Kholodilin (2024), Arnott (1995), Fallis et Smith (1985), Smith (1988) : résultats résumés qualitativement (mauvaise allocation ; décontrôle → loyers, offre, entretien ; retombées de voisinage ; recension ; première/deuxième génération ; Ontario)",,notes-D.md §ch. 34 (entrées vérifiées par DOI) ; aucun chiffre précis cité sauf CLM-34-19,2026-10-31,verifie, | |
| 818 | +CLM-34-21,34,Aucune étude arbitrée propre au régime québécois actuel de fixation des loyers n'a été identifiée,,notes-D.md (entrées non vérifiées : « Études arbitrées sur le contrôle des loyers au Québec » — rien trouvé),2026-10-31,non_verifie,constat d'absence déclaré dans limites et dans l'encadré | |
| 819 | +CLM-34-22,34,NPP OEAQ : hypothèses extraordinaires à divulguer avec leur incidence sur la valeur (règle 2.1 c)),SRC-C-001,notes-C.md §2.1 (repris du ch. 30,CLM-30-12),2026-10-31,verifie | |
| 820 | +CLM-34-23,34,Le recours de l'art. 1950 est peu exercé en pratique,,Aucune statistique officielle trouvée (TAL ne publie pas le nombre de demandes de l'art. 1950 dans les sources consultées),2026-10-31,non_verifie,formulé comme observation de pratique ; à documenter ou à nuancer avant publication | |
| 821 | +CLM-35-01,35,"Indice Teranet–Banque Nationale : méthode des ventes répétées (« properties with at least two sales ») ; exclusion des ventes non sincères, des changements de type de propriété (rénovations), des erreurs de données et des rotations « biannual or higher »",SRC-35-001,Page « Our Methodology » lue le 2026-10-31 (curl,certificat TLS invalide),2026-10-31,verifie | |
| 822 | +CLM-35-02,35,"Teranet–BN septembre 2026 : composite 294,94 (+0,1 % m/m désaisonnalisé ; −3,5 % a/a ; −8,1 % sous le sommet d'avril 2022) ; Montréal 310,62 (+1,4 % a/a ; −1,8 % sous le sommet de mars 2026) ; Québec 327,56 (+7,7 % a/a ; sommet) ; juin 2005 = 100 ; 11 RMR",SRC-C-060,notes-C.md §4.9,2026-10-31,verifie, | |
| 823 | +CLM-35-03,35,"IPLN : série mensuelle mesurant les variations des prix de vente des maisons neuves établis par les entrepreneurs « lorsque les spécifications détaillées de chaque maison demeurent les mêmes pendant deux périodes consécutives » ; prix ajustés des variations de qualité ; « n'est pas révisé et n'est pas désaisonnalisé » ; base déc. 2016 ; population : maisons individuelles, jumelées, en rangée",SRC-35-002,Fiche IMDB 2310 lue le 2026-10-31,2026-10-31,verifie,Méthodologie détaillée 62F0014M2022003 non consultée | |
| 824 | +CLM-35-04,35,"IPLN septembre 2026 (déc. 2016 = 100) : Montréal 159,5 (+1,2 %), Québec 144,2 (+1,9 %), Ottawa-Gatineau partie QC 116,6 (+0,3 %), Sherbrooke +1,2 %, Trois-Rivières +3,0 %, Canada 120,2 (−2,0 %) ; sommets de la variation annuelle : Montréal 21,9 % (sept. 2021), Québec 14,3 % (mai 2021), Gatineau 6,7 % (déc. 2020), Canada 12,2 % (août 2021)",SRC-E-016,Calcul direct sur data/raw/statcan_18100205/18100205.csv (ch35_figures.py),2026-10-31,verifie, | |
| 825 | +CLM-35-05,35,"APCIQ T3 2026 : prix médian unifamiliale 500 000 $ (province), 480 000 $ (RMR de Gatineau) ; médianes Centris = ventes par courtier, date de la promesse ; attribution obligatoire",SRC-C-053;SRC-C-055;SRC-E-001,notes-C.md §4.8 ; notes-E.md,2026-10-31,verifie, | |
| 826 | +CLM-35-06,35,"JLR septembre 2026 : médianes 12 mois province unifamiliale 483 000 $ (+7 %), 2-5 logements 622 000 $ (+9 %) ; transactions du Registre foncier ; seuil 30 ; exclusions (< 5 000 $, ventes liées, reprises) ; pas d'indice formel",SRC-C-059,notes-C.md §4.9,2026-10-31,verifie, | |
| 827 | +CLM-35-07,35,MEFQ 2026 partie 3A : taux de variation du marché ; Règlement sur la proportion médiane art. 11-12 : rajustement des prix aux conditions du marché à la date de référence ; LFM art. 264 : proportion médiane et facteur comparatif,SRC-B-001,notes-B.md §9 ; LégisQuébec consulté le 2026-10-31 (via ch18),2026-10-31,verifie,Repris du ch. 18 (CLM-18-03 et suivants) | |
| 828 | +CLM-35-08,35,"Harding, Rosenthal et Sirmans (2007) : dépréciation nette d'entretien de l'ordre de 2 à 3 % par an (États-Unis), estimée par ventes répétées",SRC-D-035,notes-D.md §ch. 24/35 ; article dans library.bib (DOI 10.1016/j.jue.2006.07.007),2026-10-31,partiellement_verifie,"Ordre de grandeur cité de mémoire de l'article (résumé : ≈ 2,5 %/an brut d'entretien) ; à confirmer à la page avant publication ; non transposable au Québec (dit dans limites)" | |
| 829 | +CLM-35-09,35,"Silverstein (2014) : révisions des indices de prix américains de l'ordre du point de pourcentage sur les variations récentes, de signe systématique lié à la surreprésentation des reventes rapides dans les premières publications",SRC-D-016,notes-D.md §ch. 35 ; rapport FRB Philadelphia dans library.bib,2026-10-31,partiellement_verifie,Sens général du rapport ; les chiffres exacts n'ont pas été relus | |
| 830 | +CLM-35-10,35,"Méthodologie publiée de l'indice S&P CoreLogic Case–Shiller (filtres numériques, moyenne mobile) : NON citée",—,Accès refusé (HTTP 403) le 2026-10-31 sur spglobal.com,2026-10-31,non_verifie,"Le chapitre s'en tient aux articles de Case et Shiller (1987, 1989) ; mentionné dans limites" | |
| 831 | +CLM-35-11,35,Shiller (2015) défend des marchés de dérivés et d'assurance adossés aux indices de prix des maisons comme instruments de partage du risque,SRC-D-016,notes-D.md (Irrational Exuberance,3e éd.,library.bib),2026-10-31 | |
| 832 | +CLM-35-12,35,"Hypothèses du jeu de reventes synthétique : p = 0,60 ; 15 % de reventes rapides (3-9 mois) à prime N(0,12 ; 0,04) ; δ² = 0,0004/mois ; σ = 0,085 ; tendance 4,43 %/an ; valeur au rôle = 0,93 × valeur au 2024-07-01, σ 0,07",—,Déclarées dans l'encadré synthetique et dans le script,2026-10-31,non_verifie,"Hypothèses pédagogiques, pas des estimations ; signalées comme telles" | |
modified
verification/figures.csv
+5 −0
@@ -172,3 +172,8 @@ ch34/ch34-fig-calcul-tal.pdf,34,analysis/python/figures/ch34_calcul_tal.py,data/ | ||
| 172 | 172 | ch34/ch34-fig-rotation-esperee.pdf,34,analysis/python/figures/ch34_rotation.py,paramètres q0 = 0,695 (logement 103 du cas 3),g_c 3,1 % |
| 173 | 173 | ch34/ch34-fig-trajectoires-rbp.pdf,34,analysis/python/figures/ch34_trajectoires.py,data/synthetic/cas3_montreal_*.csv;cas3_montreal_hypotheses_dcf.json,synthetique,"Trajectoires du RBP contractuel du cas 3 selon la rotation (5/15/25 %), indexation seule et encadrement renforcé vs RBP au marché [données synthétiques]",verifie |
| 174 | 174 | ch34/ch34-fig-valeur-scenarios.pdf,34,analysis/python/figures/ch34_valeur_scenarios.py,data/synthetic/cas3_montreal_*.csv;cas3_montreal_hypotheses_dcf.json,synthetique,"Valeur DCF du cas 3 pour la grille rotation × pourcentage de base, avec repères (base, capitalisation directe, indexation seule, encadrement renforcé) [données synthétiques]",verifie |
| 175 | +ch35/ch35-fig-indices-compares.pdf,35,analysis/python/figures/ch35_figures.py,data/synthetic/cas1_gatineau_ventes.csv;reventes synthétiques générées par analysis/python/cases/ch35_indices.py,synthetique,"Indices hédonique (IC 95 %), BMN, Case–Shiller, SPAR et vérité de génération, base 2023T1 = 100 ; écarts à la vérité [données synthétiques]",verifie | |
| 176 | +ch35/ch35-fig-biais-mediane.pdf,35,analysis/python/figures/ch35_figures.py,data/synthetic/cas1_gatineau_ventes.csv,synthetique,Indice de la médiane et de la moyenne géométrique vs indice hédonique ; moyenne géométrique corrigée de la composition ; effet de composition et part d'Aylmer par trimestre [données synthétiques],verifie | |
| 177 | +ch35/ch35-fig-poids-case-shiller.pdf,35,analysis/python/figures/ch35_figures.py,reventes synthétiques générées par analysis/python/cases/ch35_indices.py (graine 20261031),synthetique,"Carrés des résidus BMN selon l'intervalle (reventes rapides rénovées distinguées), droites ajustées avec et sans elles, loi de génération, poids relatifs de Case–Shiller [données synthétiques]",verifie | |
| 178 | +ch35/ch35-fig-revision-filtre.pdf,35,analysis/python/figures/ch35_figures.py,reventes synthétiques générées par analysis/python/cases/ch35_indices.py (graine 20261031),synthetique,"Indice BMN selon le millésime de publication (2025T2, 2025T4, 2026T3) ; effet du filtre des reventes rapides (12 et 18 mois) et oracle sans reventes rénovées [données synthétiques]",verifie | |
| 179 | +ch35/ch35-fig-ipln-variation.pdf,35,analysis/python/figures/ch35_figures.py,data/raw/statcan_18100205/18100205.csv,reelle,"IPLN total (déc. 2016 = 100), variation sur douze mois, janv. 2011 – sept. 2026 : Montréal, Québec, Gatineau (partie QC), Sherbrooke, Trois-Rivières, Canada (18-10-0205-01, licence ouverte StatCan)",verifie | |
| 175 | 180 | |