SPB Git

Ch. 34 livré et validé (compilation isolée, tests, fusion des registres)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Simon-Pierre Boucher committed (Oct 31, 2026) parent b39d09a Browse files

70 changed files +3,828 −128

70 changed files
Unified Split
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
Menu

Theme