TeX 59%
Python 40.8%
1#!/usr/bin/env python32"""Experiments 5–8, 10, 11, 13, 14, 15 — cost-approach indications under baselines B0–B4.34Reads data/processed/levels_<tier>.parquet (and the "sold" supplement), applies src/cost_model/cost.py, and writes5results/tables/exp*.csv + results/raw/exp_cost.json. Benchmarks: (i) assessor building value indexed with the StatCan6BCPI (assessor alignment), (ii) asking price (market alignment), (iii) recorded sale price after listing (supplement),7(iv) Vrai Prix production estimates. Nothing is fitted on prices.8"""9import json10import sys11from pathlib import Path1213import numpy as np14import pandas as pd15import yaml1617sys.path.insert(0, str(Path(__file__).resolve().parents[1]))18from src.cost_model import cost # noqa: E40219from src.evaluation.metrics import cluster_bootstrap, ratio_stats # noqa: E4022021ROOT = Path(__file__).resolve().parents[1]22P = ROOT / "data" / "processed"23T = ROOT / "results" / "tables"24CFG = cost.CFG25R = {}26TIERS = [t for t in ["haiku", "sonnet", "opus"] if (P / f"levels_{t}.parquet").exists()]27MAIN = "sonnet" if "sonnet" in TIERS else TIERS[0]28LEVELS = ["B0", "B1", "B2", "B3", "B4"]293031def levels(tier):32 L = pd.read_parquet(P / f"levels_{tier}.parquet")33 return {lvl: g.set_index("uid") for lvl, g in L.groupby("level")}343536def run_level(df, p=None):37 out = cost.cost_indication(df, p)38 out["price"] = df["price"]; out["weight"] = df["weight"]; out["muni"] = df["roll_municipalite"]39 out["age_band"] = df["age_band"]; out["price_tier"] = df["price_tier"]; out["cma"] = df["cma"]; out["source"] = df["source"]40 out["ratio_assessor"] = out["improvements_depreciated"] / out["assessor_building_indexed"]41 out["ratio_price"] = out["V_cost"] / out["price"]42 return out434445def summarise(out, label):46 s = {"label": label, "n": int(len(out)),47 "V_median": float(out["V_cost"].median()), "L_share_median": float((out["L"] / out["V_cost"]).median()),48 "RCN_median": float(out["RCN"].median()), "D_median": float(out["D"].median()), "eff_age_median": float(out["effective_age"].median())}49 a = ratio_stats(out["improvements_depreciated"], out["assessor_building_indexed"])50 s.update({f"assessor_{k}": v for k, v in a.items()})51 m = ratio_stats(out["V_cost"], out["price"])52 s.update({f"price_{k}": v for k, v in m.items()})53 return s545556# --------------------------------------------------------------------------- Exp 5/6: decomposition and level comparison57def exp5_6():58 rows, dec = [], []59 for tier in TIERS:60 lv = levels(tier)61 for lvl in LEVELS:62 if lvl not in lv:63 continue64 for anc in ["B", "A"]:65 out = run_level(lv[lvl], {"anchor": anc})66 if anc == "B":67 out.to_parquet(P / f"cost_{tier}_{lvl}.parquet")68 s = summarise(out, f"{tier}/{lvl}/{anc}"); s["tier"] = tier; s["level"] = lvl; s["anchor"] = anc69 # cluster bootstrap on the two headline metrics70 for metric, pred, ref in [("assessor_mdape", "improvements_depreciated", "assessor_building_indexed"), ("price_mdape", "V_cost", "price")]:71 pt, lo, hi = cluster_bootstrap(out, lambda d: float((d[pred] / d[ref] - 1).abs().median() * 100), "muni", B=499)72 s[f"{metric}_lo"], s[f"{metric}_hi"] = lo, hi73 rows.append(s)74 if anc != "B":75 continue76 for grp in ["price_tier", "age_band", "cma"]:77 g = out.groupby(grp, observed=True).agg(n=("V_cost", "size"), L=("L", "median"), RCN=("RCN", "median"), D=("D", "median"),78 S=("S", "median"), improvements=("improvements_depreciated", "median"), V=("V_cost", "median"),79 ratio_assessor_median=("ratio_assessor", "median"), ratio_price_median=("ratio_price", "median")).reset_index()80 g["tier"] = tier; g["level"] = lvl; g["group"] = grp; g = g.rename(columns={grp: "value"})81 dec.append(g)82 df = pd.DataFrame(rows); df.to_csv(T / "exp5_levels_summary.csv", index=False)83 pd.concat(dec).to_csv(T / "exp5_decomposition.csv", index=False)84 R["exp5"] = df.to_dict(orient="records")85 # Exp 6: effect of visual grade/condition — B2 vs B0 deltas by visual grade & condition tercile (main tier)86 lv = levels(MAIN)87 b0 = run_level(lv["B0"]); b2 = run_level(lv["B2"]); d2 = lv["B2"]88 eff = pd.DataFrame({"grade": d2["grade"], "grade_visible": d2.get("grade_visible", True), "condition": d2["condition"],89 "delta_RCN_pct": (b2["RCN"] / b0["RCN"] - 1) * 100, "delta_D_pts": (b2["D"] - b0["D"]) * 100,90 "delta_V_pct": (b2["V_cost"] / b0["V_cost"] - 1) * 100, "delta_impr_pct": (b2["improvements_depreciated"] / b0["improvements_depreciated"] - 1) * 100,91 "ratio_assessor_B0": b0["ratio_assessor"], "ratio_assessor_B2": b2["ratio_assessor"]})92 eff["cond_tercile"] = pd.qcut(eff["condition"], 3, labels=["low", "mid", "high"], duplicates="drop")93 g1 = eff.groupby("grade", observed=True)[["delta_RCN_pct", "delta_impr_pct", "delta_V_pct", "ratio_assessor_B0", "ratio_assessor_B2"]].median().reset_index()94 g2 = eff.groupby("cond_tercile", observed=True)[["delta_D_pts", "delta_impr_pct", "delta_V_pct", "ratio_assessor_B0", "ratio_assessor_B2"]].median().reset_index()95 g1.to_csv(T / "exp6_by_grade.csv", index=False); g2.to_csv(T / "exp6_by_condition.csv", index=False)96 eff.to_parquet(P / f"exp6_effects_{MAIN}.parquet")97 R["exp6"] = {"by_grade": g1.to_dict(orient="records"), "by_condition": g2.to_dict(orient="records"),98 "share_grade_visible": float(eff["grade_visible"].mean()) if "grade_visible" in eff else None}99 return df100101102# --------------------------------------------------------------------------- Exp 7/8: sensitivities103def exp7_8():104 lv = levels(MAIN)105 base = lv["B3"]106 rows = []107 nm = CFG["temporal_regional"]["non_metro_discount_scenario"]108 scenarios = {109 "baseline (anchor B, Québec 2026 guides)": {},110 "anchor A (Altus Montréal hard costs)": {"anchor": "A"},111 "non-metro regions −15 % (Dessins Drummond 2026)": {"regional_level_factor": base["cma"].map(lambda c: 1.0 if c in nm["metro_cmas"] else nm["factor_other"])},112 "storey multiplier flat (1.0 all)": {"storey_multiplier": {"1": 1.0, "1.5": 1.0, "2": 1.0, "2.5": 1.0, "3": 1.0}},113 "regional level 0.90": {"regional_level_factor": 0.90}, "regional level 1.10": {"regional_level_factor": 1.10},114 "relative growth since 2023 (CMA BCPI / Montréal BCPI)": {"regional_level_factor": "relative"},115 "grade costs: low band": {"grade_per_sqft": {k: v[0] for k, v in cost.anchor()["grade_sensitivity_per_sqft"].items()}},116 "grade costs: high band": {"grade_per_sqft": {k: v[1] for k, v in cost.anchor()["grade_sensitivity_per_sqft"].items()}},117 "soft costs 5 %": {"soft_cost_share": 0.05}, "soft costs 15 %": {"soft_cost_share": 0.15},118 "entrepreneurial incentive 0 %": {"entrepreneurial_incentive": 0.0}, "entrepreneurial incentive 15 %": {"entrepreneurial_incentive": 0.15},119 "add-ons −30 %": {"add_on_shift": -0.30}, "add-ons +30 %": {"add_on_shift": 0.30},120 "economic life 50 y": {"economic_life": 50}, "economic life 75 y": {"economic_life": 75},121 "condition→effective age: half strength": {"condition_strength": 0.5}, "condition→effective age: 1.5× strength": {"condition_strength": 1.5},122 "external obsolescence 10 %": {"external_obsolescence": 0.10},123 "land not indexed": {"land_index_share": 0.0}, "land half indexed": {"land_index_share": 0.5},124 "no depreciation (RCN + S + L)": {"economic_life": 1e9},125 "no depreciation, land not indexed": {"economic_life": 1e9, "land_index_share": 0.0},126 }127 for name, p in scenarios.items():128 p = dict(p)129 if isinstance(p.get("regional_level_factor"), str) and p["regional_level_factor"] == "relative":130 # ratio of the CMA's BCPI level (2023=100 base) to Montréal's at the valuation quarter131 b = cost.load_bcpi(); q = CFG["valuation_quarter"]132 lvl = b[b.refPer == q].set_index("series")["value"]133 series_of = CFG["temporal_regional"]["cma_to_series"]134 p["regional_level_factor"] = base["cma"].map(lambda c: lvl.get(series_of.get(c, "composite"), lvl["composite"]) / lvl["montreal"])135 out = run_level(base, p)136 s = summarise(out, name); s["scenario"] = name; s["block"] = "7" if any(k in name for k in ["regional", "grade costs", "soft", "entrepreneurial", "add-ons", "baseline", "anchor", "metro", "storey"]) else "8"137 rows.append(s)138 df = pd.DataFrame(rows); df.to_csv(T / "exp7_8_sensitivity.csv", index=False)139 R["exp7_8"] = df.to_dict(orient="records")140 return df141142143# --------------------------------------------------------------------------- Exp 10/11: holdout CMA, roll vintage, subgroups144def exp10_calibration():145 """Calibrate the single depreciation parameter (economic life) so that the median ratio of depreciated improvements to146 the assessor's indexed building value equals 1 on the calibration CMAs; evaluate on the geographic holdout (Québec CMA).147 Prices are never used. Reported for B0 and B3 (main tier)."""148 hold = yaml.safe_load(open(ROOT / "configs" / "data.yaml"))["sample"]["holdout_cma_for_geo_oos"]149 lv = levels(MAIN)150 rows = []151 for anc, lvl in [(a, l) for a in ["B", "A"] for l in ["B0", "B3", "B4"]]:152 d = lv[lvl]153 cal, test = d[d["cma"] != hold], d[d["cma"] == hold]154 # (a) life-only calibration (reported even if it hits the grid bound), (b) RCN level-only calibration at life 60,155 # (c) joint grid. The implied hard cost per sq ft for a standard-grade house is reported for (b)/(c).156 def med_ratio(part, p):157 return float(run_level(part, {**p, "anchor": anc})["ratio_assessor"].median())158 life_only = min(range(40, 201, 5), key=lambda L: abs(med_ratio(cal, {"economic_life": L}) - 1))159 level_only = min([round(0.8 + 0.05 * i, 2) for i in range(29)], key=lambda k: abs(med_ratio(cal, {"regional_level_factor": k}) - 1))160 joint = min([(k, L) for k in [round(0.8 + 0.1 * i, 2) for i in range(15)] for L in (50, 60, 75)],161 key=lambda kl: abs(med_ratio(cal, {"regional_level_factor": kl[0], "economic_life": kl[1]}) - 1))162 std = cost.anchor({"anchor": anc})["grade_per_sqft"]["standard"]163 variants = [("public parameters", {}), (f"life-only calibration ({life_only} y)", {"economic_life": life_only}),164 (f"RCN level-only calibration (×{level_only} → standard ≈ ${std * level_only:.0f}/sq ft hard cost)", {"regional_level_factor": level_only}),165 (f"joint calibration (×{joint[0]}, life {joint[1]} y)", {"regional_level_factor": joint[0], "economic_life": joint[1]})]166 for name, part in [("calibration", cal), ("holdout", test)]:167 for tag, p in variants:168 out = run_level(part, {**p, "anchor": anc})169 a = ratio_stats(out["improvements_depreciated"], out["assessor_building_indexed"]); m = ratio_stats(out["V_cost"], out["price"])170 rows.append({"anchor": anc, "level": lvl, "set": name, "parameters": tag, "economic_life": p.get("economic_life", CFG["depreciation"]["economic_life_years"]),171 "level_factor": p.get("regional_level_factor", 1.0), "implied_standard_per_sqft": std * p.get("regional_level_factor", 1.0),172 "n": len(out), "D_median": float(out["D"].median()), "assessor_median_ratio": a["median_ratio"], "assessor_mdape": a["mdape"],173 "assessor_cod": a["cod"], "price_median_ratio": m["median_ratio"], "price_mdape": m["mdape"], "price_within_20pct": m["within_20pct"]})174 df = pd.DataFrame(rows); df.to_csv(T / "exp10_calibration.csv", index=False)175 R["exp10_calibration"] = df.to_dict(orient="records")176 return df177178179def exp10_11():180 rows = []181 for tier in TIERS:182 lv = levels(tier)183 for lvl in ["B0", "B3"]:184 if lvl not in lv:185 continue186 out = run_level(lv[lvl])187 df0 = pd.read_parquet(P / "sample.parquet").set_index("uid").reindex(out.index)188 out["roll_vintage"] = df0["roll_market_date"].astype(str).str[:4]189 out["source"] = df0["source"]190 out["n_images"] = df0["n_images_used"]191 for grp in ["cma", "roll_vintage", "age_band", "price_tier", "source"]:192 for val, g in out.groupby(grp, observed=True):193 if len(g) < 15:194 continue195 a = ratio_stats(g["improvements_depreciated"], g["assessor_building_indexed"]); m = ratio_stats(g["V_cost"], g["price"])196 rows.append({"tier": tier, "level": lvl, "group": grp, "value": str(val), "n": len(g),197 "assessor_median_ratio": a["median_ratio"], "assessor_mdape": a["mdape"], "assessor_cod": a["cod"],198 "price_median_ratio": m["median_ratio"], "price_mdape": m["mdape"]})199 df = pd.DataFrame(rows); df.to_csv(T / "exp10_11_subgroups.csv", index=False)200 R["exp10_11"] = df.to_dict(orient="records")201 return df202203204# --------------------------------------------------------------------------- Exp 13: counterfactual scenarios205def exp13():206 lv = levels(MAIN); b3 = lv["B3"]207 base = run_level(b3)208 rows = []209 order = ["basic", "standard", "upgraded", "luxury"]210 for name, mod in [211 ("grade +1 level", lambda d: d.assign(grade=d["grade"].map(lambda g: order[min(3, order.index(g) + 1)] if g in order else g))),212 ("grade −1 level", lambda d: d.assign(grade=d["grade"].map(lambda g: order[max(0, order.index(g) - 1)] if g in order else g))),213 ("condition +1", lambda d: d.assign(condition=(d["condition"] + 1).clip(upper=5))),214 ("condition −1", lambda d: d.assign(condition=(d["condition"] - 1).clip(lower=1))),215 ("extensive renovation evidence", lambda d: d.assign(renovation="extensive")),216 ("basement finished", lambda d: d.assign(basement="finished")),217 ("attached garage added", lambda d: d.assign(garage="attached")),218 ]:219 out = run_level(mod(b3))220 dv = (out["V_cost"] / base["V_cost"]) - 1221 aff = dv.abs() > 1e-9222 rows.append({"scenario": name, "share_affected": float(aff.mean()),223 "median_delta_V_pct": float(dv.median() * 100), "median_delta_V_pct_affected": float(dv[aff].median() * 100) if aff.any() else 0.0,224 "median_delta_improvements_pct": float(((out["improvements_depreciated"] / base["improvements_depreciated"]) - 1).median() * 100),225 "median_delta_improvements_pct_affected": float(((out["improvements_depreciated"] / base["improvements_depreciated"]) - 1)[aff].median() * 100) if aff.any() else 0.0,226 "median_delta_V_cad": float((out["V_cost"] - base["V_cost"]).median()),227 "median_delta_V_cad_affected": float((out["V_cost"] - base["V_cost"])[aff].median()) if aff.any() else 0.0})228 df = pd.DataFrame(rows); df.to_csv(T / "exp13_scenarios.csv", index=False)229 R["exp13"] = df.to_dict(orient="records")230 return df231232233# --------------------------------------------------------------------------- Exp 14: Monte Carlo propagation234def exp14(n_draws=400, seed=20261020):235 rng = np.random.default_rng(seed)236 lv = levels(MAIN); b4 = lv["B4"].copy()237 props = pd.read_parquet(P / f"props_{MAIN}_K10.parquet").set_index("uid").reindex(b4.index)238 order = ["basic", "standard", "upgraded", "luxury"]239 gs = cost.anchor()["grade_sensitivity_per_sqft"]240 base = run_level(b4)241 draws = np.zeros((n_draws, len(b4)))242 for i in range(n_draws):243 d = b4.copy()244 # attribute uncertainty: grade jitter scaled by (1 − confidence) and by missing grade; condition noise sd 0.5 (1.0 if missing)245 conf = props["confidence_mean"].fillna(0.75).values246 miss_g = d["uncertain_grade"].fillna(False).astype(float).values if "uncertain_grade" in d else np.zeros(len(d))247 shift = rng.normal(0, 0.35 + 0.6 * (1 - conf) + 0.6 * miss_g)248 gi = d["grade"].map(lambda g: order.index(g) if g in order else 1).values + shift249 d["grade"] = [order[int(min(3, max(0, round(x))))] for x in gi]250 miss_c = d["uncertain_condition"].fillna(False).astype(float).values if "uncertain_condition" in d else np.zeros(len(d))251 d["condition"] = (d["condition"] + rng.normal(0, 0.5 + 0.5 * miss_c)).clip(1, 5)252 # parameter uncertainty: unit cost within band (uniform), life ~ U(50,75), soft ~ U(.05,.15), EI ~ U(0,.15), land share ~ U(0,1), regional ~ U(.9,1.1)253 u = rng.uniform()254 p = {"grade_per_sqft": {k: v[0] + u * (v[1] - v[0]) for k, v in gs.items()},255 "economic_life": rng.uniform(50, 75), "soft_cost_share": rng.uniform(0.05, 0.15),256 "entrepreneurial_incentive": rng.uniform(0.0, 0.15), "land_index_share": rng.uniform(0, 1),257 "regional_level_factor": rng.uniform(0.9, 1.1), "add_on_shift": rng.uniform(-0.3, 0.3)}258 draws[i] = cost.cost_indication(d, p)["V_cost"].values259 lo, med, hi = np.percentile(draws, [10, 50, 90], axis=0)260 mc = pd.DataFrame({"V_point": base["V_cost"].values, "V_mc_median": med, "V_p10": lo, "V_p90": hi, "price": base["price"].values,261 "assessor_total_indexed": (base["L"] + base["assessor_building_indexed"]).values}, index=b4.index)262 mc["rel_width"] = (mc["V_p90"] - mc["V_p10"]) / mc["V_mc_median"]263 mc["price_in_interval"] = (mc["price"] >= mc["V_p10"]) & (mc["price"] <= mc["V_p90"])264 mc["assessor_in_interval"] = (mc["assessor_total_indexed"] >= mc["V_p10"]) & (mc["assessor_total_indexed"] <= mc["V_p90"])265 mc.to_parquet(P / f"exp14_mc_{MAIN}.parquet")266 # variance decomposition: one-at-a-time, holding others at baseline267 oat = {}268 for name, p in [("unit cost band", {"grade_per_sqft": {k: v[1] for k, v in gs.items()}}), ("economic life 50", {"economic_life": 50}),269 ("soft+EI high", {"soft_cost_share": 0.15, "entrepreneurial_incentive": 0.15}), ("land not indexed", {"land_index_share": 0.0}),270 ("regional 1.10", {"regional_level_factor": 1.10})]:271 oat[name] = float(((cost.cost_indication(b4, p)["V_cost"] / base["V_cost"]) - 1).abs().median() * 100)272 d_g = b4.copy(); d_g["grade"] = d_g["grade"].map(lambda g: order[min(3, order.index(g) + 1)] if g in order else g)273 oat["grade +1"] = float(((cost.cost_indication(d_g)["V_cost"] / base["V_cost"]) - 1).abs().median() * 100)274 d_c = b4.copy(); d_c["condition"] = (d_c["condition"] + 1).clip(upper=5)275 oat["condition +1"] = float(((cost.cost_indication(d_c)["V_cost"] / base["V_cost"]) - 1).abs().median() * 100)276 R["exp14"] = {"n_draws": n_draws, "rel_width_median": float(mc["rel_width"].median()), "rel_width_p25_p75": [float(mc["rel_width"].quantile(.25)), float(mc["rel_width"].quantile(.75))],277 "price_coverage_80": float(mc["price_in_interval"].mean()), "assessor_total_coverage_80": float(mc["assessor_in_interval"].mean()),278 "one_at_a_time_median_abs_pct": oat}279 pd.DataFrame([R["exp14"]]).to_csv(T / "exp14_mc_summary.csv", index=False)280 pd.Series(oat, name="median_abs_delta_pct").to_csv(T / "exp14_oat.csv")281 return mc282283284# --------------------------------------------------------------------------- Exp 15: market alignment incl. recorded sales285def exp15():286 rows = []287 lv = levels(MAIN)288 smp = pd.read_parquet(P / "sample.parquet").set_index("uid")289 for lvl in LEVELS:290 out = run_level(lv[lvl])291 s = {"level": lvl, "n": len(out)}292 s.update({f"price_{k}": v for k, v in ratio_stats(out["V_cost"], out["price"]).items()})293 # Vrai Prix production estimates as external benchmarks (unknown formula for cost_est)294 for col in ["vp_est", "vp_cost_est", "vp_model_est"]:295 s.update({f"{col}_{k}": v for k, v in ratio_stats(out["V_cost"], smp.loc[out.index, col]).items() if k in ("n", "median_ratio", "mdape")})296 rows.append(s)297 # production benchmarks against price (for context)298 for col in ["vp_est", "vp_cost_est", "roll_model_est"]:299 s = {"level": f"benchmark:{col}", "n": int(smp[col].notna().sum())}300 s.update({f"price_{k}": v for k, v in ratio_stats(smp[col], smp["price"]).items()})301 rows.append(s)302 # recorded sales after listing (supplement + sample units with a later sale)303 sold_rows = []304 sales = pd.read_parquet(P / "frame_sales_after_listing.parquet").sort_values("date").drop_duplicates("uid", keep="last").set_index("uid")305 for tag, lvfile, sfile in [("sold_supplement", P / f"levels_{MAIN}_sold.parquet", P / "sample_sold.parquet"), ("sample", P / f"levels_{MAIN}.parquet", P / "sample.parquet")]:306 if not lvfile.exists():307 continue308 L = pd.read_parquet(lvfile)309 for lvl in ["B0", "B3"]:310 d = L[L.level == lvl].set_index("uid")311 d = d[d.index.isin(sales.index)]312 if len(d) == 0:313 continue314 out = run_level(d)315 sp = sales.loc[out.index, "amount"]316 r = ratio_stats(out["V_cost"], sp); r.update({"set": tag, "level": lvl, "ask_vs_sale_median": float((out["price"] / sp).median())})317 sold_rows.append(r)318 df = pd.DataFrame(rows); df.to_csv(T / "exp15_market_alignment.csv", index=False)319 ds = pd.DataFrame(sold_rows); ds.to_csv(T / "exp15_recorded_sales.csv", index=False)320 R["exp15"] = {"market": df.to_dict(orient="records"), "sales": ds.to_dict(orient="records")}321 return df322323324def main():325 R["tiers"] = TIERS; R["main_tier"] = MAIN; R["schedule_version"] = CFG["version"]326 exp5_6(); exp7_8(); exp10_calibration(); exp10_11(); exp13(); exp14(); exp15()327 json.dump(R, open(ROOT / "results" / "raw" / "exp_cost.json", "w"), indent=1, default=str)328 print(pd.read_csv(T / "exp5_levels_summary.csv")[["tier", "level", "n", "V_median", "L_share_median", "D_median", "assessor_median_ratio", "assessor_mdape", "assessor_cod", "price_median_ratio", "price_mdape", "price_within_20pct"]].round(3).to_string())329 print(pd.read_csv(T / "exp7_8_sensitivity.csv")[["scenario", "V_median", "assessor_median_ratio", "assessor_mdape", "price_median_ratio", "price_mdape"]].round(3).to_string())330 print(json.dumps(R["exp14"], indent=1)); print(pd.read_csv(T / "exp15_recorded_sales.csv").round(3).to_string())331 print(pd.read_csv(T / "exp10_calibration.csv").round(3).to_string())332 print(pd.read_csv(T / "exp15_market_alignment.csv")[[c for c in pd.read_csv(T / "exp15_market_alignment.csv").columns if c in ("level", "n", "price_median_ratio", "price_mdape", "vp_est_median_ratio", "vp_cost_est_median_ratio", "vp_cost_est_mdape", "vp_model_est_median_ratio")]].round(3).to_string())333334335if __name__ == "__main__":336 main()337