spb/traite-evaluation-qc
Public
1"""Chapitre 20 — réconciliation et incertitude : identités, chiffres cités dans le texte et dans les solutions (tolérances déclarées)."""2import sys, pathlib, math3import numpy as np, pytest4sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1] / "cases"))5import ch20_reconciliation as c206from traite import cas1, cout as ct78@pytest.fixture(scope="module")9def r():10 return c20.tout()1112# ---------------------------------------------------------------- indications héritées (ch. 16-18, 21)13def test_indications_heritees(r):14 b = r["base"]15 assert list(b["adj5"]["id_vente"]) == ["G0574", "G0404", "G0468", "G0477", "G0575"]16 assert b["adj5"]["prix_ajuste"].values == pytest.approx([640247, 541222, 572793, 568853, 584862], abs=1)17 assert b["V_comp"] == pytest.approx(575565, abs=1) and b["sd_comp_pondere"] == pytest.approx(33997, abs=1)18 assert b["poids_comp"] == pytest.approx([0.179, 0.327, 0.202, 0.159, 0.133], abs=0.001)19 assert b["V_hed"] == pytest.approx(637953, abs=1)20 assert b["ip_hed"]["bas"] == pytest.approx(539682, abs=1) and b["ip_hed"]["haut"] == pytest.approx(749087, abs=1)21 assert len(b["cand"]) == 1622 assert r["cout"]["CN"] == pytest.approx(397567.33, abs=0.01)2324def test_grille_rapide_reproduit_la_bibliotheque(r):25 """La cascade vectorisée du chapitre donne les mêmes prix ajustés et bruts que traite.cas1.ajustements_hedoniques (16 candidats)."""26 b = r["base"]; g = c20.grille_rapide(b["beta"], b["Xs"], b["Xc16"], b["P16"], b["G"])27 assert g["P_aj"] == pytest.approx(b["adj16"]["prix_ajuste"].values, rel=1e-9)28 assert g["brut"] == pytest.approx(b["adj16"]["ajust_brut_pct"].values, rel=1e-9)29 assert g["valeur"] != pytest.approx(b["V_comp"], abs=1) # 16 candidats ≠ 5 retenus30 g5 = c20.grille_rapide(b["beta"], b["Xs"], b["Xc16"][:5], b["P16"][:5], b["G"])31 assert g5["valeur"] == pytest.approx(b["V_comp"], abs=1e-6) and g5["sd_pondere"] == pytest.approx(b["sd_comp_pondere"], abs=1e-6)32 # identité P_aj = exp(x_s'β)·exp(e_j) pour les 16 candidats (prop. 17.2)33 point = math.exp(float(b["Xs"] @ b["beta"]))34 assert g["P_aj"] == pytest.approx(point * np.exp(b["adj16"]["residu_log"].values), rel=1e-9)3536def test_residus_candidats(r):37 a = r["base"]["adj16"]38 ret = a["retenu"].values39 assert np.mean(np.exp(a.loc[ret, "residu_log"]) - 1) == pytest.approx(-0.0853, abs=0.0005)40 assert np.mean(np.exp(a.loc[~ret, "residu_log"]) - 1) == pytest.approx(-0.0171, abs=0.0005)41 assert np.mean(np.exp(a["residu_log"]) - 1) == pytest.approx(-0.0384, abs=0.0005)4243# ---------------------------------------------------------------- bootstrap et énumération44def test_bootstrap(r):45 bs = r["bootstrap"]; rs = bs["resume"]46 assert len(bs["VA"]) == c20.B == 200047 assert rs["candidats"]["ecart_type"] == pytest.approx(18160, abs=30)48 assert rs["candidats"]["q05"] == pytest.approx(552872, abs=60) and rs["candidats"]["q95"] == pytest.approx(611283, abs=60)49 assert rs["ajustements"]["ecart_type"] == pytest.approx(2672, abs=10)50 assert rs["conjoint"]["ecart_type"] == pytest.approx(18385, abs=30)51 assert rs["conjoint"]["q05"] == pytest.approx(552095, abs=60) and rs["conjoint"]["q95"] == pytest.approx(611779, abs=60)52 assert rs["parametrique"]["ecart_type"] == pytest.approx(2751, abs=10)53 assert rs["prediction"]["ecart_type"] == pytest.approx(6531, abs=15) and rs["prediction"]["moyenne"] == pytest.approx(637736, abs=30)54 assert bs["rho_conjoint_pred"] == pytest.approx(0.088, abs=0.005) and bs["rho_ajustements_pred"] == pytest.approx(0.425, abs=0.01)55 assert bs["part_C_sous_hed"] > 0.9956 # l'incertitude des coefficients est petite devant celle de la sélection57 assert rs["ajustements"]["ecart_type"] < 0.2 * rs["candidats"]["ecart_type"]58 # la variance conjointe est à peu près la somme des deux (indépendance des deux tirages)59 assert rs["conjoint"]["ecart_type"] ** 2 == pytest.approx(rs["candidats"]["ecart_type"] ** 2 + rs["ajustements"]["ecart_type"] ** 2, rel=0.05)6061def test_exhaustif(r):62 ex = r["exhaustif"]63 assert ex["n"] == math.comb(16, 5) == 436864 assert ex["moyenne"] == pytest.approx(606317, abs=1) and ex["ecart_type"] == pytest.approx(20492, abs=1)65 assert ex["min"] == pytest.approx(550231, abs=1) and ex["max"] == pytest.approx(661670, abs=1)66 assert ex["rang_selection"] == 27967 assert ex["part_sous_hed"] == pytest.approx(0.935, abs=0.002)68 par = ex["par_n_retenus"]69 assert list(par["count"]) == [462, 1650, 1650, 550, 55, 1]70 assert par.loc[5, "mean"] == pytest.approx(575565, abs=1)71 # la moyenne par nombre de retenus décroît : plus on inclut les cinq « vrais » comparables, plus la valeur baisse72 assert all(np.diff(par["mean"].values) < 0)7374def test_elargissement(r):75 el = r["elargissement"].set_index("k")76 assert el.loc[5, "valeur_ponderee"] == pytest.approx(575565, abs=1)77 assert el.loc[16, "valeur_ponderee"] == pytest.approx(604689, abs=1) and el.loc[16, "moyenne"] == pytest.approx(611401, abs=1)78 assert el.loc[16, "residu_moyen_pct"] == pytest.approx(-0.0384, abs=0.0005)79 assert el.loc[10, "valeur_ponderee"] == pytest.approx(592737, abs=1)8081# ---------------------------------------------------------------- coût (chapitre 24)82def test_cout(r):83 co = r["cout"]; CN = co["CN"]84 assert CN == pytest.approx(397567.33, abs=0.01)85 assert co["V_L"] == 277000 and co["V_L_mediane"] == pytest.approx(276954, abs=1) and co["n_terrain"] == 1386 assert co["D_ext"] == pytest.approx(87461.05, abs=0.05) and co["taux_ext"] == pytest.approx(0.2200, abs=0.0001)87 assert co["D_fonc"] == pytest.approx(7605.31, abs=0.01)88 assert co["D_tot"] == pytest.approx(95066.36, abs=0.05) and co["taux_tot"] == pytest.approx(0.2391, abs=0.0001)89 assert co["valeur"] == pytest.approx(co["V_L"] + CN - co["D_tot"], abs=1e-6) and co["valeur"] == pytest.approx(579500.97, abs=0.05)90 assert co["arrondie"] == 580000 and co["ventilee"] == pytest.approx(445872.14, abs=0.05)91 assert co["cout_neuf_plus_terrain"] == pytest.approx(674567, abs=1)92 assert co["extraction_hed"]["depreciation"] == pytest.approx(36614, abs=1) and co["extraction_hed"]["taux"] == pytest.approx(0.0921, abs=0.0001)93 assert co["extraction_comp"]["depreciation"] == pytest.approx(99002, abs=1) and co["extraction_comp"]["taux"] == pytest.approx(0.2490, abs=0.0001)94 # contre-exemple : hypothèses d'usage (allocation 30 %, âge-vie 60 ans)95 u = co["usage"]96 assert u["V_L"] == pytest.approx(172669, abs=1) and u["D"] == pytest.approx(185531, abs=1) and u["valeur"] == pytest.approx(384705, abs=1)97 assert u["extraction_hed"]["depreciation"] == pytest.approx(-67716, abs=1)98 assert co["sd_spec"] == pytest.approx(2377, abs=2)99 # identité de la bibliothèque : valeur_par_cout = V_L + C_N − D100 v = ct.valeur_par_cout(100000, 400000, 150000)101 assert v["valeur"] == 350000 and v["taux_depreciation"] == pytest.approx(0.375)102103def test_bootstrap_cout(r):104 bk = r["bootstrap_cout"]; rk = bk["resume"]105 assert len(bk["VK"]) == 2000 and not np.isnan(bk["VK"]).any()106 assert rk["ecart_type"] == pytest.approx(23575, abs=60)107 assert rk["q05"] == pytest.approx(552142, abs=150) and rk["q95"] == pytest.approx(628078, abs=150)108 assert bk["rho_cout_pred"] == pytest.approx(0.225, abs=0.01) and bk["rho_cout_comp"] == pytest.approx(0.018, abs=0.01)109 assert bk["sd_VL"] == pytest.approx(29803, abs=80) and bk["sd_T28"] == pytest.approx(0.0419, abs=0.0005)110111# ---------------------------------------------------------------- combinaison112def test_poids_optimaux_et_mcg(r):113 cb = r["combinaison"]; S = cb["Sigma"]114 for w in cb["schemas"].values():115 assert w.sum() == pytest.approx(1.0, abs=1e-12)116 w = c20.poids_gls(S)117 var = w @ S @ w118 rng = np.random.default_rng(0)119 for _ in range(200): # tout autre vecteur de poids sommant à 1 a une variance ≥ celle des MCG120 d = rng.normal(size=3); d -= d.mean(); w2 = w + 0.1 * d121 assert w2 @ S @ w2 >= var - 1e-6122 # formule à deux indications = MCG sur la sous-matrice 2 × 2123 s1, s2, rho = cb["s"][0], cb["s"][1], cb["rho"]124 S2 = np.array([[s1 ** 2, rho * s1 * s2], [rho * s1 * s2, s2 ** 2]])125 assert c20.poids_optimaux_deux(s1, s2, rho) == pytest.approx(c20.poids_gls(S2)[0], rel=1e-12)126 assert c20.poids_optimaux_deux(s1, s2, 0.0) == pytest.approx((1 / s1 ** 2) / (1 / s1 ** 2 + 1 / s2 ** 2), rel=1e-12)127 assert c20.poids_optimaux_deux(s1, s2, 0.9) < 0 # poids négatif lorsque ρ > σ_h/σ_c128129def test_combinaison_chiffres(r):130 cb = r["combinaison"]; t = cb["tableau"].set_index("schema")131 assert cb["V"] == pytest.approx([575565, 637953, 579501], abs=1)132 assert cb["s"] == pytest.approx([18385, 11462, 23695], abs=60)133 assert cb["sd_spec_hed"] == pytest.approx(9420, abs=1) and cb["sd_pred_boot"] == pytest.approx(6531, abs=15)134 assert cb["R"][0, 1] == pytest.approx(0.088, abs=0.005) and cb["R"][1, 2] == pytest.approx(0.225, abs=0.01) and cb["R"][0, 2] == pytest.approx(0.018, abs=0.01)135 assert t.loc["égale (un tiers chacune)", "valeur"] == pytest.approx(597673, abs=1)136 assert cb["moyenne_simple_usage"] == pytest.approx(532741, abs=1)137 assert t.loc["inverse de la variance (indépendance)", "valeur"] == pytest.approx(614579, abs=200)138 assert t.loc["inverse de la variance (indépendance)", ["w_comp", "w_hed", "w_cout"]].values == pytest.approx([0.240, 0.616, 0.144], abs=0.005)139 assert t.loc["variance minimale avec corrélation (MCG)", "valeur"] == pytest.approx(616823, abs=250)140 assert t.loc["variance minimale avec corrélation (MCG)", ["w_comp", "w_hed", "w_cout"]].values == pytest.approx([0.248, 0.655, 0.097], abs=0.006)141 assert t.loc["raisonnée (retenue)", "valeur"] == pytest.approx(604230, abs=1)142 assert t.loc["raisonnée (retenue)", "ecart_type"] == pytest.approx(10450, abs=40)143 assert cb["conclusion"] == 605000 and cb["fourchette90"] == (590000, 625000) and cb["fourchette_conclue"] == (575000, 640000)144 assert cb["q"]["q05"] == pytest.approx(592310, abs=150) and cb["q"]["q95"] == pytest.approx(622009, abs=150)145 assert cb["ecart_conclusion_comp"] == pytest.approx(0.0511, abs=0.0005) and cb["ecart_conclusion_hed"] == pytest.approx(-0.0517, abs=0.0005)146 assert cb["w_opt_deux"][0.0] == pytest.approx(0.280, abs=0.002) and cb["w_opt_deux"][0.6] == pytest.approx(0.023, abs=0.002)147148def test_verite_synthetique(r):149 vt = r["verite"]; b = r["base"]150 assert vt["V_true"] == pytest.approx(641159, abs=5)151 assert vt["se_c"] == pytest.approx(0.0034, abs=0.0002) and vt["sigma_e"] == pytest.approx(0.0827, abs=0.001)152 assert abs(vt["ecart_hed"]) < 0.01 and vt["ecart_comp"] == pytest.approx(-0.1023, abs=0.001)153 assert r["combinaison"]["conclusion"] / vt["V_true"] - 1 == pytest.approx(-0.0564, abs=0.001)154 # la prédiction hédonique est dans l'intervalle de la vérité ± 2 erreurs-types de la constante155 assert vt["V_true_bas"] <= b["V_hed"] * 1.01 and b["V_hed"] * 0.99 <= vt["V_true_haut"]156157# ---------------------------------------------------------------- solutions des exercices158def test_solutions_exercices(r):159 e = r["exercices"]160 assert e["ex1"]["w1"] == pytest.approx(0.1379, abs=0.0001) and e["ex1"]["V"] == pytest.approx(628552, abs=1) and e["ex1"]["sd"] == pytest.approx(11142, abs=1)161 assert e["ex1"]["V_egal"] == 609000 and e["ex1"]["sd_egal"] == pytest.approx(16155, abs=1)162 assert e["ex2"]["ic_bas"] == pytest.approx(625586, abs=5) and e["ex2"]["ic_haut"] == pytest.approx(650596, abs=5)163 assert e["ex2"]["ip_bas"] == pytest.approx(539682, abs=60) and e["ex2"]["ip_haut"] == pytest.approx(749087, abs=60)164 assert e["ex2"]["rapport_largeurs"] == pytest.approx(8.36, abs=0.02)165 assert e["ex6"]["V_30_55_15"] == pytest.approx(610469, abs=1) and e["ex6"]["arrondi_30_55_15"] == 610000166 assert e["ex6"]["V_k16_raisonnee"] == pytest.approx(615880, abs=1) and e["ex6"]["arrondi_k16"] == 615000167 assert e["ex6"]["V_55_30_15"] == pytest.approx(594872, abs=1) and e["ex6"]["arrondi_55_30_15"] == 595000168 assert e["ex3"][0.3]["w1"] == pytest.approx(0.0435, abs=0.0001) and e["ex3"][0.5]["w1"] == pytest.approx(-0.0526, abs=0.0001)169 assert e["ex3"][0.3]["sd"] == pytest.approx(11935, abs=1)170 assert e["ex4"]["moyenne"] == 586150 and e["ex4"]["ecart_type"] == pytest.approx(14954, abs=1)171 assert e["ex4"]["q05"] == pytest.approx(567650, abs=1) and e["ex4"]["q95"] == pytest.approx(611650, abs=1)172 assert e["ex5"]["a"] == pytest.approx(453240, abs=1) and e["ex5"]["b"] == pytest.approx(450590, abs=1)173 assert e["ex5"]["extraction_hed"]["depreciation"] == pytest.approx(36614, abs=1) and e["ex5"]["extraction_comp"]["depreciation"] == pytest.approx(99002, abs=1)174 assert e["ex5"]["V_L_pour_hed"] == pytest.approx(335452, abs=1)175 assert e["ex7"]["delta"] == 14000 and e["ex7"]["esperance"] == 600800 and e["ex7"]["V_H"] == 605000176