"""Chapitre 1 — vérification des identités et des chiffres cités dans le texte et les solutions (données synthétiques, cas 1).""" import sys, pathlib, math import numpy as np, pytest from scipy.stats import norm from scipy.optimize import brentq sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1] / "cases")) import ch01_valeur_prix as c1 @pytest.fixture(scope="module") def t(): return c1.tout() # ---------------------------------------------------------------------------- modèle et dispersion def test_modele_et_dispersion(t): m, d = t["modele"], t["dispersion"] assert m["n"] == 600 and m["k"] == 16 assert m["sigma"] == pytest.approx(0.0830, abs=0.0005) assert d["ecart_type_residus"] == pytest.approx(0.0820, abs=0.0005) assert m["r2"] == pytest.approx(0.853, abs=0.001) and m["r2_ajuste"] == pytest.approx(0.849, abs=0.001) assert d["part_dans_pm10pct"] == pytest.approx(0.745, abs=0.002) and d["part_dans_pm5pct"] == pytest.approx(0.448, abs=0.002) assert abs(d["asymetrie"]) < 0.15 and abs(d["aplatissement_exces"]) < 0.5 assert abs(float(np.mean(m["residus"]))) < 1e-8 # MCO avec constante : résidus centrés def test_esperance_conditionnelle_sujet(t): p = t["pred"]; sigma = p["sigma"] assert p["mediane"] == pytest.approx(635821, abs=1) assert p["mediane"] == pytest.approx(math.exp(p["ln_pred"]), rel=1e-12) assert p["moyenne_lognormale"] == pytest.approx(p["mediane"] * math.exp(sigma ** 2 / 2), rel=1e-12) assert p["mode_lognormal"] == pytest.approx(p["mediane"] * math.exp(-sigma ** 2), rel=1e-12) assert p["facteur_duan"] == pytest.approx(1.00335, abs=0.00005) assert p["facteur_duan"] == pytest.approx(math.exp(sigma ** 2 / 2), abs=0.001) # Duan ≈ correction log-normale assert p["moyenne_duan"] == pytest.approx(637953, abs=1) and p["mode_lognormal"] == pytest.approx(631455, abs=1) assert p["ip_bas"] == pytest.approx(539682, abs=1) and p["ip_haut"] == pytest.approx(749087, abs=1) assert p["ip_bas"] < p["mode_lognormal"] < p["mediane"] < p["moyenne_lognormale"] < p["ip_haut"] # l'intervalle est symétrique en log autour de la prédiction assert math.log(p["ip_haut"]) - p["ln_pred"] == pytest.approx(p["ln_pred"] - math.log(p["ip_bas"]), rel=1e-9) def test_probabilites_ecart(t): sigma = t["modele"]["sigma"] assert c1.proba_ecart(sigma, 0.10) == pytest.approx(0.251, abs=0.002) assert c1.proba_ecart(sigma, 0.05) == pytest.approx(0.557, abs=0.002) assert c1.proba_ecart(sigma, 0.20) == pytest.approx(0.0281, abs=0.0005) assert c1.proba_ecart(sigma, 0.0) == pytest.approx(1.0) assert c1.proba_ecart(0.04, 0.10) < c1.proba_ecart(0.083, 0.10) < c1.proba_ecart(0.16, 0.10) # ---------------------------------------------------------------------------- principes def test_contributions(t): c = t["contrib"]; p = t["pred"] assert c.loc["garage", "contribution_dollars"] == pytest.approx(20291, abs=1) assert c.loc["sous_sol", "contribution_dollars"] == pytest.approx(13829, abs=1) assert c.loc["etages", "contribution_dollars"] == pytest.approx(18860, abs=1) assert c.loc["ln_S", "contribution_dollars"] == pytest.approx(53893, abs=1) assert c.loc["age", "contribution_dollars"] + c.loc["age2_100", "contribution_dollars"] == pytest.approx(-84497, abs=2) assert c.loc["mois", "contribution_dollars"] == pytest.approx(57385, abs=1) # identité : constante + somme des β_k x_k = ln V̂ const = float(t["modele"]["beta"][0]) assert const + c["beta_x"].sum() == pytest.approx(p["ln_pred"], abs=1e-9) # contribution par retrait : P̂ [1 − exp(−βx)] assert c.loc["garage", "contribution_dollars"] == pytest.approx(p["mediane"] * (1 - math.exp(-c.loc["garage", "beta_x"])), rel=1e-12) assert c.loc["garage", "effet_pct_unitaire"] == pytest.approx(0.0330, abs=0.0002) assert c.loc["piscine", "effet_pct_unitaire"] == pytest.approx(0.0267, abs=0.0002) assert c.loc["qualite", "effet_pct_unitaire"] == pytest.approx(0.0630, abs=0.0002) def test_rendements_decroissants(t): r = t["rendements"]; p = t["pred"] assert r["elasticite_S"] == pytest.approx(0.5391, abs=0.0005) assert r["prix_moyen_m2"] == pytest.approx(3853, abs=1) and r["prix_marginal_m2"] == pytest.approx(2077, abs=1) assert r["prix_marginal_m2"] == pytest.approx(r["elasticite_S"] * r["prix_moyen_m2"], rel=1e-12) assert r["gain_10m2"] == pytest.approx(20491, abs=1) and r["gain_10m2_suivants"] == pytest.approx(19958, abs=1) assert r["gain_10m2_suivants"] < r["gain_10m2"] assert r["gain_10m2"] + r["gain_10m2_suivants"] == pytest.approx(p["mediane"] * ((185 / 165) ** r["elasticite_S"] - 1), rel=1e-9) def test_anticipation(t): a = t["anticipation"] assert a["croissance_mensuelle"] == pytest.approx(0.003642, abs=0.00001) assert a["croissance_annuelle"] == pytest.approx(0.0446, abs=0.0002) assert a["croissance_annuelle"] == pytest.approx((1 + a["croissance_mensuelle"]) ** 12 - 1, rel=1e-12) assert a["mois_depuis_reference"] == pytest.approx(26.0, abs=0.1) assert a["facteur_cumule"] == pytest.approx(1.0992, abs=0.0002) assert a["valeur_a_la_reference"] == pytest.approx(578436, abs=1) assert a["valeur_role"] == 468000 def test_conformite(t): c = t["conformite"] assert c.loc["Aylmer", "effet_pct"] == pytest.approx(0.0892, abs=0.0002) and c.loc["Aylmer", "valeur"] == pytest.approx(692518, abs=1) 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) assert c.loc["Buckingham", "valeur"] == pytest.approx(494999, abs=1) and c.loc["Gatineau", "valeur"] == pytest.approx(621798, abs=1) assert c.loc["Hull", "valeur"] == pytest.approx(t["pred"]["mediane"]) def test_comparables_ch16(t): c = t["comparables"].set_index("id_vente") assert list(c.index) == ["G0574", "G0404", "G0468", "G0477", "G0575"] assert c.loc["G0574", "ecart_pct"] == pytest.approx(0.007, abs=0.001) assert c.loc["G0404", "ecart_pct"] == pytest.approx(-0.149, abs=0.001) assert c.loc["G0468", "ecart_pct"] == pytest.approx(-0.099, abs=0.001) assert c.loc["G0477", "ecart_pct"] == pytest.approx(-0.105, abs=0.001) assert c.loc["G0575", "ecart_pct"] == pytest.approx(-0.080, abs=0.001) assert c.loc["G0404", "valeur_estimee"] == pytest.approx(595030, abs=1) assert (c["ecart_pct"] == pytest.approx(np.exp(c["residu_log"].values) - 1, rel=1e-9)) if "residu_log" in c else True # ---------------------------------------------------------------------------- recherche def test_recherche(t): s = t["recherche"]; m = t["pred"]["mediane"]; P = c1.PARAMS_RECHERCHE assert s["prix_reserve"] / m == pytest.approx(1.0267, abs=0.0005) assert s["prix_moyen"] / m == pytest.approx(1.068, abs=0.003) assert s["cv_prix"] == pytest.approx(0.0317, abs=0.001) assert s["delai_moyen"] == pytest.approx(5.08, abs=0.1) and s["delai_theorique"] == pytest.approx(5.05, abs=0.02) assert s["delai_moyen"] == pytest.approx(s["delai_theorique"], rel=0.03) assert s["prix"].min() >= s["prix_reserve"] # aucun prix accepté sous le prix de réserve # point fixe : W = −c + δ [p E max(O, W) + (1 − p) W] r = s["prix_reserve"]; delta = 1 / (1 + P["taux_annuel"] / 12); c = P["c_mois"] * m z = np.linspace(-6, 6, 20001); w = norm.pdf(z); w /= w.sum(); O = m * np.exp(P["sigma_o"] * z) EmaxO = float(np.sum(w * np.maximum(O, r))) assert r == pytest.approx(-c + delta * (P["p_offre"] * EmaxO + (1 - P["p_offre"]) * r), rel=1e-4) tab = t["recherche_tableau"] 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) 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) 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_decreasing def test_phmu(t): p = t["phmu"] assert list(p["valeur_residuelle_terrain"]) == [170_000, 160_000, 140_000] assert p["valeur_residuelle_terrain"].idxmax() == "Unifamiliale (usage actuel)" def test_sensibilite_sigma(t): s = t["sensibilite"] 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) 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) 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) assert s.loc[0.04, "proba_ecart_10pct"] == pytest.approx(0.0172, abs=0.0005) 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) assert list(s["n_pour_se_2pct"]) == [16, 36, 68, 142, 251] # ---------------------------------------------------------------------------- solutions des exercices def test_solutions_exercices(): P = 635821.26 # ex. 1.2 : contribution d'un ajout = P [exp(β) − 1] assert P * (math.exp(0.02633) - 1) == pytest.approx(16964, abs=2) assert P * (math.exp(0.03243) - 1) == pytest.approx(20958, abs=2) # ex. 1.3 : prix moyen, marginal, agrandissement de 25 m² b = 0.53907 assert P / 165 == pytest.approx(3853, abs=1) and b * P / 165 == pytest.approx(2077, abs=1) assert P * ((190 / 165) ** b - 1) == pytest.approx(50241, abs=2) assert P * ((190 / 165) ** b - 1) / 25 == pytest.approx(2010, abs=1) # ex. 1.4 : log-normale σ = 0,12, médiane 500 000 s, med = 0.12, 500_000 assert med * math.exp(s ** 2 / 2) == pytest.approx(503613, abs=1) and med * math.exp(-s ** 2) == pytest.approx(492852, abs=1) assert med * math.exp(-1.96 * s) == pytest.approx(395206, abs=1) and med * math.exp(1.96 * s) == pytest.approx(632581, abs=1) assert c1.proba_ecart(s, 0.10) == pytest.approx(0.427, abs=0.001) # ex. 1.5 : Gordon et coût d'usage assert 24_000 / (0.06 - 0.02) == pytest.approx(600_000) 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) # ex. 1.6 : prix de réserve avec offres uniformes sur [0,9 ; 1,1], c = 0,005, δ = 1 a, bb = 0.9, 1.1 def fixe(c): return brentq(lambda r: -c + r * (r - a) / (bb - a) + (bb ** 2 - r ** 2) / (2 * (bb - a)) - r, a, bb) r1 = fixe(0.005); assert r1 == pytest.approx(1.0553, abs=0.0001) assert (bb - a) / (bb - r1) == pytest.approx(4.47, abs=0.01) and (r1 + bb) / 2 == pytest.approx(1.0776, abs=0.0001) 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) assert r1 == pytest.approx(1.1 - math.sqrt(0.4 * 0.005), abs=1e-9) # forme fermée : r = b − sqrt(2c(b−a)) # ex. 1.7 : ancrage, λ par MCO sans constante x = np.array([40, -30, 25, 60, -20, 15.0]); y = np.array([18, -12, 9, 27, -10, 6.0]) lam = float(x @ y / (x @ x)); assert lam == pytest.approx(0.437, abs=0.001) assert lam * 50 == pytest.approx(21.9, abs=0.05) assert c1.estimation_ancree(600_000, 650_000, lam) == pytest.approx(600_000 + lam * 50_000)