Ch. 28 livré et validé (compilation isolée, tests, fusion des registres)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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 | 26 | COLS_GROUPES = {"conditions_marche": ["mois"], "localisation": ["dist_centre"] + [f"sect_{x}" for x in cas1.SECTEURS[1:]], |
| 27 | 27 | "superficie": ["ln_S"], "terrain": ["ln_T"], "age": ["age", "age2_100"], "qualite": ["qualite"], |
| 28 | 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) | |
| 30 | −ALPHA_TERRAIN = 0.30 # part du terrain dans la valeur (méthode d'allocation) — hypothèse pédagogique, NON_VERIFIE | |
| 31 | −VIE_ECONOMIQUE = 60 # vie économique totale (ans), méthode âge-vie — hypothèse pédagogique, NON_VERIFIE | |
| 32 | −AGE_EFFECTIF = 28 # âge effectif = âge chronologique (entretien normal présumé, aucune inspection dans le dossier) | |
| 33 | −GRILLE_ALPHA = [0.20, 0.25, 0.30, 0.35, 0.40] | |
| 34 | −GRILLE_VIE = [50, 60, 75] | |
| 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 | +# extraite du marché à 28 ans + désuétude fonctionnelle). Les « hypothèses d'usage » ci-dessous ne servent que de contre-exemple. | |
| 31 | +ALPHA_TERRAIN_USAGE = 0.30 # allocation (part du terrain) — hypothèse d'usage, contre-exemple | |
| 32 | +VIE_ECONOMIQUE_USAGE = 60 # âge-vie — hypothèse d'usage, contre-exemple | |
| 33 | +AGE_EFFECTIF = 28 | |
| 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 | 35 | # Prédictions du sujet sous les quatre spécifications « en logarithme » du ch. 18 (tab. 18.variantes : semi-log, log-log, |
| 36 | 36 | # Box–Cox, indicatrices trimestrielles) : sert à l'incertitude de spécification du modèle hédonique. |
| 37 | 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 | 39 | ARRONDI_CONCLUSION = 5000 |
| 40 | 40 | |
| 41 | 41 | |
@@ -123,7 +123,7 @@ def bootstrap(b: dict, B: int = B, graine: int = GRAINE) -> dict: | ||
| 123 | 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 | 124 | rho = float(np.corrcoef(VC, pred)[0, 1]) |
| 125 | 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 | 127 | "resume": {"candidats": resume(VA), "ajustements": resume(VB), "conjoint": resume(VC), "parametrique": resume(VP), "prediction": resume(pred)}, |
| 128 | 128 | "rho_conjoint_pred": rho, "rho_ajustements_pred": rhoB, |
| 129 | 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 | 158 | return pd.DataFrame(rows) |
| 159 | 159 | |
| 160 | 160 | |
| 161 | −# ---------------------------------------------------------------- 3. indication par le coût (hypothèses déclarées) | |
| 162 | −def cout(b: dict, alpha: float = ALPHA_TERRAIN, vie: float = VIE_ECONOMIQUE, age_eff: float = AGE_EFFECTIF) -> dict: | |
| 163 | − devis, sujet = c21.charger() | |
| 164 | − cn = c21.cout_neuf_sujet(devis) | |
| 165 | − CN = cn["total"] | |
| 166 | − V_L = alpha * b["V_comp"] # allocation : part α de l'indication par comparaison | |
| 167 | − D = ct.depreciation_age_vie(CN, age_eff, vie) | |
| 168 | − val = ct.valeur_par_cout(V_L, CN, D) | |
| 169 | − extraction_hed = ct.extraction_depreciation_marche(b["V_hed"], V_L, CN, age_eff) | |
| 170 | − extraction_comp = ct.extraction_depreciation_marche(b["V_comp"], V_L, CN, age_eff) | |
| 171 | − grille = pd.DataFrame([{"alpha": a, "vie": vv, "V_L": a * b["V_comp"], "D": ct.depreciation_age_vie(CN, age_eff, vv), | |
| 172 | − "valeur": ct.valeur_par_cout(a * b["V_comp"], CN, ct.depreciation_age_vie(CN, age_eff, vv))["valeur"]} | |
| 173 | − for a in GRILLE_ALPHA for vv in GRILLE_VIE]) | |
| 174 | − # âge effectif réduit (entretien supérieur) : variante | |
| 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)} | |
| 176 | − # terrain nécessaire pour que le coût rejoigne la comparaison ou le modèle (D âge-vie inchangée) | |
| 177 | − return {"CN": CN, "V_L": V_L, "D": D, "taux_D": D / CN, "contribution_batiment": val["contribution_batiment"], "valeur": val["valeur"], | |
| 178 | − "extraction_hed": extraction_hed, "extraction_comp": extraction_comp, "grille": grille, | |
| 179 | − "sd_grille": float(grille["valeur"].std(ddof=0)), "min_grille": float(grille["valeur"].min()), "max_grille": float(grille["valeur"].max()), | |
| 180 | − "age_effectif_variantes": age_var, "V_L_pour_comp": b["V_comp"] - (CN - D), "V_L_pour_hed": b["V_hed"] - (CN - D), | |
| 181 | − "cout_neuf_plus_terrain": CN + V_L, "alpha": alpha, "vie": vie, "age_eff": age_eff} | |
| 161 | +# ---------------------------------------------------------------- 3. indication par le coût (reprise du chapitre 24) | |
| 162 | +def _ch24() -> dict: | |
| 163 | + import io, contextlib | |
| 164 | + import ch24_depreciation as c24 | |
| 165 | + with contextlib.redirect_stdout(io.StringIO()): | |
| 166 | + return c24.main() | |
| 167 | + | |
| 168 | + | |
| 169 | +def cout(b: dict) -> dict: | |
| 170 | + """Indication par le coût du chapitre 24 : V = V_L + C_N − D_extraite(28 ans) − D_fonctionnelle. Ajoute les diagnostics | |
| 171 | + d'extraction aux deux autres indications, la lecture « hypothèses d'usage » (allocation 30 %, âge-vie 60 ans) comme | |
| 172 | + contre-exemple, et l'incertitude de spécification (variantes d'extraction du ch. 24).""" | |
| 173 | + import ch24_depreciation as c24 | |
| 174 | + r24 = _ch24() | |
| 175 | + CN = r24["cn"]["total"]; vs = r24["valeur"]; V_L = r24["vl_retenue"]["retenue"]; fonc = r24["salle_eau"]["desuetude"] | |
| 176 | + D_ext = vs["d_extraite"]; D_tot = vs["B_extraction"]["depreciation_totale"] | |
| 177 | + # variantes d'extraction du ch. 24 (fenêtre des ventes récentes, élasticité, qualité) → valeur sous chaque variante | |
| 178 | + sx = r24["sens_extraction"].copy() | |
| 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 | + # contre-exemple : hypothèses d'usage (allocation sur l'indication par comparaison, âge-vie) | |
| 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 | 217 | # ---------------------------------------------------------------- 4. combinaison |
@@ -194,13 +227,14 @@ def poids_gls(Sigma: np.ndarray) -> np.ndarray: | ||
| 194 | 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 | 231 | V = np.array([b["V_comp"], b["V_hed"], co["valeur"]]) |
| 199 | 232 | sd_spec = float(np.std(PRED_SPEC_CH18, ddof=1)) |
| 200 | 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"]]) | |
| 202 | − rho = bs["rho_conjoint_pred"] | |
| 203 | − Sigma = np.diag(s ** 2); Sigma[0, 1] = Sigma[1, 0] = rho * s[0] * s[1] | |
| 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)]) | |
| 235 | + rho = bs["rho_conjoint_pred"]; rho_ck = bk["rho_cout_comp"]; rho_hk = bk["rho_cout_pred"] | |
| 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 | 238 | schemas = { |
| 205 | 239 | "comparaison seule": np.array([1.0, 0.0, 0.0]), |
| 206 | 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 | 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 | 247 | for k, w in schemas.items()]) |
| 214 | 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) | |
| 216 | − cout_b = rng.choice(co["grille"]["valeur"].values, size=len(bs["VC"])) | |
| 249 | + n_b = len(bk["VK"]); cout_b = bk["VK"] | |
| 217 | 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 | 252 | q = np.percentile(Vb, [2.5, 5, 50, 95, 97.5]) |
| 220 | 253 | V_raisonnee = float(w @ V) |
| 221 | 254 | arrondi = ARRONDI_CONCLUSION |
@@ -225,11 +258,14 @@ def combinaison(b: dict, bs: dict, co: dict, graine: int = GRAINE) -> dict: | ||
| 225 | 258 | fourchette_conclue = (math.floor(min(V[0], V[1]) / arrondi) * arrondi, math.ceil(max(V[0], V[1]) / arrondi) * arrondi) |
| 226 | 259 | # courbes de sensibilité aux poids : w_hed de 0 à 1 − w_cout, pour trois poids du coût |
| 227 | 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 | 264 | # variance de la combinaison de deux indications (comparaison, hédonique) selon ρ |
| 230 | 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 | 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 | 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 | 270 | "conclusion": conclusion, "fourchette90": fourchette90, "fourchette_conclue": fourchette_conclue, |
| 235 | 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 | 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 | 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 | 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 | 325 | CN = co["CN"] |
| 290 | 326 | out["ex5"] = {"a": ct.valeur_par_cout(0.35 * b["V_comp"], CN, ct.depreciation_age_vie(CN, 22, 60))["valeur"], |
| 291 | 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 | 331 | # ex. 6 : autres pondérations et comparaison élargie (k = 16) |
| 294 | 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 | 334 | V = np.array([b["V_comp"], b["V_hed"], co["valeur"]]); V16v = np.array([V16, b["V_hed"], co["valeur"]]) |
| 297 | 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 | 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 | 339 | # ex. 7 : incidence d'une hypothèse extraordinaire (eq. 10.incidence / 10.esperance) |
| 302 | 340 | out["ex7"] = {"V_H": cb["conclusion"], "V_nonH": cb["conclusion"] - 14000.0, "p": 0.7} |
| 303 | 341 | out["ex7"]["delta"] = out["ex7"]["V_H"] - out["ex7"]["V_nonH"] |
@@ -316,10 +354,11 @@ def tout(verbose: bool = False) -> dict: | ||
| 316 | 354 | ex = exhaustif(b, bs["P_aj16"], bs["brut16"]) |
| 317 | 355 | el = elargissement(b, bs["P_aj16"], bs["brut16"]); _CACHE["el"] = el |
| 318 | 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 | 359 | vt = verite(b) |
| 321 | 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 | 362 | _CACHE["r"] = r |
| 324 | 363 | if verbose: |
| 325 | 364 | imprimer(r) |
@@ -348,14 +387,20 @@ def imprimer(r: dict) -> None: | ||
| 348 | 387 | print(ex["par_n_retenus"].round(0).to_string()) |
| 349 | 388 | print("\n=== Élargissement k = 5 … 16 (modèle fixe) ===") |
| 350 | 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"])) | |
| 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'])}") | |
| 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})") | |
| 354 | − print(f"terrain implicite pour rejoindre la comparaison : {f(co['V_L_pour_comp'])} ; le modèle : {f(co['V_L_pour_hed'])}") | |
| 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()]}") | |
| 356 | − print(co["grille"].pivot(index="alpha", columns="vie", values="valeur").round(0).to_string()) | |
| 390 | + bk = r["bootstrap_cout"] | |
| 391 | + print("\n=== Coût (chapitre 24) ===") | |
| 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})") | |
| 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})") | |
| 394 | + u = co["usage"] | |
| 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 | 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 | 404 | print(cb["tableau"].round(3).to_string(index=False)) |
| 360 | 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 | 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 | 17 | import ch36_panel as c36 |
| 18 | 18 | |
| 19 | 19 | TRIMS = pd.period_range("2016Q1", "2026Q2", freq="Q") |
| 20 | +def _v(x, d=3): return f"{x:.{d}f}".replace(".", ",").replace("-", "−") | |
| 20 | 21 | def _etiq(ax, pas=8): |
| 21 | 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 | 45 | ax.fill_between(es.index, es["ic_bas"], es["ic_haut"], color=palette.cat[0], alpha=0.18, lw=0) |
| 45 | 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 | 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 | 49 | ax.set_ylabel("Effet sur ln RNE (points de log)") |
| 49 | 50 | ax = axes[1] |
| 50 | 51 | ax.fill_between(cs.index, cs["ic_bas"], cs["ic_haut"], color=palette.cat[1], alpha=0.18, lw=0) |
| 51 | 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 | 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 | 55 | for ax in axes: |
| 55 | 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 | 57 | savefig(fig, "ch36/ch36-fig-event-study.pdf") |
@@ -63,12 +64,15 @@ def fig_bacon(res): | ||
| 63 | 64 | for j, (typ, mk) in enumerate(marqueurs.items()): |
| 64 | 65 | s = t[t["type"] == typ] |
| 65 | 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(vrai, color=palette.ink2, lw=1.0, ls="--", label=f"Effet moyen vrai = {vrai:.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) | |
| 68 | + ax.axhline(vrai, color=palette.ink2, lw=1.0, ls="--", label="Effet moyen vrai (ATT)" if ax is axes[0] else None) | |
| 68 | 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 | 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 | 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 | 76 | savefig(fig, "ch36/ch36-fig-bacon.pdf") |
| 73 | 77 | |
| 74 | 78 | def fig_synthetique(res): |
@@ -78,7 +82,7 @@ def fig_synthetique(res): | ||
| 78 | 82 | ax.plot(t, s["traite"], color=palette.cat[0], lw=1.7, label="Cohorte A (moyenne des 8 immeubles)") |
| 79 | 83 | ax.plot(t, s["synthetique"], color=palette.cat[1], lw=1.5, ls="--", label="Contrôle synthétique") |
| 80 | 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 | 86 | ax = axes[1] |
| 83 | 87 | W = res["panel"].pivot(index="id_immeuble", columns="t", values="y"); coh = res["panel"].groupby("id_immeuble")["cohorte"].first() |
| 84 | 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 | 93 | ax.plot(t, s["ecarts"], color=palette.cat[0], lw=1.8, label="Cohorte A") |
| 90 | 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 | 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 | 97 | ax = axes[2] |
| 94 | 98 | w = s["poids"].sort_values(ascending=True); w = w[w > 0.005] |
| 95 | 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 | 101 | savefig(fig, "ch36/ch36-fig-synthetique.pdf") |
| 98 | 102 | |
| 99 | 103 | def fig_discontinuite(res): |
@@ -108,7 +112,7 @@ def fig_discontinuite(res): | ||
| 108 | 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 | 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 | 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 | 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 | 117 | ax.legend(fontsize=7, loc="upper left"); ax.grid(True, lw=0.4) |
| 114 | 118 | savefig(fig, "ch36/ch36-fig-discontinuite.pdf") |
@@ -122,7 +126,7 @@ def fig_sensibilite(res): | ||
| 122 | 126 | ax.step(es0.index, es0["vrai"], where="mid", color=palette.ink, lw=1.1, ls="--", label="Effet déclaré δ(k)") |
| 123 | 127 | pre = esv.loc[-7:-2]; p = np.polyfit(pre.index.values.astype(float), pre["coef"].values, 1) |
| 124 | 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 | 130 | ax.axhline(0, color=palette.ink2, lw=0.8); ax.axvline(-0.5, color=palette.ink2, lw=0.8, ls=":") |
| 127 | 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 | 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 | 214 | \begin{quebec}[Les indices de prix et l'évaluation foncière] |
| 215 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 1 | id,chapitre,affirmation,source_id,verification,date,statut,notes |
| 2 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 1 | id,type,titre,organisme,version_ou_date,url,date_consultation,statut,utilise_dans,notes |
| 2 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 3 | ## Livrables |
| 4 | 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 | 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 | 8 | | `analysis/python/figures/ch28_figures.py` | 5 figures → `assets/figures/ch28/ch28-fig-{cascade,structure,evolution,ratio-loyer,sensibilite}.pdf` | |
| 9 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | |