SPB Git

Ch. 28 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 11f6c54 Browse files

13 changed files +961 −75

modified analysis/python/cases/ch20_reconciliation.py +97 −52
@@ -26,16 +26,16 @@ GROUPES = ["conditions_marche", "localisation", "superficie", "terrain", "age",
26 COLS_GROUPES = {"conditions_marche": ["mois"], "localisation": ["dist_centre"] + [f"sect_{x}" for x in cas1.SECTEURS[1:]], 26 COLS_GROUPES = {"conditions_marche": ["mois"], "localisation": ["dist_centre"] + [f"sect_{x}" for x in cas1.SECTEURS[1:]],
27 "superficie": ["ln_S"], "terrain": ["ln_T"], "age": ["age", "age2_100"], "qualite": ["qualite"], 27 "superficie": ["ln_S"], "terrain": ["ln_T"], "age": ["age", "age2_100"], "qualite": ["qualite"],
28 "garage": ["garage"], "sous_sol": ["sous_sol"], "piscine": ["piscine"], "type": ["etages"]} 28 "garage": ["garage"], "sous_sol": ["sous_sol"], "piscine": ["piscine"], "type": ["etages"]}
29−# Hypothèses déclarées de l'indication par le coût (à aligner avec les ch. 24 et 25 ; voir verification/chapters/ch20.md) 29+# Indication par le coût : reprise du chapitre 24 (ch24_depreciation.py : terrain extrait des ventes récentes de Hull, dépréciation
30−ALPHA_TERRAIN = 0.30 # part du terrain dans la valeur (méthode d'allocation) — hypothèse pédagogique, NON_VERIFIE 30+# extraite du marché à 28 ans + désuétude fonctionnelle). Les « hypothèses d'usage » ci-dessous ne servent que de contre-exemple.
31−VIE_ECONOMIQUE = 60 # vie économique totale (ans), méthode âge-vie — hypothèse pédagogique, NON_VERIFIE 31+ALPHA_TERRAIN_USAGE = 0.30 # allocation (part du terrain) — hypothèse d'usage, contre-exemple
32−AGE_EFFECTIF = 28 # âge effectif = âge chronologique (entretien normal présumé, aucune inspection dans le dossier) 32+VIE_ECONOMIQUE_USAGE = 60 # âge-vie — hypothèse d'usage, contre-exemple
33−GRILLE_ALPHA = [0.20, 0.25, 0.30, 0.35, 0.40] 33+AGE_EFFECTIF = 28
34−GRILLE_VIE = [50, 60, 75] 34+B_COUT = B # réplications du bootstrap de l'indication par le coût (mêmes tirages des 600 ventes que le modèle)
35 # Prédictions du sujet sous les quatre spécifications « en logarithme » du ch. 18 (tab. 18.variantes : semi-log, log-log, 35 # Prédictions du sujet sous les quatre spécifications « en logarithme » du ch. 18 (tab. 18.variantes : semi-log, log-log,
36 # Box–Cox, indicatrices trimestrielles) : sert à l'incertitude de spécification du modèle hédonique. 36 # Box–Cox, indicatrices trimestrielles) : sert à l'incertitude de spécification du modèle hédonique.
37 PRED_SPEC_CH18 = [637953.0, 627260.0, 635171.0, 649980.0] 37 PRED_SPEC_CH18 = [637953.0, 627260.0, 635171.0, 649980.0]
38−POIDS_RAISONNES = {"comparaison": 0.40, "hedonique": 0.55, "cout": 0.05} 38+POIDS_RAISONNES = {"comparaison": 0.40, "hedonique": 0.45, "cout": 0.15}
39 ARRONDI_CONCLUSION = 5000 39 ARRONDI_CONCLUSION = 5000
40 40
41 41
@@ -123,7 +123,7 @@ def bootstrap(b: dict, B: int = B, graine: int = GRAINE) -> dict:
123 return {"moyenne": float(x.mean()), "ecart_type": float(x.std(ddof=1)), "q025": q[0], "q05": q[1], "q25": q[2], "q50": q[3], "q75": q[4], "q95": q[5], "q975": q[6]} 123 return {"moyenne": float(x.mean()), "ecart_type": float(x.std(ddof=1)), "q025": q[0], "q05": q[1], "q25": q[2], "q50": q[3], "q75": q[4], "q95": q[5], "q975": q[6]}
124 rho = float(np.corrcoef(VC, pred)[0, 1]) 124 rho = float(np.corrcoef(VC, pred)[0, 1])
125 rhoB = float(np.corrcoef(VB, pred)[0, 1]) 125 rhoB = float(np.corrcoef(VB, pred)[0, 1])
126− return {"P_aj16": P_aj16, "brut16": brut16, "idxA": idxA, "VA": VA, "VB": VB, "VC": VC, "VP": VP, "pred": pred, "betas": betas, 126+ return {"P_aj16": P_aj16, "brut16": brut16, "idxA": idxA, "idxB": idxB, "VA": VA, "VB": VB, "VC": VC, "VP": VP, "pred": pred, "betas": betas,
127 "resume": {"candidats": resume(VA), "ajustements": resume(VB), "conjoint": resume(VC), "parametrique": resume(VP), "prediction": resume(pred)}, 127 "resume": {"candidats": resume(VA), "ajustements": resume(VB), "conjoint": resume(VC), "parametrique": resume(VP), "prediction": resume(pred)},
128 "rho_conjoint_pred": rho, "rho_ajustements_pred": rhoB, 128 "rho_conjoint_pred": rho, "rho_ajustements_pred": rhoB,
129 "part_A_sous_hed": float((VA < b["V_hed"]).mean()), "part_C_sous_hed": float((VC < b["V_hed"]).mean()), 129 "part_A_sous_hed": float((VA < b["V_hed"]).mean()), "part_C_sous_hed": float((VC < b["V_hed"]).mean()),
@@ -158,27 +158,60 @@ def elargissement(b: dict, P_aj16: np.ndarray, brut16: np.ndarray) -> pd.DataFra
158 return pd.DataFrame(rows) 158 return pd.DataFrame(rows)
159 159
160 160
161−# ---------------------------------------------------------------- 3. indication par le coût (hypothèses déclarées) 161+# ---------------------------------------------------------------- 3. indication par le coût (reprise du chapitre 24)
162−def cout(b: dict, alpha: float = ALPHA_TERRAIN, vie: float = VIE_ECONOMIQUE, age_eff: float = AGE_EFFECTIF) -> dict: 162+def _ch24() -> dict:
163− devis, sujet = c21.charger() 163+ import io, contextlib
164− cn = c21.cout_neuf_sujet(devis) 164+ import ch24_depreciation as c24
165− CN = cn["total"] 165+ with contextlib.redirect_stdout(io.StringIO()):
166− V_L = alpha * b["V_comp"] # allocation : part α de l'indication par comparaison 166+ return c24.main()
167− D = ct.depreciation_age_vie(CN, age_eff, vie) 167+
168− val = ct.valeur_par_cout(V_L, CN, D) 168+
169− extraction_hed = ct.extraction_depreciation_marche(b["V_hed"], V_L, CN, age_eff) 169+def cout(b: dict) -> dict:
170− extraction_comp = ct.extraction_depreciation_marche(b["V_comp"], V_L, CN, age_eff) 170+ """Indication par le coût du chapitre 24 : V = V_L + C_N − D_extraite(28 ans) − D_fonctionnelle. Ajoute les diagnostics
171− grille = pd.DataFrame([{"alpha": a, "vie": vv, "V_L": a * b["V_comp"], "D": ct.depreciation_age_vie(CN, age_eff, vv), 171+ d'extraction aux deux autres indications, la lecture « hypothèses d'usage » (allocation 30 %, âge-vie 60 ans) comme
172− "valeur": ct.valeur_par_cout(a * b["V_comp"], CN, ct.depreciation_age_vie(CN, age_eff, vv))["valeur"]} 172+ contre-exemple, et l'incertitude de spécification (variantes d'extraction du ch. 24)."""
173− for a in GRILLE_ALPHA for vv in GRILLE_VIE]) 173+ import ch24_depreciation as c24
174− # âge effectif réduit (entretien supérieur) : variante 174+ r24 = _ch24()
175− age_var = {ae: ct.valeur_par_cout(V_L, CN, ct.depreciation_age_vie(CN, ae, vie))["valeur"] for ae in (20, 24, 28)} 175+ CN = r24["cn"]["total"]; vs = r24["valeur"]; V_L = r24["vl_retenue"]["retenue"]; fonc = r24["salle_eau"]["desuetude"]
176− # terrain nécessaire pour que le coût rejoigne la comparaison ou le modèle (D âge-vie inchangée) 176+ D_ext = vs["d_extraite"]; D_tot = vs["B_extraction"]["depreciation_totale"]
177− return {"CN": CN, "V_L": V_L, "D": D, "taux_D": D / CN, "contribution_batiment": val["contribution_batiment"], "valeur": val["valeur"], 177+ # variantes d'extraction du ch. 24 (fenêtre des ventes récentes, élasticité, qualité) → valeur sous chaque variante
178− "extraction_hed": extraction_hed, "extraction_comp": extraction_comp, "grille": grille, 178+ sx = r24["sens_extraction"].copy()
179− "sd_grille": float(grille["valeur"].std(ddof=0)), "min_grille": float(grille["valeur"].min()), "max_grille": float(grille["valeur"].max()), 179+ sx["valeur"] = [ct.valeur_par_cout(float(vl), CN, float(t) * CN, fonc)["valeur"] for vl, t in zip(sx["vl_hull"], sx["taux_28"])]
180− "age_effectif_variantes": age_var, "V_L_pour_comp": b["V_comp"] - (CN - D), "V_L_pour_hed": b["V_hed"] - (CN - D), 180+ # contre-exemple : hypothèses d'usage (allocation sur l'indication par comparaison, âge-vie)
181− "cout_neuf_plus_terrain": CN + V_L, "alpha": alpha, "vie": vie, "age_eff": age_eff} 181+ V_L_usage = ALPHA_TERRAIN_USAGE * b["V_comp"]; D_usage = ct.depreciation_age_vie(CN, AGE_EFFECTIF, VIE_ECONOMIQUE_USAGE)
182+ usage = ct.valeur_par_cout(V_L_usage, CN, D_usage)
183+ return {"r24": r24, "CN": CN, "V_L": V_L, "V_L_mediane": r24["vl_retenue"]["mediane"], "n_terrain": r24["vl_retenue"]["n"],
184+ "part_terrain": r24["vl_retenue"]["part"], "D_ext": D_ext, "taux_ext": vs["taux_extrait_28"], "D_fonc": fonc, "D_tot": D_tot,
185+ "taux_tot": vs["B_extraction"]["taux_depreciation"], "contribution_batiment": vs["B_extraction"]["contribution_batiment"],
186+ "valeur": vs["valeur_retenue"], "arrondie": vs["valeur_arrondie"], "ventilee": vs["A_ventilee"]["valeur"],
187+ "taux_ventilee": vs["A_ventilee"]["taux_depreciation"], "tornade": r24["tornade"], "sens_extraction": sx,
188+ "sd_spec": float(sx["valeur"].std(ddof=1)), "cout_neuf_plus_terrain": CN + V_L,
189+ "extraction_hed": ct.extraction_depreciation_marche(b["V_hed"], V_L, CN, AGE_EFFECTIF),
190+ "extraction_comp": ct.extraction_depreciation_marche(b["V_comp"], V_L, CN, AGE_EFFECTIF),
191+ "usage": {"alpha": ALPHA_TERRAIN_USAGE, "vie": VIE_ECONOMIQUE_USAGE, "V_L": V_L_usage, "D": D_usage, "valeur": usage["valeur"],
192+ "extraction_hed": ct.extraction_depreciation_marche(b["V_hed"], V_L_usage, CN, AGE_EFFECTIF)},
193+ "fonc_fn": fonc}
194+
195+
196+def bootstrap_cout(b: dict, bs: dict, co: dict, B: int = B_COUT) -> dict:
197+ """Bootstrap de l'indication par le coût du ch. 24 : mêmes rééchantillons des 600 ventes que le modèle (idxB) ; pour chaque
198+ réplication, prix ramenés à la date par la tendance du modèle rééstimé, terrain extrait des ventes récentes de Hull,
199+ dépréciation extraite à 28 ans (ajustement quadratique), désuétude fonctionnelle fixe."""
200+ import ch24_depreciation as c24
201+ v = b["ventes"]; CN = co["CN"]; fonc = co["D_fonc"]; i_mois = b["cols"].index("mois")
202+ VK = np.empty(B); VL = np.empty(B); T28 = np.empty(B)
203+ for r in range(B):
204+ vb = v.iloc[bs["idxB"][r]].reset_index(drop=True)
205+ mb = {"croissance_mensuelle": float(np.exp(bs["betas"][r][i_mois]) - 1.0)}
206+ w, vl = c24.extraction(vb, mb, CN)
207+ q = c24.ajuster_profil(w)["quadratique"]; t28 = float(c24.taux_quadratique(q, AGE_EFFECTIF))
208+ VL[r] = float(vl.loc["Hull", "vl_mediane"]); T28[r] = t28
209+ VK[r] = ct.valeur_par_cout(VL[r], CN, t28 * CN, fonc)["valeur"]
210+ qv = np.percentile(VK, [2.5, 5, 50, 95, 97.5])
211+ return {"VK": VK, "VL": VL, "T28": T28,
212+ "resume": {"moyenne": float(VK.mean()), "ecart_type": float(VK.std(ddof=1)), "q025": qv[0], "q05": qv[1], "q50": qv[2], "q95": qv[3], "q975": qv[4]},
213+ "sd_VL": float(VL.std(ddof=1)), "sd_T28": float(T28.std(ddof=1)),
214+ "rho_cout_pred": float(np.corrcoef(VK, bs["pred"][:B])[0, 1]), "rho_cout_comp": float(np.corrcoef(VK, bs["VC"][:B])[0, 1])}
182 215
183 216
184 # ---------------------------------------------------------------- 4. combinaison 217 # ---------------------------------------------------------------- 4. combinaison
@@ -194,13 +227,14 @@ def poids_gls(Sigma: np.ndarray) -> np.ndarray:
194 return w / w.sum() 227 return w / w.sum()
195 228
196 229
197−def combinaison(b: dict, bs: dict, co: dict, graine: int = GRAINE) -> dict: 230+def combinaison(b: dict, bs: dict, co: dict, bk: dict, graine: int = GRAINE) -> dict:
198 V = np.array([b["V_comp"], b["V_hed"], co["valeur"]]) 231 V = np.array([b["V_comp"], b["V_hed"], co["valeur"]])
199 sd_spec = float(np.std(PRED_SPEC_CH18, ddof=1)) 232 sd_spec = float(np.std(PRED_SPEC_CH18, ddof=1))
200 sd_pred_boot = bs["resume"]["prediction"]["ecart_type"] 233 sd_pred_boot = bs["resume"]["prediction"]["ecart_type"]
201− s = np.array([bs["resume"]["conjoint"]["ecart_type"], math.sqrt(sd_pred_boot ** 2 + sd_spec ** 2), co["sd_grille"]]) 234+ s = np.array([bs["resume"]["conjoint"]["ecart_type"], math.sqrt(sd_pred_boot ** 2 + sd_spec ** 2), math.sqrt(bk["resume"]["ecart_type"] ** 2 + co["sd_spec"] ** 2)])
202− rho = bs["rho_conjoint_pred"] 235+ rho = bs["rho_conjoint_pred"]; rho_ck = bk["rho_cout_comp"]; rho_hk = bk["rho_cout_pred"]
203− Sigma = np.diag(s ** 2); Sigma[0, 1] = Sigma[1, 0] = rho * s[0] * s[1] 236+ R = np.array([[1.0, rho, rho_ck], [rho, 1.0, rho_hk], [rho_ck, rho_hk, 1.0]])
237+ Sigma = R * np.outer(s, s)
204 schemas = { 238 schemas = {
205 "comparaison seule": np.array([1.0, 0.0, 0.0]), 239 "comparaison seule": np.array([1.0, 0.0, 0.0]),
206 "hédonique seule": np.array([0.0, 1.0, 0.0]), 240 "hédonique seule": np.array([0.0, 1.0, 0.0]),
@@ -212,10 +246,9 @@ def combinaison(b: dict, bs: dict, co: dict, graine: int = GRAINE) -> dict:
212 tab = pd.DataFrame([{"schema": k, "w_comp": w[0], "w_hed": w[1], "w_cout": w[2], "valeur": float(w @ V), "ecart_type": float(math.sqrt(w @ Sigma @ w))} 246 tab = pd.DataFrame([{"schema": k, "w_comp": w[0], "w_hed": w[1], "w_cout": w[2], "valeur": float(w @ V), "ecart_type": float(math.sqrt(w @ Sigma @ w))}
213 for k, w in schemas.items()]) 247 for k, w in schemas.items()])
214 # distribution bootstrap de la conclusion retenue (réplication conjointe : comparaison et modèle issus du même tirage ; coût tiré de la grille) 248 # distribution bootstrap de la conclusion retenue (réplication conjointe : comparaison et modèle issus du même tirage ; coût tiré de la grille)
215− rng = np.random.default_rng(graine + 1) 249+ n_b = len(bk["VK"]); cout_b = bk["VK"]
216− cout_b = rng.choice(co["grille"]["valeur"].values, size=len(bs["VC"]))
217 w = schemas["raisonnée (retenue)"] 250 w = schemas["raisonnée (retenue)"]
218− Vb = w[0] * bs["VC"] + w[1] * bs["pred"] + w[2] * cout_b 251+ Vb = w[0] * bs["VC"][:n_b] + w[1] * bs["pred"][:n_b] + w[2] * cout_b
219 q = np.percentile(Vb, [2.5, 5, 50, 95, 97.5]) 252 q = np.percentile(Vb, [2.5, 5, 50, 95, 97.5])
220 V_raisonnee = float(w @ V) 253 V_raisonnee = float(w @ V)
221 arrondi = ARRONDI_CONCLUSION 254 arrondi = ARRONDI_CONCLUSION
@@ -225,11 +258,14 @@ def combinaison(b: dict, bs: dict, co: dict, graine: int = GRAINE) -> dict:
225 fourchette_conclue = (math.floor(min(V[0], V[1]) / arrondi) * arrondi, math.ceil(max(V[0], V[1]) / arrondi) * arrondi) 258 fourchette_conclue = (math.floor(min(V[0], V[1]) / arrondi) * arrondi, math.ceil(max(V[0], V[1]) / arrondi) * arrondi)
226 # courbes de sensibilité aux poids : w_hed de 0 à 1 − w_cout, pour trois poids du coût 259 # courbes de sensibilité aux poids : w_hed de 0 à 1 − w_cout, pour trois poids du coût
227 wh = np.linspace(0, 1, 101) 260 wh = np.linspace(0, 1, 101)
228− courbes = {wc: np.array([ (1 - h - wc) * V[0] + h * V[1] + wc * V[2] if h <= 1 - wc + 1e-12 else np.nan for h in wh]) for wc in (0.0, 0.05, 0.10)} 261+ courbes = {wc: np.array([ (1 - h - wc) * V[0] + h * V[1] + wc * V[2] if h <= 1 - wc + 1e-12 else np.nan for h in wh]) for wc in (0.0, 0.15, 0.30)}
262+ # moyenne simple sous les hypothèses d'usage du coût (contre-exemple)
263+ V_usage = np.array([b["V_comp"], b["V_hed"], co["usage"]["valeur"]])
229 # variance de la combinaison de deux indications (comparaison, hédonique) selon ρ 264 # variance de la combinaison de deux indications (comparaison, hédonique) selon ρ
230 var_deux = {r: np.array([((1 - h) ** 2) * s[0] ** 2 + h ** 2 * s[1] ** 2 + 2 * h * (1 - h) * r * s[0] * s[1] for h in wh]) for r in (0.0, 0.3, 0.6, 0.9)} 265 var_deux = {r: np.array([((1 - h) ** 2) * s[0] ** 2 + h ** 2 * s[1] ** 2 + 2 * h * (1 - h) * r * s[0] * s[1] for h in wh]) for r in (0.0, 0.3, 0.6, 0.9)}
231 w_opt_deux = {r: poids_optimaux_deux(s[0], s[1], r) for r in (0.0, rho, 0.3, 0.6, 0.9)} 266 w_opt_deux = {r: poids_optimaux_deux(s[0], s[1], r) for r in (0.0, rho, 0.3, 0.6, 0.9)}
232− return {"V": V, "s": s, "sd_spec_hed": sd_spec, "sd_pred_boot": sd_pred_boot, "rho": rho, "Sigma": Sigma, "schemas": schemas, "tableau": tab, 267+ return {"V": V, "s": s, "sd_spec_hed": sd_spec, "sd_pred_boot": sd_pred_boot, "rho": rho, "R": R, "Sigma": Sigma, "schemas": schemas, "tableau": tab,
268+ "moyenne_simple_usage": float(V_usage.mean()), "inverse_variance_usage": None,
233 "Vb": Vb, "cout_b": cout_b, "q": {"q025": q[0], "q05": q[1], "q50": q[2], "q95": q[3], "q975": q[4]}, "V_raisonnee": V_raisonnee, 269 "Vb": Vb, "cout_b": cout_b, "q": {"q025": q[0], "q05": q[1], "q50": q[2], "q95": q[3], "q975": q[4]}, "V_raisonnee": V_raisonnee,
234 "conclusion": conclusion, "fourchette90": fourchette90, "fourchette_conclue": fourchette_conclue, 270 "conclusion": conclusion, "fourchette90": fourchette90, "fourchette_conclue": fourchette_conclue,
235 "largeur_relative_conclue": (fourchette_conclue[1] - fourchette_conclue[0]) / conclusion, "wh": wh, "courbes": courbes, "var_deux": var_deux, "w_opt_deux": w_opt_deux, 271 "largeur_relative_conclue": (fourchette_conclue[1] - fourchette_conclue[0]) / conclusion, "wh": wh, "courbes": courbes, "var_deux": var_deux, "w_opt_deux": w_opt_deux,
@@ -285,19 +321,21 @@ def exercices(b: dict, bs: dict, co: dict, cb: dict) -> dict:
285 rep = np.array([561, 568, 571, 574, 576, 577, 579, 580, 582, 583, 585, 586, 588, 590, 592, 595, 598, 603, 611, 624]) * 1000.0 321 rep = np.array([561, 568, 571, 574, 576, 577, 579, 580, 582, 583, 585, 586, 588, 590, 592, 595, 598, 603, 611, 624]) * 1000.0
286 out["ex4"] = {"moyenne": float(rep.mean()), "ecart_type": float(rep.std(ddof=1)), "q05": float(np.percentile(rep, 5)), "q95": float(np.percentile(rep, 95)), 322 out["ex4"] = {"moyenne": float(rep.mean()), "ecart_type": float(rep.std(ddof=1)), "q05": float(np.percentile(rep, 5)), "q95": float(np.percentile(rep, 95)),
287 "q10": float(np.percentile(rep, 10)), "q90": float(np.percentile(rep, 90))} 323 "q10": float(np.percentile(rep, 10)), "q90": float(np.percentile(rep, 90))}
288− # ex. 5 : coût avec autres hypothèses 324+ # ex. 5 : coût sous hypothèses d'usage, puis extraction avec le terrain du ch. 24
289 CN = co["CN"] 325 CN = co["CN"]
290 out["ex5"] = {"a": ct.valeur_par_cout(0.35 * b["V_comp"], CN, ct.depreciation_age_vie(CN, 22, 60))["valeur"], 326 out["ex5"] = {"a": ct.valeur_par_cout(0.35 * b["V_comp"], CN, ct.depreciation_age_vie(CN, 22, 60))["valeur"],
291 "b": ct.valeur_par_cout(0.35 * b["V_comp"], CN, ct.depreciation_age_vie(CN, 28, 75))["valeur"], 327 "b": ct.valeur_par_cout(0.35 * b["V_comp"], CN, ct.depreciation_age_vie(CN, 28, 75))["valeur"],
292− "extraction": ct.extraction_depreciation_marche(b["V_hed"], 0.35 * b["V_comp"], CN, 28)} 328+ "extraction_hed": ct.extraction_depreciation_marche(b["V_hed"], co["V_L"], CN, 28),
329+ "extraction_comp": ct.extraction_depreciation_marche(b["V_comp"], co["V_L"], CN, 28),
330+ "V_L_pour_hed": b["V_hed"] - (CN - co["D_tot"])}
293 # ex. 6 : autres pondérations et comparaison élargie (k = 16) 331 # ex. 6 : autres pondérations et comparaison élargie (k = 16)
294 V16 = float(r_el["valeur_ponderee"].iloc[-1]) if (r_el := _CACHE.get("el")) is not None else float(elargissement(b, bs["P_aj16"], bs["brut16"])["valeur_ponderee"].iloc[-1]) 332 V16 = float(r_el["valeur_ponderee"].iloc[-1]) if (r_el := _CACHE.get("el")) is not None else float(elargissement(b, bs["P_aj16"], bs["brut16"])["valeur_ponderee"].iloc[-1])
295− w2 = np.array([0.30, 0.65, 0.05]); w1 = np.array([POIDS_RAISONNES["comparaison"], POIDS_RAISONNES["hedonique"], POIDS_RAISONNES["cout"]]) 333+ w2 = np.array([0.30, 0.55, 0.15]); w1 = np.array([POIDS_RAISONNES["comparaison"], POIDS_RAISONNES["hedonique"], POIDS_RAISONNES["cout"]])
296 V = np.array([b["V_comp"], b["V_hed"], co["valeur"]]); V16v = np.array([V16, b["V_hed"], co["valeur"]]) 334 V = np.array([b["V_comp"], b["V_hed"], co["valeur"]]); V16v = np.array([V16, b["V_hed"], co["valeur"]])
297 a = ARRONDI_CONCLUSION 335 a = ARRONDI_CONCLUSION
298− out["ex6"] = {"V_30_65_05": float(w2 @ V), "arrondi_30_65_05": round(float(w2 @ V) / a) * a, "V16": V16, 336+ out["ex6"] = {"V_30_55_15": float(w2 @ V), "arrondi_30_55_15": round(float(w2 @ V) / a) * a, "V16": V16,
299 "V_k16_raisonnee": float(w1 @ V16v), "arrondi_k16": round(float(w1 @ V16v) / a) * a, 337 "V_k16_raisonnee": float(w1 @ V16v), "arrondi_k16": round(float(w1 @ V16v) / a) * a,
300− "V_60_35_05": float(np.array([0.60, 0.35, 0.05]) @ V)} 338+ "V_55_30_15": float(np.array([0.55, 0.30, 0.15]) @ V), "arrondi_55_30_15": round(float(np.array([0.55, 0.30, 0.15]) @ V) / a) * a}
301 # ex. 7 : incidence d'une hypothèse extraordinaire (eq. 10.incidence / 10.esperance) 339 # ex. 7 : incidence d'une hypothèse extraordinaire (eq. 10.incidence / 10.esperance)
302 out["ex7"] = {"V_H": cb["conclusion"], "V_nonH": cb["conclusion"] - 14000.0, "p": 0.7} 340 out["ex7"] = {"V_H": cb["conclusion"], "V_nonH": cb["conclusion"] - 14000.0, "p": 0.7}
303 out["ex7"]["delta"] = out["ex7"]["V_H"] - out["ex7"]["V_nonH"] 341 out["ex7"]["delta"] = out["ex7"]["V_H"] - out["ex7"]["V_nonH"]
@@ -316,10 +354,11 @@ def tout(verbose: bool = False) -> dict:
316 ex = exhaustif(b, bs["P_aj16"], bs["brut16"]) 354 ex = exhaustif(b, bs["P_aj16"], bs["brut16"])
317 el = elargissement(b, bs["P_aj16"], bs["brut16"]); _CACHE["el"] = el 355 el = elargissement(b, bs["P_aj16"], bs["brut16"]); _CACHE["el"] = el
318 co = cout(b) 356 co = cout(b)
319− cb = combinaison(b, bs, co) 357+ bk = bootstrap_cout(b, bs, co)
358+ cb = combinaison(b, bs, co, bk)
320 vt = verite(b) 359 vt = verite(b)
321 exo = exercices(b, bs, co, cb) 360 exo = exercices(b, bs, co, cb)
322− r = {"base": b, "bootstrap": bs, "exhaustif": ex, "elargissement": el, "cout": co, "combinaison": cb, "verite": vt, "exercices": exo} 361+ r = {"base": b, "bootstrap": bs, "exhaustif": ex, "elargissement": el, "cout": co, "bootstrap_cout": bk, "combinaison": cb, "verite": vt, "exercices": exo}
323 _CACHE["r"] = r 362 _CACHE["r"] = r
324 if verbose: 363 if verbose:
325 imprimer(r) 364 imprimer(r)
@@ -348,14 +387,20 @@ def imprimer(r: dict) -> None:
348 print(ex["par_n_retenus"].round(0).to_string()) 387 print(ex["par_n_retenus"].round(0).to_string())
349 print("\n=== Élargissement k = 5 … 16 (modèle fixe) ===") 388 print("\n=== Élargissement k = 5 … 16 (modèle fixe) ===")
350 print(r["elargissement"].round(4).to_string(index=False)) 389 print(r["elargissement"].round(4).to_string(index=False))
351− print("\n=== Coût (hypothèses : α = %.2f, vie = %d ans, âge effectif = %d) ===" % (co["alpha"], co["vie"], co["age_eff"])) 390+ bk = r["bootstrap_cout"]
352− print(f"C_N {f(co['CN'])} ; V_L {f(co['V_L'])} ; D âge-vie {f(co['D'])} ({co['taux_D']:.3f}) ; contribution bâtiment {f(co['contribution_batiment'])} ; valeur {f(co['valeur'])}") 391+ print("\n=== Coût (chapitre 24) ===")
353− print(f"C_N + V_L = {f(co['cout_neuf_plus_terrain'])} ; extraction du marché à la prédiction hédonique : D = {f(co['extraction_hed']['depreciation'])} (taux {co['extraction_hed']['taux']:.3f}) ; à la comparaison : D = {f(co['extraction_comp']['depreciation'])} (taux {co['extraction_comp']['taux']:.3f})") 392+ print(f"C_N {f(co['CN'])} ; V_L {f(co['V_L'])} (médiane {f(co['V_L_mediane'])} de {co['n_terrain']} ventes récentes de Hull, part {co['part_terrain']:.3f}) ; D extraite {f(co['D_ext'])} ({co['taux_ext']:.4f}) ; D fonctionnelle {f(co['D_fonc'])} ; D totale {f(co['D_tot'])} ({co['taux_tot']:.4f}) ; contribution bâtiment {f(co['contribution_batiment'])} ; valeur {f(co['valeur'])} → {f(co['arrondie'])} ; lecture ventilée {f(co['ventilee'])} ({co['taux_ventilee']:.4f})")
354− print(f"terrain implicite pour rejoindre la comparaison : {f(co['V_L_pour_comp'])} ; le modèle : {f(co['V_L_pour_hed'])}") 393+ print(f"C_N + V_L = {f(co['cout_neuf_plus_terrain'])} ; extraction à la prédiction hédonique : D = {f(co['extraction_hed']['depreciation'])} (taux {co['extraction_hed']['taux']:.4f}) ; à la comparaison : D = {f(co['extraction_comp']['depreciation'])} (taux {co['extraction_comp']['taux']:.4f})")
355− print(f"grille α × vie : min {f(co['min_grille'])}, max {f(co['max_grille'])}, é.-t. {f(co['sd_grille'])} ; âge effectif 20/24/28 : {[f(x) for x in co['age_effectif_variantes'].values()]}") 394+ u = co["usage"]
356− print(co["grille"].pivot(index="alpha", columns="vie", values="valeur").round(0).to_string()) 395+ print(f"hypothèses d'usage (allocation {u['alpha']:.2f} × comparaison = {f(u['V_L'])}, âge-vie {u['vie']} ans : D = {f(u['D'])}) : valeur {f(u['valeur'])} ; extraction au modèle avec ce terrain : D = {f(u['extraction_hed']['depreciation'])} ({u['extraction_hed']['taux']:.3f})")
396+ print("variantes d'extraction (ch. 24) :"); print(co["sens_extraction"][["variante", "vl_hull", "taux_28", "valeur"]].round(4).to_string(index=False)); print(f"écart-type de spécification {f(co['sd_spec'])}")
397+ print("tornade ch. 24 :"); [print(f" {t['param']:50s} {f(t['bas'])} – {f(t['haut'])}") for t in co["tornade"]]
398+ rk = bk["resume"]
399+ print(f"bootstrap du coût (B = {len(bk['VK'])}) : moy {f(rk['moyenne'])} é.-t. {f(rk['ecart_type'])} q5 {f(rk['q05'])} q50 {f(rk['q50'])} q95 {f(rk['q95'])} ; é.-t. V_L {f(bk['sd_VL'])} ; é.-t. taux 28 ans {bk['sd_T28']:.4f} ; ρ(coût, prédiction) {bk['rho_cout_pred']:.3f} ; ρ(coût, comparaison) {bk['rho_cout_comp']:.3f}")
357 print("\n=== Combinaison ===") 400 print("\n=== Combinaison ===")
358− print(f"indications : {[f(x) for x in cb['V']]} ; écarts-types : {[f(x) for x in cb['s']]} (hédonique : boot {f(cb['sd_pred_boot'])} + spécification {f(cb['sd_spec_hed'])}) ; ρ = {cb['rho']:.3f}") 401+ print(f"indications : {[f(x) for x in cb['V']]} ; écarts-types : {[f(x) for x in cb['s']]} (hédonique : boot {f(cb['sd_pred_boot'])} + spécification {f(cb['sd_spec_hed'])} ; coût : boot {f(bk['resume']['ecart_type'])} + spécification {f(co['sd_spec'])})")
402+ print("matrice de corrélation :"); print(np.round(cb["R"], 3))
403+ print(f"moyenne simple avec le coût sous hypothèses d'usage : {f(cb['moyenne_simple_usage'])}")
359 print(cb["tableau"].round(3).to_string(index=False)) 404 print(cb["tableau"].round(3).to_string(index=False))
360 print(f"poids optimal de la comparaison à deux indications selon ρ : { {round(k,3): round(v,3) for k, v in cb['w_opt_deux'].items()} }") 405 print(f"poids optimal de la comparaison à deux indications selon ρ : { {round(k,3): round(v,3) for k, v in cb['w_opt_deux'].items()} }")
361 q = cb["q"] 406 q = cb["q"]
added analysis/python/cases/ch39_bayes.py +515 −0
@@ -0,0 +1,515 @@
1+#!/usr/bin/env python3
2+"""Chapitre 39 — Modèles bayésiens et intervalles : exemple intégral sur les 600 ventes du cas 1 (Gatineau, DONNÉES SYNTHÉTIQUES,
3+graine 20261031). Numpy/scipy uniquement (pymc et arviz absents) : tout est implémenté ici.
4+
5+Contenu :
6+ * régression linéaire bayésienne conjuguée (a priori normale–inverse-gamma, NIG) : postérieure, prédictive Student-t, a priori
7+ faiblement informatif vs a priori informatif « emprunté » au marché voisin (autres secteurs) pour un petit échantillon ;
8+ * modèle hiérarchique à effets de secteur aléatoires, échantillonneur de Gibbs (paramétrisation centrée de Gelfand, Sahu et Carlin
9+ 1995), rétrécissement, expérience « Masson-Angers réduit à 5, 10, 20 ventes » ;
10+ * diagnostics MCMC : R̂ de Gelman–Rubin (version scindée, BDA3 §11.4), taille d'échantillon effective (autocorrélations, Geyer) ;
11+ * distribution prédictive du sujet (intervalles de crédibilité 90/95 %) vs intervalle de prédiction fréquentiste du ch. 18 ;
12+ * critères : WAIC (Watanabe 2010 ; Vehtari, Gelman et Gabry 2017), LOO exact pour le modèle conjugué, LOO par échantillonnage
13+ préférentiel tronqué pour le modèle hiérarchique ;
14+ * simulation à partir des lois du générateur (data/synthetic/README.md) pour la récupération des paramètres (tests).
15+Le modèle de référence (régresseurs) est `traite.cas1.matrice` ; il n'est pas redéfini.
16+Usage (racine du dépôt) : PYTHONPATH=analysis/python python3 analysis/python/cases/ch39_bayes.py"""
17+from __future__ import annotations
18+import sys, math, pathlib
19+import numpy as np, pandas as pd
20+from scipy import stats
21+sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1]))
22+sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent))
23+from traite import cas1
24+from traite import hedonique as h
25+import ch18_hedonique as c18
26+
27+ROOT = pathlib.Path(__file__).resolve().parents[3]
28+GRAINE = 20261031
29+VRAIS = c18.VRAIS
30+SECTEURS = cas1.SECTEURS # Hull, Gatineau, Aylmer, Buckingham, Masson-Angers
31+IC18 = {"point": 637_953, "bas95": 539_682, "haut95": 749_087} # chapitre 18 (traite.cas1.intervalle_prediction)
32+LN_MEDIANE_HULL = math.log(475_000) # médiane APCIQ T3 2026 secteur Hull : centre de l'a priori sur la constante
33+
34+# =============================================================================================================================
35+# 1. Régression linéaire bayésienne conjuguée (normale–inverse-gamma)
36+# =============================================================================================================================
37+
38+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)."""
41+ k = len(colonnes); m0 = np.zeros(k)
42+ 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"}
44+
45+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 ;
47+ 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+ X = np.asarray(X, float); y = np.asarray(y, float); n, k = X.shape
49+ V0inv = np.linalg.inv(prior["V0"]); m0 = prior["m0"]
50+ Vn_inv = V0inv + X.T @ X; Vn = np.linalg.inv(Vn_inv)
51+ mn = Vn @ (V0inv @ m0 + X.T @ y)
52+ an = prior["a0"] + n / 2
53+ bn = prior["b0"] + 0.5 * (y @ y + m0 @ V0inv @ m0 - mn @ Vn_inv @ mn)
54+ nu = 2 * an
55+ sd = np.sqrt(bn / an * np.diag(Vn)) * (math.sqrt(nu / (nu - 2)) if nu > 2 else np.nan) # écart-type de la marginale t
56+ echelle = np.sqrt(bn / an * np.diag(Vn))
57+ tq = stats.t.ppf(0.975, nu)
58+ return {"m_n": mn, "V_n": Vn, "a_n": an, "b_n": bn, "nu": nu, "sd": sd, "echelle": echelle,
59+ "ic95_bas": mn - tq * echelle, "ic95_haut": mn + tq * echelle,
60+ "sigma2_moy": bn / (an - 1), "sigma_moy": math.sqrt(bn / (an - 1)), "sigma2_mode": bn / (an + 1),
61+ "n": n, "k": k, "X": X, "y": y, "prior": prior}
62+
63+def nig_tirages(post: dict, S: int, rng: np.random.Generator) -> dict:
64+ """Tirages exacts de la postérieure NIG : σ² ~ IG(a_n, b_n) puis β | σ² ~ N(m_n, σ² V_n)."""
65+ sigma2 = 1.0 / rng.gamma(post["a_n"], 1.0 / post["b_n"], S)
66+ L = np.linalg.cholesky(post["V_n"])
67+ z = rng.standard_normal((S, post["k"]))
68+ beta = post["m_n"] + (z @ L.T) * np.sqrt(sigma2)[:, None]
69+ return {"beta": beta, "sigma2": sigma2}
70+
71+def predictive_t(post: dict, x: np.ndarray, niveaux=(0.90, 0.95)) -> dict:
72+ """Prédictive a posteriori d'une nouvelle observation : y* ~ t_{2a_n}(x'm_n, (b_n/a_n)(1 + x'V_n x)) ; quantiles retransformés en $."""
73+ x = np.asarray(x, float); loc = float(x @ post["m_n"]); s2 = post["b_n"] / post["a_n"] * (1 + x @ post["V_n"] @ x)
74+ out = {"loc": loc, "echelle": math.sqrt(s2), "nu": post["nu"], "mediane": math.exp(loc),
75+ "part_coefficients": float((x @ post["V_n"] @ x) / (1 + x @ post["V_n"] @ x))}
76+ for p in niveaux:
77+ q = stats.t.ppf(0.5 + p / 2, post["nu"]) * math.sqrt(s2)
78+ out[f"bas{int(p*100)}"] = math.exp(loc - q); out[f"haut{int(p*100)}"] = math.exp(loc + q)
79+ return out
80+
81+def prior_marche_voisin(post_voisin: dict, colonnes_voisin, colonnes_cible, kappa: float, v0_const: float = 1e4) -> dict:
82+ """A priori informatif pour un petit marché : les prix implicites (pentes) sont centrés sur la postérieure du marché voisin,
83+ avec une matrice d'échelle V0 = κ·V_n(voisin) (κ = 1 : confiance entière ; κ = 4 : écart-type doublé) ; la constante (niveau de
84+ prix du marché cible) reste pratiquement plate. a0, b0 : on transmet aussi l'information sur σ² (a_n, b_n du voisin)."""
85+ cv = list(colonnes_voisin); cc = list(colonnes_cible); k = len(cc)
86+ m0 = np.zeros(k); V0 = np.zeros((k, k))
87+ idx = [cv.index(c) for c in cc if c != "const"]; pos = [i for i, c in enumerate(cc) if c != "const"]
88+ m0[pos] = post_voisin["m_n"][idx]
89+ V0[np.ix_(pos, pos)] = kappa * post_voisin["V_n"][np.ix_(idx, idx)]
90+ 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}"}
92+
93+def loo_exact_nig(X: np.ndarray, y: np.ndarray, prior: dict) -> dict:
94+ """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)."""
95+ n = len(y); lp = np.empty(n)
96+ for i in range(n):
97+ m = np.ones(n, bool); m[i] = False
98+ p = nig_posterieure(X[m], y[m], prior)
99+ s2 = p["b_n"] / p["a_n"] * (1 + X[i] @ p["V_n"] @ X[i])
100+ lp[i] = stats.t.logpdf(y[i], p["nu"], loc=X[i] @ p["m_n"], scale=math.sqrt(s2))
101+ return {"elpd_loo": float(lp.sum()), "looic": float(-2 * lp.sum()), "pointwise": lp}
102+
103+# =============================================================================================================================
104+# 2. Modèle hiérarchique à effets de secteur aléatoires : échantillonneur de Gibbs
105+# =============================================================================================================================
106+
107+def matrice_hier(df: pd.DataFrame, moyennes: pd.Series | None = None):
108+ """Régresseurs du modèle hiérarchique : ceux de traite.cas1.matrice sans la constante ni les indicatrices de secteur, centrés
109+ (paramétrisation centrée : les niveaux α_s portent l'ordonnée à l'origine). Retourne (X centré, moyennes, indices de secteur)."""
110+ X = cas1.matrice(df).drop(columns=["const"] + [c for c in cas1.matrice(df).columns if c.startswith("sect_")])
111+ if moyennes is None: moyennes = X.mean()
112+ Xc = X - moyennes
113+ z = np.array([SECTEURS.index(s) for s in df["secteur"]])
114+ return Xc, moyennes, z
115+
116+def gibbs_hierarchique(y: np.ndarray, X: np.ndarray, z: np.ndarray, J: int = 5, n_iter: int = 3000, brulage: int = 1000,
117+ 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:
119+ """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).
121+ Conditionnelles complètes (toutes conjuguées) :
122+ β | · ~ N((X'X)⁻¹X'(y − Zα), σ²(X'X)⁻¹)
123+ α_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+ α0 | · ~ N(moyenne des α_s, τ²/J)
125+ σ² | · ~ IG(n/2, ½ Σ (y − Xβ − Zα)²)
126+ τ² | · ~ IG((J−1)/2, ½ Σ (α_s − α0)²) [uniforme sur τ] ou IG(a + J/2, b + ½ Σ (α_s − α0)²) [IG]
127+ Retourne les tirages après brûlage, de forme (chaînes, n_iter − brulage, ·)."""
128+ y = np.asarray(y, float); X = np.asarray(X, float); n, k = X.shape
129+ XtX_inv = np.linalg.inv(X.T @ X); L_beta = np.linalg.cholesky(XtX_inv)
130+ ns = np.bincount(z, minlength=J).astype(float)
131+ garder = n_iter - brulage
132+ out = {"beta": np.empty((chaines, garder, k)), "alpha": np.empty((chaines, garder, J)), "alpha0": np.empty((chaines, garder)),
133+ "sigma2": np.empty((chaines, garder)), "tau2": np.empty((chaines, garder))}
134+ for c in range(chaines):
135+ 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)
137+ beta = XtX_inv @ X.T @ y + rng.normal(0, 0.05, k)
138+ alpha = np.full(J, float(np.mean(y))) + rng.normal(0, 0.3, J); alpha0 = float(np.mean(alpha))
139+ 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+ if tau_fixe is not None: tau2 = tau_fixe ** 2
141+ 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)
151+ # α0 | α, τ²
152+ alpha0 = float(alpha.mean() + rng.standard_normal() * math.sqrt(tau2 / J))
153+ # σ² | β, α
154+ res = e - alpha[z]
155+ sigma2 = 1.0 / rng.gamma(n / 2, 2.0 / (res @ res))
156+ # τ² | α, α0
157+ ss = float(((alpha - alpha0) ** 2).sum())
158+ if tau_fixe is None:
159+ if prior_tau == "uniforme": tau2 = 1.0 / rng.gamma((J - 1) / 2, 2.0 / ss)
160+ elif prior_tau == "ig": tau2 = 1.0 / rng.gamma(ig_a + J / 2, 1.0 / (ig_b + ss / 2))
161+ else: raise ValueError(prior_tau)
162+ if it >= brulage:
163+ j = it - brulage
164+ out["beta"][c, j] = beta; out["alpha"][c, j] = alpha; out["alpha0"][c, j] = alpha0
165+ out["sigma2"][c, j] = sigma2; out["tau2"][c, j] = tau2
166+ 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}
168+ return out
169+
170+def facteur_retrecissement(n_s: float, sigma2: float, tau2: float) -> float:
171+ """B_s = n_s τ² / (n_s τ² + σ²) : poids de la moyenne du secteur dans la moyenne conditionnelle de α_s (1 − B_s vers α0)."""
172+ return n_s * tau2 / (n_s * tau2 + sigma2)
173+
174+# ----------------------------------------------------------------------------------------------------------------- diagnostics
175+def rhat_scinde(ch: np.ndarray) -> float:
176+ """R̂ scindé (Gelman et Rubin 1992 ; BDA3 §11.4) : chaque chaîne est coupée en deux ; m demi-chaînes de longueur n ;
177+ B = n·Var(moyennes), W = moyenne des variances ; var⁺ = (n−1)/n W + B/n ; R̂ = sqrt(var⁺/W)."""
178+ ch = np.asarray(ch, float); c, T = ch.shape; T2 = T // 2
179+ halves = np.concatenate([ch[:, :T2], ch[:, T2:2 * T2]], axis=0) # (2c, T2)
180+ m, n = halves.shape
181+ moy = halves.mean(axis=1); B = n * moy.var(ddof=1); W = halves.var(axis=1, ddof=1).mean()
182+ var_plus = (n - 1) / n * W + B / n
183+ return float(math.sqrt(var_plus / W))
184+
185+def ess(ch: np.ndarray) -> float:
186+ """Taille d'échantillon effective (BDA3 §11.5) : ρ̂_t = 1 − V_t/(2 var⁺) avec V_t le variogramme empirique moyen des chaînes ;
187+ somme des autocorrélations arrêtée au premier couple (ρ̂_{2t'} + ρ̂_{2t'+1}) négatif (Geyer) ; ESS = m n / (1 + 2 Σ ρ̂_t)."""
188+ ch = np.asarray(ch, float); m, n = ch.shape
189+ moy = ch.mean(axis=1); B = n * moy.var(ddof=1) if m > 1 else 0.0; W = ch.var(axis=1, ddof=1).mean()
190+ var_plus = (n - 1) / n * W + B / n
191+ rho = np.empty(n)
192+ for t in range(n):
193+ if t == 0: Vt = 0.0
194+ else: Vt = float(((ch[:, t:] - ch[:, :-t]) ** 2).sum() / (m * (n - t)))
195+ rho[t] = 1.0 - Vt / (2 * var_plus)
196+ s = 0.0; t = 1
197+ while t + 1 < n:
198+ pair = rho[t] + rho[t + 1]
199+ if pair < 0: break
200+ s += pair; t += 2
201+ return float(m * n / (1 + 2 * s))
202+
203+def resume_tirages(x: np.ndarray, nom: str = "") -> dict:
204+ """Résumé d'un paramètre scalaire de forme (chaînes, tirages) : moyenne, écart-type, quantiles, R̂, ESS."""
205+ flat = x.reshape(-1)
206+ return {"nom": nom, "moyenne": float(flat.mean()), "sd": float(flat.std(ddof=1)), "mediane": float(np.median(flat)),
207+ "q025": float(np.quantile(flat, 0.025)), "q975": float(np.quantile(flat, 0.975)),
208+ "q05": float(np.quantile(flat, 0.05)), "q95": float(np.quantile(flat, 0.95)), "rhat": rhat_scinde(x), "ess": ess(x)}
209+
210+def resume_gibbs(g: dict, colonnes) -> pd.DataFrame:
211+ lignes = []
212+ for j, c in enumerate(colonnes): lignes.append(resume_tirages(g["beta"][:, :, j], c))
213+ for j, s in enumerate(SECTEURS): lignes.append(resume_tirages(g["alpha"][:, :, j], f"alpha_{s}"))
214+ lignes.append(resume_tirages(g["alpha0"], "alpha0"))
215+ lignes.append(resume_tirages(np.sqrt(g["sigma2"]), "sigma"))
216+ lignes.append(resume_tirages(np.sqrt(g["tau2"]), "tau"))
217+ return pd.DataFrame(lignes).set_index("nom")
218+
219+def effets_relatifs(g: dict, ref: str = "Hull") -> pd.DataFrame:
220+ """Effets de secteur relatifs à Hull (α_s − α_Hull), comparables aux coefficients des indicatrices du ch. 18."""
221+ i0 = SECTEURS.index(ref); lignes = []
222+ for j, s in enumerate(SECTEURS):
223+ if j == i0: continue
224+ lignes.append(resume_tirages(g["alpha"][:, :, j] - g["alpha"][:, :, i0], s))
225+ return pd.DataFrame(lignes).set_index("nom")
226+
227+# ----------------------------------------------------------------------------------------------------------------- critères
228+def log_vrais_pointwise_hier(g: dict, y: np.ndarray, X: np.ndarray, z: np.ndarray, S: int = 2000, graine: int = GRAINE) -> np.ndarray:
229+ """Matrice S × n des log-densités log p(y_i | θ_s) pour S tirages (sous-échantillonnés) du modèle hiérarchique."""
230+ rng = np.random.default_rng(graine)
231+ beta = g["beta"].reshape(-1, g["beta"].shape[-1]); alpha = g["alpha"].reshape(-1, g["alpha"].shape[-1]); s2 = g["sigma2"].reshape(-1)
232+ idx = rng.choice(len(s2), size=min(S, len(s2)), replace=False)
233+ mu = beta[idx] @ X.T + alpha[idx][:, z]
234+ return stats.norm.logpdf(y[None, :], loc=mu, scale=np.sqrt(s2[idx])[:, None])
235+
236+def log_vrais_pointwise_nig(post: dict, S: int = 2000, graine: int = GRAINE) -> np.ndarray:
237+ rng = np.random.default_rng(graine); t = nig_tirages(post, S, rng)
238+ mu = t["beta"] @ post["X"].T
239+ return stats.norm.logpdf(post["y"][None, :], loc=mu, scale=np.sqrt(t["sigma2"])[:, None])
240+
241+def waic(lp: np.ndarray) -> dict:
242+ """WAIC (Watanabe 2010 ; Vehtari, Gelman et Gabry 2017) : lppd = Σ_i log moyenne_s p(y_i|θ_s) ; p_waic = Σ_i Var_s log p(y_i|θ_s) ;
243+ elpd_waic = lppd − p_waic ; WAIC = −2 elpd_waic."""
244+ S = lp.shape[0]
245+ lppd_i = np.logaddexp.reduce(lp, axis=0) - math.log(S)
246+ p_i = lp.var(axis=0, ddof=1)
247+ return {"lppd": float(lppd_i.sum()), "p_waic": float(p_i.sum()), "elpd_waic": float((lppd_i - p_i).sum()),
248+ "waic": float(-2 * (lppd_i - p_i).sum()), "se_elpd": float(math.sqrt(len(lppd_i)) * (lppd_i - p_i).std(ddof=1))}
249+
250+def loo_tronque(lp: np.ndarray) -> dict:
251+ """LOO approximatif par échantillonnage préférentiel tronqué (Ionides 2008 ; forme simplifiée de PSIS, Vehtari et al. 2017) :
252+ poids w_is ∝ 1/p(y_i|θ_s), tronqués à sqrt(S)·w̄_i ; p(y_i|y_{−i}) ≈ Σ_s w_is p(y_i|θ_s) / Σ_s w_is."""
253+ S = lp.shape[0]
254+ lw = -lp; lw -= lw.max(axis=0, keepdims=True)
255+ w = np.exp(lw); w /= w.sum(axis=0, keepdims=True)
256+ w = np.minimum(w, math.sqrt(S) / S); w /= w.sum(axis=0, keepdims=True)
257+ lp_loo = np.log((w * np.exp(lp)).sum(axis=0))
258+ return {"elpd_loo": float(lp_loo.sum()), "looic": float(-2 * lp_loo.sum()), "pointwise": lp_loo}
259+
260+# =============================================================================================================================
261+# 3. Simulation à partir des lois du générateur (récupération des paramètres)
262+# =============================================================================================================================
263+
264+def simuler_depuis_generateur(v: pd.DataFrame, n: int, graine: int, sigma: float = VRAIS["sigma"], const: float = 13.2) -> pd.DataFrame:
265+ """Rééchantillonne n lignes d'attributs du jeu (bootstrap des covariables) et engendre ln P selon les lois déclarées
266+ (README synthétique) : ln P = const + Σ β_vrai x + prime de secteur + ε, ε ~ N(0, σ²). Les attributs sont ceux du jeu ; le prix
267+ est régénéré. Sert aux tests de récupération des paramètres."""
268+ rng = np.random.default_rng(graine)
269+ d = v.sample(n=n, replace=True, random_state=int(rng.integers(1 << 31))).reset_index(drop=True)
270+ X = cas1.matrice(d)
271+ beta = pd.Series({c: VRAIS.get(c, 0.0) for c in X.columns}); beta["const"] = const
272+ lnp = X.values @ beta.values + rng.normal(0, sigma, n)
273+ d["prix_vente"] = np.round(np.exp(lnp) / 500) * 500
274+ return d
275+
276+# =============================================================================================================================
277+# 4. Exemple intégral
278+# =============================================================================================================================
279+
280+def sous_echantillons(v: pd.DataFrame, s: dict) -> dict:
281+ import ch16_selection as c16
282+ vp = c16.preparer(v.copy(), s)
283+ micro = vp[(vp["mois"].between(0, 18)) & (vp["dist_km"] <= 3.0)].drop(columns=["mois", "dist_km", "prix_m2", "distance"])
284+ return {"tout": v, "hull": v[v["secteur"] == "Hull"], "voisin": v[v["secteur"] != "Hull"], "micro": micro}
285+
286+def colonnes_actives(X: pd.DataFrame) -> list:
287+ return [c for c in X.columns if c == "const" or X[c].std() > 0]
288+
289+def ajuster_nig(df: pd.DataFrame, prior_fn=None, colonnes=None) -> dict:
290+ X = cas1.matrice(df); cols = colonnes or colonnes_actives(X); X = X[cols]
291+ y = np.log(df["prix_vente"].astype(float)).values
292+ prior = prior_fn(cols) if prior_fn else prior_faible(cols)
293+ p = nig_posterieure(X.values, y, prior); p["colonnes"] = cols
294+ return p
295+
296+def x_sujet(s: dict, cols, secteur: str | None = None, dist_centre: float | None = None) -> np.ndarray:
297+ d = dict(s)
298+ if secteur: d["secteur"] = secteur
299+ if dist_centre is not None: d["dist_centre_km"] = dist_centre
300+ Xs = cas1.matrice(cas1.sujet_df(d))
301+ return Xs[cols].values[0]
302+
303+def exemple_conjugue(v: pd.DataFrame, s: dict, ref: dict) -> dict:
304+ """Étapes 1-2 : postérieure NIG sur 600 ventes (a priori faible) ; petit échantillon (40 ventes à 3 km / 18 mois) avec a priori
305+ faible vs informatif emprunté aux autres secteurs (κ = 1, 4, 16) ; prédictive du sujet."""
306+ se = sous_echantillons(v, s)
307+ p600 = ajuster_nig(se["tout"]); cols = p600["colonnes"]
308+ xs = x_sujet(s, cols)
309+ tab = pd.DataFrame({"mco": ref["coef"]["coef"].reindex(cols), "se_hc1": ref["coef"]["se_hc1"].reindex(cols),
310+ "post_moy": p600["m_n"], "post_sd": p600["sd"], "ic_bas": p600["ic95_bas"], "ic_haut": p600["ic95_haut"],
311+ "vrai": [VRAIS.get(c, np.nan) for c in cols]}, index=cols)
312+ tab["couvre"] = (tab["vrai"] >= tab["ic_bas"]) & (tab["vrai"] <= tab["ic_haut"])
313+ pred600 = predictive_t(p600, xs)
314+ # cohérence séquentielle : 2023-2024 puis 2025-2026
315+ anc = v[v["date_vente"] < "2025-01-01"]; rec = v[v["date_vente"] >= "2025-01-01"]
316+ 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"}
318+ p_seq = ajuster_nig(rec, prior_fn=lambda c: prior_seq, colonnes=cols)
319+ # petit échantillon : 40 ventes autour du sujet ; a priori emprunté aux autres secteurs
320+ p_vois = ajuster_nig(se["voisin"])
321+ micro = se["micro"]; Xm = cas1.matrice(micro); cols_m = colonnes_actives(Xm)
322+ petits = {}
323+ for nom, pf in [("faible", None)] + [(f"kappa_{k}", (lambda k: (lambda c: prior_marche_voisin(p_vois, p_vois["colonnes"], c, k)))(k)) for k in (1, 4, 16)]:
324+ p = ajuster_nig(micro, prior_fn=pf, colonnes=cols_m)
325+ pr = predictive_t(p, x_sujet(s, cols_m))
326+ petits[nom] = {"post": p, "pred": pr, "largeur95_pct": (pr["haut95"] - pr["bas95"]) / pr["mediane"] * 100}
327+ # Hull seul (186), a priori faible
328+ 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),
330+ "n_rec": len(rec), "p_vois": p_vois, "micro": micro, "cols_micro": cols_m, "petits": petits, "p_hull": p_hull,
331+ "pred_hull": pred_hull, "n_micro": len(micro)}
332+
333+def exemple_hierarchique(v: pd.DataFrame, s: dict, ref: dict, n_iter: int = 3000, brulage: int = 1000) -> dict:
334+ """Étapes 3-4 : Gibbs sur les 600 ventes ; effets relatifs à Hull vs effets fixes (ch. 18) ; prédictive du sujet ;
335+ expérience Masson-Angers réduit à 5, 10, 20 ventes ; sensibilité à l'a priori sur τ."""
336+ 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)
339+ fixes = ref["coef"].loc[[f"sect_{x}" for x in SECTEURS[1:]], ["coef", "se_hc1"]]; fixes.index = SECTEURS[1:]
340+ rel["fixe"] = fixes["coef"]; rel["fixe_se"] = fixes["se_hc1"]; rel["vrai"] = [VRAIS[f"sect_{x}"] for x in rel.index]
341+ # prédictive du sujet (Hull) : ln P* = (x_s − x̄)'β + α_Hull + ε
342+ xs = (cas1.matrice(cas1.sujet_df(s))[cols].iloc[0] - moy).values
343+ beta = g["beta"].reshape(-1, len(cols)); alpha = g["alpha"].reshape(-1, 5); s2 = g["sigma2"].reshape(-1)
344+ rng = np.random.default_rng(GRAINE)
345+ lnp = beta @ xs + alpha[:, 0] + rng.standard_normal(len(s2)) * np.sqrt(s2)
346+ P = np.exp(lnp)
347+ pred = {"tirages": P, "mediane": float(np.median(P)), "moyenne": float(P.mean()), "moyenne_log": float(lnp.mean()),
348+ "bas90": float(np.quantile(P, 0.05)), "haut90": float(np.quantile(P, 0.95)),
349+ "bas95": float(np.quantile(P, 0.025)), "haut95": float(np.quantile(P, 0.975)),
350+ "sd_log": float(lnp.std()), "sd_moyenne_log": float((beta @ xs + alpha[:, 0]).std())}
351+ # rétrécissement a posteriori : facteur B_s à la médiane de σ², τ²
352+ s2m = float(np.median(g["sigma2"])); t2m = float(np.median(g["tau2"]))
353+ B = {sct: facteur_retrecissement(g["meta"]["ns"][j], s2m, t2m) for j, sct in enumerate(SECTEURS)}
354+ # expérience Masson-Angers : 5, 10, 20, 47 ventes (sous-ensembles emboîtés)
355+ ma = v[v["secteur"] == "Masson-Angers"]; autres = v[v["secteur"] != "Masson-Angers"]
356+ perm = np.random.default_rng(GRAINE).permutation(len(ma))
357+ dist_ma = float(ma["dist_centre_km"].median())
358+ exp_rows = []
359+ for n_ma in (5, 10, 20, len(ma)):
360+ vv = pd.concat([autres, ma.iloc[perm[:n_ma]]]).reset_index(drop=True)
361+ # effets fixes (MCO, HC1) et prédictive conjuguée (plat) pour un sujet hypothétique à Masson-Angers
362+ pf = ajuster_nig(vv); i = pf["colonnes"].index("sect_Masson-Angers")
363+ xma = x_sujet(s, pf["colonnes"], secteur="Masson-Angers", dist_centre=dist_ma)
364+ pr_f = predictive_t(pf, xma)
365+ # hiérarchique
366+ Xv, moy_v, zv = matrice_hier(vv); yv = np.log(vv["prix_vente"].astype(float)).values
367+ gv = gibbs_hierarchique(yv, Xv.values, zv, n_iter=n_iter, brulage=brulage)
368+ rr = effets_relatifs(gv).loc["Masson-Angers"]
369+ xma_c = (cas1.matrice(cas1.sujet_df({**s, "secteur": "Masson-Angers", "dist_centre_km": dist_ma}))[cols].iloc[0] - moy_v).values
370+ bv = gv["beta"].reshape(-1, len(cols)); av = gv["alpha"].reshape(-1, 5); sv = gv["sigma2"].reshape(-1)
371+ lnv = bv @ xma_c + av[:, 4] + np.random.default_rng(GRAINE + n_ma).standard_normal(len(sv)) * np.sqrt(sv)
372+ Pv = np.exp(lnv)
373+ s2v = float(np.median(gv["sigma2"])); t2v = float(np.median(gv["tau2"]))
374+ # moyenne brute des résidus du secteur (sans rétrécissement) à la postérieure : r̄ − α0 relatif
375+ exp_rows.append({"n_ma": n_ma, "fixe": pf["m_n"][i], "fixe_sd": pf["sd"][i], "fixe_bas": pf["ic95_bas"][i], "fixe_haut": pf["ic95_haut"][i],
376+ "hier": rr["moyenne"], "hier_sd": rr["sd"], "hier_bas": rr["q025"], "hier_haut": rr["q975"], "hier_rhat": rr["rhat"],
377+ "tau_med": math.sqrt(t2v), "B": facteur_retrecissement(n_ma, s2v, t2v),
378+ "pred_f_med": pr_f["mediane"], "pred_f_bas": pr_f["bas95"], "pred_f_haut": pr_f["haut95"],
379+ "pred_h_med": float(np.median(Pv)), "pred_h_bas": float(np.quantile(Pv, 0.025)), "pred_h_haut": float(np.quantile(Pv, 0.975)),
380+ "vrai": VRAIS["sect_Masson-Angers"]})
381+ experience = pd.DataFrame(exp_rows).set_index("n_ma")
382+ experience["largeur_f_pct"] = (experience["pred_f_haut"] - experience["pred_f_bas"]) / experience["pred_f_med"] * 100
383+ experience["largeur_h_pct"] = (experience["pred_h_haut"] - experience["pred_h_bas"]) / experience["pred_h_med"] * 100
384+ # sensibilité à l'a priori sur τ (600 ventes et n_MA = 5)
385+ v5 = pd.concat([autres, ma.iloc[perm[:5]]]).reset_index(drop=True)
386+ X5, _, z5 = matrice_hier(v5); y5 = np.log(v5["prix_vente"].astype(float)).values
387+ tau_eb = float(np.std(ref["coef"].loc[[f"sect_{x}" for x in SECTEURS[1:]], "coef"].tolist() + [0.0], ddof=1))
388+ sens_rows = []
389+ for nom, kw in [("uniforme sur τ", {"prior_tau": "uniforme"}), ("IG(0,001 ; 0,001) sur τ²", {"prior_tau": "ig"}),
390+ ("IG(1 ; 0,01) sur τ²", {"prior_tau": "ig", "ig_a": 1.0, "ig_b": 0.01}), (f"τ fixé à {tau_eb:.3f} (bayésien empirique)", {"tau_fixe": tau_eb})]:
391+ g600 = gibbs_hierarchique(y, Xc.values, z, n_iter=n_iter, brulage=brulage, **kw)
392+ g5 = gibbs_hierarchique(y5, X5.values, z5, n_iter=n_iter, brulage=brulage, **kw)
393+ r600 = effets_relatifs(g600).loc["Masson-Angers"]; r5 = effets_relatifs(g5).loc["Masson-Angers"]
394+ sens_rows.append({"prior": nom, "tau_med_600": float(np.median(np.sqrt(g600["tau2"]))), "tau_q05_600": float(np.quantile(np.sqrt(g600["tau2"]), 0.05)),
395+ "tau_q95_600": float(np.quantile(np.sqrt(g600["tau2"]), 0.95)),
396+ "ma_600": r600["moyenne"], "ma_600_sd": r600["sd"], "tau_med_5": float(np.median(np.sqrt(g5["tau2"]))),
397+ "ma_5": r5["moyenne"], "ma_5_sd": r5["sd"], "ma_5_bas": r5["q025"], "ma_5_haut": r5["q975"]})
398+ 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,
400+ "sigma2_med": s2m, "tau2_med": t2m, "experience": experience, "dist_ma": dist_ma, "sensibilite": sensibilite, "tau_eb": tau_eb,
401+ "perm_ma": perm}
402+
403+def criteres(v: pd.DataFrame, hier: dict, ex: dict) -> pd.DataFrame:
404+ """Comparaison de trois modèles : pooling complet (sans secteur), effets fixes (indicatrices), hiérarchique."""
405+ y = hier["y"]
406+ # pooling complet : matrice de référence sans indicatrices de secteur
407+ 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
409+ p_fixe = ex["p600"]
410+ lignes = []
411+ 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"])),
412+ ("effets fixes de secteur (ch. 18)", log_vrais_pointwise_nig(p_fixe), loo_exact_nig(p_fixe["X"], y, p_fixe["prior"])),
413+ ("effets aléatoires de secteur (hiérarchique)", log_vrais_pointwise_hier(hier["g"], y, hier["Xc"].values, hier["z"]), None)]:
414+ w = waic(lp); lt = loo_tronque(lp)
415+ lignes.append({"modele": nom, "lppd": w["lppd"], "p_waic": w["p_waic"], "elpd_waic": w["elpd_waic"], "waic": w["waic"], "se_elpd": w["se_elpd"],
416+ "elpd_loo_tronque": lt["elpd_loo"], "elpd_loo_exact": loo["elpd_loo"] if loo else np.nan})
417+ t = pd.DataFrame(lignes).set_index("modele")
418+ t["d_elpd_waic"] = t["elpd_waic"] - t["elpd_waic"].max()
419+ return t
420+
421+# =============================================================================================================================
422+# 5. Exercices (chiffres des solutions)
423+# =============================================================================================================================
424+
425+def exercices(ex: dict, hier: dict) -> dict:
426+ r = {}
427+ # ex 1 : mise à jour normale-normale, variance connue (prix au m² d'un marché mince)
428+ m0, s0, sig, n, xbar = 3300.0, 200.0, 300.0, 4, 3520.0
429+ prec = 1 / s0 ** 2 + n / sig ** 2; mn = (m0 / s0 ** 2 + n * xbar / sig ** 2) / prec; sn = math.sqrt(1 / prec)
430+ w = (n / sig ** 2) / prec
431+ r["ex1"] = {"m_n": mn, "s_n": sn, "poids_donnees": w, "ic95": (mn - 1.96 * sn, mn + 1.96 * sn), "ic90": (mn - 1.645 * sn, mn + 1.645 * sn),
432+ "n_equivalent_prior": sig ** 2 / s0 ** 2, "valeur_165": mn * 165, "sd_165": sn * 165}
433+ # ex 2 : lecture d'intervalles — probabilité a posteriori que la valeur dépasse 650 000 $ sous une postérieure log-normale
434+ mu, sd = ex["pred600"]["loc"], ex["pred600"]["echelle"]
435+ r["ex2"] = {"p_sup_650": float(1 - stats.t.cdf((math.log(650_000) - mu) / sd, ex["pred600"]["nu"])),
436+ "p_inf_550": float(stats.t.cdf((math.log(550_000) - mu) / sd, ex["pred600"]["nu"])),
437+ "q10": math.exp(mu + stats.t.ppf(0.10, ex["pred600"]["nu"]) * sd), "q90": math.exp(mu + stats.t.ppf(0.90, ex["pred600"]["nu"]) * sd)}
438+ # ex 3 : facteur de rétrécissement
439+ s2, t2 = 0.083 ** 2, 0.15 ** 2
440+ r["ex3"] = {n_: facteur_retrecissement(n_, s2, t2) for n_ in (5, 10, 20, 47)}
441+ r["ex3"]["alpha_5"] = facteur_retrecissement(5, s2, t2) * (-0.30) + (1 - facteur_retrecissement(5, s2, t2)) * (-0.08)
442+ r["ex3"]["tau_005"] = {n_: facteur_retrecissement(n_, s2, 0.05 ** 2) for n_ in (5, 10, 20, 47)}
443+ r["ex3"]["sd_cond_5"] = math.sqrt(1 / (5 / s2 + 1 / t2)); r["ex3"]["sd_fixe_5"] = math.sqrt(s2 / 5)
444+ # ex 4 : prédictive Student-t
445+ p = ex["p600"]; xs = ex["xs"]; q = float(xs @ p["V_n"] @ xs)
446+ r["ex4"] = {"a_n": p["a_n"], "b_n": p["b_n"], "nu": p["nu"], "xVx": q, "b_sur_a": p["b_n"] / p["a_n"], "echelle": ex["pred600"]["echelle"],
447+ "t975": float(stats.t.ppf(0.975, p["nu"])), "z975": 1.959964, "bas95": ex["pred600"]["bas95"], "haut95": ex["pred600"]["haut95"],
448+ "nu_petit": 20, "t975_20": float(stats.t.ppf(0.975, 20)), "mediane": ex["pred600"]["mediane"]}
449+ # ex 5 : R̂ sur des moyennes/variances de chaînes données
450+ moy = np.array([-0.262, -0.258, -0.271, -0.249]); var = np.array([0.0049, 0.0052, 0.0047, 0.0055]); n_ = 1000
451+ B = n_ * moy.var(ddof=1); W = var.mean(); vp = (n_ - 1) / n_ * W + B / n_
452+ r["ex5"] = {"B": B, "W": W, "var_plus": vp, "rhat": math.sqrt(vp / W)}
453+ moy2 = np.array([-0.262, -0.258, -0.371, -0.249]); B2 = n_ * moy2.var(ddof=1); vp2 = (n_ - 1) / n_ * W + B2 / n_
454+ r["ex5"]["rhat_divergent"] = math.sqrt(vp2 / W); r["ex5"]["B2"] = B2
455+ # ex 6 : script (κ) — valeurs du tableau des petits échantillons
456+ r["ex6"] = {k: {"ln_S": v_["post"]["m_n"][ex["cols_micro"].index("ln_S")], "ln_S_sd": v_["post"]["sd"][ex["cols_micro"].index("ln_S")],
457+ "mediane": v_["pred"]["mediane"], "largeur95_pct": v_["largeur95_pct"]} for k, v_ in ex["petits"].items()}
458+ # ex 7 : conditionnelle de α0 avec a priori N(m, s²) — valeurs numériques à τ et α médians
459+ g = hier["g"]; alpha_bar = float(g["alpha"].reshape(-1, 5).mean()); tau2 = hier["tau2_med"]
460+ for (m_, s_) in [(13.0, 1.0), (13.0, 0.05)]:
461+ prec = 5 / tau2 + 1 / s_ ** 2
462+ r.setdefault("ex7", {})[f"m{m_}_s{s_}"] = {"moy": (5 * alpha_bar / tau2 + m_ / s_ ** 2) / prec, "sd": math.sqrt(1 / prec), "alpha_bar": alpha_bar, "tau": math.sqrt(tau2)}
463+ return r
464+
465+# =============================================================================================================================
466+# 6. Main
467+# =============================================================================================================================
468+
469+def main(verbose: bool = True, n_iter: int = 3000, brulage: int = 1000) -> dict:
470+ d = cas1.charger(); v = d["ventes"]; s = d["sujet"]
471+ ref = cas1.hedonique(v)
472+ ex = exemple_conjugue(v, s, ref)
473+ hier = exemple_hierarchique(v, s, ref, n_iter=n_iter, brulage=brulage)
474+ crit = criteres(v, hier, ex)
475+ exo = exercices(ex, hier)
476+ out = {"ventes": v, "sujet": s, "modele": ref, "conjugue": ex, "hier": hier, "criteres": crit, "exercices": exo}
477+ if verbose: imprimer(out)
478+ return out
479+
480+def _f(x, d=4): return f"{x:.{d}f}"
481+def _d(x): return f"{x:,.0f} $".replace(",", " ")
482+
483+def imprimer(r: dict) -> None:
484+ ex = r["conjugue"]; hi = r["hier"]
485+ print("=== CH39 — modèle conjugué, 600 ventes, a priori faiblement informatif ===")
486+ t = ex["tab600"]
487+ print(t.round(4).to_string())
488+ print(f"couverture des vrais paramètres (IC 95 %) : {int(t['couvre'].sum())}/{int(t['vrai'].notna().sum())}")
489+ 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}")
490+ pr = ex["pred600"]
491+ 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+ 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'])}])")
493+ print(f"cohérence séquentielle : n_anc={ex['n_anc']}, n_rec={ex['n_rec']} ; max|m_seq − m_600| = {np.abs(ex['p_rec_seq']['m_n'] - p['m_n']).max():.2e} ; a_n seq {ex['p_rec_seq']['a_n']:.1f} vs {p['a_n']:.1f} ; b_n {ex['p_rec_seq']['b_n']:.6f} vs {p['b_n']:.6f}")
494+ print(f"\n=== petit échantillon : {ex['n_micro']} ventes à 3 km / 18 mois ; colonnes {ex['cols_micro']}")
495+ for nom, pe in ex["petits"].items():
496+ po = pe["post"]; i = ex["cols_micro"].index("ln_S"); iq = ex["cols_micro"].index("qualite"); ig = ex["cols_micro"].index("garage")
497+ 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+ 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+ 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é ===")
501+ print(hi["resume"].round(4).to_string())
502+ print("\neffets relatifs à Hull (hiérarchique vs effets fixes ch. 18 vs vrai) :"); print(hi["relatifs"].round(4).to_string())
503+ 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+ print("facteurs de rétrécissement B_s :", {k: round(v_, 4) for k, v_ in hi["B"].items()})
505+ pd_ = hi["pred"]
506+ print(f"prédictive hiérarchique du sujet : médiane {_d(pd_['mediane'])}, moyenne {_d(pd_['moyenne'])}, 90 % [{_d(pd_['bas90'])} ; {_d(pd_['haut90'])}], 95 % [{_d(pd_['bas95'])} ; {_d(pd_['haut95'])}] ; sd log {pd_['sd_log']:.4f} (dont moyenne {pd_['sd_moyenne_log']:.4f})")
507+ print(f"\n=== expérience Masson-Angers (dist. médiane {hi['dist_ma']:.1f} km) ===")
508+ print(hi["experience"].round(4).to_string())
509+ print("\n=== sensibilité à l'a priori sur τ ==="); print(hi["sensibilite"].round(4).to_string())
510+ print("\n=== critères ==="); print(r["criteres"].round(2).to_string())
511+ print("\n=== exercices ===")
512+ for k, v_ in r["exercices"].items(): print(k, v_)
513+
514+if __name__ == "__main__":
515+ main()
modified analysis/python/figures/ch36_figures.py +14 −10
@@ -17,6 +17,7 @@ from traite.style import setup, palette, savefig, LARGEUR
17 import ch36_panel as c36 17 import ch36_panel as c36
18 18
19 TRIMS = pd.period_range("2016Q1", "2026Q2", freq="Q") 19 TRIMS = pd.period_range("2016Q1", "2026Q2", freq="Q")
20+def _v(x, d=3): return f"{x:.{d}f}".replace(".", ",").replace("-", "−")
20 def _etiq(ax, pas=8): 21 def _etiq(ax, pas=8):
21 ax.set_xticks(range(0, len(TRIMS), pas)); ax.set_xticklabels([str(TRIMS[i]).replace("Q", "T") for i in range(0, len(TRIMS), pas)], fontsize=7.5) 22 ax.set_xticks(range(0, len(TRIMS), pas)); ax.set_xticklabels([str(TRIMS[i]).replace("Q", "T") for i in range(0, len(TRIMS), pas)], fontsize=7.5)
22 23
@@ -44,13 +45,13 @@ def fig_event_study(res):
44 ax.fill_between(es.index, es["ic_bas"], es["ic_haut"], color=palette.cat[0], alpha=0.18, lw=0) 45 ax.fill_between(es.index, es["ic_bas"], es["ic_haut"], color=palette.cat[0], alpha=0.18, lw=0)
45 ax.plot(es.index, es["coef"], "o-", color=palette.cat[0], ms=3.8, lw=1.4, label="Coefficient estimé (IC à 95 %, grappes)") 46 ax.plot(es.index, es["coef"], "o-", color=palette.cat[0], ms=3.8, lw=1.4, label="Coefficient estimé (IC à 95 %, grappes)")
46 ax.step(es.index, es["vrai"], where="mid", color=palette.ink, lw=1.1, ls="--", label="Effet déclaré δ(k)") 47 ax.step(es.index, es["vrai"], where="mid", color=palette.ink, lw=1.1, ls="--", label="Effet déclaré δ(k)")
47− ax.set_title("Étude d'événement TWFE (RMR × trimestre), k de −8 à +8 regroupés", fontsize=8); ax.set_xlabel("Trimestres depuis la mise en service, 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_ylabel("Effet sur ln RNE (points de log)") 49 ax.set_ylabel("Effet sur ln RNE (points de log)")
49 ax = axes[1] 50 ax = axes[1]
50 ax.fill_between(cs.index, cs["ic_bas"], cs["ic_haut"], color=palette.cat[1], alpha=0.18, lw=0) 51 ax.fill_between(cs.index, cs["ic_bas"], cs["ic_haut"], color=palette.cat[1], alpha=0.18, lw=0)
51 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)") 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)")
52 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)") 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)")
53− ax.set_title("Agrégation dynamique de Callaway–Sant'Anna, témoins de la même RMR", fontsize=8); 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.5); ax.set_xlabel("Trimestres depuis la mise en service, k")
54 for ax in axes: 55 for ax in axes:
55 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) 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)
56 savefig(fig, "ch36/ch36-fig-event-study.pdf") 57 savefig(fig, "ch36/ch36-fig-event-study.pdf")
@@ -63,12 +64,15 @@ def fig_bacon(res):
63 for j, (typ, mk) in enumerate(marqueurs.items()): 64 for j, (typ, mk) in enumerate(marqueurs.items()):
64 s = t[t["type"] == typ] 65 s = t[t["type"] == typ]
65 ax.scatter(s["poids"], s["beta"], marker=mk, s=55, color=palette.cat[j], edgecolor=palette.ink, lw=0.5, label=typ, zorder=5) 66 ax.scatter(s["poids"], s["beta"], marker=mk, s=55, color=palette.cat[j], edgecolor=palette.ink, lw=0.5, label=typ, zorder=5)
66− ax.axhline(b["beta_twfe"], color=palette.ink, lw=1.1, label=f"TWFE = Σ poids × 2×2 = {b['beta_twfe']:.3f}") 67+ ax.axhline(b["beta_twfe"], color=palette.ink, lw=1.1, label="TWFE = Σ poids × (2 × 2)" if ax is axes[0] else None)
67− ax.axhline(vrai, color=palette.ink2, lw=1.0, ls="--", label=f"Effet moyen vrai = {vrai:.3f}") 68+ ax.axhline(vrai, color=palette.ink2, lw=1.0, ls="--", label="Effet moyen vrai (ATT)" if ax is axes[0] else None)
68 ax.axhline(0, color=palette.grid, lw=0.8) 69 ax.axhline(0, color=palette.grid, lw=0.8)
70+ ax.text(0.99, b["beta_twfe"], f"TWFE {_v(b['beta_twfe'])}", fontsize=7, color=palette.ink, ha="right", va="bottom", transform=ax.get_yaxis_transform())
71+ ax.text(0.99, vrai, f"vrai {_v(vrai)}", fontsize=7, color=palette.ink2, ha="right", va="top" if vrai < b["beta_twfe"] else "bottom", transform=ax.get_yaxis_transform())
69 ax.set_xlabel("Poids de la comparaison 2 × 2"); ax.set_title(titre, fontsize=8.5); ax.grid(True, lw=0.4) 72 ax.set_xlabel("Poids de la comparaison 2 × 2"); ax.set_title(titre, fontsize=8.5); ax.grid(True, lw=0.4)
70− ax.legend(fontsize=6.3, loc="best")
71 axes[0].set_ylabel("Estimation 2 × 2 (points de log)") 73 axes[0].set_ylabel("Estimation 2 × 2 (points de log)")
74+ h_, l_ = axes[0].get_legend_handles_labels()
75+ fig.legend(h_, l_, loc="lower center", ncol=3, fontsize=6.8, bbox_to_anchor=(0.5, -0.1), frameon=False)
72 savefig(fig, "ch36/ch36-fig-bacon.pdf") 76 savefig(fig, "ch36/ch36-fig-bacon.pdf")
73 77
74 def fig_synthetique(res): 78 def fig_synthetique(res):
@@ -78,7 +82,7 @@ def fig_synthetique(res):
78 ax.plot(t, s["traite"], color=palette.cat[0], lw=1.7, label="Cohorte A (moyenne des 8 immeubles)") 82 ax.plot(t, s["traite"], color=palette.cat[0], lw=1.7, label="Cohorte A (moyenne des 8 immeubles)")
79 ax.plot(t, s["synthetique"], color=palette.cat[1], lw=1.5, ls="--", label="Contrôle synthétique") 83 ax.plot(t, s["synthetique"], color=palette.cat[1], lw=1.5, ls="--", label="Contrôle synthétique")
80 ax.plot(t, s["traite"] - np.concatenate([np.zeros(T0), c36.effet_vrai(np.arange(len(t) - T0))]), color=palette.cat[0], lw=0.9, ls=":", label="Y(0) déclaré") 84 ax.plot(t, s["traite"] - np.concatenate([np.zeros(T0), c36.effet_vrai(np.arange(len(t) - T0))]), color=palette.cat[0], lw=0.9, ls=":", label="Y(0) déclaré")
81− ax.axvline(T0 - 0.5, color=palette.ink2, lw=0.9, ls="--"); ax.set_title("Trajectoires (ln RNE centré sur la moyenne pré)", fontsize=8); _etiq(ax, 10); ax.legend(fontsize=6.3, loc="upper left"); ax.grid(True, lw=0.4) 85+ ax.axvline(T0 - 0.5, color=palette.ink2, lw=0.9, ls="--"); ax.set_title("(a) Trajectoires (ln RNE centré)", fontsize=8.5); _etiq(ax, 10); ax.legend(fontsize=6.3, loc="lower right"); ax.grid(True, lw=0.4)
82 ax = axes[1] 86 ax = axes[1]
83 W = res["panel"].pivot(index="id_immeuble", columns="t", values="y"); coh = res["panel"].groupby("id_immeuble")["cohorte"].first() 87 W = res["panel"].pivot(index="id_immeuble", columns="t", values="y"); coh = res["panel"].groupby("id_immeuble")["cohorte"].first()
84 donneurs = W.loc[coh[coh == "jamais"].index]; Y0 = (donneurs.values - donneurs.values[:, :T0].mean(axis=1, keepdims=True)).T 88 donneurs = W.loc[coh[coh == "jamais"].index]; Y0 = (donneurs.values - donneurs.values[:, :T0].mean(axis=1, keepdims=True)).T
@@ -89,11 +93,11 @@ def fig_synthetique(res):
89 ax.plot(t, s["ecarts"], color=palette.cat[0], lw=1.8, label="Cohorte A") 93 ax.plot(t, s["ecarts"], color=palette.cat[0], lw=1.8, label="Cohorte A")
90 ax.plot(t, np.concatenate([np.zeros(T0), c36.effet_vrai(np.arange(len(t) - T0))]), color=palette.ink, lw=1.0, ls="--", label="Effet déclaré") 94 ax.plot(t, np.concatenate([np.zeros(T0), c36.effet_vrai(np.arange(len(t) - T0))]), color=palette.ink, lw=1.0, ls="--", label="Effet déclaré")
91 ax.axvline(T0 - 0.5, color=palette.ink2, lw=0.9, ls="--"); ax.axhline(0, color=palette.ink2, lw=0.7) 95 ax.axvline(T0 - 0.5, color=palette.ink2, lw=0.9, ls="--"); ax.axhline(0, color=palette.ink2, lw=0.7)
92− ax.set_title("Écarts traité − synthétique et placebos", fontsize=8); _etiq(ax, 10); ax.legend(fontsize=6.3, loc="upper left"); ax.grid(True, lw=0.4); ax.set_ylim(-0.12, 0.12) 96+ ax.set_title("(b) Écarts et placebos", fontsize=8.5); _etiq(ax, 10); ax.legend(fontsize=6.3, loc="upper left"); ax.grid(True, lw=0.4); ax.set_ylim(-0.12, 0.12)
93 ax = axes[2] 97 ax = axes[2]
94 w = s["poids"].sort_values(ascending=True); w = w[w > 0.005] 98 w = s["poids"].sort_values(ascending=True); w = w[w > 0.005]
95 ax.barh(range(len(w)), w.values, color=palette.cat[1], height=0.7) 99 ax.barh(range(len(w)), w.values, color=palette.cat[1], height=0.7)
96− ax.set_yticks(range(len(w))); ax.set_yticklabels(w.index, fontsize=7); ax.set_xlabel("Poids"); ax.set_title(f"Poids > 0,5 % ({len(w)} donneurs)", fontsize=8); ax.grid(True, axis="x", lw=0.4) 100+ ax.set_yticks(range(len(w))); ax.set_yticklabels(w.index, fontsize=7); ax.set_xlabel("Poids"); ax.set_title(f"(c) Poids > 0,5 % ({len(w)} donneurs)", fontsize=8.5); ax.grid(True, axis="x", lw=0.4)
97 savefig(fig, "ch36/ch36-fig-synthetique.pdf") 101 savefig(fig, "ch36/ch36-fig-synthetique.pdf")
98 102
99 def fig_discontinuite(res): 103 def fig_discontinuite(res):
@@ -108,7 +112,7 @@ def fig_discontinuite(res):
108 xd = np.linspace(0, 1, 50); ax.plot(xd, b[0] + b[1] + (b[2] + b[3]) * xd, color=palette.cat[1], lw=1.8) 112 xd = np.linspace(0, 1, 50); ax.plot(xd, b[0] + b[1] + (b[2] + b[3]) * xd, color=palette.cat[1], lw=1.8)
109 ax.axvline(0, color=palette.ink2, lw=0.9, ls="--"); ax.text(0.03, y.min() + 0.01, "frontière de zone", fontsize=7.5, color=palette.ink2) 113 ax.axvline(0, color=palette.ink2, lw=0.9, ls="--"); ax.text(0.03, y.min() + 0.01, "frontière de zone", fontsize=7.5, color=palette.ink2)
110 ax.annotate("", xy=(0, b[0] + b[1]), xytext=(0, b[0]), arrowprops=dict(arrowstyle="<->", color=palette.ink, lw=1.0)) 114 ax.annotate("", xy=(0, b[0] + b[1]), xytext=(0, b[0]), arrowprops=dict(arrowstyle="<->", color=palette.ink, lw=1.0))
111− ax.text(0.05, b[0] + b[1] / 2, f"saut estimé τ̂ = {e['tau']:.3f}\n(vrai 0,050)", fontsize=7.5, color=palette.ink, va="center") 115+ ax.annotate(f"saut estimé τ̂ = {_v(e['tau'])} (vrai 0,050)", xy=(0, b[0] + b[1] / 2), xytext=(0.35, 12.72), fontsize=7.5, color=palette.ink, arrowprops=dict(arrowstyle="-", color=palette.ink2, lw=0.6))
112 ax.set_xlabel("Distance signée à la frontière (km ; positif = zone de densité supérieure)"); ax.set_ylabel("ln de la valeur simulée") 116 ax.set_xlabel("Distance signée à la frontière (km ; positif = zone de densité supérieure)"); ax.set_ylabel("ln de la valeur simulée")
113 ax.legend(fontsize=7, loc="upper left"); ax.grid(True, lw=0.4) 117 ax.legend(fontsize=7, loc="upper left"); ax.grid(True, lw=0.4)
114 savefig(fig, "ch36/ch36-fig-discontinuite.pdf") 118 savefig(fig, "ch36/ch36-fig-discontinuite.pdf")
@@ -122,7 +126,7 @@ def fig_sensibilite(res):
122 ax.step(es0.index, es0["vrai"], where="mid", color=palette.ink, lw=1.1, ls="--", label="Effet déclaré δ(k)") 126 ax.step(es0.index, es0["vrai"], where="mid", color=palette.ink, lw=1.1, ls="--", label="Effet déclaré δ(k)")
123 pre = esv.loc[-7:-2]; p = np.polyfit(pre.index.values.astype(float), pre["coef"].values, 1) 127 pre = esv.loc[-7:-2]; p = np.polyfit(pre.index.values.astype(float), pre["coef"].values, 1)
124 xx = np.linspace(-8, -1, 20); ax.plot(xx, np.polyval(p, xx), color=palette.cat[1], lw=0.9, ls=":") 128 xx = np.linspace(-8, -1, 20); ax.plot(xx, np.polyval(p, xx), color=palette.cat[1], lw=0.9, ls=":")
125− ax.text(-7.9, np.polyval(p, -7.9) + 0.012, f"pente pré-traitement\n{p[0]:.4f} par trimestre", fontsize=7, color=palette.ink) 129+ ax.text(-7.9, -0.1, f"pente des coefficients pré-traitement : {_v(p[0], 4)} par trimestre", fontsize=7, color=palette.ink)
126 ax.axhline(0, color=palette.ink2, lw=0.8); ax.axvline(-0.5, color=palette.ink2, lw=0.8, ls=":") 130 ax.axhline(0, color=palette.ink2, lw=0.8); ax.axvline(-0.5, color=palette.ink2, lw=0.8, ls=":")
127 ax.set_xlabel("Trimestres depuis la mise en service, k"); ax.set_ylabel("Effet sur ln RNE (points de log)"); ax.legend(fontsize=7, loc="upper left"); ax.grid(True, lw=0.4) 131 ax.set_xlabel("Trimestres depuis la mise en service, k"); ax.set_ylabel("Effet sur ln RNE (points de log)"); ax.legend(fontsize=7, loc="upper left"); ax.grid(True, lw=0.4)
128 savefig(fig, "ch36/ch36-fig-sensibilite.pdf") 132 savefig(fig, "ch36/ch36-fig-sensibilite.pdf")
modified manuscript/chapters/ch35.tex +274 −0
@@ -214,3 +214,277 @@ Les \motcle{médianes} de l'APCIQ et de JLR ne sont pas des indices, et leurs pr
214 \begin{quebec}[Les indices de prix et l'évaluation foncière] 214 \begin{quebec}[Les indices de prix et l'évaluation foncière]
215 Le Manuel d'évaluation foncière du Québec consacre la partie 3A de son édition 2026 à la mesure des \emph{taux de variation du marché}, c'est-à-dire des indices que l'évaluateur municipal construit lui-même pour ramener les prix de vente à la date de référence du rôle, pour l'équilibration (partie 3B) et pour les études de ratios \parencite{mefqpartie3}. Le Règlement sur la proportion médiane du rôle d'évaluation foncière prévoit que les prix des ventes qui ne sont pas contemporaines de la date de référence sont rajustés aux conditions du marché à cette date avant le calcul des ratios \parencite[art.~11--12]{reglpropmed}, ce qui suppose un indice par municipalité et par catégorie ; ces indices ne sont pas publiés, mais la proportion médiane et le facteur comparatif de chaque rôle (art.~264 de la Loi sur la fiscalité municipale) en sont la trace visible, comme le \cref{ch:09} l'a montré pour Gatineau. L'indice SPAR de la \cref{sec:35:repetees} est, de ce point de vue, l'indice naturel du système québécois : il utilise les valeurs au rôle, publiques depuis l'ouverture des données du MAMH, comme dénominateur de chaque prix de vente, et il mesure la dérive du marché depuis la date de référence du rôle, qui est exactement ce que la proportion médiane résume en un seul chiffre. Sa validité dépend de l'uniformité du rôle, que les statistiques de ratio du \cref{ch:18} mesurent. Textes consultés sur LégisQuébec et Québec.ca le 31 octobre 2026. 215 Le Manuel d'évaluation foncière du Québec consacre la partie 3A de son édition 2026 à la mesure des \emph{taux de variation du marché}, c'est-à-dire des indices que l'évaluateur municipal construit lui-même pour ramener les prix de vente à la date de référence du rôle, pour l'équilibration (partie 3B) et pour les études de ratios \parencite{mefqpartie3}. Le Règlement sur la proportion médiane du rôle d'évaluation foncière prévoit que les prix des ventes qui ne sont pas contemporaines de la date de référence sont rajustés aux conditions du marché à cette date avant le calcul des ratios \parencite[art.~11--12]{reglpropmed}, ce qui suppose un indice par municipalité et par catégorie ; ces indices ne sont pas publiés, mais la proportion médiane et le facteur comparatif de chaque rôle (art.~264 de la Loi sur la fiscalité municipale) en sont la trace visible, comme le \cref{ch:09} l'a montré pour Gatineau. L'indice SPAR de la \cref{sec:35:repetees} est, de ce point de vue, l'indice naturel du système québécois : il utilise les valeurs au rôle, publiques depuis l'ouverture des données du MAMH, comme dénominateur de chaque prix de vente, et il mesure la dérive du marché depuis la date de référence du rôle, qui est exactement ce que la proportion médiane résume en un seul chiffre. Sa validité dépend de l'uniformité du rôle, que les statistiques de ratio du \cref{ch:18} mesurent. Textes consultés sur LégisQuébec et Québec.ca le 31 octobre 2026.
216 \end{quebec} 216 \end{quebec}
217+
218+\section{Exemple intégral : cinq indices sur le marché du cas 1}\label{sec:35:exemple}
219+
220+\begin{synthetique}
221+Les 600 ventes du cas 1 (\texttt{data/synthetic/cas1\_gatineau\_ventes.csv}, graine 20261031, lois dans \texttt{data/synthetic/README.md}) ont été engendrées avec une tendance de marché exponentielle de 4,43 \% par année composée, soit $\beta = 0{,}003614$ par mois, et un bruit de transaction normal d'écart-type $\sigma = 0{,}085$ ; la vérité à retrouver est donc un indice lisse, et chaque écart des indices estimés à cette vérité est une erreur mesurable. Le jeu de \emph{reventes} est lui aussi synthétique et construit dans le script du chapitre, \texttt{analysis/python/cases/ch35\_indices.py} (fonction \texttt{generer\_reventes}, même graine) : chaque vente observée est tenue pour la première vente d'une paire ; avec une probabilité de 0,60, une revente est simulée à un intervalle $\Delta$ tiré d'une loi déclarée (85 \% de reventes ordinaires d'intervalle $6 + \mathrm{Exp}(16)$ mois, 15 \% de reventes rapides de 3 à 9 mois après rénovation, avec une prime de prix de $\mathcal{N}(0{,}12 ; 0{,}04^2)$ en logarithme) ; le logarithme du prix de revente vaut $\ln P_1 + \beta\Delta + \text{prime} + w + \varepsilon_2 - \varepsilon_1'$, où $\varepsilon_1'$ et $\varepsilon_2$ sont deux bruits de transaction indépendants d'écart-type 0,085 et $w$ une marche aléatoire idiosyncrasique de variance $\delta^2\Delta$ avec $\delta^2 = 0{,}0004$ par mois ; les reventes postérieures au 31 août 2026 sont écartées et les prix arrondis à \dollars{500}. Ces paramètres sont des hypothèses pédagogiques, non des estimations sur des reventes réelles de Gatineau. L'indice SPAR utilise la valeur au rôle fictive du jeu (\texttt{valeur\_role\_2025}, engendrée comme 93 \% de la valeur au 1\ier{} juillet 2024 avec un bruit d'écart-type 0,07). Tous les chiffres de cette section et de la suivante sont imprimés par le script et vérifiés par \texttt{analysis/python/tests/test\_ch35.py}.
222+\end{synthetique}
223+
224+\begin{exemple}{Médiane, hédonique, ventes répétées et SPAR sur les quinze trimestres du cas 1}{indices-cas1}
225+L'exemple découpe les 600 ventes en quinze trimestres, de 2023T1 à 2026T3, qui comptent de 22 à 51 ventes chacun, et construit six indices base 2023T1 = 100. La vérité de génération, évaluée au milieu de chaque trimestre, monte régulièrement jusqu'à 116,37 au troisième trimestre de 2026.
226+
227+\emph{Étape 1 : la médiane et la moyenne.} L'indice de la médiane des prix (\cref{tab:35:indices}, \cref{fig:35:biais-mediane}) atteint 128,63 au dernier trimestre et culmine à 133,68 au quatrième trimestre de 2025 : il annonce une hausse de 28,6 \% là où les prix à qualité constante ont monté de 16,4 \%. Son écart absolu moyen à la vérité est de 10,97 points de pourcentage et son écart maximal de 19,06 \% ; d'un trimestre à l'autre, il saute de 10 à 13 points dans un sens puis dans l'autre, comme entre 2024T4 (128,45) et 2025T1 (115,33), alors que la vérité progresse de 1,1 point. La moyenne géométrique fait un peu mieux (écart absolu moyen de 7,93 points), parce qu'elle est moins sensible au tirage des ventes extrêmes, mais elle souffre du même mal. La décomposition \eqref{eq:35:decomposition}, calculée avec les prix implicites du modèle à indicatrices, identifie ce mal : l'effet de composition $(\bar{\mathbf{x}}_t - \bar{\mathbf{x}}_0)\T\hat{\boldsymbol\beta}$ vaut $+0{,}076$ en 2024T4, $+0{,}089$ en 2025T4 et $+0{,}101$ en 2026T1, c'est-à-dire que les maisons vendues au cours de ces trimestres valaient, à prix constants, de 8 à 11 \% de plus que celles de 2023T1 ; la part des ventes du secteur d'Aylmer, le plus cher, passe de 25,6 \% à 36,4 \% entre le premier et le dernier trimestre. La corrélation entre le logarithme du rapport de l'indice de la médiane à l'indice hédonique et l'effet de composition est de 0,86 : la médiane s'écarte de l'indice à qualité constante précisément quand et parce que la composition change. Une fois l'effet de composition soustrait, la moyenne géométrique corrigée coïncide exactement avec l'indice hédonique, comme l'identité l'exige (cercles de la \cref{fig:35:biais-mediane}).
228+
229+\emph{Étape 2 : l'indice hédonique.} Le modèle de référence du \cref{ch:18}, où la tendance linéaire est remplacée par quatorze indicatrices trimestrielles (29 paramètres, $\hat\sigma = 0{,}083$, $R^2$ ajusté de 0,849), donne l'indice à indicatrices de l'\cref{eq:35:td-indice}. Il s'établit à 122,04 au troisième trimestre de 2026, avec un intervalle de confiance à 95 \% de $[117{,}72 ; 126{,}52]$, soit une largeur de 7,5 \% ; les erreurs-types des $\hat\tau_t$ vont de 0,015 à 0,020 selon le nombre de ventes du trimestre. Son écart absolu moyen à la vérité est de 2,81 points, son écart maximal de 5,03 \% et ses variations trimestrielles ont un écart-type de 1,8 point, contre 1,1 point pour la vérité. Deux traits méritent attention. D'abord, l'indice est au-dessus de la vérité à \emph{tous} les trimestres, de 1,5 à 5,7 points : le premier trimestre de 2023, qui sert de base, s'est trouvé tiré en dessous de la tendance par le bruit de ses 39 ventes, et cette erreur de base se transmet à toute la série, ce qui rappelle que la période de base d'un indice n'est pas un point fixe mais une estimation comme les autres. Ensuite, la dernière valeur est la moins précise (22 ventes), et c'est elle que l'ajustement de conditions du marché utilise, comme le \cref{ch:18} l'avait noté. L'indice chaîné de l'\cref{eq:35:chaine}, qui libère les prix implicites d'un maillon à l'autre, finit à 121,69, très près de l'indice direct, mais son écart absolu moyen est plus élevé (4,01 points) : sur un marché où les prix implicites sont en vérité constants, la liberté supplémentaire n'ajoute que du bruit, et la variance cumulée des maillons s'accroît avec la distance à la base. L'indice d'imputation géométrique de l'\cref{eq:35:imputation}, calculé avec les coefficients communs du modèle à indicatrices, coïncide avec l'indice direct à $10^{-13}$ près pour Laspeyres, Paasche et Fisher, ce qui vérifie numériquement la \cref{prop:imputation-td}.
230+
231+\emph{Étape 3 : le jeu de reventes.} La simulation produit 220 paires de reventes (\cref{tab:35:reventes}), dont 47 reventes rapides rénovées. L'intervalle médian est de 10 mois, l'intervalle moyen de 12,8 mois, l'étendue de 3 à 38 mois, et 55,9 \% des paires ont un intervalle inférieur à douze mois, une proportion élevée qui résulte de la fenêtre courte : une revente ne peut être observée que si elle survient avant la fin d'août 2026, ce qui favorise mécaniquement les intervalles courts et les premières ventes anciennes (89 des 220 premières ventes datent de 2023, 2 seulement de 2026). L'écart-type du logarithme des rapports de prix est de 0,144, à comparer à $\sqrt{2}\times 0{,}085 = 0{,}120$ pour le seul bruit de transaction : la marche aléatoire et les primes de rénovation ajoutent le reste. La variation moyenne en logarithme des reventes rénovées est de 0,096 contre 0,044 pour les autres, pour des intervalles plus courts : ce sont elles qui porteront le biais.
232+
233+\emph{Étape 4 : BMN et Case--Shiller.} La matrice $\mathbf{D}$ compte 220 lignes et 14 colonnes ; sa diagonale $\mathbf{D}\T\mathbf{D}$ indique combien de paires touchent chaque trimestre, de 14 pour 2026T3 à 39 pour 2023T4, 2025T1 et 2025T2. L'estimateur de BMN donne un indice qui finit à 120,68, avec un intervalle de confiance de $[109{,}78 ; 132{,}65]$, soit une largeur de 20,8 \%, presque trois fois celle de l'indice hédonique : l'écart-type résiduel des paires, $\hat\sigma_\eta = 0{,}146$, est près du double de celui du modèle hédonique, et chaque trimestre n'est relié aux autres que par quelques dizaines de paires. Les erreurs-types des $\hat\tau_t$ vont de 0,035 à 0,048. L'écart absolu moyen à la vérité est de 3,02 points, l'écart maximal de 6,42 \%, et les variations trimestrielles ont un écart-type de 3,1 points, trois fois celui de la vérité : l'indice fait des zigzags que le marché n'a pas faits, comme le recul de 113,42 à 108,35 entre 2025T2 et 2025T4, puis le bond à 122,43 en 2026T2. La deuxième étape de Case--Shiller illustre la mise en garde de la \cref{sec:35:repetees} : la régression des carrés des résidus sur l'intervalle (\cref{fig:35:poids-cs}) donne $\hat a_0 = 0{,}0208$ et $\hat a_1 = -0{,}00023$ par trimestre, une pente \emph{négative}, alors que la loi de génération impose $2\sigma^2 = 0{,}01445$ et $\delta^2 = 0{,}0012$ par trimestre. Les 47 reventes rapides rénovées, concentrées aux intervalles de un à trois trimestres avec des variations anormalement grandes, tirent la variance des courts intervalles vers le haut et renversent la pente. Sans elles, la régression retrouve $\hat a_0 = 0{,}0157$ et $\hat a_1 = +0{,}00053$ : l'ordonnée est proche de la théorie, la pente moitié moindre mais du bon signe, sur 173 paires. Avec les poids estimés sur toutes les paires, presque uniformes, l'indice Case--Shiller (120,48 au dernier trimestre, écart absolu moyen de 2,90 points) ne s'écarte jamais de plus de 0,5 point de celui de BMN ; avec les poids théoriques, une paire de douze trimestres pèserait 0,74 fois une paire d'un trimestre (\cref{tab:35:cs}).
234+
235+\emph{Étape 5 : SPAR.} Le rapport du prix de vente à la valeur au rôle fictive a un écart-type logarithmique de 0,087 sur les 600 ventes et une médiane de 1,095, ce qui reflète un rôle établi à 93 \% de la valeur au 1\ier{} juillet 2024 et des prix qui ont continué de monter ensuite. L'indice SPAR géométrique de l'\cref{eq:35:spar} finit à 115,06 et la version arithmétique à 114,36, pour une vérité de 116,37 ; l'écart absolu moyen est de 1,26 point, le plus faible des six indices, et la largeur de l'intervalle de 8,2 \%. Ce résultat est à lire avec précaution : la valeur au rôle du jeu a été engendrée comme une estimation \emph{sans biais vertical} de la valeur vraie, avec une dispersion de 7 \%, c'est-à-dire comme un rôle idéalement uniforme ; elle contient en quelque sorte l'information d'un modèle hédonique parfait. Sur un rôle réel, dont le COD est de 6 à 15 et dont le PRB n'est pas nul, l'indice SPAR hériterait de ces défauts. L'exemple montre néanmoins pourquoi la méthode est séduisante partout où un rôle uniforme existe : elle utilise toutes les ventes, n'a besoin d'aucune caractéristique et converge vite.
236+
237+\emph{Étape 6 : comparaison.} Le \cref{tab:35:indices} aligne les six indices par trimestre et le \cref{tab:35:precision} résume leur précision ; la \cref{fig:35:indices} les superpose à la vérité. Trois enseignements. La médiane est hors concours : son erreur est de trois à dix fois celle des indices à qualité constante, et son biais est systématique. L'indice hédonique et les indices de ventes répétées ont des erreurs moyennes comparables, de 2,8 à 3,0 points, mais de nature différente : l'hédonique est biaisé par sa base et lisse, les ventes répétées sont centrés mais bruités, avec un intervalle de confiance trois fois plus large parce qu'ils n'exploitent que 220 paires au lieu de 600 ventes. Enfin, la croissance totale mesurée va de 15,1 \% (SPAR) à 22,0 \% (hédonique) pour une vérité de 16,4 \%, et de 28,6 \% pour la médiane : sur un marché synthétique parfaitement régulier, avec quelques dizaines de ventes par trimestre, l'incertitude sur la variation des prix en trois ans et demi est de l'ordre de cinq points, soit un tiers de la variation elle-même.
238+
239+\emph{Étape 7 : l'ajustement de conditions du marché de G0404.} Le comparable G0404 du \cref{ch:16} s'est vendu \dollars{506500} le 27 mai 2025 (trimestre 2025T2), 15,2 mois avant la date d'évaluation du 1\ier{} septembre 2026 (trimestre 2026T3). Le \cref{tab:35:ajustement} calcule l'ajustement $P\,(I_{2026\mathrm{T}3}/I_{2025\mathrm{T}2} - 1)$ selon l'indice retenu. La vérité de génération donne \dollars{28558} ; la tendance linéaire du modèle de référence, \dollars{28733} ; l'indice hédonique à indicatrices, \dollars{44760}, parce que son dernier trimestre est 5 \% au-dessus de la tendance ; l'indice chaîné, \dollars{30987} ; BMN et Case--Shiller, \dollars{32406} et \dollars{32359} ; le SPAR, \dollars{26575} ; et la médiane, \dollars{44009}. L'étendue, de \dollars{26575} à \dollars{44760}, représente de 5,2 \% à 8,8 \% du prix du comparable : pour un ajustement que la grille du \cref{ch:17} traite en une ligne, l'incertitude liée au seul choix de l'indice est du même ordre que celle des ajustements de propriété mesurée au \cref{ch:18}. Le \cref{ch:20} intègre cette incertitude dans la fourchette finale.
240+\end{exemple}
241+
242+\begin{table}[htbp]
243+\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]@{}}
246+\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)$} \\
248+\midrule
249+Toutes les paires & 220 & 47 & 10.0 & 12.8 & 0.0547 & 0.1441 \\
250+Reventes ordinaires & 173 & 0 & {---} & {---} & 0.0436 & 0.1416 \\
251+Reventes rapides rénovées (3 à 9 mois) & 47 & 47 & {---} & {---} & 0.0957 & 0.1476 \\
252+Intervalle d'au moins 12 mois & 97 & 0 & {---} & {---} & {---} & {---} \\
253+Intervalle d'au moins 18 mois & 45 & 0 & {---} & {---} & {---} & {---} \\
254+\bottomrule
255+\end{tabular}
256+\tabsource{\synth{\texttt{analysis/python/cases/ch35\_indices.py}, fonction \texttt{generer\_reventes}, graine 20261031}. Première vente par année : 2023 : 89 ; 2024 : 77 ; 2025 : 52 ; 2026 : 2. Seconde vente par année : 2023 : 16 ; 2024 : 51 ; 2025 : 91 ; 2026 : 62. Intervalles de 3 à 38 mois ; 55,9 \% des paires ont un intervalle inférieur à 12 mois.}
257+\end{table}
258+
259+\begin{table}[htbp]
260+\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}
262+\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+\toprule
264+Trim. & {$n$} & {Vérité} & {Médiane} & {Hédon.} & IC 95 \% hédon. & {Chaîné} & {BMN} & IC 95 \% BMN & {Case--Shiller} & {SPAR} \\
265+\midrule
266+2023T1 & 39 & 100.00 & 100.00 & 100.00 & {[100.00 ; 100.00]} & 100.00 & 100.00 & {[100.00 ; 100.00]} & 100.00 & 100.00 \\
267+2023T2 & 39 & 101.06 & 112.19 & 106.15 & {[102.81 ; 109.59]} & 105.74 & 103.11 & {[94.63 ; 112.36]} & 102.93 & 99.79 \\
268+2023T3 & 42 & 102.17 & 108.87 & 105.25 & {[101.91 ; 108.70]} & 105.22 & 107.03 & {[99.81 ; 114.77]} & 106.83 & 102.63 \\
269+2023T4 & 40 & 103.29 & 111.33 & 106.44 & {[103.13 ; 109.87]} & 107.85 & 109.92 & {[102.52 ; 117.86]} & 109.83 & 102.71 \\
270+2024T1 & 50 & 104.43 & 111.21 & 106.54 & {[103.12 ; 110.08]} & 108.10 & 107.86 & {[100.70 ; 115.54]} & 107.58 & 102.90 \\
271+2024T2 & 44 & 105.55 & 116.13 & 107.13 & {[103.56 ; 110.82]} & 108.92 & 106.38 & {[98.99 ; 114.32]} & 106.21 & 103.64 \\
272+2024T3 & 51 & 106.71 & 117.86 & 110.45 & {[107.10 ; 113.90]} & 112.80 & 106.25 & {[98.62 ; 114.47]} & 106.07 & 102.62 \\
273+2024T4 & 39 & 107.88 & 128.45 & 112.24 & {[108.90 ; 115.69]} & 115.07 & 109.99 & {[102.23 ; 118.33]} & 109.79 & 108.08 \\
274+2025T1 & 32 & 109.07 & 115.33 & 110.79 & {[107.14 ; 114.57]} & 112.36 & 111.15 & {[103.21 ; 119.71]} & 110.90 & 108.24 \\
275+2025T2 & 43 & 110.23 & 118.35 & 112.13 & {[108.56 ; 115.82]} & 114.68 & 113.42 & {[105.92 ; 121.46]} & 113.25 & 109.32 \\
276+2025T3 & 50 & 111.44 & 122.35 & 112.90 & {[109.44 ; 116.46]} & 114.92 & 114.05 & {[106.28 ; 122.39]} & 113.85 & 111.79 \\
277+2025T4 & 30 & 112.66 & 133.68 & 115.46 & {[111.14 ; 119.95]} & 116.95 & 108.35 & {[100.79 ; 116.48]} & 108.07 & 112.10 \\
278+2026T1 & 33 & 113.90 & 130.91 & 116.90 & {[112.99 ; 120.95]} & 118.26 & 115.70 & {[107.41 ; 124.64]} & 115.49 & 112.48 \\
279+2026T2 & 46 & 115.11 & 132.57 & 118.00 & {[114.07 ; 122.07]} & 118.15 & 122.43 & {[112.34 ; 133.43]} & 122.26 & 111.26 \\
280+2026T3 & 22 & 116.37 & 128.63 & 122.04 & {[117.72 ; 126.52]} & 121.69 & 120.68 & {[109.78 ; 132.65]} & 120.48 & 115.06 \\
281+\bottomrule
282+\end{tabular}
283+\tabsource{\synth{\texttt{analysis/python/cases/ch35\_indices.py}}. Hédonique : modèle de référence avec indicatrices trimestrielles (HC1) ; chaîné : périodes adjacentes ; BMN et Case--Shiller : 220 paires de reventes synthétiques ; SPAR : version géométrique sur la valeur au rôle fictive. Vérité : $100\exp(\beta\,(m_t - m_0))$ au milieu du trimestre, $\beta = 0{,}003614$ par mois.}
284+\end{table}
285+
286+\begin{table}[htbp]
287+\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}
289+\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+\toprule
291+Indice & {Hausse totale (\%)} & {Écart final (\%)} & {EAM (pts)} & {RMSE (pts)} & {Écart max (\%)} & {É.-t. des var. trim. (pts)} & {Largeur IC finale (\%)} \\
292+\midrule
293+Médiane des prix & 28.63 & 10.54 & 10.97 & 11.76 & 19.06 & 5.50 & {---} \\
294+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 & {---} \\
297+Ventes répétées, BMN & 20.68 & 3.70 & 3.02 & 3.51 & 6.42 & 3.07 & 20.83 \\
298+Ventes répétées, Case--Shiller & 20.48 & 3.53 & 2.90 & 3.40 & 6.33 & 3.10 & 20.62 \\
299+SPAR géométrique & 15.06 & -1.13 & 1.26 & 1.64 & 3.83 & 1.71 & 8.18 \\
300+SPAR arithmétique & 14.36 & -1.73 & 1.46 & 1.90 & 4.43 & 1.80 & {---} \\
301+\midrule
302+Vérité de génération & 16.37 & 0.00 & 0.00 & 0.00 & 0.00 & 1.09 & {---} \\
303+\bottomrule
304+\end{tabular}
305+\tabsource{\synth{\texttt{analysis/python/cases/ch35\_indices.py}}. EAM : écart absolu moyen à la vérité sur les quatorze trimestres hors base, en points d'indice ; écart max : plus grand écart relatif ; largeur de l'intervalle de confiance à 95 \% du dernier trimestre en pourcentage de sa borne inférieure.}
306+\end{table}
307+
308+\begin{figure}[htbp]
309+\centering
310+\includegraphics[width=\textwidth]{ch35/ch35-fig-indices-compares.pdf}
311+\caption{Quatre indices à qualité constante du cas 1 et la vérité de génération (haut), et leur écart à la vérité en pourcentage (bas). L'indice hédonique est lisse mais décalé vers le haut par le bruit de sa période de base ; les indices de ventes répétées sont centrés mais oscillent de trimestre en trimestre, avec un intervalle de confiance (non tracé) trois fois plus large ; le SPAR, qui profite d'un rôle fictif idéalement uniforme, est le plus proche de la vérité.}\label{fig:35:indices}
312+\figsource{\synth{script \texttt{analysis/python/figures/ch35\_figures.py}, graine 20261031}.}
313+\end{figure}
314+
315+\begin{figure}[htbp]
316+\centering
317+\includegraphics[width=\textwidth]{ch35/ch35-fig-biais-mediane.pdf}
318+\caption{Le biais de composition de la médiane. En haut, l'indice de la médiane et celui de la moyenne géométrique des prix s'écartent de l'indice hédonique de 5 à 20 points ; la moyenne géométrique corrigée de l'effet de composition (cercles) coïncide exactement avec l'indice hédonique, comme l'impose l'identité \eqref{eq:35:decomposition}. En bas, l'effet de composition $(\bar{\mathbf{x}}_t - \bar{\mathbf{x}}_0)\T\hat{\boldsymbol\beta}$ et la part des ventes du secteur d'Aylmer : les trimestres où la médiane s'emballe sont ceux où les maisons vendues valaient, à prix constants, 8 à 11 \% de plus que celles du trimestre de base.}\label{fig:35:biais-mediane}
319+\figsource{\synth{script \texttt{analysis/python/figures/ch35\_figures.py}, graine 20261031}.}
320+\end{figure}
321+
322+\begin{table}[htbp]
323+\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
325+\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+\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.} \\
328+\midrule
329+Toutes les paires (220) & 0.02079 & -0.00023 & 1.007 & 1.027 & 1.050 \\
330+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 \\
332+\bottomrule
333+\end{tabular}
334+\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.}
335+\end{table}
336+
337+\begin{figure}[htbp]
338+\centering
339+\includegraphics[width=\textwidth]{ch35/ch35-fig-poids-case-shiller.pdf}
340+\caption{À gauche, le carré des résidus de l'estimateur BMN selon l'intervalle entre les deux ventes, les reventes rapides rénovées étant distinguées ; la droite ajustée sur toutes les paires a une pente négative, celle ajustée sans les reventes rénovées retrouve le signe de la loi de génération $2\sigma^2 + \delta^2\Delta$. À droite, les poids relatifs de Case--Shiller qui en découlent : une paire de douze trimestres pèse 0,86 (estimé) ou 0,74 (théorique) fois une paire d'un trimestre.}\label{fig:35:poids-cs}
341+\figsource{\synth{script \texttt{analysis/python/figures/ch35\_figures.py}, graine 20261031}.}
342+\end{figure}
343+
344+\begin{table}[htbp]
345+\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
347+\begin{tabular}{@{}l S[table-format=3.2] S[table-format=3.2] S[table-format=1.2] S[table-format=5.0]@{}}
348+\toprule
349+Indice & {$I_{2025\mathrm{T}2}$} & {$I_{2026\mathrm{T}3}$} & {Variation (\%)} & {Ajustement (\$)} \\
350+\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 \\
353+Hédonique, indicatrices trimestrielles & 112.13 & 122.04 & 8.84 & 44760 \\
354+Hédonique chaîné & 114.68 & 121.69 & 6.12 & 30987 \\
355+Ventes répétées, BMN & 113.42 & 120.68 & 6.40 & 32406 \\
356+Ventes répétées, Case--Shiller & 113.25 & 120.48 & 6.39 & 32359 \\
357+SPAR géométrique & 109.32 & 115.06 & 5.25 & 26575 \\
358+Médiane des prix & 118.35 & 128.63 & 8.69 & 44009 \\
359+\bottomrule
360+\end{tabular}
361+\tabsource{\synth{\texttt{analysis/python/cases/ch35\_indices.py}, fonction \texttt{ajustement\_comparable}}. Ajustement $= P\,(I_{2026\mathrm{T}3}/I_{2025\mathrm{T}2} - 1)$ ; pour la tendance, $P\,[\exp(\hat\beta_{\text{mois}} \times 15{,}2) - 1]$.}
362+\end{table}
363+
364+\section{Sensibilité : révision, filtres, taille et fenêtre}\label{sec:35:sensibilite}
365+
366+Quatre décisions gouvernent un indice de ventes répétées ou hédonique : la date de publication, les filtres appliqués aux paires, le nombre d'observations et la fenêtre d'estimation. Le \cref{tab:35:sensibilite} et la \cref{fig:35:revision} mesurent leur effet sur le cas 1.
367+
368+\begin{table}[htbp]
369+\caption{Sensibilité des indices du cas 1 au millésime, au filtre des reventes rapides, à la taille de l'échantillon et à la fenêtre d'estimation (données synthétiques)}\label{tab:35:sensibilite}
370+\centering\small
371+\begin{tabularx}{\textwidth}{@{}Y S[table-format=3.0] S[table-format=3.2] S[table-format=2.2] S[table-format=2.2]@{}}
372+\toprule
373+Variante & {Paires ou ventes} & {Valeur ou hausse} & {EAM (pts)} & {Note} \\
374+\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)}} \\
376+Publié en 2025T2 & 110 & 116.39 & {---} & {---} \\
377+Publié en 2025T4 & 158 & 114.69 & {---} & -1.70 \\
378+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 \\
380+\midrule
381+\multicolumn{5}{@{}l}{\emph{Filtre des reventes rapides (hausse totale BMN 2023T1--2026T3, en \% ; vérité 16,37)}} \\
382+Toutes les paires & 220 & 20.68 & 3.02 & {---} \\
383+Intervalle d'au moins 12 mois & 97 & 17.98 & 2.76 & {---} \\
384+Intervalle d'au moins 18 mois & 45 & 19.41 & 6.56 & {---} \\
385+Sans les reventes rénovées (oracle, inobservable en pratique) & 173 & 18.39 & 2.01 & {---} \\
386+\midrule
387+\multicolumn{5}{@{}l}{\emph{Taille de l'échantillon de paires (BMN ; moyenne de 200 sous-échantillons aléatoires)}} \\
388+Toutes les paires & 220 & {---} & 3.02 & {---} \\
389+Moitié des paires & 110 & {---} & 4.26 & 4.77 \\
390+Quart des paires & 55 & {---} & 6.46 & 8.96 \\
391+\midrule
392+\multicolumn{5}{@{}l}{\emph{Fenêtre d'estimation de l'indice hédonique (huit derniers trimestres, rechaîné sur 2024T4)}} \\
393+Fenêtre complète (15 trimestres) & 600 & 122.04 & {---} & {---} \\
394+Huit derniers trimestres & 295 & 122.38 & {---} & 0.37 \\
395+\bottomrule
396+\end{tabularx}
397+\tabsource{\synth{\texttt{analysis/python/cases/ch35\_indices.py}, fonction \texttt{sensibilite}}. Note : révision en points d'indice pour le premier bloc ; écart-type de la valeur finale de l'indice entre sous-échantillons (points) pour le troisième ; écart relatif maximal (\%) entre les deux fenêtres pour le quatrième.}
398+\end{table}
399+
400+\begin{figure}[htbp]
401+\centering
402+\includegraphics[width=\textwidth]{ch35/ch35-fig-revision-filtre.pdf}
403+\caption{À gauche, l'indice BMN du cas 1 tel qu'il aurait été publié en 2025T2, en 2025T4 et en 2026T3 : chaque millésime révise les valeurs antérieures, de près de trois points pour 2025T2 et de 4,5 points pour 2024T4, parce que les paires nouvelles relient les trimestres récents aux anciens. À droite, l'effet du filtre des reventes rapides : écarter les intervalles inférieurs à douze mois élimine les reventes rénovées et rapproche la hausse totale de la vérité, mais un filtre à dix-huit mois ne laisse que 45 paires et l'indice devient erratique.}\label{fig:35:revision}
404+\figsource{\synth{script \texttt{analysis/python/figures/ch35\_figures.py}, graine 20261031}.}
405+\end{figure}
406+
407+La \emph{révision} est la plus spectaculaire. L'indice BMN du deuxième trimestre de 2025 valait 116,39 avec les 110 paires connues à la fin de ce trimestre ; il est passé à 114,69 six mois plus tard, puis à 113,42 avec les 220 paires d'août 2026, soit une révision cumulée de près de trois points. Celui du quatrième trimestre de 2024 a été révisé de 4,5 points à la baisse. Les premières publications sont tirées vers le haut parce que les paires disponibles à une date donnée sont, par construction, celles dont l'intervalle est court, où les reventes rénovées sont surreprésentées ; à mesure que des reventes ordinaires d'intervalle plus long s'ajoutent, l'indice redescend. C'est exactement le mécanisme que \textcite{silverstein2014} a documenté sur les indices américains, et il implique qu'un ajustement de conditions du marché calculé avec la dernière valeur publiée doit être documenté avec sa date de publication.
408+
409+Le \emph{filtre des reventes rapides} est un arbitrage entre biais et variance. Sur toutes les paires, la hausse totale mesurée par BMN est de 20,7 \%, soit 4,3 points de trop, dont 2,3 points sont imputables aux reventes rénovées, puisque l'indice calculé sans elles, ce qu'un producteur ne peut pas faire faute de connaître les rénovations, donne 18,4 \%. Écarter les paires d'intervalle inférieur à douze mois, filtre que le producteur peut appliquer, élimine ici toutes les reventes rénovées et ramène la hausse à 18,0 \% et l'écart absolu moyen de 3,02 à 2,76 points, au prix de la perte de 123 paires. Un filtre à dix-huit mois ne laisse que 45 paires : la variance l'emporte, l'écart absolu moyen monte à 6,56 points et l'indice devient inutilisable. La leçon est que le seuil du filtre doit être proportionné à la taille de l'échantillon, et que les producteurs qui disposent de centaines de milliers de paires peuvent se permettre des filtres stricts que l'évaluateur qui construit un indice local ne peut pas s'offrir. La \emph{taille de l'échantillon} confirme le résultat de \textcite{goetzmann1992} : avec la moitié des paires, l'écart absolu moyen passe de 3,0 à 4,3 points et la valeur finale de l'indice varie, d'un sous-échantillon à l'autre, avec un écart-type de 4,8 points ; avec le quart, l'écart absolu moyen est de 6,5 points et l'écart-type de 9,0 points, c'est-à-dire que deux analystes disposant chacun de 55 paires du même marché pourraient conclure à des hausses de 10 \% et de 28 \% sur la même période. Enfin, la \emph{fenêtre d'estimation} de l'indice hédonique compte peu sur ce marché : estimé sur les huit derniers trimestres seulement (295 ventes) et rechaîné, l'indice ne s'écarte jamais de plus de 0,37 \% de celui de la fenêtre complète, parce que les prix implicites sont en vérité constants ; sur un marché réel où ils dérivent, la fenêtre courte est le prix à payer pour que les indicatrices temporelles ne capturent pas, en plus du marché, le changement des prix implicites.
410+
411+\begin{pratique}
412+Un ajustement de conditions du marché défendable nomme l'indice (producteur, série, territoire, catégorie), sa méthode (médiane, hédonique, ventes répétées, SPAR), sa base, les périodes comparées, la date de publication du millésime utilisé et, lorsqu'il est disponible, son intervalle de confiance ; il indique si la date de la vente comparable est celle de la promesse ou de l'acte, et il compare le taux retenu à au moins une autre source. Lorsque l'évaluateur construit son propre indice sur les ventes du secteur, le dossier contient le script, les filtres et le nombre d'observations par période, et le rapport déclare la précision obtenue ; un indice trimestriel estimé sur vingt ventes a une incertitude de plusieurs points, et il est souvent préférable de lui substituer une tendance lissée, comme le \cref{ch:18} l'a fait, en le disant.
413+\end{pratique}
414+
415+\begin{erreurs}
416+\begin{itemize}
417+\item \emph{Lire une variation de médiane comme une variation de prix.} Sur le cas 1, la médiane annonce 28,6 \% de hausse pour 16,4 \% de hausse réelle, et ses sauts trimestriels de 10 à 13 points ne correspondent à aucun mouvement du marché.
418+\item \emph{Appliquer l'indice d'un autre territoire ou d'un autre segment.} L'IPLN montre des hausses annuelles de 21,9 \% à Montréal et de 6,7 \% à Gatineau au même moment ; l'indice Teranet--Banque Nationale ne couvre ni Gatineau ni les secteurs d'une région métropolitaine.
419+\item \emph{Utiliser l'IPLN comme indice de revente.} Il mesure les prix demandés par les constructeurs pour des maisons neuves à spécifications constantes ; sa direction informe, son taux ne s'applique pas à une revente.
420+\item \emph{Oublier que la dernière valeur d'un indice est la moins précise et la plus révisée.} L'indice BMN de 2025T2 a été révisé de trois points en quinze mois ; l'indice hédonique du dernier trimestre repose sur 22 ventes.
421+\item \emph{Appliquer un indice de ventes répétées sans considérer l'âge.} L'indice mesure la variation du prix d'un immeuble qui vieillit ; l'ajustement d'âge ou d'état du comparable est distinct.
422+\item \emph{Filtrer les reventes rapides sans regarder ce qui reste.} Un filtre à dix-huit mois a divisé l'échantillon par cinq et doublé l'erreur.
423+\item \emph{Lire l'indice hédonique comme la vérité.} Sa période de base est une estimation ; sur le cas 1, un bruit négatif en 2023T1 a décalé toute la série de deux à cinq points vers le haut.
424+\item \emph{Construire un indice SPAR avec des valeurs au rôle de dates de référence différentes}, ou sans vérifier l'uniformité du rôle par les statistiques de ratio.
425+\end{itemize}
426+\end{erreurs}
427+
428+\begin{limites}
429+\begin{itemize}
430+\item Les 600 ventes et les 220 reventes du cas 1 sont synthétiques ; la tendance vraie est une exponentielle régulière, les prix implicites sont constants, les bruits sont normaux et homoscédastiques, et la part des reventes rénovées, leur prime et la variance de la marche aléatoire sont des hypothèses déclarées. Les erreurs mesurées ici sont celles de l'échantillonnage sur un marché idéal ; sur un marché réel, les erreurs de spécification, la dépréciation, les changements de prix implicites et les données erronées s'y ajoutent.
431+\item La valeur au rôle fictive a été engendrée sans biais vertical et avec une dispersion de 7 \% ; la précision remarquable du SPAR dans l'exemple ne doit pas être extrapolée à un rôle réel, dont l'uniformité doit être mesurée au préalable.
432+\item La méthodologie publiée de l'indice Teranet--Banque Nationale, consultée le 31 octobre 2026, décrit les principes et les exclusions sans donner l'estimateur exact, les seuils numériques des filtres ni la politique de révision ; la description complète annoncée comme téléchargeable n'a pas pu être consultée. Les conditions d'utilisation des données C11 n'ont pas été lues.
433+\item La méthodologie de l'IPLN est tirée de la fiche de l'enquête 2310 de Statistique Canada ; le document méthodologique détaillé de la Série analytique des prix (62F0014M) n'a pas été consulté.
434+\item Les ordres de grandeur de la dépréciation nette cités d'après \textcite{hardingRosenthalSirmans2007} portent sur des marchés américains et ne sont pas transposables tels quels au Québec.
435+\item La méthodologie publiée de l'indice S\&P CoreLogic Case--Shiller n'a pas pu être consultée (accès refusé le 31 octobre 2026) ; le chapitre ne cite donc pas ses filtres numériques et s'en tient aux articles de \textcite{caseShiller1987,caseShiller1989}.
436+\item Les modèles hybrides et les estimateurs bayésiens de ventes répétées sont mentionnés sans être mis en œuvre ; le \cref{ch:39} traite du rétrécissement bayésien dans un cadre plus général.
437+\end{itemize}
438+\end{limites}
439+
440+\begin{synthese}
441+\begin{itemize}
442+\item Un indice de prix immobiliers mesure la variation du prix d'un logement de caractéristiques constantes, $I_t = 100\,\E[P \mid \mathbf{x}, t]/\E[P \mid \mathbf{x}, 0]$ ; la médiane et la moyenne des prix observés confondent cette variation avec l'effet de composition, que l'\cref{eq:35:decomposition} isole exactement.
443+\item L'indice hédonique à indicatrices temporelles, $100\exp(\hat\tau_t)$, utilise toutes les ventes et suppose des prix implicites constants sur la fenêtre ; le chaînage les libère au prix d'une variance cumulée ; l'imputation géométrique (Laspeyres, Paasche, Fisher) coïncide avec les indicatrices lorsque les coefficients sont communs.
444+\item L'estimateur de ventes répétées de Bailey, Muth et Nourse régresse $\ln(P_{j2}/P_{j1})$ sur une matrice de $-1$ et de $+1$ ; il est sans biais si la qualité est constante et les reventes non sélectionnées, et il n'utilise que les immeubles vendus deux fois. Case et Shiller le pondèrent par l'inverse de la racine d'une variance qui croît avec l'intervalle, $2\sigma^2 + \delta^2\Delta$.
445+\item Les indices de ventes répétées sont exposés à la rénovation, à la dépréciation et à la sélection des reventes rapides ; ils sont révisés à chaque publication, et leurs dernières valeurs sont les moins fiables. Le filtre des reventes rapides arbitre entre biais et variance.
446+\item L'indice SPAR rapporte chaque prix à la valeur au rôle de l'immeuble ; il est précis quand le rôle est uniforme et s'accorde naturellement au système québécois d'évaluation foncière.
447+\item Au Canada, l'indice Teranet--Banque Nationale est le seul indice de revente à qualité constante (ventes répétées, onze régions métropolitaines dont Montréal et Québec) ; l'IPLN mesure les prix des maisons neuves à spécifications constantes et n'est pas révisé ; les médianes de l'APCIQ et de JLR sont des médianes, précieuses par leur granularité, trompeuses sur un trimestre.
448+\item Sur le cas 1, les indices à qualité constante ont des erreurs moyennes de 1,3 à 3 points, la médiane de 11 points ; l'ajustement de conditions du marché d'un même comparable varie de \dollars{26575} à \dollars{44760} selon l'indice retenu, soit de 5,2 \% à 8,8 \% de son prix : le choix de l'indice est une décision à documenter, non une formalité.
449+\end{itemize}
450+\end{synthese}
451+
452+\section*{Exercices}
453+\addcontentsline{toc}{section}{Exercices}
454+
455+\begin{exercice}[1]{Médiane et effet de composition}{ex:35:1}
456+Dans un secteur, le prix médian des unifamiliales passe de \dollars{450000} à \dollars{480000} en un an, tandis que la superficie habitable médiane des maisons vendues passe de \msq{140} à \msq{150}. Avec une élasticité du prix à la superficie de 0,539 (\cref{ch:18}), calculez la variation de la médiane, l'effet de composition attribuable à la superficie et la variation « pure » des prix qui en résulte. Quelles autres caractéristiques faudrait-il connaître pour compléter la correction ?
457+\end{exercice}
458+
459+\begin{exercice}[1]{Lire un indice à indicatrices temporelles}{ex:35:2}
460+Un modèle semi-logarithmique estimé sur quatre trimestres donne $\hat\tau_1 = 0{,}031$, $\hat\tau_2 = 0{,}058$ et $\hat\tau_3 = 0{,}102$ (2023T1 en référence), avec une erreur-type de 0,021 pour $\hat\tau_3$. Calculez l'indice de chaque trimestre par l'\cref{eq:35:td-indice}, la variation de prix entre le deuxième et le troisième trimestre et l'intervalle de confiance à 95 \% de l'indice du troisième trimestre. Pourquoi la variation entre deux trimestres non consécutifs se lit-elle directement sur la différence des $\hat\tau$ ?
461+\end{exercice}
462+
463+\begin{exercice}[2]{L'estimateur BMN à la main}{ex:35:3}
464+Quatre immeubles se sont vendus deux fois sur trois périodes (0, 1, 2) : A, \dollars{430000} en 0 et \dollars{462000} en 1 ; B, \dollars{470000} en 1 et \dollars{512000} en 2 ; C, \dollars{495000} en 0 et \dollars{540000} en 2 ; D, \dollars{436000} en 0 et \dollars{455000} en 1. Écrivez le vecteur $\mathbf{y}$ et la matrice $\mathbf{D}$ de l'\cref{eq:35:bmn}, calculez $\mathbf{D}\T\mathbf{D}$ et $\mathbf{D}\T\mathbf{y}$, résolvez l'\cref{eq:35:bmn-mco} et donnez l'indice des périodes 1 et 2. Vérifiez que les résidus sont orthogonaux aux colonnes de $\mathbf{D}$. Que se passerait-il si la paire B n'existait pas ?
465+\end{exercice}
466+
467+\begin{exercice}[2]{Variance et poids de Case--Shiller}{ex:35:4}
468+La deuxième étape de Case--Shiller donne $\hat a_0 = 0{,}0145$ et $\hat a_1 = 0{,}0012$ par trimestre. Calculez la variance et l'écart-type (en pourcentage) de $\ln(P_2/P_1)$ pour des intervalles de 1, 4, 8 et 12 trimestres, les poids $\hat w_j$ de l'\cref{eq:35:cs-poids} et les poids relatifs à une paire d'un trimestre. Déduisez-en les estimations de $\sigma$ et de $\delta$. Comment la pente changerait-elle si l'échantillon contenait de nombreuses reventes rénovées à court intervalle ?
469+\end{exercice}
470+
471+\begin{exercice}[2]{Un indice SPAR sur dix ventes}{ex:35:5}
472+Dix ventes d'un trimestre (prix en milliers de dollars : 512 ; 468,5 ; 601 ; 445 ; 530 ; 487,5 ; 575 ; 455 ; 498 ; 522) ont des valeurs au rôle de 470 ; 452 ; 545 ; 425 ; 480 ; 468 ; 510 ; 440 ; 455 ; 471. Le rapport des sommes du trimestre de base était de 1,045. Calculez le rapport arithmétique et le rapport géométrique prix/valeur du trimestre, puis l'indice SPAR arithmétique et géométrique \eqref{eq:35:spar}. Quelle statistique du \cref{ch:18} faudrait-il vérifier avant de se fier à cet indice ?
473+\end{exercice}
474+
475+\begin{exercice}[3]{L'ajustement de conditions du marché selon l'indice}{ex:35:6}
476+À partir du \cref{tab:35:indices}, calculez l'ajustement de conditions du marché du comparable G0404 (\dollars{506500}, 2025T2, évalué en 2026T3) selon l'indice hédonique, l'indice BMN, l'indice Case--Shiller et la médiane, puis selon la tendance linéaire du modèle de référence (0,364 \% par mois sur 15,2 mois). Classez les résultats, expliquez l'écart entre l'indice hédonique et la tendance par la précision du dernier trimestre, et rédigez le paragraphe du rapport d'évaluation qui justifie le taux retenu et en déclare l'incertitude.
477+\end{exercice}
478+
479+\begin{exercice}[3]{Révision et filtres : expérience sur le script}{ex:35:7}
480+À l'aide de \texttt{analysis/python/cases/ch35\_indices.py} : (a) faites passer la part des reventes rénovées (\texttt{PART\_FLIP}) de 0,15 à 0 puis à 0,30 et mesurez l'effet sur la hausse totale BMN et sur la pente $\hat a_1$ de Case--Shiller ; (b) pour chaque cas, appliquez les filtres à 12 et 18 mois et commentez l'arbitrage biais--variance ; (c) reproduisez les millésimes de la \cref{fig:35:revision} et expliquez pourquoi la révision est systématiquement à la baisse dans cette simulation. Rédigez une note de 200 mots sur ce qu'un producteur d'indice devrait publier pour que l'utilisateur puisse juger de ces effets.
481+\end{exercice}
482+
483+\begin{exercice}[3]{Dérivations : imputation, moyenne géométrique et estimateur arithmétique}{ex:35:8}
484+(a) Démontrez la \cref{prop:imputation-td} et expliquez pourquoi, avec des coefficients propres à chaque période, les indices de Laspeyres et de Paasche diffèrent. (b) Démontrez que, pour deux périodes et des paires reliant toutes la période 0 à la période 1, l'indice BMN est la moyenne géométrique des rapports $P_{j2}/P_{j1}$. (c) Deux maisons vendues \dollars{100000} et \dollars{200000} en période 0 se revendent \dollars{110000} et \dollars{230000} en période 1 : calculez l'indice géométrique, la moyenne arithmétique des rapports et l'indice arithmétique pondéré par la valeur de \textcite{shiller1991}, $\sum_j P_{j2}/\sum_j P_{j1}$, et expliquez à quelle question chacun répond.
485+\end{exercice}
486+
487+\section*{Pour aller plus loin}
488+\addcontentsline{toc}{section}{Pour aller plus loin}
489+
490+Le manuel international des indices de prix des logements \parencite{eurostat2013} est la référence d'ensemble : il compare les méthodes de stratification, hédoniques, de ventes répétées et SPAR, et son chapitre sur les méthodes hédoniques, dû à \textcite{dehaanDiewert2013}, détaille les constructions à indicatrices, chaînées et par imputation ; \textcite{hill2013} en donne une taxonomie critique et une évaluation empirique. Sur les ventes répétées, on lira dans l'ordre \textcite{baileyMuthNourse1963}, qui fondent l'estimateur, \textcite{caseShiller1987} et \textcite{caseShiller1989}, qui le pondèrent et en tirent les premières conclusions sur l'efficience du marché des maisons, \textcite{shiller1991} pour les versions arithmétiques, \textcite{goetzmann1992} pour la précision dans les petits échantillons et \textcite{silverstein2014} pour les révisions. \textcite{clappGiaccotto1992} comparent ventes répétées et valeurs au rôle, \textcite{bourassaHoesliSun2006} formalisent le SPAR, \textcite{quigley1995} et \textcite{englundQuigleyRedfearn1998} proposent et appliquent les modèles hybrides, et \textcite{contatLarson2024} renouvellent la construction par agrégats. Sur la dépréciation dans les indices de ventes répétées, \textcite{knightSirmans1996} et \textcite{hardingRosenthalSirmans2007}. \textcite{fisherGeltnerWebb1994} étendent la comparaison des méthodes aux immeubles commerciaux, et \textcite{shiller2015} replace les indices dans la question plus large des bulles et du partage du risque immobilier.
modified verification/calculations.csv +20 −2
@@ -477,7 +477,7 @@ CALC-28-006,28,"Classement : fixes 23 317,78 (29,7 %), variables 47 067,41 (60,0
477 CALC-28-007,28,"Groupes en % du RBE : taxes 7,08 ; assurances 6,76 ; énergie 4,37 ; entretien 6,97 ; conciergerie et services 10,11 ; honoraires 2,49 ; gestion 4,00 ; réserve 4,81",analysis/python/cases/ch28_depenses.py::classement,analysis/python/tests/test_ch28.py::test_valeurs_citees_cas3,voir description,conforme,verifie,2026-10-31 477 CALC-28-007,28,"Groupes en % du RBE : taxes 7,08 ; assurances 6,76 ; énergie 4,37 ; entretien 6,97 ; conciergerie et services 10,11 ; honoraires 2,49 ; gestion 4,00 ; réserve 4,81",analysis/python/cases/ch28_depenses.py::classement,analysis/python/tests/test_ch28.py::test_valeurs_citees_cas3,voir description,conforme,verifie,2026-10-31
478 CALC-28-008,28,"Historique 2023-2025 : totaux 66 992 / 70 438 / 70 693 ; +5,5 % ; TCAC 2,7 % ; variations par poste",analysis/python/cases/ch28_depenses.py::historique_cas3,analysis/python/tests/test_ch28.py::test_historique,66992 ; 70438 ; 70693 ; 0.055 ; 0.027,66992 ; 70438 ; 70693 ; 0.0552 ; 0.0273,verifie,2026-10-31 478 CALC-28-008,28,"Historique 2023-2025 : totaux 66 992 / 70 438 / 70 693 ; +5,5 % ; TCAC 2,7 % ; variations par poste",analysis/python/cases/ch28_depenses.py::historique_cas3,analysis/python/tests/test_ch28.py::test_historique,66992 ; 70438 ; 70693 ; 0.055 ; 0.027,66992 ; 70438 ; 70693 ; 0.0552 ; 0.0273,verifie,2026-10-31
479 CALC-28-009,28,"Écart de normalisation : dépenses déclarées 2025 70 693 vs normalisées 78 488,19 (+7 795,19) ; RNE historique 97 750,94 ; surévaluation 176 561 $ (8,7 %) à 4,415 %",analysis/python/cases/ch28_depenses.py,analysis/python/tests/test_ch28.py::test_historique,7795.19 ; 97750.94 ; 176561,7795.19 ; 97750.94 ; 176561,verifie,2026-10-31 479 CALC-28-009,28,"Écart de normalisation : dépenses déclarées 2025 70 693 vs normalisées 78 488,19 (+7 795,19) ; RNE historique 97 750,94 ; surévaluation 176 561 $ (8,7 %) à 4,415 %",analysis/python/cases/ch28_depenses.py,analysis/python/tests/test_ch28.py::test_historique,7795.19 ; 97750.94 ; 176561,7795.19 ; 97750.94 ; 176561,verifie,2026-10-31
480−CALC-28-010,28,"Normalisation alternative (moyenne indexée à 3 %) : assurances 12 437,31 vs 11 391,80 (+1 045,51) ; entretien −130,75 ; conciergerie +131,24 ; déneigement −266,55 ; honoraires −163,69 ; électricité −107,53",analysis/python/cases/ch28_depenses.py::normalisation_alternative,analysis/python/tests/test_ch28.py::test_normalisation_alternative,voir description,conforme (0,01 $),verifie 480+CALC-28-010,28,"Normalisation alternative (moyenne indexée à 3 %) : assurances 12 437,31 vs 11 391,80 (+1 045,51) ; entretien −130,75 ; conciergerie +131,24 ; déneigement −266,55 ; honoraires −163,69 ; électricité −107,53",analysis/python/cases/ch28_depenses.py::normalisation_alternative,analysis/python/tests/test_ch28.py::test_normalisation_alternative,voir description,"conforme (0,01 $)",verifie,2026-10-31
481 CALC-28-011,28,"Démonstration réserve/taux : R' = R + RR/V = 4,8127 % (+39,8 pb) ; V' = 1 869 136 ; ΔV = RR/R' = 168 367 ; erreur −8,26 % = −RR/(RNE+RR) ; inverse +9,0 %",analysis/python/cases/ch28_depenses.py::demonstration_reserve,analysis/python/tests/test_ch28.py::test_demonstration_reserve,identités + valeurs,identités (1e-6) + valeurs (1 $),verifie,2026-10-31 481 CALC-28-011,28,"Démonstration réserve/taux : R' = R + RR/V = 4,8127 % (+39,8 pb) ; V' = 1 869 136 ; ΔV = RR/R' = 168 367 ; erreur −8,26 % = −RR/(RNE+RR) ; inverse +9,0 %",analysis/python/cases/ch28_depenses.py::demonstration_reserve,analysis/python/tests/test_ch28.py::test_demonstration_reserve,identités + valeurs,identités (1e-6) + valeurs (1 $),verifie,2026-10-31
482 CALC-28-012,28,"Réserve par composantes (hypothèses pédagogiques) : linéaire 687,62 $/log. (8 251,43) ; fonds 3 % 580,71 $/log. (6 968,47) ; fonds 5 % 518,77 ; limite i→0 = linéaire",analysis/python/cases/ch28_depenses.py::reserve_par_composantes (traite.finance.facteur_fonds_amortissement),analysis/python/tests/test_ch28.py::test_reserve_composantes,687.62 ; 580.71 ; 518.77,687.62 ; 580.71 ; 518.77,verifie,2026-10-31 482 CALC-28-012,28,"Réserve par composantes (hypothèses pédagogiques) : linéaire 687,62 $/log. (8 251,43) ; fonds 3 % 580,71 $/log. (6 968,47) ; fonds 5 % 518,77 ; limite i→0 = linéaire",analysis/python/cases/ch28_depenses.py::reserve_par_composantes (traite.finance.facteur_fonds_amortissement),analysis/python/tests/test_ch28.py::test_reserve_composantes,687.62 ; 580.71 ; 518.77,687.62 ; 580.71 ; 518.77,verifie,2026-10-31
483 CALC-28-013,28,"Équivalence VA des remplacements perpétuels C/((1+i)^N − 1) et VA de la réserve au fonds d'amortissement RR/i (C = 26 400, N = 12, i = 3 %) : 62 006,64 ; RR = 1 860,20",analysis/python/cases/ch28_depenses.py::exercices (ex7),analysis/python/tests/test_ch28.py::test_equivalence_reserve_capex,62006.64 ; 1860.20,62006.64 ; 1860.20,verifie,2026-10-31 483 CALC-28-013,28,"Équivalence VA des remplacements perpétuels C/((1+i)^N − 1) et VA de la réserve au fonds d'amortissement RR/i (C = 26 400, N = 12, i = 3 %) : 62 006,64 ; RR = 1 860,20",analysis/python/cases/ch28_depenses.py::exercices (ex7),analysis/python/tests/test_ch28.py::test_equivalence_reserve_capex,62006.64 ; 1860.20,62006.64 ; 1860.20,verifie,2026-10-31
@@ -489,7 +489,7 @@ CALC-28-018,28,"Sensibilité taxe : compte indexé 5,3 % 11 194,44 (−1,1 %) ;
489 CALC-28-019,28,"Sensibilité gestion : 0 % +7,5 % ; 3 % +1,9 % ; 5 % −1,9 % ; 6 % −3,7 % ; 10 % −11,2 % ; ∂V/∂DE = −1/R = −22,65 ; élasticité RNE/dépenses −0,873 ; élasticité RNE/RBE 1,80 ; 25 pb ≡ 4 820,78 $ de dépenses",analysis/python/cases/ch28_depenses.py::sensibilites,analysis/python/tests/test_ch28.py::test_sensibilites,voir description,conforme,verifie,2026-10-31 489 CALC-28-019,28,"Sensibilité gestion : 0 % +7,5 % ; 3 % +1,9 % ; 5 % −1,9 % ; 6 % −3,7 % ; 10 % −11,2 % ; ∂V/∂DE = −1/R = −22,65 ; élasticité RNE/dépenses −0,873 ; élasticité RNE/RBE 1,80 ; 25 pb ≡ 4 820,78 $ de dépenses",analysis/python/cases/ch28_depenses.py::sensibilites,analysis/python/tests/test_ch28.py::test_sensibilites,voir description,conforme,verifie,2026-10-31
490 CALC-28-020,28,Pentes citées (légende fig. 28.5) : 12/R = 272 $ par $ de réserve/log. ; 2 200/R ≈ 49 830 $ par 10 pb de taxe ; RBE/100/R = 38 153 $ par point de gestion,analysis/python/cases/ch28_depenses.py,analysis/python/tests/test_ch28.py::test_sensibilites,272 ; 49830 ; 38153,271.8 ; 49830 ; 38153,verifie,2026-10-31 490 CALC-28-020,28,Pentes citées (légende fig. 28.5) : 12/R = 272 $ par $ de réserve/log. ; 2 200/R ≈ 49 830 $ par 10 pb de taxe ; RBE/100/R = 38 153 $ par point de gestion,analysis/python/cases/ch28_depenses.py,analysis/python/tests/test_ch28.py::test_sensibilites,272 ; 49830 ; 38153,271.8 ; 49830 ; 38153,verifie,2026-10-31
491 CALC-28-021,28,"Indexation IPC Québec (18-10-0004-01, v41691783) : 124,7 (2015-04) → 167,7 (2026-09) = +34,5 % ; barème SCHL 2015 indexé : entretien 672 $, salaires 403 $, réserve 134 $",analysis/python/cases/ch28_depenses.py::indexation_ipc,analysis/python/tests/test_ch28.py::test_indexation_ipc,1.3448 ; 672.4 ; 403.4 ; 134.5,1.3448 ; 672.4 ; 403.4 ; 134.5,verifie,2026-10-31 491 CALC-28-021,28,"Indexation IPC Québec (18-10-0004-01, v41691783) : 124,7 (2015-04) → 167,7 (2026-09) = +34,5 % ; barème SCHL 2015 indexé : entretien 672 $, salaires 403 $, réserve 134 $",analysis/python/cases/ch28_depenses.py::indexation_ipc,analysis/python/tests/test_ch28.py::test_indexation_ipc,1.3448 ; 672.4 ; 403.4 ; 134.5,1.3448 ; 672.4 ; 403.4 ; 134.5,verifie,2026-10-31
492−CALC-28-022,28,"Cas 2 : RBE 40 577,04 ; gestion imputée 1 623,08 ; dépenses hors réserve 16 743,08 ; totales 18 543,08 ; ratio 45,7 % ; RNE 22 033,96 ; 7 344,65 $/log. ; 84,10 $/m² ; fixes 52,7 % ; dépenses 6 181,03 $/log., 70,78 $/m²",analysis/python/cases/ch28_depenses.py::cas2_detail (traite.cas2.rne),analysis/python/tests/test_ch28.py::test_cas2,voir description,conforme (0,01 $),verifie 492+CALC-28-022,28,"Cas 2 : RBE 40 577,04 ; gestion imputée 1 623,08 ; dépenses hors réserve 16 743,08 ; totales 18 543,08 ; ratio 45,7 % ; RNE 22 033,96 ; 7 344,65 $/log. ; 84,10 $/m² ; fixes 52,7 % ; dépenses 6 181,03 $/log., 70,78 $/m²",analysis/python/cases/ch28_depenses.py::cas2_detail (traite.cas2.rne),analysis/python/tests/test_ch28.py::test_cas2,voir description,"conforme (0,01 $)",verifie,2026-10-31
493 CALC-28-023,28,"Cas 2 : vérification des taxes de Québec 2026 : 495 000 × 0,7464 % = 3 694,68 + 3 × (386 + 195) = 5 437,68 vs 5 900 déclaré (écart 462,32) ; scolaire (495 000 − 25 000) × 0,07899 % = 371,25 vs 470",analysis/python/cases/ch28_depenses.py::cas2_detail,analysis/python/tests/test_ch28.py::test_cas2,5437.68 ; 462.32 ; 371.25,5437.68 ; 462.32 ; 371.25,verifie,2026-10-31 493 CALC-28-023,28,"Cas 2 : vérification des taxes de Québec 2026 : 495 000 × 0,7464 % = 3 694,68 + 3 × (386 + 195) = 5 437,68 vs 5 900 déclaré (écart 462,32) ; scolaire (495 000 − 25 000) × 0,07899 % = 371,25 vs 470",analysis/python/cases/ch28_depenses.py::cas2_detail,analysis/python/tests/test_ch28.py::test_cas2,5437.68 ; 462.32 ; 371.25,5437.68 ; 462.32 ; 371.25,verifie,2026-10-31
494 CALC-28-024,28,"Exercices 28.1 à 28.7 (solutions/ch28.tex) : RBE 118 800, RNE 79 999,70, ratio 32,66 % ; écart 1 046,00 → 23 692 $ ; taxe 1 006,24 → −22 791 $ ; réserve 687,62 / 580,71 / 518,77 ; comparable 20 log. : 4,439 % / 4,768 %, V' 1 886 540 (−6,91 %) ; cas 2 sans gestion 23 657,04, +7,37 % ; CAPEX 25 586,15, V 1 457 975",analysis/python/cases/ch28_depenses.py::exercices,analysis/python/tests/test_ch28.py::test_exercices,voir description,conforme,verifie,2026-10-31 494 CALC-28-024,28,"Exercices 28.1 à 28.7 (solutions/ch28.tex) : RBE 118 800, RNE 79 999,70, ratio 32,66 % ; écart 1 046,00 → 23 692 $ ; taxe 1 006,24 → −22 791 $ ; réserve 687,62 / 580,71 / 518,77 ; comparable 20 log. : 4,439 % / 4,768 %, V' 1 886 540 (−6,91 %) ; cas 2 sans gestion 23 657,04, +7,37 % ; CAPEX 25 586,15, V 1 457 975",analysis/python/cases/ch28_depenses.py::exercices,analysis/python/tests/test_ch28.py::test_exercices,voir description,conforme,verifie,2026-10-31
495 CALC-33-001,33,Lecture SCHL île de Montréal : 6-19 unités inoccupation 2,2 % / loyer 2 ch. 1 177 $ / tous 1 162 $ ; 100+ : 5,1 % / 1 966 $ ; 2015+ : 5,6 % / 1 882 $,analysis/python/cases/ch33_six_logements.py,analysis/python/tests/test_ch33.py::test_schl_lecture_coherente_avec_notes,"2,2 ; 1177 ; 1162 ; 5,1 ; 1966 ; 5,6 ; 1882" 495 CALC-33-001,33,Lecture SCHL île de Montréal : 6-19 unités inoccupation 2,2 % / loyer 2 ch. 1 177 $ / tous 1 162 $ ; 100+ : 5,1 % / 1 966 $ ; 2015+ : 5,6 % / 1 882 $,analysis/python/cases/ch33_six_logements.py,analysis/python/tests/test_ch33.py::test_schl_lecture_coherente_avec_notes,"2,2 ; 1177 ; 1162 ; 5,1 ; 1966 ; 5,6 ; 1882"
@@ -516,3 +516,21 @@ CALC-33-021,33,Ex. 33.4 : écart 1 591 507 $ (+23,7 %),analysis/python/cases/ch3
516 CALC-33-022,33,Ex. 33.5 : ΔRBP 5 760 ; ΔRNE 5 319,48 ; ΔV 97 406 (1,45 %),analysis/python/cases/ch33_six_logements.py,test_ch33.py::test_exercices,"5760 ; 5319,48 ; 97406 ; 0,0145",idem 516 CALC-33-022,33,Ex. 33.5 : ΔRBP 5 760 ; ΔRNE 5 319,48 ; ΔV 97 406 (1,45 %),analysis/python/cases/ch33_six_logements.py,test_ch33.py::test_exercices,"5760 ; 5319,48 ; 97406 ; 0,0145",idem
517 CALC-33-023,33,Ex. 33.6 : 24 log. état 3 45 ans : Montréal 4,52 % ; Québec 5,74 ; Gatineau 5,41 ; Sherbrooke 5,67 ; Trois-Rivières 6,34 ; Saguenay 6,01 ; IC Gatineau [14 517 CALC-33-023,33,Ex. 33.6 : 24 log. état 3 45 ans : Montréal 4,52 % ; Québec 5,74 ; Gatineau 5,41 ; Sherbrooke 5,67 ; Trois-Rivières 6,34 ; Saguenay 6,01 ; IC Gatineau [14
518 CALC-33-024,33,Ex. 33.7 : coût 2026 6 988 800 ; total 8 088 800 (337 033 $/log.) ; RBP 568 800 ; RBE 536 947 ; RNE 365 124 ; V 8 113 869 (4,50 %) et 7 686 823 (4,75 %) ; taux implicite 4,51 %,analysis/python/cases/ch33_six_logements.py,test_ch33.py::test_exercices,"6988800 ; 8088800 ; 365124 ; 8113869 ; 7686823 ; 0,0451" 518 CALC-33-024,33,Ex. 33.7 : coût 2026 6 988 800 ; total 8 088 800 (337 033 $/log.) ; RBP 568 800 ; RBE 536 947 ; RNE 365 124 ; V 8 113 869 (4,50 %) et 7 686 823 (4,75 %) ; taux implicite 4,51 %,analysis/python/cases/ch33_six_logements.py,test_ch33.py::test_exercices,"6988800 ; 8088800 ; 365124 ; 8113869 ; 7686823 ; 0,0451"
519+CALC-34-01,34,"Pourcentage de base 2026 selon l'art. 3.1 à partir des moyennes annuelles de l'IPC Québec (A 161,033 ; B 157,158 ; C 153,583 ; D 146,983) vs 3,1 % publié par le TAL",analysis/python/cases/ch34_tal.py,analysis/python/tests/test_ch34.py::test_pourcentage_base_reproduit_tal,"3,1 %","3,095 %",verifie,2026-10-31
520+CALC-34-02,34,"Pourcentage de base 2025 selon l'art. 3.1 (A 157,158 ; B 153,583 ; C 146,983 ; D 137,792) vs 4,5 % publié",analysis/python/cases/ch34_tal.py,analysis/python/tests/test_ch34.py::test_pourcentage_base_reproduit_tal,"4,5 %","4,496 %",verifie,2026-10-31
521+CALC-34-03,34,"Historique contrefactuel 2006-2026 de la formule : plancher à zéro, lissage (écart-type formule < écart-type inflation), inflation 2023 = 6,67 %",analysis/python/cases/ch34_tal.py,analysis/python/tests/test_ch34.py::test_historique_coherent,"formule 2023 : 3,75 % ; 2024 : 4,98 %","0,0375 ; 0,0498",verifie,2026-10-31
522+CALC-34-04,34,"Règles des art. 4 (taux net = max(variation − |base|, 0) si variation > 0), 5 (part = 12 L / revenus) et 15.1 (arrondi demi vers le haut)",analysis/python/cases/ch34_tal.py,analysis/python/tests/test_ch34.py::test_taux_net_art_4 ; ::test_part_attribuable ; ::test_arrondi_art_15_1,"net(8 %, 3,1 %) = 4,9 % ; 750,50 → 751 ; 750,49 → 750","0,049 ; 751 ; 750",verifie,2026-10-31
523+CALC-34-05,34,"Fixation nouvelle méthode, cas de contrôle (loyer 1 000 $, part 10 %, taxes +10 %, scolaire +2 %, assurances +10 %, immobilisations 60 000 $, services 1 200 $/an, base 3 %) : composantes 30 + 14 + 7 + 25 + 10 = 86 $",analysis/python/cases/ch34_tal.py,analysis/python/tests/test_ch34.py::test_fixation_nouvelle_composantes,86 $ ; nouveau loyer 1 086 $,86 ; 1086,verifie,2026-10-31
524+CALC-34-06,34,"Exemple logement 103 (728 $ ; marché 1 047 $ ; part 4,995 % sur revenus 174 900 $), variante A : base 22,57 $, taxes/assurances nulles (−4,2 % ; +1,9 % ; −12,3 %), nouveau loyer 751 $ (+23 $, 3,16 %)",analysis/python/cases/ch34_tal.py,analysis/python/tests/test_ch34.py::test_exemple_logement_103_valeurs_citees,751 $,"751 ; 22,568 $",verifie,2026-10-31
525+CALC-34-07,34,"Exemple logement 103, variante B (taxes +8 % → net 4,9 % → 2,17 $ ; assurances +10 % → net 6,9 % → 3,62 $ ; immobilisations 85 000 $ → 17,69 $) : total 46,05 $, nouveau loyer 774 $ (6,32 %)",analysis/python/cases/ch34_tal.py,analysis/python/tests/test_ch34.py::test_exemple_logement_103_valeurs_citees,774 $,"774 ; 46,05 $",verifie,2026-10-31
526+CALC-34-08,34,"Reconstitution pédagogique de l'ancienne méthode (pourcentages 2026 des avis avant 2026) pour le logement 103 : 755 $ (A) et 786 $ (B) ; années de rattrapage à 3,1 %/an marché figé : 11,9",analysis/python/cases/ch34_tal.py,analysis/python/tests/test_ch34.py::test_exemple_logement_103_valeurs_citees,"755 ; 786 ; 11,9","755 ; 786 ; 11,9",verifie,2026-10-31
527+CALC-34-09,34,"Forme fermée E[q_t] = q0 a^t + ρ(1−a^t)/(1−a) = récursion = Monte-Carlo (20 000 trajectoires, écart max < 0,006) ; q_10 = 0,9249 pour q0 0,695, ρ 15 %",analysis/python/cases/ch34_tal.py,analysis/python/tests/test_ch34.py::test_forme_fermee_egale_recursion_et_simulation,"0,9249",0.9249,verifie,2026-10-31
528+CALC-34-10,34,"Limite q_∞ = ρ/(1−(1−ρ)δ) : 0,9316 / 0,9786 / 0,9885 pour ρ = 5/15/25 % (g_c 3,1 %, g_m 3,5 %) ; = 1 si g_c = g_m ; convergence de la forme fermée",analysis/python/cases/ch34_tal.py,analysis/python/tests/test_ch34.py::test_limite_et_monotonie,"0,9316 ; 0,9786 ; 0,9885",0.93159 ; 0.97857 ; 0.98854,verifie,2026-10-31
529+CALC-34-11,34,"Cas 3 : rapport de loyer de l'immeuble 0,8898 (2026) → 0,9904 (déterministe) / 0,9618 (espérance) / 0,8560 (sans relocation) en 2036",analysis/python/cases/ch34_tal.py,analysis/python/tests/test_ch34.py::test_trajectoire_cas3,"0,9904 ; 0,9618 ; 0,8560",0.9904 ; 0.9618 ; 0.8560,verifie,2026-10-31
530+CALC-34-12,34,projection_scenario (paramètres du dossier) reproduit traite.cas3.projection_dcf colonne par colonne (tol. 1e-6) ; 2 logements relocalisés par an,analysis/python/cases/ch34_tal.py,analysis/python/tests/test_ch34.py::test_projection_locale_reproduit_module_commun,identité,identité,verifie,2026-10-31
531+CALC-34-13,34,"Valeur de base 2 208 566 $ et scénario « encadrement serré » du ch. 30 (base 2 %, rotation 8 %, marché 3 %) 1 912 046 $ reproduits",analysis/python/cases/ch34_tal.py,analysis/python/tests/test_ch34.py::test_valeur_base_et_scenario_ch30,2 208 566 ; 1 912 046,2208565.91 ; 1912046.35,verifie,2026-10-31
532+CALC-34-14,34,"Grille rotation (5/15/25 %) × base (2/3,1/4 %) : 1 974 822 à 2 340 208 $ ; monotone croissante en rotation et en base ; cellules citées",analysis/python/cases/ch34_tal.py,analysis/python/tests/test_ch34.py::test_grille_monotone_et_valeurs_citees,9 cellules du tableau 34.7,1974822 ; 2140916 ; 2284775 ; 2118624 ; 2208566 ; 2326053 ; 2174597 ; 2231655 ; 2340208,verifie,2026-10-31
533+CALC-34-15,34,"Scénarios complémentaires : indexation seule 1 756 414 ; φ = 0,5 2 056 076 ; encadrement renforcé 1 701 507 ; récupération 5 % CAPEX 2 289 503 ; espérance 2 091 629 ; tout au marché 2 267 398 ; VA de l'écart 56 008 (base) / 184 956 (indexation seule)",analysis/python/cases/ch34_tal.py,analysis/python/tests/test_ch34.py::test_scenarios_complementaires,valeurs du tableau 34.8,toutes à ±1 $,verifie,2026-10-31
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+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+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
modified verification/chapters/ch28-calculations.csv +2 −2
@@ -8,7 +8,7 @@ CALC-28-006,28,"Classement : fixes 23 317,78 (29,7 %), variables 47 067,41 (60,0
8 CALC-28-007,28,"Groupes en % du RBE : taxes 7,08 ; assurances 6,76 ; énergie 4,37 ; entretien 6,97 ; conciergerie et services 10,11 ; honoraires 2,49 ; gestion 4,00 ; réserve 4,81",analysis/python/cases/ch28_depenses.py::classement,analysis/python/tests/test_ch28.py::test_valeurs_citees_cas3,voir description,conforme,verifie,2026-10-31 8 CALC-28-007,28,"Groupes en % du RBE : taxes 7,08 ; assurances 6,76 ; énergie 4,37 ; entretien 6,97 ; conciergerie et services 10,11 ; honoraires 2,49 ; gestion 4,00 ; réserve 4,81",analysis/python/cases/ch28_depenses.py::classement,analysis/python/tests/test_ch28.py::test_valeurs_citees_cas3,voir description,conforme,verifie,2026-10-31
9 CALC-28-008,28,"Historique 2023-2025 : totaux 66 992 / 70 438 / 70 693 ; +5,5 % ; TCAC 2,7 % ; variations par poste",analysis/python/cases/ch28_depenses.py::historique_cas3,analysis/python/tests/test_ch28.py::test_historique,66992 ; 70438 ; 70693 ; 0.055 ; 0.027,66992 ; 70438 ; 70693 ; 0.0552 ; 0.0273,verifie,2026-10-31 9 CALC-28-008,28,"Historique 2023-2025 : totaux 66 992 / 70 438 / 70 693 ; +5,5 % ; TCAC 2,7 % ; variations par poste",analysis/python/cases/ch28_depenses.py::historique_cas3,analysis/python/tests/test_ch28.py::test_historique,66992 ; 70438 ; 70693 ; 0.055 ; 0.027,66992 ; 70438 ; 70693 ; 0.0552 ; 0.0273,verifie,2026-10-31
10 CALC-28-009,28,"Écart de normalisation : dépenses déclarées 2025 70 693 vs normalisées 78 488,19 (+7 795,19) ; RNE historique 97 750,94 ; surévaluation 176 561 $ (8,7 %) à 4,415 %",analysis/python/cases/ch28_depenses.py,analysis/python/tests/test_ch28.py::test_historique,7795.19 ; 97750.94 ; 176561,7795.19 ; 97750.94 ; 176561,verifie,2026-10-31 10 CALC-28-009,28,"Écart de normalisation : dépenses déclarées 2025 70 693 vs normalisées 78 488,19 (+7 795,19) ; RNE historique 97 750,94 ; surévaluation 176 561 $ (8,7 %) à 4,415 %",analysis/python/cases/ch28_depenses.py,analysis/python/tests/test_ch28.py::test_historique,7795.19 ; 97750.94 ; 176561,7795.19 ; 97750.94 ; 176561,verifie,2026-10-31
11−CALC-28-010,28,"Normalisation alternative (moyenne indexée à 3 %) : assurances 12 437,31 vs 11 391,80 (+1 045,51) ; entretien −130,75 ; conciergerie +131,24 ; déneigement −266,55 ; honoraires −163,69 ; électricité −107,53",analysis/python/cases/ch28_depenses.py::normalisation_alternative,analysis/python/tests/test_ch28.py::test_normalisation_alternative,voir description,conforme (0,01 $),verifie,2026-10-31 11+CALC-28-010,28,"Normalisation alternative (moyenne indexée à 3 %) : assurances 12 437,31 vs 11 391,80 (+1 045,51) ; entretien −130,75 ; conciergerie +131,24 ; déneigement −266,55 ; honoraires −163,69 ; électricité −107,53",analysis/python/cases/ch28_depenses.py::normalisation_alternative,analysis/python/tests/test_ch28.py::test_normalisation_alternative,voir description,"conforme (0,01 $)",verifie,2026-10-31
12 CALC-28-011,28,"Démonstration réserve/taux : R' = R + RR/V = 4,8127 % (+39,8 pb) ; V' = 1 869 136 ; ΔV = RR/R' = 168 367 ; erreur −8,26 % = −RR/(RNE+RR) ; inverse +9,0 %",analysis/python/cases/ch28_depenses.py::demonstration_reserve,analysis/python/tests/test_ch28.py::test_demonstration_reserve,identités + valeurs,identités (1e-6) + valeurs (1 $),verifie,2026-10-31 12 CALC-28-011,28,"Démonstration réserve/taux : R' = R + RR/V = 4,8127 % (+39,8 pb) ; V' = 1 869 136 ; ΔV = RR/R' = 168 367 ; erreur −8,26 % = −RR/(RNE+RR) ; inverse +9,0 %",analysis/python/cases/ch28_depenses.py::demonstration_reserve,analysis/python/tests/test_ch28.py::test_demonstration_reserve,identités + valeurs,identités (1e-6) + valeurs (1 $),verifie,2026-10-31
13 CALC-28-012,28,"Réserve par composantes (hypothèses pédagogiques) : linéaire 687,62 $/log. (8 251,43) ; fonds 3 % 580,71 $/log. (6 968,47) ; fonds 5 % 518,77 ; limite i→0 = linéaire",analysis/python/cases/ch28_depenses.py::reserve_par_composantes (traite.finance.facteur_fonds_amortissement),analysis/python/tests/test_ch28.py::test_reserve_composantes,687.62 ; 580.71 ; 518.77,687.62 ; 580.71 ; 518.77,verifie,2026-10-31 13 CALC-28-012,28,"Réserve par composantes (hypothèses pédagogiques) : linéaire 687,62 $/log. (8 251,43) ; fonds 3 % 580,71 $/log. (6 968,47) ; fonds 5 % 518,77 ; limite i→0 = linéaire",analysis/python/cases/ch28_depenses.py::reserve_par_composantes (traite.finance.facteur_fonds_amortissement),analysis/python/tests/test_ch28.py::test_reserve_composantes,687.62 ; 580.71 ; 518.77,687.62 ; 580.71 ; 518.77,verifie,2026-10-31
14 CALC-28-013,28,"Équivalence VA des remplacements perpétuels C/((1+i)^N − 1) et VA de la réserve au fonds d'amortissement RR/i (C = 26 400, N = 12, i = 3 %) : 62 006,64 ; RR = 1 860,20",analysis/python/cases/ch28_depenses.py::exercices (ex7),analysis/python/tests/test_ch28.py::test_equivalence_reserve_capex,62006.64 ; 1860.20,62006.64 ; 1860.20,verifie,2026-10-31 14 CALC-28-013,28,"Équivalence VA des remplacements perpétuels C/((1+i)^N − 1) et VA de la réserve au fonds d'amortissement RR/i (C = 26 400, N = 12, i = 3 %) : 62 006,64 ; RR = 1 860,20",analysis/python/cases/ch28_depenses.py::exercices (ex7),analysis/python/tests/test_ch28.py::test_equivalence_reserve_capex,62006.64 ; 1860.20,62006.64 ; 1860.20,verifie,2026-10-31
@@ -20,6 +20,6 @@ CALC-28-018,28,"Sensibilité taxe : compte indexé 5,3 % 11 194,44 (−1,1 %) ;
20 CALC-28-019,28,"Sensibilité gestion : 0 % +7,5 % ; 3 % +1,9 % ; 5 % −1,9 % ; 6 % −3,7 % ; 10 % −11,2 % ; ∂V/∂DE = −1/R = −22,65 ; élasticité RNE/dépenses −0,873 ; élasticité RNE/RBE 1,80 ; 25 pb ≡ 4 820,78 $ de dépenses",analysis/python/cases/ch28_depenses.py::sensibilites,analysis/python/tests/test_ch28.py::test_sensibilites,voir description,conforme,verifie,2026-10-31 20 CALC-28-019,28,"Sensibilité gestion : 0 % +7,5 % ; 3 % +1,9 % ; 5 % −1,9 % ; 6 % −3,7 % ; 10 % −11,2 % ; ∂V/∂DE = −1/R = −22,65 ; élasticité RNE/dépenses −0,873 ; élasticité RNE/RBE 1,80 ; 25 pb ≡ 4 820,78 $ de dépenses",analysis/python/cases/ch28_depenses.py::sensibilites,analysis/python/tests/test_ch28.py::test_sensibilites,voir description,conforme,verifie,2026-10-31
21 CALC-28-020,28,"Pentes citées (légende fig. 28.5) : 12/R = 272 $ par $ de réserve/log. ; 2 200/R ≈ 49 830 $ par 10 pb de taxe ; RBE/100/R = 38 153 $ par point de gestion",analysis/python/cases/ch28_depenses.py,analysis/python/tests/test_ch28.py::test_sensibilites,272 ; 49830 ; 38153,271.8 ; 49830 ; 38153,verifie,2026-10-31 21 CALC-28-020,28,"Pentes citées (légende fig. 28.5) : 12/R = 272 $ par $ de réserve/log. ; 2 200/R ≈ 49 830 $ par 10 pb de taxe ; RBE/100/R = 38 153 $ par point de gestion",analysis/python/cases/ch28_depenses.py,analysis/python/tests/test_ch28.py::test_sensibilites,272 ; 49830 ; 38153,271.8 ; 49830 ; 38153,verifie,2026-10-31
22 CALC-28-021,28,"Indexation IPC Québec (18-10-0004-01, v41691783) : 124,7 (2015-04) → 167,7 (2026-09) = +34,5 % ; barème SCHL 2015 indexé : entretien 672 $, salaires 403 $, réserve 134 $",analysis/python/cases/ch28_depenses.py::indexation_ipc,analysis/python/tests/test_ch28.py::test_indexation_ipc,1.3448 ; 672.4 ; 403.4 ; 134.5,1.3448 ; 672.4 ; 403.4 ; 134.5,verifie,2026-10-31 22 CALC-28-021,28,"Indexation IPC Québec (18-10-0004-01, v41691783) : 124,7 (2015-04) → 167,7 (2026-09) = +34,5 % ; barème SCHL 2015 indexé : entretien 672 $, salaires 403 $, réserve 134 $",analysis/python/cases/ch28_depenses.py::indexation_ipc,analysis/python/tests/test_ch28.py::test_indexation_ipc,1.3448 ; 672.4 ; 403.4 ; 134.5,1.3448 ; 672.4 ; 403.4 ; 134.5,verifie,2026-10-31
23−CALC-28-022,28,"Cas 2 : RBE 40 577,04 ; gestion imputée 1 623,08 ; dépenses hors réserve 16 743,08 ; totales 18 543,08 ; ratio 45,7 % ; RNE 22 033,96 ; 7 344,65 $/log. ; 84,10 $/m² ; fixes 52,7 % ; dépenses 6 181,03 $/log., 70,78 $/m²",analysis/python/cases/ch28_depenses.py::cas2_detail (traite.cas2.rne),analysis/python/tests/test_ch28.py::test_cas2,voir description,conforme (0,01 $),verifie,2026-10-31 23+CALC-28-022,28,"Cas 2 : RBE 40 577,04 ; gestion imputée 1 623,08 ; dépenses hors réserve 16 743,08 ; totales 18 543,08 ; ratio 45,7 % ; RNE 22 033,96 ; 7 344,65 $/log. ; 84,10 $/m² ; fixes 52,7 % ; dépenses 6 181,03 $/log., 70,78 $/m²",analysis/python/cases/ch28_depenses.py::cas2_detail (traite.cas2.rne),analysis/python/tests/test_ch28.py::test_cas2,voir description,"conforme (0,01 $)",verifie,2026-10-31
24 CALC-28-023,28,"Cas 2 : vérification des taxes de Québec 2026 : 495 000 × 0,7464 % = 3 694,68 + 3 × (386 + 195) = 5 437,68 vs 5 900 déclaré (écart 462,32) ; scolaire (495 000 − 25 000) × 0,07899 % = 371,25 vs 470",analysis/python/cases/ch28_depenses.py::cas2_detail,analysis/python/tests/test_ch28.py::test_cas2,5437.68 ; 462.32 ; 371.25,5437.68 ; 462.32 ; 371.25,verifie,2026-10-31 24 CALC-28-023,28,"Cas 2 : vérification des taxes de Québec 2026 : 495 000 × 0,7464 % = 3 694,68 + 3 × (386 + 195) = 5 437,68 vs 5 900 déclaré (écart 462,32) ; scolaire (495 000 − 25 000) × 0,07899 % = 371,25 vs 470",analysis/python/cases/ch28_depenses.py::cas2_detail,analysis/python/tests/test_ch28.py::test_cas2,5437.68 ; 462.32 ; 371.25,5437.68 ; 462.32 ; 371.25,verifie,2026-10-31
25 CALC-28-024,28,"Exercices 28.1 à 28.7 (solutions/ch28.tex) : RBE 118 800, RNE 79 999,70, ratio 32,66 % ; écart 1 046,00 → 23 692 $ ; taxe 1 006,24 → −22 791 $ ; réserve 687,62 / 580,71 / 518,77 ; comparable 20 log. : 4,439 % / 4,768 %, V' 1 886 540 (−6,91 %) ; cas 2 sans gestion 23 657,04, +7,37 % ; CAPEX 25 586,15, V 1 457 975",analysis/python/cases/ch28_depenses.py::exercices,analysis/python/tests/test_ch28.py::test_exercices,voir description,conforme,verifie,2026-10-31 25 CALC-28-024,28,"Exercices 28.1 à 28.7 (solutions/ch28.tex) : RBE 118 800, RNE 79 999,70, ratio 32,66 % ; écart 1 046,00 → 23 692 $ ; taxe 1 006,24 → −22 791 $ ; réserve 687,62 / 580,71 / 518,77 ; comparable 20 log. : 4,439 % / 4,768 %, V' 1 886 540 (−6,91 %) ; cas 2 sans gestion 23 657,04, +7,37 % ; CAPEX 25 586,15, V 1 457 975",analysis/python/cases/ch28_depenses.py::exercices,analysis/python/tests/test_ch28.py::test_exercices,voir description,conforme,verifie,2026-10-31
modified verification/chapters/ch28-claims.csv +3 −3
@@ -1,7 +1,7 @@
1 id,chapitre,affirmation,source_id,verification,date,statut,notes 1 id,chapitre,affirmation,source_id,verification,date,statut,notes
2 CLM-28-001,28,"Barème SCHL « Frais d'exploitation — immeubles locatifs au Québec » (avril 2015) : entretien 500 $/log. ; salaires 125 $ (5-6 log.), 170 $ (7-11), 300 $ (12+) ; gestion-administration 3 % (5-6), 3-4 % (7-11), 5 % (12+) du revenu brut ; réserve 100 $/log. (cuisinière-réfrigérateur) ; taxes, assurance, énergie au coût réel ; « à titre indicatif »",SRC-E-047,sources/notes-E.md §ch. 28 ; data/synthetic/calibration.json,2026-10-31,verifie,"Document daté (2015), hébergé par un tiers (PMML) ; aucune mise à jour publique trouvée" 2 CLM-28-001,28,"Barème SCHL « Frais d'exploitation — immeubles locatifs au Québec » (avril 2015) : entretien 500 $/log. ; salaires 125 $ (5-6 log.), 170 $ (7-11), 300 $ (12+) ; gestion-administration 3 % (5-6), 3-4 % (7-11), 5 % (12+) du revenu brut ; réserve 100 $/log. (cuisinière-réfrigérateur) ; taxes, assurance, énergie au coût réel ; « à titre indicatif »",SRC-E-047,sources/notes-E.md §ch. 28 ; data/synthetic/calibration.json,2026-10-31,verifie,"Document daté (2015), hébergé par un tiers (PMML) ; aucune mise à jour publique trouvée"
3 CLM-28-002,28,"TAL, ancienne méthode : frais de gestion forfaitaires 5 % des revenus, frais réels admis jusqu'à 10 % ; formulaire RN 2026-2027 détaille taxes, assurances, énergie, entretien, services, gestion (si > 5 %), dépenses d'immobilisation 2025",SRC-E-049;SRC-A-009;SRC-A-010,sources/notes-A.md §6.2 ; notes-E §ch. 28,2026-10-31,verifie,"Règle de fixation de loyer, pas une norme d'évaluation" 3 CLM-28-002,28,"TAL, ancienne méthode : frais de gestion forfaitaires 5 % des revenus, frais réels admis jusqu'à 10 % ; formulaire RN 2026-2027 détaille taxes, assurances, énergie, entretien, services, gestion (si > 5 %), dépenses d'immobilisation 2025",SRC-E-049;SRC-A-009;SRC-A-010,sources/notes-A.md §6.2 ; notes-E §ch. 28,2026-10-31,verifie,"Règle de fixation de loyer, pas une norme d'évaluation"
4−CLM-28-003,28,"Règlement sur les critères de fixation de loyer (T-15.01, r. 2) modifié par D. 1455-2025, en vigueur le 1er janvier 2026 : six critères (art. 3) ; pourcentage de base = moyenne de trois variations annuelles de l'IPC Québec (art. 3.1) ; critères 2° à 6° selon la part attribuable au logement (art. 5) ; 5 % des dépenses d'immobilisation ; transition pour les avis donnés avant le 1er janvier 2026",SRC-A-003,sources/notes-A.md §6.2 (texte des art. 3, 3.1, 4, 5 relu dans les notes),2026-10-31,verifie, 4+CLM-28-003,28,"Règlement sur les critères de fixation de loyer (T-15.01, r. 2) modifié par D. 1455-2025, en vigueur le 1er janvier 2026 : six critères (art. 3) ; pourcentage de base = moyenne de trois variations annuelles de l'IPC Québec (art. 3.1) ; critères 2° à 6° selon la part attribuable au logement (art. 5) ; 5 % des dépenses d'immobilisation ; transition pour les avis donnés avant le 1er janvier 2026",SRC-A-003,"sources/notes-A.md §6.2 (texte des art. 3, 3.1, 4, 5 relu dans les notes)",2026-10-31,verifie,
5 CLM-28-004,28,"TAL pourcentages 2026 : taux de base 3,1 % (avis dès le 1er janvier 2026) ; ancienne méthode 2025/2026 : gaz −5,8 / +13,7 ; électricité 2,9 / 2,9 ; entretien 6,9 / 5,7 ; gestion 8,2 / 7,2 ; dépenses d'immobilisation amorties au taux CPG 5 ans + 1 % (ancienne méthode)",SRC-A-005;SRC-A-010,sources/notes-A.md §6.2 ; notes-E §ch. 28,2026-10-31,verifie, 5 CLM-28-004,28,"TAL pourcentages 2026 : taux de base 3,1 % (avis dès le 1er janvier 2026) ; ancienne méthode 2025/2026 : gaz −5,8 / +13,7 ; électricité 2,9 / 2,9 ; entretien 6,9 / 5,7 ; gestion 8,2 / 7,2 ; dépenses d'immobilisation amorties au taux CPG 5 ans + 1 % (ancienne méthode)",SRC-A-005;SRC-A-010,sources/notes-A.md §6.2 ; notes-E §ch. 28,2026-10-31,verifie,
6 CLM-28-005,28,"C.c.Q. art. 1947 (demande de fixation dans le mois du refus) et 1953 (fixation selon les normes réglementaires)",SRC-A-001,sources/notes-A.md §bail (art. 1947 et 1953 résumés),2026-10-31,verifie,"Texte intégral non relu par le rédacteur du ch. 28 ; résumé de notes-A" 6 CLM-28-005,28,"C.c.Q. art. 1947 (demande de fixation dans le mois du refus) et 1953 (fixation selon les normes réglementaires)",SRC-A-001,sources/notes-A.md §bail (art. 1947 et 1953 résumés),2026-10-31,verifie,"Texte intégral non relu par le rédacteur du ch. 28 ; résumé de notes-A"
7 CLM-28-006,28,"Taxe scolaire : LIP art. 303-303.6, taux unique, assiette = évaluation uniformisée ajustée excédant 25 000 $ ; 0,07899 $/100 $ pour 2026-2027 (communiqué CSS) ; 0,08423 $ pour 2025-2026",SRC-B-035;SRC-B-037;SRC-E-026,notes-B.md §9.9 ; ch09 (CLM-09-013),2026-10-31,partiellement_verifie,"Avis officiel 2026-2027 ((2026) 158 G.O. 1, 520) non consulté ; mécanisme d'ajustement non modélisé" 7 CLM-28-006,28,"Taxe scolaire : LIP art. 303-303.6, taux unique, assiette = évaluation uniformisée ajustée excédant 25 000 $ ; 0,07899 $/100 $ pour 2026-2027 (communiqué CSS) ; 0,08423 $ pour 2025-2026",SRC-B-035;SRC-B-037;SRC-E-026,notes-B.md §9.9 ; ch09 (CLM-09-013),2026-10-31,partiellement_verifie,"Avis officiel 2026-2027 ((2026) 158 G.O. 1, 520) non consulté ; mécanisme d'ajustement non modélisé"
@@ -11,10 +11,10 @@ CLM-28-009,28,"Gatineau 2026 (règl. 994-2025) : taux de base 5,224 $/1 000 $ ;
11 CLM-28-010,28,"Prime moyenne d'assurance habitation au Québec : 1 103 $ en 2024, +9 % sur un an, +63 % en dix ans (BAC via Infoassurance) ; porte sur les ménages, aucune statistique pour les immeubles locatifs",SRC-E-039,notes-E §ch. 28,2026-10-31,partiellement_verifie,"Industrie ; la page attribue le même chiffre à 2023 et 2024 (incohérence signalée dans notes-E)" 11 CLM-28-010,28,"Prime moyenne d'assurance habitation au Québec : 1 103 $ en 2024, +9 % sur un an, +63 % en dix ans (BAC via Infoassurance) ; porte sur les ménages, aucune statistique pour les immeubles locatifs",SRC-E-039,notes-E §ch. 28,2026-10-31,partiellement_verifie,"Industrie ; la page attribue le même chiffre à 2023 et 2024 (incohérence signalée dans notes-E)"
12 CLM-28-011,28,"Hydro-Québec tarif D au 2026-04-01 : 46,154 ¢/jour ; 7,065 ¢/kWh jusqu'à 40 kWh/jour ; 11,142 ¢/kWh au-delà",SRC-E-037,notes-E §ch. 28 ; document tarifaire,2026-10-31,verifie, 12 CLM-28-011,28,"Hydro-Québec tarif D au 2026-04-01 : 46,154 ¢/jour ; 7,065 ¢/kWh jusqu'à 40 kWh/jour ; 11,142 ¢/kWh au-delà",SRC-E-037,notes-E §ch. 28 ; document tarifaire,2026-10-31,verifie,
13 CLM-28-012,28,"Régie de l'énergie D-2026-033 : hausse clientèle domestique 3,0 % (2026), 3,0 % (2027), 2,6 % (2028)",SRC-E-038,notes-E §ch. 28 ; communiqué du 2026-03-16,2026-10-31,verifie, 13 CLM-28-012,28,"Régie de l'énergie D-2026-033 : hausse clientèle domestique 3,0 % (2026), 3,0 % (2027), 2,6 % (2028)",SRC-E-038,notes-E §ch. 28 ; communiqué du 2026-03-16,2026-10-31,verifie,
14−CLM-28-013,28,"IPC Québec (18-10-0004-01, ensemble, 2002 = 100) : +3,1 % sur un an en septembre 2026 ; niveaux 124,7 (avril 2015) et 167,7 (septembre 2026), soit +34,5 %",SRC-E-015;SRC-C-049,data/raw/statcan_18100004/18100004.csv (vecteur v41691783, relu le 2026-10-31) ; notes-E,2026-10-31,verifie,"Calcul de l'auteur (indexation du barème SCHL 2015)" 14+CLM-28-013,28,"IPC Québec (18-10-0004-01, ensemble, 2002 = 100) : +3,1 % sur un an en septembre 2026 ; niveaux 124,7 (avril 2015) et 167,7 (septembre 2026), soit +34,5 %",SRC-E-015;SRC-C-049,"data/raw/statcan_18100004/18100004.csv (vecteur v41691783, relu le 2026-10-31) ; notes-E",2026-10-31,verifie,"Calcul de l'auteur (indexation du barème SCHL 2015)"
15 CLM-28-014,28,"SCHL Rapport sur le marché locatif (données d'octobre 2025) : inoccupation RMR Québec 2,4 %",SRC-E-007,notes-E §ch. 4/46,2026-10-31,verifie, 15 CLM-28-014,28,"SCHL Rapport sur le marché locatif (données d'octobre 2025) : inoccupation RMR Québec 2,4 %",SRC-E-007,notes-E §ch. 4/46,2026-10-31,verifie,
16 CLM-28-015,28,"Aucune statistique officielle du ratio de dépenses d'exploitation typique ni de la réserve de remplacement par logement au Québec ; fourchettes 35-50 % des blogues de courtage non retenues",SRC-E-047,notes-E §ch. 28 et « Non trouvé » (points 5-6) ; calibration.json (NON_VERIFIE),2026-10-31,verifie,"Déclaré dans limites ; conventions du dossier déclarées comme telles" 16 CLM-28-015,28,"Aucune statistique officielle du ratio de dépenses d'exploitation typique ni de la réserve de remplacement par logement au Québec ; fourchettes 35-50 % des blogues de courtage non retenues",SRC-E-047,notes-E §ch. 28 et « Non trouvé » (points 5-6) ; calibration.json (NON_VERIFIE),2026-10-31,verifie,"Déclaré dans limites ; conventions du dossier déclarées comme telles"
17−CLM-28-016,28,"Valeur au rôle : la vente ne modifie pas en elle-même l'inscription ; taxes normalisées sur le rôle en vigueur × taux de l'exercice (renvoi ch. 9 et 29)",SRC-B-001,ch29 (CLM-29-010, CLM-29-011) ; notes-B §9,2026-10-31,partiellement_verifie,"Même réserve qu'au ch. 29 : art. 174 LFM non relu intégralement" 17+CLM-28-016,28,"Valeur au rôle : la vente ne modifie pas en elle-même l'inscription ; taxes normalisées sur le rôle en vigueur × taux de l'exercice (renvoi ch. 9 et 29)",SRC-B-001,"ch29 (CLM-29-010, CLM-29-011) ; notes-B §9",2026-10-31,partiellement_verifie,"Même réserve qu'au ch. 29 : art. 174 LFM non relu intégralement"
18 CLM-28-017,28,"Appraisal Institute (2026) et Fisher & Martin (2007) : état des résultats reconstitué, classement fixes/variables/réserve, réserve de remplacement ; Brueggeman & Fisher (2022) : RNE vs flux de trésorerie ; Geltner et al. (2014) : cohérence revenu/CAPEX/taux",SRC-D-001;SRC-D-009;SRC-D-005;SRC-D-004,sources/notes-D.md §ch. 28 ; ouvrages de référence (registry-D),2026-10-31,partiellement_verifie,"Attribution générale de notions standard ; pages précises non citées" 18 CLM-28-017,28,"Appraisal Institute (2026) et Fisher & Martin (2007) : état des résultats reconstitué, classement fixes/variables/réserve, réserve de remplacement ; Brueggeman & Fisher (2022) : RNE vs flux de trésorerie ; Geltner et al. (2014) : cohérence revenu/CAPEX/taux",SRC-D-001;SRC-D-009;SRC-D-005;SRC-D-004,sources/notes-D.md §ch. 28 ; ouvrages de référence (registry-D),2026-10-31,partiellement_verifie,"Attribution générale de notions standard ; pages précises non citées"
19 CLM-28-018,28,"Bokhari & Geltner (2018, Real Estate Economics 46(4)) : ampleur des dépenses en capital récurrentes du multirésidentiel et rôle dans la dépréciation nette",lib-D:bokhariGeltner2018,DOI 10.1111/1540-6229.12156 vérifié par Crossref (notes-D) ; résumé de notes-D §ch. 28,2026-10-31,partiellement_verifie,"Article non relu intégralement par le rédacteur ; aucun chiffre repris" 19 CLM-28-018,28,"Bokhari & Geltner (2018, Real Estate Economics 46(4)) : ampleur des dépenses en capital récurrentes du multirésidentiel et rôle dans la dépréciation nette",lib-D:bokhariGeltner2018,DOI 10.1111/1540-6229.12156 vérifié par Crossref (notes-D) ; résumé de notes-D §ch. 28,2026-10-31,partiellement_verifie,"Article non relu intégralement par le rédacteur ; aucun chiffre repris"
20 CLM-28-019,28,"Harding, Rosenthal & Sirmans (2007, JUE 61(2)) : dépréciation du capital résidentiel par ventes répétées ; entretien de l'ordre de 1 %/an de la valeur (propriétaires-occupants américains)",lib-D:hardingRosenthalSirmans2007,DOI 10.1016/j.jue.2006.07.007 vérifié par Crossref (notes-D) ; ordre de grandeur de notes-D §ch. 28,2026-10-31,partiellement_verifie,"Ordre de grandeur cité avec « de l'ordre de » ; article non relu intégralement" 20 CLM-28-019,28,"Harding, Rosenthal & Sirmans (2007, JUE 61(2)) : dépréciation du capital résidentiel par ventes répétées ; entretien de l'ordre de 1 %/an de la valeur (propriétaires-occupants américains)",lib-D:hardingRosenthalSirmans2007,DOI 10.1016/j.jue.2006.07.007 vérifié par Crossref (notes-D) ; ordre de grandeur de notes-D §ch. 28,2026-10-31,partiellement_verifie,"Ordre de grandeur cité avec « de l'ordre de » ; article non relu intégralement"
modified verification/chapters/ch28-registry.csv +2 −2
@@ -1,7 +1,7 @@
1 id,type,titre,organisme,version_ou_date,url,date_consultation,statut,utilise_dans,notes 1 id,type,titre,organisme,version_ou_date,url,date_consultation,statut,utilise_dans,notes
2 SRC-E-047,document,Frais d'exploitation de la SCHL — immeubles locatifs au Québec (avril 2015),SCHL (diffusé par PMML),avril 2015,https://pmml.ca/wp-content/uploads/2020/10/Frais-dexploitation-avril-2015.pdf,2026-10-31,verifie,ch28,"RATTACHEMENT : barème entretien 500 $/log., salaires, gestion 3-5 %, réserve 100 $/log. (tab. 28.7, §28.3, §28.5, §28.6)" 2 SRC-E-047,document,Frais d'exploitation de la SCHL — immeubles locatifs au Québec (avril 2015),SCHL (diffusé par PMML),avril 2015,https://pmml.ca/wp-content/uploads/2020/10/Frais-dexploitation-avril-2015.pdf,2026-10-31,verifie,ch28,"RATTACHEMENT : barème entretien 500 $/log., salaires, gestion 3-5 %, réserve 100 $/log. (tab. 28.7, §28.3, §28.5, §28.6)"
3 SRC-A-005,page_web,Pourcentages applicables aux critères de fixation de loyer (2025 et 2026),Tribunal administratif du logement,2026,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,ch28,"RATTACHEMENT : taux de base 3,1 % ; gaz −5,8/+13,7 (encadré Québec, §28.3)" 3 SRC-A-005,page_web,Pourcentages applicables aux critères de fixation de loyer (2025 et 2026),Tribunal administratif du logement,2026,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,ch28,"RATTACHEMENT : taux de base 3,1 % ; gaz −5,8/+13,7 (encadré Québec, §28.3)"
4−SRC-A-003,reglement,"Règlement sur les critères de fixation de loyer (RLRQ, c. T-15.01, r. 2)",Québec (LégisQuébec),À jour au 1er juillet 2026 (D. 1455-2025),https://www.legisquebec.gouv.qc.ca/fr/document/rc/T-15.01,%20r.%202,2026-10-31,verifie,ch28,"RATTACHEMENT : art. 3, 3.1, 5 (encadré Québec)" 4+SRC-A-003,reglement,"Règlement sur les critères de fixation de loyer (RLRQ, c. T-15.01, r. 2)",Québec (LégisQuébec),À jour au 1er juillet 2026 (D. 1455-2025),"https://www.legisquebec.gouv.qc.ca/fr/document/rc/T-15.01,%20r.%202",2026-10-31,verifie,ch28,"RATTACHEMENT : art. 3, 3.1, 5 (encadré Québec)"
5 SRC-A-009,guide,Formulaire « Renseignements nécessaires à la fixation du loyer » (RN) — Fixation 2026-2027,Tribunal administratif du logement,TAL-004-E RN (2025-11),https://www.tal.gouv.qc.ca/sites/default/files/RN_2026_F.pdf,2026-10-31,verifie,ch28,"RATTACHEMENT : lignes de dépenses du formulaire (encadré Québec, §28.3)" 5 SRC-A-009,guide,Formulaire « Renseignements nécessaires à la fixation du loyer » (RN) — Fixation 2026-2027,Tribunal administratif du logement,TAL-004-E RN (2025-11),https://www.tal.gouv.qc.ca/sites/default/files/RN_2026_F.pdf,2026-10-31,verifie,ch28,"RATTACHEMENT : lignes de dépenses du formulaire (encadré Québec, §28.3)"
6 SRC-A-010,page_web,Augmentation de loyer — critères de fixation (avis à compter du / avant le 1er janvier 2026),Tribunal administratif du logement,Consultée le 2026-10-31,https://www.tal.gouv.qc.ca/fr/reconduction-du-bail-et-fixation-de-loyer/augmentation-de-loyer,2026-10-31,verifie,ch28,"RATTACHEMENT : ancienne méthode (gestion 5 %, CPG + 1 %), nouvelle méthode" 6 SRC-A-010,page_web,Augmentation de loyer — critères de fixation (avis à compter du / avant le 1er janvier 2026),Tribunal administratif du logement,Consultée le 2026-10-31,https://www.tal.gouv.qc.ca/fr/reconduction-du-bail-et-fixation-de-loyer/augmentation-de-loyer,2026-10-31,verifie,ch28,"RATTACHEMENT : ancienne méthode (gestion 5 %, CPG + 1 %), nouvelle méthode"
7 SRC-E-049,document,"Formulaire de calcul de l'ajustement des loyers (frais de gestion : forfait 5 %, max 10 % des revenus)",Tribunal administratif du logement,2021 (règle stable),https://www.tal.gouv.qc.ca/sites/default/files/CSAL_2021_F.pdf,2026-10-31,partiellement_verifie,ch28,"RATTACHEMENT : gestion 5 %/10 % (§28.3, §28.8)" 7 SRC-E-049,document,"Formulaire de calcul de l'ajustement des loyers (frais de gestion : forfait 5 %, max 10 % des revenus)",Tribunal administratif du logement,2021 (règle stable),https://www.tal.gouv.qc.ca/sites/default/files/CSAL_2021_F.pdf,2026-10-31,partiellement_verifie,ch28,"RATTACHEMENT : gestion 5 %/10 % (§28.3, §28.8)"
@@ -22,4 +22,4 @@ SRC-D-001,manuel,"The Appraisal of Real Estate, 16th Edition",Appraisal Institut
22 SRC-D-003,manuel,"The Appraisal of Real Estate, Third Canadian Edition",UBC Real Estate Division / AIC,2010,https://openlibrary.org/search?q=Appraisal+of+Real+Estate+Canadian+Edition,2026-10-31,verifie,ch28,"RATTACHEMENT : Pour aller plus loin" 22 SRC-D-003,manuel,"The Appraisal of Real Estate, Third Canadian Edition",UBC Real Estate Division / AIC,2010,https://openlibrary.org/search?q=Appraisal+of+Real+Estate+Canadian+Edition,2026-10-31,verifie,ch28,"RATTACHEMENT : Pour aller plus loin"
23 SRC-D-009,manuel,"Income Property Valuation, 3rd ed.",Fisher & Martin (Dearborn),2007,,2026-10-31,verifie,ch28,"RATTACHEMENT : normalisation, classement (§28.1, §28.2, §28.4)" 23 SRC-D-009,manuel,"Income Property Valuation, 3rd ed.",Fisher & Martin (Dearborn),2007,,2026-10-31,verifie,ch28,"RATTACHEMENT : normalisation, classement (§28.1, §28.2, §28.4)"
24 SRC-D-005,manuel,"Real Estate Finance and Investments, 17th ed.",Brueggeman & Fisher (McGraw Hill),2022,,2026-10-31,verifie,ch28,"RATTACHEMENT : §28.4, Pour aller plus loin" 24 SRC-D-005,manuel,"Real Estate Finance and Investments, 17th ed.",Brueggeman & Fisher (McGraw Hill),2022,,2026-10-31,verifie,ch28,"RATTACHEMENT : §28.4, Pour aller plus loin"
25−SRC-D-004,manuel,"Commercial Real Estate Analysis and Investments, 3rd ed.",Geltner, Miller, Clayton & Eichholtz (OnCourse),2014,,2026-10-31,verifie,ch28,"RATTACHEMENT : RNE vs flux, cohérence avec le taux (§28.1, Pour aller plus loin)" 25+SRC-D-004,manuel,"Commercial Real Estate Analysis and Investments, 3rd ed.","Geltner, Miller, Clayton & Eichholtz (OnCourse)",2014,,2026-10-31,verifie,ch28,"RATTACHEMENT : RNE vs flux, cohérence avec le taux (§28.1, Pour aller plus loin)"
modified verification/chapters/ch28.md +1 −1
@@ -3,7 +3,7 @@
3 ## Livrables 3 ## Livrables
4 | Fichier | Contenu | 4 | Fichier | Contenu |
5 |---|---| 5 |---|---|
6−| `manuscript/chapters/ch28.tex` | ≈ 12 650 mots (compteur `valider_chapitre.sh`, tableaux compris ; ≈ 10 500 mots de prose) ; 8 sections (28.1 périmètre du RNE, 28.2 classement fixes/variables/réserve, 28.3 postes québécois, 28.4 normalisation, 28.5 réserve vs CAPEX et cohérence avec le taux, 28.6 ratios de référence, 28.7 exemple intégral cas 3 + cas 2, 28.8 sensibilité) ; encadrés `synthetique`, `quebec` (dépenses de l'évaluateur vs dépenses du TAL), `erreurs`, `limites`, `synthese` ; 2 définitions, 1 proposition avec démonstration (erreur d'une réserve asymétrique : ΔV = RR/R'), 1 exemple | 6+| `manuscript/chapters/ch28.tex` | ≈ 12 650 mots (compteur `valider_chapitre.sh`, tableaux compris ; 10 458 mots hors tableaux et figures) ; 8 sections (28.1 périmètre du RNE, 28.2 classement fixes/variables/réserve, 28.3 postes québécois, 28.4 normalisation, 28.5 réserve vs CAPEX et cohérence avec le taux, 28.6 ratios de référence, 28.7 exemple intégral cas 3 + cas 2, 28.8 sensibilité) ; encadrés `synthetique`, `quebec` (dépenses de l'évaluateur vs dépenses du TAL), `erreurs`, `limites`, `synthese` ; 2 définitions, 1 proposition avec démonstration (erreur d'une réserve asymétrique : ΔV = RR/R'), 1 exemple |
7 | `analysis/python/cases/ch28_depenses.py` | tous les chiffres du chapitre et des solutions : `calculer()` (historique, normalisation, classement, démonstration réserve/taux, réserve par composantes, annuité équivalente des CAPEX, loyers au marché, comparables, sensibilités, indexation IPC, cas 2), `exercices()` ; appuyé sur `traite.cas3`, `traite.cas2`, `traite.finance` (aucune formule partagée réécrite, aucune modification de `traite/`) | 7 | `analysis/python/cases/ch28_depenses.py` | tous les chiffres du chapitre et des solutions : `calculer()` (historique, normalisation, classement, démonstration réserve/taux, réserve par composantes, annuité équivalente des CAPEX, loyers au marché, comparables, sensibilités, indexation IPC, cas 2), `exercices()` ; appuyé sur `traite.cas3`, `traite.cas2`, `traite.finance` (aucune formule partagée réécrite, aucune modification de `traite/`) |
8 | `analysis/python/figures/ch28_figures.py` | 5 figures → `assets/figures/ch28/ch28-fig-{cascade,structure,evolution,ratio-loyer,sensibilite}.pdf` | 8 | `analysis/python/figures/ch28_figures.py` | 5 figures → `assets/figures/ch28/ch28-fig-{cascade,structure,evolution,ratio-loyer,sensibilite}.pdf` |
9 | `analysis/python/tests/test_ch28.py` | 17 tests : identités (RNE = RBE − DE − RR ; R' = R + RR/V ; ΔV = RR/R' ; VA CAPEX perpétuels = VA réserve au fonds d'amortissement ; fonds → linéaire quand i → 0), cohérence avec `cas3.tout()` et `cas2.tout()` (ch. 29/30/32/46/47), valeurs citées (0,01 $ / 1 $), exercices, présence des figures | 9 | `analysis/python/tests/test_ch28.py` | 17 tests : identités (RNE = RBE − DE − RR ; R' = R + RR/V ; ΔV = RR/R' ; VA CAPEX perpétuels = VA réserve au fonds d'amortissement ; fonds → linéaire quand i → 0), cohérence avec `cas3.tout()` et `cas2.tout()` (ch. 29/30/32/46/47), valeurs citées (0,01 $ / 1 $), exercices, présence des figures |
added verification/chapters/ch34-calculations.csv +19 −0
@@ -0,0 +1,19 @@
1+id,chapitre,description,script,test,resultat_attendu,resultat_obtenu,statut,date
2+CALC-34-01,34,"Pourcentage de base 2026 selon l'art. 3.1 à partir des moyennes annuelles de l'IPC Québec (A 161,033 ; B 157,158 ; C 153,583 ; D 146,983) vs 3,1 % publié par le TAL",analysis/python/cases/ch34_tal.py,analysis/python/tests/test_ch34.py::test_pourcentage_base_reproduit_tal,"3,1 %","3,095 %",verifie,2026-10-31
3+CALC-34-02,34,"Pourcentage de base 2025 selon l'art. 3.1 (A 157,158 ; B 153,583 ; C 146,983 ; D 137,792) vs 4,5 % publié",analysis/python/cases/ch34_tal.py,analysis/python/tests/test_ch34.py::test_pourcentage_base_reproduit_tal,"4,5 %","4,496 %",verifie,2026-10-31
4+CALC-34-03,34,"Historique contrefactuel 2006-2026 de la formule : plancher à zéro, lissage (écart-type formule < écart-type inflation), inflation 2023 = 6,67 %",analysis/python/cases/ch34_tal.py,analysis/python/tests/test_ch34.py::test_historique_coherent,"formule 2023 : 3,75 % ; 2024 : 4,98 %","0,0375 ; 0,0498",verifie,2026-10-31
5+CALC-34-04,34,"Règles des art. 4 (taux net = max(variation − |base|, 0) si variation > 0), 5 (part = 12 L / revenus) et 15.1 (arrondi demi vers le haut)",analysis/python/cases/ch34_tal.py,analysis/python/tests/test_ch34.py::test_taux_net_art_4 ; ::test_part_attribuable ; ::test_arrondi_art_15_1,"net(8 %, 3,1 %) = 4,9 % ; 750,50 → 751 ; 750,49 → 750","0,049 ; 751 ; 750",verifie,2026-10-31
6+CALC-34-05,34,"Fixation nouvelle méthode, cas de contrôle (loyer 1 000 $, part 10 %, taxes +10 %, scolaire +2 %, assurances +10 %, immobilisations 60 000 $, services 1 200 $/an, base 3 %) : composantes 30 + 14 + 7 + 25 + 10 = 86 $",analysis/python/cases/ch34_tal.py,analysis/python/tests/test_ch34.py::test_fixation_nouvelle_composantes,"86 $ ; nouveau loyer 1 086 $","86 ; 1086",verifie,2026-10-31
7+CALC-34-06,34,"Exemple logement 103 (728 $ ; marché 1 047 $ ; part 4,995 % sur revenus 174 900 $), variante A : base 22,57 $, taxes/assurances nulles (−4,2 % ; +1,9 % ; −12,3 %), nouveau loyer 751 $ (+23 $, 3,16 %)",analysis/python/cases/ch34_tal.py,analysis/python/tests/test_ch34.py::test_exemple_logement_103_valeurs_citees,"751 $","751 ; 22,568 $",verifie,2026-10-31
8+CALC-34-07,34,"Exemple logement 103, variante B (taxes +8 % → net 4,9 % → 2,17 $ ; assurances +10 % → net 6,9 % → 3,62 $ ; immobilisations 85 000 $ → 17,69 $) : total 46,05 $, nouveau loyer 774 $ (6,32 %)",analysis/python/cases/ch34_tal.py,analysis/python/tests/test_ch34.py::test_exemple_logement_103_valeurs_citees,"774 $","774 ; 46,05 $",verifie,2026-10-31
9+CALC-34-08,34,"Reconstitution pédagogique de l'ancienne méthode (pourcentages 2026 des avis avant 2026) pour le logement 103 : 755 $ (A) et 786 $ (B) ; années de rattrapage à 3,1 %/an marché figé : 11,9",analysis/python/cases/ch34_tal.py,analysis/python/tests/test_ch34.py::test_exemple_logement_103_valeurs_citees,"755 ; 786 ; 11,9","755 ; 786 ; 11,9",verifie,2026-10-31
10+CALC-34-09,34,"Forme fermée E[q_t] = q0 a^t + ρ(1−a^t)/(1−a) = récursion = Monte-Carlo (20 000 trajectoires, écart max < 0,006) ; q_10 = 0,9249 pour q0 0,695, ρ 15 %",analysis/python/cases/ch34_tal.py,analysis/python/tests/test_ch34.py::test_forme_fermee_egale_recursion_et_simulation,"0,9249","0.9249",verifie,2026-10-31
11+CALC-34-10,34,"Limite q_∞ = ρ/(1−(1−ρ)δ) : 0,9316 / 0,9786 / 0,9885 pour ρ = 5/15/25 % (g_c 3,1 %, g_m 3,5 %) ; = 1 si g_c = g_m ; convergence de la forme fermée",analysis/python/cases/ch34_tal.py,analysis/python/tests/test_ch34.py::test_limite_et_monotonie,"0,9316 ; 0,9786 ; 0,9885","0.93159 ; 0.97857 ; 0.98854",verifie,2026-10-31
12+CALC-34-11,34,"Cas 3 : rapport de loyer de l'immeuble 0,8898 (2026) → 0,9904 (déterministe) / 0,9618 (espérance) / 0,8560 (sans relocation) en 2036",analysis/python/cases/ch34_tal.py,analysis/python/tests/test_ch34.py::test_trajectoire_cas3,"0,9904 ; 0,9618 ; 0,8560","0.9904 ; 0.9618 ; 0.8560",verifie,2026-10-31
13+CALC-34-12,34,"projection_scenario (paramètres du dossier) reproduit traite.cas3.projection_dcf colonne par colonne (tol. 1e-6) ; 2 logements relocalisés par an",analysis/python/cases/ch34_tal.py,analysis/python/tests/test_ch34.py::test_projection_locale_reproduit_module_commun,identité,identité,verifie,2026-10-31
14+CALC-34-13,34,"Valeur de base 2 208 566 $ et scénario « encadrement serré » du ch. 30 (base 2 %, rotation 8 %, marché 3 %) 1 912 046 $ reproduits",analysis/python/cases/ch34_tal.py,analysis/python/tests/test_ch34.py::test_valeur_base_et_scenario_ch30,"2 208 566 ; 1 912 046","2208565.91 ; 1912046.35",verifie,2026-10-31
15+CALC-34-14,34,"Grille rotation (5/15/25 %) × base (2/3,1/4 %) : 1 974 822 à 2 340 208 $ ; monotone croissante en rotation et en base ; cellules citées",analysis/python/cases/ch34_tal.py,analysis/python/tests/test_ch34.py::test_grille_monotone_et_valeurs_citees,"9 cellules du tableau 34.7","1974822 ; 2140916 ; 2284775 ; 2118624 ; 2208566 ; 2326053 ; 2174597 ; 2231655 ; 2340208",verifie,2026-10-31
16+CALC-34-15,34,"Scénarios complémentaires : indexation seule 1 756 414 ; φ = 0,5 2 056 076 ; encadrement renforcé 1 701 507 ; récupération 5 % CAPEX 2 289 503 ; espérance 2 091 629 ; tout au marché 2 267 398 ; VA de l'écart 56 008 (base) / 184 956 (indexation seule)",analysis/python/cases/ch34_tal.py,analysis/python/tests/test_ch34.py::test_scenarios_complementaires,"valeurs du tableau 34.8","toutes à ±1 $",verifie,2026-10-31
17+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
18+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
19+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
added verification/chapters/ch34-figures.csv +6 −0
@@ -0,0 +1,6 @@
1+fichier,chapitre,script,donnees,type_donnees,legende_courte,statut
2+ch34/ch34-fig-ipc-base.pdf,34,analysis/python/figures/ch34_ipc_base.py,data/processed/ipc_quebec_mensuel.csv (extrait de data/raw/statcan_18100004/18100004.csv v41691783),reelle,"Inflation de la période de référence (IPC Québec) et pourcentage de base selon la formule de l'art. 3.1, 2006-2026, avec les pourcentages TAL 2025 (4,5 %) et 2026 (3,1 %) (Statistique Canada 18-10-0004-01 et TAL, consultés le 2026-10-31)",verifie
3+ch34/ch34-fig-calcul-tal.pdf,34,analysis/python/figures/ch34_calcul_tal.py,data/synthetic/cas3_montreal_etat_locatif.csv;cas3_montreal_depenses.csv;cas3_montreal_hypotheses_dcf.json,synthetique,"Composantes de l'augmentation mensuelle du logement 103 (méthode 2026, variantes A et B), cas 3 [données synthétiques]",verifie
4+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 %, g_m 3,5 % ; simulation graine 20261031,synthetique,"Espérance du rapport loyer/marché sous rotation 5/15/25 % (forme fermée vs Monte-Carlo) et probabilité de maintien du locataire [données synthétiques]",verifie
5+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
6+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
modified verification/claims.csv +3 −3
@@ -745,7 +745,7 @@ CLM-32-026,32,Le jeu synthétique du cas 2 est normalisé à la médiane APCIQ L
745 CLM-25-019,25,"Valeur de terrain du sujet du cas 1 : 277 000 $ (447 $/m²), extraite au ch. 24 sur 13 ventes récentes de Hull et confirmée par la comparaison directe (267 388 $) et l'allocation (280 412 $) ; indication par le coût du ch. 24 inchangée (580 000 $)",CALC-25-016,ch24.md ; ch25_terrain.py (synthese) ; test_ch25.py,2026-10-31,verifie,Valeur du monde synthétique ; à reprendre telle quelle par les ch. 20 et 45 745 CLM-25-019,25,"Valeur de terrain du sujet du cas 1 : 277 000 $ (447 $/m²), extraite au ch. 24 sur 13 ventes récentes de Hull et confirmée par la comparaison directe (267 388 $) et l'allocation (280 412 $) ; indication par le coût du ch. 24 inchangée (580 000 $)",CALC-25-016,ch24.md ; ch25_terrain.py (synthese) ; test_ch25.py,2026-10-31,verifie,Valeur du monde synthétique ; à reprendre telle quelle par les ch. 20 et 45
746 CLM-28-001,28,"Barème SCHL « Frais d'exploitation — immeubles locatifs au Québec » (avril 2015) : entretien 500 $/log. ; salaires 125 $ (5-6 log.), 170 $ (7-11), 300 $ (12+) ; gestion-administration 3 % (5-6), 3-4 % (7-11), 5 % (12+) du revenu brut ; réserve 100 $/log. (cuisinière-réfrigérateur) ; taxes, assurance, énergie au coût réel ; « à titre indicatif »",SRC-E-047,sources/notes-E.md §ch. 28 ; data/synthetic/calibration.json,2026-10-31,verifie,"Document daté (2015), hébergé par un tiers (PMML) ; aucune mise à jour publique trouvée" 746 CLM-28-001,28,"Barème SCHL « Frais d'exploitation — immeubles locatifs au Québec » (avril 2015) : entretien 500 $/log. ; salaires 125 $ (5-6 log.), 170 $ (7-11), 300 $ (12+) ; gestion-administration 3 % (5-6), 3-4 % (7-11), 5 % (12+) du revenu brut ; réserve 100 $/log. (cuisinière-réfrigérateur) ; taxes, assurance, énergie au coût réel ; « à titre indicatif »",SRC-E-047,sources/notes-E.md §ch. 28 ; data/synthetic/calibration.json,2026-10-31,verifie,"Document daté (2015), hébergé par un tiers (PMML) ; aucune mise à jour publique trouvée"
747 CLM-28-002,28,"TAL, ancienne méthode : frais de gestion forfaitaires 5 % des revenus, frais réels admis jusqu'à 10 % ; formulaire RN 2026-2027 détaille taxes, assurances, énergie, entretien, services, gestion (si > 5 %), dépenses d'immobilisation 2025",SRC-E-049;SRC-A-009;SRC-A-010,sources/notes-A.md §6.2 ; notes-E §ch. 28,2026-10-31,verifie,"Règle de fixation de loyer, pas une norme d'évaluation" 747 CLM-28-002,28,"TAL, ancienne méthode : frais de gestion forfaitaires 5 % des revenus, frais réels admis jusqu'à 10 % ; formulaire RN 2026-2027 détaille taxes, assurances, énergie, entretien, services, gestion (si > 5 %), dépenses d'immobilisation 2025",SRC-E-049;SRC-A-009;SRC-A-010,sources/notes-A.md §6.2 ; notes-E §ch. 28,2026-10-31,verifie,"Règle de fixation de loyer, pas une norme d'évaluation"
748−CLM-28-003,28,"Règlement sur les critères de fixation de loyer (T-15.01, r. 2) modifié par D. 1455-2025, en vigueur le 1er janvier 2026 : six critères (art. 3) ; pourcentage de base = moyenne de trois variations annuelles de l'IPC Québec (art. 3.1) ; critères 2° à 6° selon la part attribuable au logement (art. 5) ; 5 % des dépenses d'immobilisation ; transition pour les avis donnés avant le 1er janvier 2026",SRC-A-003,sources/notes-A.md §6.2 (texte des art. 3,3.1,4,5 relu dans les notes) 748+CLM-28-003,28,"Règlement sur les critères de fixation de loyer (T-15.01, r. 2) modifié par D. 1455-2025, en vigueur le 1er janvier 2026 : six critères (art. 3) ; pourcentage de base = moyenne de trois variations annuelles de l'IPC Québec (art. 3.1) ; critères 2° à 6° selon la part attribuable au logement (art. 5) ; 5 % des dépenses d'immobilisation ; transition pour les avis donnés avant le 1er janvier 2026",SRC-A-003,"sources/notes-A.md §6.2 (texte des art. 3, 3.1, 4, 5 relu dans les notes)",2026-10-31,verifie,
749 CLM-28-004,28,"TAL pourcentages 2026 : taux de base 3,1 % (avis dès le 1er janvier 2026) ; ancienne méthode 2025/2026 : gaz −5,8 / +13,7 ; électricité 2,9 / 2,9 ; entretien 6,9 / 5,7 ; gestion 8,2 / 7,2 ; dépenses d'immobilisation amorties au taux CPG 5 ans + 1 % (ancienne méthode)",SRC-A-005;SRC-A-010,sources/notes-A.md §6.2 ; notes-E §ch. 28,2026-10-31,verifie, 749 CLM-28-004,28,"TAL pourcentages 2026 : taux de base 3,1 % (avis dès le 1er janvier 2026) ; ancienne méthode 2025/2026 : gaz −5,8 / +13,7 ; électricité 2,9 / 2,9 ; entretien 6,9 / 5,7 ; gestion 8,2 / 7,2 ; dépenses d'immobilisation amorties au taux CPG 5 ans + 1 % (ancienne méthode)",SRC-A-005;SRC-A-010,sources/notes-A.md §6.2 ; notes-E §ch. 28,2026-10-31,verifie,
750 CLM-28-005,28,C.c.Q. art. 1947 (demande de fixation dans le mois du refus) et 1953 (fixation selon les normes réglementaires),SRC-A-001,sources/notes-A.md §bail (art. 1947 et 1953 résumés),2026-10-31,verifie,Texte intégral non relu par le rédacteur du ch. 28 ; résumé de notes-A 750 CLM-28-005,28,C.c.Q. art. 1947 (demande de fixation dans le mois du refus) et 1953 (fixation selon les normes réglementaires),SRC-A-001,sources/notes-A.md §bail (art. 1947 et 1953 résumés),2026-10-31,verifie,Texte intégral non relu par le rédacteur du ch. 28 ; résumé de notes-A
751 CLM-28-006,28,"Taxe scolaire : LIP art. 303-303.6, taux unique, assiette = évaluation uniformisée ajustée excédant 25 000 $ ; 0,07899 $/100 $ pour 2026-2027 (communiqué CSS) ; 0,08423 $ pour 2025-2026",SRC-B-035;SRC-B-037;SRC-E-026,notes-B.md §9.9 ; ch09 (CLM-09-013),2026-10-31,partiellement_verifie,"Avis officiel 2026-2027 ((2026) 158 G.O. 1, 520) non consulté ; mécanisme d'ajustement non modélisé" 751 CLM-28-006,28,"Taxe scolaire : LIP art. 303-303.6, taux unique, assiette = évaluation uniformisée ajustée excédant 25 000 $ ; 0,07899 $/100 $ pour 2026-2027 (communiqué CSS) ; 0,08423 $ pour 2025-2026",SRC-B-035;SRC-B-037;SRC-E-026,notes-B.md §9.9 ; ch09 (CLM-09-013),2026-10-31,partiellement_verifie,"Avis officiel 2026-2027 ((2026) 158 G.O. 1, 520) non consulté ; mécanisme d'ajustement non modélisé"
@@ -755,10 +755,10 @@ CLM-28-009,28,"Gatineau 2026 (règl. 994-2025) : taux de base 5,224 $/1 000 $ ;
755 CLM-28-010,28,"Prime moyenne d'assurance habitation au Québec : 1 103 $ en 2024, +9 % sur un an, +63 % en dix ans (BAC via Infoassurance) ; porte sur les ménages, aucune statistique pour les immeubles locatifs",SRC-E-039,notes-E §ch. 28,2026-10-31,partiellement_verifie,Industrie ; la page attribue le même chiffre à 2023 et 2024 (incohérence signalée dans notes-E) 755 CLM-28-010,28,"Prime moyenne d'assurance habitation au Québec : 1 103 $ en 2024, +9 % sur un an, +63 % en dix ans (BAC via Infoassurance) ; porte sur les ménages, aucune statistique pour les immeubles locatifs",SRC-E-039,notes-E §ch. 28,2026-10-31,partiellement_verifie,Industrie ; la page attribue le même chiffre à 2023 et 2024 (incohérence signalée dans notes-E)
756 CLM-28-011,28,"Hydro-Québec tarif D au 2026-04-01 : 46,154 ¢/jour ; 7,065 ¢/kWh jusqu'à 40 kWh/jour ; 11,142 ¢/kWh au-delà",SRC-E-037,notes-E §ch. 28 ; document tarifaire,2026-10-31,verifie, 756 CLM-28-011,28,"Hydro-Québec tarif D au 2026-04-01 : 46,154 ¢/jour ; 7,065 ¢/kWh jusqu'à 40 kWh/jour ; 11,142 ¢/kWh au-delà",SRC-E-037,notes-E §ch. 28 ; document tarifaire,2026-10-31,verifie,
757 CLM-28-012,28,"Régie de l'énergie D-2026-033 : hausse clientèle domestique 3,0 % (2026), 3,0 % (2027), 2,6 % (2028)",SRC-E-038,notes-E §ch. 28 ; communiqué du 2026-03-16,2026-10-31,verifie, 757 CLM-28-012,28,"Régie de l'énergie D-2026-033 : hausse clientèle domestique 3,0 % (2026), 3,0 % (2027), 2,6 % (2028)",SRC-E-038,notes-E §ch. 28 ; communiqué du 2026-03-16,2026-10-31,verifie,
758−CLM-28-013,28,"IPC Québec (18-10-0004-01, ensemble, 2002 = 100) : +3,1 % sur un an en septembre 2026 ; niveaux 124,7 (avril 2015) et 167,7 (septembre 2026), soit +34,5 %",SRC-E-015;SRC-C-049,data/raw/statcan_18100004/18100004.csv (vecteur v41691783,relu le 2026-10-31) ; notes-E,2026-10-31,verifie 758+CLM-28-013,28,"IPC Québec (18-10-0004-01, ensemble, 2002 = 100) : +3,1 % sur un an en septembre 2026 ; niveaux 124,7 (avril 2015) et 167,7 (septembre 2026), soit +34,5 %",SRC-E-015;SRC-C-049,"data/raw/statcan_18100004/18100004.csv (vecteur v41691783, relu le 2026-10-31) ; notes-E",2026-10-31,verifie,Calcul de l'auteur (indexation du barème SCHL 2015)
759 CLM-28-014,28,"SCHL Rapport sur le marché locatif (données d'octobre 2025) : inoccupation RMR Québec 2,4 %",SRC-E-007,notes-E §ch. 4/46,2026-10-31,verifie, 759 CLM-28-014,28,"SCHL Rapport sur le marché locatif (données d'octobre 2025) : inoccupation RMR Québec 2,4 %",SRC-E-007,notes-E §ch. 4/46,2026-10-31,verifie,
760 CLM-28-015,28,Aucune statistique officielle du ratio de dépenses d'exploitation typique ni de la réserve de remplacement par logement au Québec ; fourchettes 35-50 % des blogues de courtage non retenues,SRC-E-047,notes-E §ch. 28 et « Non trouvé » (points 5-6) ; calibration.json (NON_VERIFIE),2026-10-31,verifie,Déclaré dans limites ; conventions du dossier déclarées comme telles 760 CLM-28-015,28,Aucune statistique officielle du ratio de dépenses d'exploitation typique ni de la réserve de remplacement par logement au Québec ; fourchettes 35-50 % des blogues de courtage non retenues,SRC-E-047,notes-E §ch. 28 et « Non trouvé » (points 5-6) ; calibration.json (NON_VERIFIE),2026-10-31,verifie,Déclaré dans limites ; conventions du dossier déclarées comme telles
761−CLM-28-016,28,Valeur au rôle : la vente ne modifie pas en elle-même l'inscription ; taxes normalisées sur le rôle en vigueur × taux de l'exercice (renvoi ch. 9 et 29),SRC-B-001,ch29 (CLM-29-010,CLM-29-011) ; notes-B §9,2026-10-31,partiellement_verifie 761+CLM-28-016,28,Valeur au rôle : la vente ne modifie pas en elle-même l'inscription ; taxes normalisées sur le rôle en vigueur × taux de l'exercice (renvoi ch. 9 et 29),SRC-B-001,"ch29 (CLM-29-010, CLM-29-011) ; notes-B §9",2026-10-31,partiellement_verifie,Même réserve qu'au ch. 29 : art. 174 LFM non relu intégralement
762 CLM-28-017,28,"Appraisal Institute (2026) et Fisher & Martin (2007) : état des résultats reconstitué, classement fixes/variables/réserve, réserve de remplacement ; Brueggeman & Fisher (2022) : RNE vs flux de trésorerie ; Geltner et al. (2014) : cohérence revenu/CAPEX/taux",SRC-D-001;SRC-D-009;SRC-D-005;SRC-D-004,sources/notes-D.md §ch. 28 ; ouvrages de référence (registry-D),2026-10-31,partiellement_verifie,Attribution générale de notions standard ; pages précises non citées 762 CLM-28-017,28,"Appraisal Institute (2026) et Fisher & Martin (2007) : état des résultats reconstitué, classement fixes/variables/réserve, réserve de remplacement ; Brueggeman & Fisher (2022) : RNE vs flux de trésorerie ; Geltner et al. (2014) : cohérence revenu/CAPEX/taux",SRC-D-001;SRC-D-009;SRC-D-005;SRC-D-004,sources/notes-D.md §ch. 28 ; ouvrages de référence (registry-D),2026-10-31,partiellement_verifie,Attribution générale de notions standard ; pages précises non citées
763 CLM-28-018,28,"Bokhari & Geltner (2018, Real Estate Economics 46(4)) : ampleur des dépenses en capital récurrentes du multirésidentiel et rôle dans la dépréciation nette",lib-D:bokhariGeltner2018,DOI 10.1111/1540-6229.12156 vérifié par Crossref (notes-D) ; résumé de notes-D §ch. 28,2026-10-31,partiellement_verifie,Article non relu intégralement par le rédacteur ; aucun chiffre repris 763 CLM-28-018,28,"Bokhari & Geltner (2018, Real Estate Economics 46(4)) : ampleur des dépenses en capital récurrentes du multirésidentiel et rôle dans la dépréciation nette",lib-D:bokhariGeltner2018,DOI 10.1111/1540-6229.12156 vérifié par Crossref (notes-D) ; résumé de notes-D §ch. 28,2026-10-31,partiellement_verifie,Article non relu intégralement par le rédacteur ; aucun chiffre repris
764 CLM-28-019,28,"Harding, Rosenthal & Sirmans (2007, JUE 61(2)) : dépréciation du capital résidentiel par ventes répétées ; entretien de l'ordre de 1 %/an de la valeur (propriétaires-occupants américains)",lib-D:hardingRosenthalSirmans2007,DOI 10.1016/j.jue.2006.07.007 vérifié par Crossref (notes-D) ; ordre de grandeur de notes-D §ch. 28,2026-10-31,partiellement_verifie,Ordre de grandeur cité avec « de l'ordre de » ; article non relu intégralement 764 CLM-28-019,28,"Harding, Rosenthal & Sirmans (2007, JUE 61(2)) : dépréciation du capital résidentiel par ventes répétées ; entretien de l'ordre de 1 %/an de la valeur (propriétaires-occupants américains)",lib-D:hardingRosenthalSirmans2007,DOI 10.1016/j.jue.2006.07.007 vérifié par Crossref (notes-D) ; ordre de grandeur de notes-D §ch. 28,2026-10-31,partiellement_verifie,Ordre de grandeur cité avec « de l'ordre de » ; article non relu intégralement
modified verification/figures.csv +5 −0
@@ -167,3 +167,8 @@ ch33/ch33-fig-schl-typologie.pdf,33,analysis/python/figures/ch33_figures.py,data
167 ch33/ch33-fig-rmr.pdf,33,analysis/python/figures/ch33_figures.py,data/synthetic/cas4_portefeuille.csv,synthetique,Valeur par logement et taux global par RMR du portefeuille du cas 4 [données synthétiques],verifie 167 ch33/ch33-fig-rmr.pdf,33,analysis/python/figures/ch33_figures.py,data/synthetic/cas4_portefeuille.csv,synthetique,Valeur par logement et taux global par RMR du portefeuille du cas 4 [données synthétiques],verifie
168 ch33/ch33-fig-taux-taille.pdf,33,analysis/python/figures/ch33_figures.py,data/synthetic/cas3_montreal_comparables.csv;cas4_portefeuille.csv,synthetique,Taux global selon le nombre de logements : 40 ventes du cas 3 et 48 immeubles du cas 4 avec régressions [données synthétiques],verifie 168 ch33/ch33-fig-taux-taille.pdf,33,analysis/python/figures/ch33_figures.py,data/synthetic/cas3_montreal_comparables.csv;cas4_portefeuille.csv,synthetique,Taux global selon le nombre de logements : 40 ventes du cas 3 et 48 immeubles du cas 4 avec régressions [données synthétiques],verifie
169 ch33/ch33-fig-p45-sensibilite.pdf,33,analysis/python/figures/ch33_figures.py,data/synthetic/cas4_portefeuille.csv,synthetique,Grille RMR × état des taux et valeurs par logement prédits pour P45 (36 logements) [données synthétiques],verifie 169 ch33/ch33-fig-p45-sensibilite.pdf,33,analysis/python/figures/ch33_figures.py,data/synthetic/cas4_portefeuille.csv,synthetique,Grille RMR × état des taux et valeurs par logement prédits pour P45 (36 logements) [données synthétiques],verifie
170+ch34/ch34-fig-ipc-base.pdf,34,analysis/python/figures/ch34_ipc_base.py,data/processed/ipc_quebec_mensuel.csv (extrait de data/raw/statcan_18100004/18100004.csv v41691783),reelle,"Inflation de la période de référence (IPC Québec) et pourcentage de base selon la formule de l'art. 3.1, 2006-2026, avec les pourcentages TAL 2025 (4,5 %) et 2026 (3,1 %) (Statistique Canada 18-10-0004-01 et TAL, consultés le 2026-10-31)",verifie
171+ch34/ch34-fig-calcul-tal.pdf,34,analysis/python/figures/ch34_calcul_tal.py,data/synthetic/cas3_montreal_etat_locatif.csv;cas3_montreal_depenses.csv;cas3_montreal_hypotheses_dcf.json,synthetique,"Composantes de l'augmentation mensuelle du logement 103 (méthode 2026, variantes A et B), cas 3 [données synthétiques]",verifie
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+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+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
170 175
Menu

Theme