spb/traite-evaluation-qc
Public
1"""Chapitre 1 — vérification des identités et des chiffres cités dans le texte et les solutions (données synthétiques, cas 1)."""2import sys, pathlib, math3import numpy as np, pytest4from scipy.stats import norm5from scipy.optimize import brentq6sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1] / "cases"))7import ch01_valeur_prix as c189@pytest.fixture(scope="module")10def t():11 return c1.tout()1213# ---------------------------------------------------------------------------- modèle et dispersion1415def test_modele_et_dispersion(t):16 m, d = t["modele"], t["dispersion"]17 assert m["n"] == 600 and m["k"] == 1618 assert m["sigma"] == pytest.approx(0.0830, abs=0.0005)19 assert d["ecart_type_residus"] == pytest.approx(0.0820, abs=0.0005)20 assert m["r2"] == pytest.approx(0.853, abs=0.001) and m["r2_ajuste"] == pytest.approx(0.849, abs=0.001)21 assert d["part_dans_pm10pct"] == pytest.approx(0.745, abs=0.002) and d["part_dans_pm5pct"] == pytest.approx(0.448, abs=0.002)22 assert abs(d["asymetrie"]) < 0.15 and abs(d["aplatissement_exces"]) < 0.523 assert abs(float(np.mean(m["residus"]))) < 1e-8 # MCO avec constante : résidus centrés2425def test_esperance_conditionnelle_sujet(t):26 p = t["pred"]; sigma = p["sigma"]27 assert p["mediane"] == pytest.approx(635821, abs=1)28 assert p["mediane"] == pytest.approx(math.exp(p["ln_pred"]), rel=1e-12)29 assert p["moyenne_lognormale"] == pytest.approx(p["mediane"] * math.exp(sigma ** 2 / 2), rel=1e-12)30 assert p["mode_lognormal"] == pytest.approx(p["mediane"] * math.exp(-sigma ** 2), rel=1e-12)31 assert p["facteur_duan"] == pytest.approx(1.00335, abs=0.00005)32 assert p["facteur_duan"] == pytest.approx(math.exp(sigma ** 2 / 2), abs=0.001) # Duan ≈ correction log-normale33 assert p["moyenne_duan"] == pytest.approx(637953, abs=1) and p["mode_lognormal"] == pytest.approx(631455, abs=1)34 assert p["ip_bas"] == pytest.approx(539682, abs=1) and p["ip_haut"] == pytest.approx(749087, abs=1)35 assert p["ip_bas"] < p["mode_lognormal"] < p["mediane"] < p["moyenne_lognormale"] < p["ip_haut"]36 # l'intervalle est symétrique en log autour de la prédiction37 assert math.log(p["ip_haut"]) - p["ln_pred"] == pytest.approx(p["ln_pred"] - math.log(p["ip_bas"]), rel=1e-9)3839def test_probabilites_ecart(t):40 sigma = t["modele"]["sigma"]41 assert c1.proba_ecart(sigma, 0.10) == pytest.approx(0.251, abs=0.002)42 assert c1.proba_ecart(sigma, 0.05) == pytest.approx(0.557, abs=0.002)43 assert c1.proba_ecart(sigma, 0.20) == pytest.approx(0.0281, abs=0.0005)44 assert c1.proba_ecart(sigma, 0.0) == pytest.approx(1.0)45 assert c1.proba_ecart(0.04, 0.10) < c1.proba_ecart(0.083, 0.10) < c1.proba_ecart(0.16, 0.10)4647# ---------------------------------------------------------------------------- principes4849def test_contributions(t):50 c = t["contrib"]; p = t["pred"]51 assert c.loc["garage", "contribution_dollars"] == pytest.approx(20291, abs=1)52 assert c.loc["sous_sol", "contribution_dollars"] == pytest.approx(13829, abs=1)53 assert c.loc["etages", "contribution_dollars"] == pytest.approx(18860, abs=1)54 assert c.loc["ln_S", "contribution_dollars"] == pytest.approx(53893, abs=1)55 assert c.loc["age", "contribution_dollars"] + c.loc["age2_100", "contribution_dollars"] == pytest.approx(-84497, abs=2)56 assert c.loc["mois", "contribution_dollars"] == pytest.approx(57385, abs=1)57 # identité : constante + somme des β_k x_k = ln V̂58 const = float(t["modele"]["beta"][0])59 assert const + c["beta_x"].sum() == pytest.approx(p["ln_pred"], abs=1e-9)60 # contribution par retrait : P̂ [1 − exp(−βx)]61 assert c.loc["garage", "contribution_dollars"] == pytest.approx(p["mediane"] * (1 - math.exp(-c.loc["garage", "beta_x"])), rel=1e-12)62 assert c.loc["garage", "effet_pct_unitaire"] == pytest.approx(0.0330, abs=0.0002)63 assert c.loc["piscine", "effet_pct_unitaire"] == pytest.approx(0.0267, abs=0.0002)64 assert c.loc["qualite", "effet_pct_unitaire"] == pytest.approx(0.0630, abs=0.0002)6566def test_rendements_decroissants(t):67 r = t["rendements"]; p = t["pred"]68 assert r["elasticite_S"] == pytest.approx(0.5391, abs=0.0005)69 assert r["prix_moyen_m2"] == pytest.approx(3853, abs=1) and r["prix_marginal_m2"] == pytest.approx(2077, abs=1)70 assert r["prix_marginal_m2"] == pytest.approx(r["elasticite_S"] * r["prix_moyen_m2"], rel=1e-12)71 assert r["gain_10m2"] == pytest.approx(20491, abs=1) and r["gain_10m2_suivants"] == pytest.approx(19958, abs=1)72 assert r["gain_10m2_suivants"] < r["gain_10m2"]73 assert r["gain_10m2"] + r["gain_10m2_suivants"] == pytest.approx(p["mediane"] * ((185 / 165) ** r["elasticite_S"] - 1), rel=1e-9)7475def test_anticipation(t):76 a = t["anticipation"]77 assert a["croissance_mensuelle"] == pytest.approx(0.003642, abs=0.00001)78 assert a["croissance_annuelle"] == pytest.approx(0.0446, abs=0.0002)79 assert a["croissance_annuelle"] == pytest.approx((1 + a["croissance_mensuelle"]) ** 12 - 1, rel=1e-12)80 assert a["mois_depuis_reference"] == pytest.approx(26.0, abs=0.1)81 assert a["facteur_cumule"] == pytest.approx(1.0992, abs=0.0002)82 assert a["valeur_a_la_reference"] == pytest.approx(578436, abs=1)83 assert a["valeur_role"] == 4680008485def test_conformite(t):86 c = t["conformite"]87 assert c.loc["Aylmer", "effet_pct"] == pytest.approx(0.0892, abs=0.0002) and c.loc["Aylmer", "valeur"] == pytest.approx(692518, abs=1)88 assert c.loc["Masson-Angers", "effet_pct"] == pytest.approx(-0.2314, abs=0.0002) and c.loc["Masson-Angers", "valeur"] == pytest.approx(488712, abs=1)89 assert c.loc["Buckingham", "valeur"] == pytest.approx(494999, abs=1) and c.loc["Gatineau", "valeur"] == pytest.approx(621798, abs=1)90 assert c.loc["Hull", "valeur"] == pytest.approx(t["pred"]["mediane"])9192def test_comparables_ch16(t):93 c = t["comparables"].set_index("id_vente")94 assert list(c.index) == ["G0574", "G0404", "G0468", "G0477", "G0575"]95 assert c.loc["G0574", "ecart_pct"] == pytest.approx(0.007, abs=0.001)96 assert c.loc["G0404", "ecart_pct"] == pytest.approx(-0.149, abs=0.001)97 assert c.loc["G0468", "ecart_pct"] == pytest.approx(-0.099, abs=0.001)98 assert c.loc["G0477", "ecart_pct"] == pytest.approx(-0.105, abs=0.001)99 assert c.loc["G0575", "ecart_pct"] == pytest.approx(-0.080, abs=0.001)100 assert c.loc["G0404", "valeur_estimee"] == pytest.approx(595030, abs=1)101 assert (c["ecart_pct"] == pytest.approx(np.exp(c["residu_log"].values) - 1, rel=1e-9)) if "residu_log" in c else True102103# ---------------------------------------------------------------------------- recherche104105def test_recherche(t):106 s = t["recherche"]; m = t["pred"]["mediane"]; P = c1.PARAMS_RECHERCHE107 assert s["prix_reserve"] / m == pytest.approx(1.0267, abs=0.0005)108 assert s["prix_moyen"] / m == pytest.approx(1.068, abs=0.003)109 assert s["cv_prix"] == pytest.approx(0.0317, abs=0.001)110 assert s["delai_moyen"] == pytest.approx(5.08, abs=0.1) and s["delai_theorique"] == pytest.approx(5.05, abs=0.02)111 assert s["delai_moyen"] == pytest.approx(s["delai_theorique"], rel=0.03)112 assert s["prix"].min() >= s["prix_reserve"] # aucun prix accepté sous le prix de réserve113 # point fixe : W = −c + δ [p E max(O, W) + (1 − p) W]114 r = s["prix_reserve"]; delta = 1 / (1 + P["taux_annuel"] / 12); c = P["c_mois"] * m115 z = np.linspace(-6, 6, 20001); w = norm.pdf(z); w /= w.sum(); O = m * np.exp(P["sigma_o"] * z)116 EmaxO = float(np.sum(w * np.maximum(O, r)))117 assert r == pytest.approx(-c + delta * (P["p_offre"] * EmaxO + (1 - P["p_offre"]) * r), rel=1e-4)118 tab = t["recherche_tableau"]119 assert tab.loc[0.002, "prix_reserve_sur_V"] == pytest.approx(1.0377, abs=0.0005) and tab.loc[0.002, "delai_moyen_mois"] == pytest.approx(6.22, abs=0.15)120 assert tab.loc[0.015, "prix_reserve_sur_V"] == pytest.approx(0.9869, abs=0.0005) and tab.loc[0.015, "delai_moyen_mois"] == pytest.approx(2.88, abs=0.1)121 assert tab["prix_reserve_sur_V"].is_monotonic_decreasing and tab["delai_moyen_mois"].is_monotonic_decreasing and tab["prix_moyen_sur_V"].is_monotonic_decreasing122123def test_phmu(t):124 p = t["phmu"]125 assert list(p["valeur_residuelle_terrain"]) == [170_000, 160_000, 140_000]126 assert p["valeur_residuelle_terrain"].idxmax() == "Unifamiliale (usage actuel)"127128def test_sensibilite_sigma(t):129 s = t["sensibilite"]130 assert s.loc[0.04, "ip95_bas_pct"] == pytest.approx(-0.0754, abs=0.0003) and s.loc[0.04, "ip95_haut_pct"] == pytest.approx(0.0816, abs=0.0003)131 assert s.loc[0.083, "ip95_bas_pct"] == pytest.approx(-0.150, abs=0.001) and s.loc[0.083, "ip95_haut_pct"] == pytest.approx(0.177, abs=0.001)132 assert s.loc[0.16, "ip95_haut_pct"] == pytest.approx(0.368, abs=0.001) and s.loc[0.16, "proba_ecart_10pct"] == pytest.approx(0.551, abs=0.002)133 assert s.loc[0.04, "proba_ecart_10pct"] == pytest.approx(0.0172, abs=0.0005)134 assert s.loc[0.083, "moyenne_sur_mediane"] == pytest.approx(0.0035, abs=0.0001) and s.loc[0.16, "moyenne_sur_mediane"] == pytest.approx(0.0129, abs=0.0001)135 assert list(s["n_pour_se_2pct"]) == [16, 36, 68, 142, 251]136137# ---------------------------------------------------------------------------- solutions des exercices138139def test_solutions_exercices():140 P = 635821.26141 # ex. 1.2 : contribution d'un ajout = P [exp(β) − 1]142 assert P * (math.exp(0.02633) - 1) == pytest.approx(16964, abs=2)143 assert P * (math.exp(0.03243) - 1) == pytest.approx(20958, abs=2)144 # ex. 1.3 : prix moyen, marginal, agrandissement de 25 m²145 b = 0.53907146 assert P / 165 == pytest.approx(3853, abs=1) and b * P / 165 == pytest.approx(2077, abs=1)147 assert P * ((190 / 165) ** b - 1) == pytest.approx(50241, abs=2)148 assert P * ((190 / 165) ** b - 1) / 25 == pytest.approx(2010, abs=1)149 # ex. 1.4 : log-normale σ = 0,12, médiane 500 000150 s, med = 0.12, 500_000151 assert med * math.exp(s ** 2 / 2) == pytest.approx(503613, abs=1) and med * math.exp(-s ** 2) == pytest.approx(492852, abs=1)152 assert med * math.exp(-1.96 * s) == pytest.approx(395206, abs=1) and med * math.exp(1.96 * s) == pytest.approx(632581, abs=1)153 assert c1.proba_ecart(s, 0.10) == pytest.approx(0.427, abs=0.001)154 # ex. 1.5 : Gordon et coût d'usage155 assert 24_000 / (0.06 - 0.02) == pytest.approx(600_000)156 assert 600_000 * (0.045 + 0.01 + 0.015 - 0.02) == pytest.approx(30_000) and 600_000 * (0.06 + 0.01 + 0.015 - 0.02) == pytest.approx(39_000)157 # ex. 1.6 : prix de réserve avec offres uniformes sur [0,9 ; 1,1], c = 0,005, δ = 1158 a, bb = 0.9, 1.1159 def fixe(c):160 return brentq(lambda r: -c + r * (r - a) / (bb - a) + (bb ** 2 - r ** 2) / (2 * (bb - a)) - r, a, bb)161 r1 = fixe(0.005); assert r1 == pytest.approx(1.0553, abs=0.0001)162 assert (bb - a) / (bb - r1) == pytest.approx(4.47, abs=0.01) and (r1 + bb) / 2 == pytest.approx(1.0776, abs=0.0001)163 r2 = fixe(0.02); assert r2 == pytest.approx(1.0106, abs=0.0001) and (bb - a) / (bb - r2) == pytest.approx(2.24, abs=0.01)164 assert r1 == pytest.approx(1.1 - math.sqrt(0.4 * 0.005), abs=1e-9) # forme fermée : r = b − sqrt(2c(b−a))165 # ex. 1.7 : ancrage, λ par MCO sans constante166 x = np.array([40, -30, 25, 60, -20, 15.0]); y = np.array([18, -12, 9, 27, -10, 6.0])167 lam = float(x @ y / (x @ x)); assert lam == pytest.approx(0.437, abs=0.001)168 assert lam * 50 == pytest.approx(21.9, abs=0.05)169 assert c1.estimation_ancree(600_000, 650_000, lam) == pytest.approx(600_000 + lam * 50_000)170