TeX 59%
Python 40.8%
1"""LaTeX table fragments (booktabs, no vertical rules) and the numbers.tex macro file, generated from saved results only.2Every number that appears in the manuscript text comes from paper/tables/numbers.tex."""3from __future__ import annotations45import json6import re7from pathlib import Path89import numpy as np10import pandas as pd11import yaml1213ROOT = Path(__file__).resolve().parents[2]14P = ROOT / "data" / "processed"15T = ROOT / "results" / "tables"16OUT = ROOT / "results" / "tables" / "tex"17OUT.mkdir(parents=True, exist_ok=True)18NUM = {}192021def esc(s) -> str:22 s = str(s).replace("−", "$-$").replace("→", "$\\rightarrow$").replace("≈", "$\\approx$").replace("×", "$\\times$").replace("≥", "$\\geq$").replace("≤", "$\\leq$")23 for ch in ["&", "%", "_", "#"]:24 s = re.sub(r"(?<!\\)" + re.escape(ch), "\\" + ch, s)25 return s262728def fmt(x, nd=1, pct=False, money=False, comma=True):29 if x is None or (isinstance(x, float) and np.isnan(x)):30 return "--"31 if money:32 return f"\\${x:,.0f}"33 if pct:34 return f"{x * 100:.{nd}f}"35 if isinstance(x, (int, np.integer)) or (isinstance(x, float) and x.is_integer() and abs(x) >= 100):36 return f"{int(x):,}" if comma else str(int(x))37 return f"{x:.{nd}f}"383940DIGITS = {"0": "Zero", "1": "One", "2": "Two", "3": "Three", "4": "Four", "5": "Five", "6": "Six", "7": "Seven", "8": "Eight", "9": "Nine"}414243def key(s) -> str:44 s = str(s).replace("+", "Plus").replace("−", "Minus").replace("-", "Minus")45 return "".join(DIGITS.get(ch, ch) for ch in s)464748def macro(name: str, value: str):49 name = key(name)50 name = re.sub(r"[^A-Za-z]", "", name)51 if name in NUM and NUM[name] != value:52 print("WARNING macro collision", name, NUM[name], value)53 NUM[name] = value545556def tabular(df: pd.DataFrame, colspec: str, header: list[str] | None = None, note_cols=None) -> str:57 header = header or list(df.columns)58 lines = [f"\\begin{{tabular}}{{{colspec}}}", "\\toprule", " & ".join(esc(h) for h in header) + " \\\\", "\\midrule"]59 for _, r in df.iterrows():60 lines.append(" & ".join(esc(v) for v in r.values) + " \\\\")61 lines += ["\\bottomrule", "\\end{tabular}"]62 return "\n".join(lines)636465def write(name: str, content: str):66 (OUT / f"{name}.tex").write_text(content + "\n", encoding="utf-8")676869# ------------------------------------------------------------------ T1 exclusions70def t_exclusions():71 df = pd.read_csv(T / "frame_exclusions.csv")72 df["description"] = df["description"].str.replace(r": \d+$", "", regex=True).str.replace("('Maison', 'Détachée', 'Jumelé', 'Maison de ville')", "house types", regex=False)73 d = df[["rule", "description", "removed", "remaining"]].copy()74 d["removed"] = d["removed"].map(lambda v: f"{v:,}"); d["remaining"] = d["remaining"].map(lambda v: f"{v:,}")75 write("frame_exclusions", tabular(d, "lp{10.5cm}rr", ["Rule", "Description", "Removed", "Remaining"]))76 macro("nFrameStart", f"{int(df.iloc[0]['remaining']):,}"); macro("nFrame", f"{int(df.iloc[-1]['remaining']):,}")77 r10 = df[df.rule == "R10"]78 if len(r10):79 macro("nRobotsExcluded", f"{int(r10.iloc[0]['removed']):,}")808182# ------------------------------------------------------------------ T2 sample descriptives83def t_sample():84 smp = pd.read_parquet(P / "sample.parquet"); frame = pd.read_parquet(P / "frame.parquet")85 rows = []86 for col, lab in [("cma", "Metropolitan area"), ("age_band", "Building age (years)"), ("price_tier", "Asking-price quartile")]:87 for v in (sorted(frame[col].astype(str).unique()) if col != "cma" else frame[col].value_counts().index):88 rows.append({"Stratum": f"{lab}: {v}", "Frame n": f"{int((frame[col].astype(str) == str(v)).sum()):,}",89 "Frame %": f"{(frame[col].astype(str) == str(v)).mean() * 100:.1f}",90 "Sample n": f"{int((smp[col].astype(str) == str(v)).sum()):,}", "Sample %": f"{(smp[col].astype(str) == str(v)).mean() * 100:.1f}"})91 write("sample_strata", tabular(pd.DataFrame(rows), "lrrrr"))92 desc = []93 for col, lab, f in [("price", "Asking price (\\$)", lambda s: s.quantile([.1, .5, .9])), ("roll_floor_area_m2", "Floor area, roll (m$^2$)", lambda s: s.quantile([.1, .5, .9])),94 ("roll_lot_m2", "Lot area, roll (m$^2$)", lambda s: s.quantile([.1, .5, .9])), ("age_2026", "Age in 2026 (years)", lambda s: s.quantile([.1, .5, .9])),95 ("roll_land_value", "Roll land value (\\$)", lambda s: s.quantile([.1, .5, .9])), ("roll_building_value", "Roll building value (\\$)", lambda s: s.quantile([.1, .5, .9])),96 ("n_images", "Images available", lambda s: s.quantile([.1, .5, .9])), ("desc_len", "Description length (chars)", lambda s: s.quantile([.1, .5, .9]))]:97 q = f(smp[col].astype(float)); qf = f(frame[col].astype(float))98 desc.append({"Variable": lab, "Sample P10": fmt(q.iloc[0], 0), "Sample median": fmt(q.iloc[1], 0), "Sample P90": fmt(q.iloc[2], 0), "Frame median": fmt(qf.iloc[1], 0)})99 write("sample_descriptives", tabular(pd.DataFrame(desc), "lrrrr"))100 macro("nSample", f"{len(smp):,}"); macro("nImagesSample", f"{int(pd.read_parquet(P / 'sample_images.parquet').shape[0]):,}")101 macro("medianPriceSample", f"{smp.price.median():,.0f}"); macro("medianAreaSample", f"{smp.roll_floor_area_m2.median():.0f}")102 macro("nStrata", str(smp["stratum"].nunique())); macro("nMunicipalitiesSample", str(smp["roll_municipalite"].nunique()))103 macro("medianImagesSample", f"{smp.n_images.median():.0f}")104 bal = pd.read_csv(T / "robots_exclusion_balance.csv", index_col=0)105 macro("robotsExcludedPriceMedian", f"{bal.loc['price', 'excluded_robots_median']:,.0f}"); macro("robotsRetainedPriceMedian", f"{bal.loc['price', 'retained_median']:,.0f}")106 macro("robotsExcludedLandMedian", f"{bal.loc['roll_land_value', 'excluded_robots_median']:,.0f}"); macro("robotsRetainedLandMedian", f"{bal.loc['roll_land_value', 'retained_median']:,.0f}")107 for c in ["claim_siding", "claim_roof", "claim_basement", "claim_garage", "claim_year_built", "claim_assess_building"]:108 macro("cov" + c.replace("claim_", "").title().replace("_", ""), f"{smp[c].notna().mean() * 100:.0f}")109110111# ------------------------------------------------------------------ T3 extraction run summary112def t_run():113 rows = []114 usage = pd.read_json(ROOT / "results" / "raw" / "api_usage.jsonl", lines=True)115 price = {"claude-haiku-5-5": (0.10, 0.50), "claude-sonnet-5-5": (2.0, 10.0), "claude-opus-5-5": (4.0, 20.0)}116 total = 0.0117 for tier in ["haiku", "sonnet", "opus"]:118 f = P / f"images_{tier}.parquet"119 if not f.exists():120 continue121 img = pd.read_parquet(f); txt = pd.read_parquet(P / f"text_{tier}.parquet") if (P / f"text_{tier}.parquet").exists() else None122 u = usage[usage.model == f"claude-{tier}-5-5"]123 usd = ((u.input_tokens.sum() * price[f"claude-{tier}-5-5"][0] + u.output_tokens.sum() * price[f"claude-{tier}-5-5"][1]) / 1e6) * 0.5124 total += usd125 err = img[~img.ok]126 rows.append({"Extractor": f"Claude {tier.capitalize()} 5.5", "Listings": f"{img.uid.nunique():,}", "Image calls": f"{len(img):,}",127 "Succeeded": f"{img.ok.mean() * 100:.1f}\\%", "URL failures": f"{int(err.error_message.fillna('').str.contains('download').sum()):,}",128 "robots.txt refusals": f"{int(err.error_message.fillna('').str.contains('robots').sum()):,}",129 "Text calls": f"{len(txt):,}" if txt is not None else "--",130 "Mean tokens in / out": f"{img.input_tokens.mean():,.0f} / {img.output_tokens.mean():,.0f}", "Cost (US\\$, batch)": f"{usd:,.0f}"})131 macro(f"n{tier.capitalize()}Images", f"{len(img):,}"); macro(f"n{tier.capitalize()}Listings", f"{img.uid.nunique():,}")132 macro(f"ok{tier.capitalize()}Pct", f"{img.ok.mean() * 100:.1f}")133 write("extraction_runs", tabular(pd.DataFrame(rows), "lrrrrrrrr"))134 macro("apiCostUSD", f"{total:,.0f}")135136137# ------------------------------------------------------------------ T4 agreement138def t_agreement():139 df = pd.read_csv(T / "exp1_agreement.csv")140 df = df[~df.attribute.str.contains("kitchen")]141 d = df.copy()142 d["attribute"] = d["attribute"].str.replace("∈", "in").str.replace("TEXT ", "Text: ")143 out = d[["tier", "channel", "attribute", "n_ref", "coverage", "n_compared", "accuracy", "kappa"]].copy()144 out["coverage"] = out["coverage"].map(lambda v: fmt(v, 1, pct=True)); out["accuracy"] = out["accuracy"].map(lambda v: fmt(v, 1, pct=True))145 out["kappa"] = out["kappa"].map(lambda v: fmt(v, 2)); out["n_ref"] = out["n_ref"].map(lambda v: f"{int(v):,}"); out["n_compared"] = out["n_compared"].map(lambda v: f"{int(v):,}")146 write("agreement", tabular(out, "lllrrrrr", ["Extractor", "Channel", "Attribute vs reference", "Ref. n", "Coverage (\\%)", "Compared", "Agreement (\\%)", "$\\kappa$"]))147 main = "sonnet" if "sonnet" in df.tier.values else df.tier.iloc[0]148 for _, r in df[df.tier == main].iterrows():149 kk = re.sub(r"[^A-Za-z]", "", r["attribute"].split(" vs ")[0].split(" in ")[0].split(" where")[0].replace("TEXT", "Text"))[:28]150 macro(f"acc{kk}", fmt(r["accuracy"], 1, pct=True)); macro(f"cov{kk}", fmt(r["coverage"], 1, pct=True)); macro(f"n{kk}", f"{int(r['n_compared']):,}")151 if not np.isnan(r.get("kappa", np.nan)):152 macro(f"kappa{kk}", fmt(r["kappa"], 2))153 if "accuracy_strict" in r and not np.isnan(r["accuracy_strict"]):154 macro(f"accStrict{kk}", fmt(r["accuracy_strict"], 1, pct=True))155 if "mdape" in r and not np.isnan(r.get("mdape", np.nan)):156 macro(f"mdape{kk}", fmt(r["mdape"], 1)); macro(f"medianRatio{kk}", fmt(r["median_ratio"], 3))157158159# ------------------------------------------------------------------ T5 channels160def t_channels():161 df = pd.read_csv(T / "exp2_channels.csv")162 main = "sonnet" if "sonnet" in df.tier.values else df.tier.iloc[0]163 d = df[df.tier == main].copy()164 for c in ["visual_coverage", "text_coverage", "either_coverage", "agreement_when_both", "visual_only_share", "text_only_share"]:165 d[c] = d[c].map(lambda v: fmt(v, 1, pct=True))166 write("channels", tabular(d[["attribute", "visual_coverage", "text_coverage", "either_coverage", "visual_only_share", "text_only_share", "both", "agreement_when_both"]], "lrrrrrrr",167 ["Attribute", "Images (\\%)", "Text (\\%)", "Either (\\%)", "Images only (\\%)", "Text only (\\%)", "Both n", "Agreement (\\%)"]))168 for _, r in df[df.tier == main].iterrows():169 k = key(r["attribute"].title())170 macro(f"chanVis{k}", fmt(r["visual_coverage"], 0, pct=True)); macro(f"chanTxt{k}", fmt(r["text_coverage"], 0, pct=True))171 macro(f"chanAgree{k}", fmt(r["agreement_when_both"], 0, pct=True)); macro(f"chanEither{k}", fmt(r["either_coverage"], 0, pct=True))172173174# ------------------------------------------------------------------ T6 image count175def t_image_count():176 df = pd.read_csv(T / "exp3_image_count.csv")177 d = df.copy()178 for c in ["share_with_kitchen", "share_with_bathroom", "share_with_basement", "share_with_exterior", "grade_available", "grade_stable_vs_full", "cladding_stable_vs_full"]:179 d[c] = d[c].map(lambda v: fmt(v, 0, pct=True))180 d["condition_mad_vs_full"] = d["condition_mad_vs_full"].map(lambda v: fmt(v, 2))181 write("image_count", tabular(d[["tier", "K", "share_with_exterior", "share_with_kitchen", "share_with_bathroom", "share_with_basement", "grade_available", "grade_stable_vs_full", "condition_mad_vs_full"]],182 "lrrrrrrrr", ["Extractor", "K", "Exterior (\\%)", "Kitchen (\\%)", "Bathroom (\\%)", "Basement (\\%)", "Grade avail. (\\%)", "Grade = full (\\%)", "Cond. MAD"]))183 for _, r in df.iterrows():184 macro(f"k{r['tier']}{int(r['K'])}Basement", fmt(r["share_with_basement"], 1, pct=True)); macro(f"k{r['tier']}{int(r['K'])}Kitchen", fmt(r["share_with_kitchen"], 1, pct=True))185 macro(f"k{r['tier']}{int(r['K'])}Bathroom", fmt(r["share_with_bathroom"], 1, pct=True)); macro(f"k{r['tier']}{int(r['K'])}Grade", fmt(r["grade_available"], 1, pct=True))186187188# ------------------------------------------------------------------ T7 cross-model189def t_cross_model():190 f = T / "exp4_cross_model.csv"191 if not f.exists():192 return193 df = pd.read_csv(f)194 cols = [c for c in df.columns if c.startswith(("agree_", "kappa_", "corr_"))]195 d = df[["attribute", "n_listings", "alpha_nominal"] + cols].copy()196 for c in ["alpha_nominal"] + cols:197 d[c] = d[c].map(lambda v: fmt(v, 2)) if not c.startswith("agree_") else d[c].map(lambda v: fmt(v, 0, pct=True))198 d["attribute"] = d["attribute"].str.replace("_", " ")199 hdr = ["Attribute", "n", "$\\alpha$"] + [c.replace("agree_", "agree. (\\%) ").replace("kappa_", "$\\kappa$ ").replace("corr_", "r ").replace("_", "–") for c in cols]200 write("cross_model", tabular(d, "lr" + "r" * (len(cols) + 1), hdr))201 R = json.load(open(ROOT / "results" / "raw" / "exp_agreement.json"))202 sa = R.get("exp4_scene_agreement", {})203 if sa:204 macro("sceneAgreement", fmt(sa["agreement"], 1, pct=True)); macro("sceneKappa", fmt(sa["kappa"], 2)); macro("nSceneImages", f"{sa['n_images']:,}")205 for _, r in df.iterrows():206 k = key(r["attribute"].title())207 if not np.isnan(r["alpha_nominal"]):208 macro(f"alpha{k}", fmt(r["alpha_nominal"], 2))209 for c in cols:210 if not np.isnan(r[c]):211 macro(key(c.title()) + k, fmt(r[c], 1, pct=True) if c.startswith("agree_") else fmt(r[c], 2))212213214# ------------------------------------------------------------------ T8 scene presence (MNAR)215def t_scene_presence():216 df = pd.read_csv(T / "exp9_scene_presence.csv")217 main = "sonnet" if "sonnet" in df.tier.values else df.tier.iloc[0]218 d = df[df.tier == main].copy()219 out = pd.DataFrame({"Room": d["scene"], "Shown (\\%)": d["share_shown"].map(lambda v: fmt(v, 1, pct=True)),220 "Median price, shown": d["shown_median_price"].map(lambda v: f"{v:,.0f}"), "Median price, not shown": d["notshown_median_price"].map(lambda v: f"{v:,.0f}"),221 "Mean age, shown": d["shown_mean_age"].map(lambda v: fmt(v, 0)), "Mean age, not shown": d["notshown_mean_age"].map(lambda v: fmt(v, 0)),222 "Condition, shown": d["shown_cond"].map(lambda v: fmt(v, 2)), "Condition, not shown": d["notshown_cond"].map(lambda v: fmt(v, 2))})223 write("scene_presence", tabular(out, "lrrrrrrr"))224 for _, r in d.iterrows():225 macro(f"shown{r['scene'].title()}", fmt(r["share_shown"], 1, pct=True))226 if r["scene"] == "basement":227 for lab in ["finished", "partially_finished", "unfinished"]:228 if not np.isnan(r.get(f"shown_given_{lab}", np.nan)):229 macro("basementShownGiven" + lab.title().replace("_", ""), fmt(r[f"shown_given_{lab}"], 1, pct=True)); macro("nBasementRef" + lab.title().replace("_", ""), f"{int(r[f'n_{lab}'])}")230 pos = pd.read_csv(T / "exp9_first_position.csv", index_col=0)231 for sc in ["kitchen", "bathroom", "basement", "exterior_front", "living_or_dining"]:232 if sc in pos.index:233 macro("firstPos" + sc.title().replace("_", ""), fmt(pos.loc[sc, "50%"] + 1, 0)); macro("firstPosMean" + sc.title().replace("_", ""), fmt(pos.loc[sc, "mean"] + 1, 1))234235236# ------------------------------------------------------------------ T8b MNAR simulation + error taxonomy237def t_mnar_sim():238 f = T / "exp9_simulation.csv"239 if f.exists():240 r = pd.read_csv(f).iloc[0]241 macro("mnarSimDraws", str(int(r["n_draws"]))); macro("mnarSimDropped", fmt(r["share_images_dropped"], 0, pct=True))242 macro("mnarSimCondShift", f"{r['cond_shift']:+.2f}"); macro("mnarSimGradeUp", fmt(r["share_grade_up"], 1, pct=True)); macro("mnarSimGradeDown", fmt(r["share_grade_down"], 1, pct=True))243 macro("mnarSimGradeAvail", fmt(r["grade_available"], 0, pct=True)); macro("mnarSimGradeAvailFull", fmt(r["grade_available_full"], 0, pct=True))244 macro("mnarSimVRatio", fmt(r["median_V_ratio"], 3)); macro("mnarSimVRatioPct", f"{(r['median_V_ratio'] - 1) * 100:+.1f}")245 f = T / "exp1_error_taxonomy.csv"246 if f.exists():247 df = pd.read_csv(f)248 out = pd.DataFrame({"Attribute": df["attribute"], "Category of disagreement": df["category"], "n": df["n"], "Share (\\%)": df["share_of_disagreements"].map(lambda v: fmt(v, 0, pct=True))})249 write("error_taxonomy", tabular(out, "llrr"))250 R = json.load(open(ROOT / "results" / "raw" / "exp_agreement.json")).get("exp1_taxonomy", {})251 for k, v in R.items():252 macro("tax" + key(k.title()), f"{v:,}")253254255# ------------------------------------------------------------------ T9 calibration256def t_calibration():257 df = pd.read_csv(T / "exp12_calibration.csv")258 d = df.copy()259 for c in ["conf_mean", "roof_acc", "clad_acc"]:260 d[c] = d[c].map(lambda v: fmt(v, 2))261 write("confidence_calibration", tabular(d[["tier", "conf_bin", "n", "conf_mean", "roof_n", "roof_acc", "clad_n", "clad_acc"]], "llrrrrrr",262 ["Extractor", "Confidence bin", "Images", "Mean conf.", "Roof n", "Roof agreement", "Cladding n", "Cladding agreement"]))263 sel = pd.read_csv(T / "exp12_selective.csv")264 for _, r in sel.iterrows():265 macro(f"sel{r['tier']}{str(r['threshold']).replace('.', '')}Kept", fmt(r["kept_share"], 0, pct=True)); macro(f"sel{r['tier']}{str(r['threshold']).replace('.', '')}Acc", fmt(r["roof_accuracy"], 1, pct=True))266267268# ------------------------------------------------------------------ T10 levels summary269def t_levels():270 df = pd.read_csv(T / "exp5_levels_summary.csv")271 main = "sonnet" if "sonnet" in df.tier.values else df.tier.iloc[0]272 d = df[df.tier == main].copy()273 out = pd.DataFrame({"Level": d["level"], "Anchor": d["anchor"], "Median $V$ (\\$)": d["V_median"].map(lambda v: f"{v:,.0f}"),274 "Land share": d["L_share_median"].map(lambda v: fmt(v, 2)), "Median $D$": d["D_median"].map(lambda v: fmt(v, 2)),275 "Assessor ratio": d["assessor_median_ratio"].map(lambda v: fmt(v, 2)), "Assessor MdAPE": d["assessor_mdape"].map(lambda v: fmt(v, 1)),276 "Price ratio": d["price_median_ratio"].map(lambda v: fmt(v, 2)),277 "Price MdAPE [95\\% CI]": [f"{m:.1f} [{lo:.1f}, {hi:.1f}]" for m, lo, hi in zip(d["price_mdape"], d["price_mdape_lo"], d["price_mdape_hi"])],278 "Within 20\\%": d["price_within_20pct"].map(lambda v: fmt(v, 0, pct=True))})279 write("levels_summary", tabular(out, "llrrrrrrrr"))280 for _, r in df.iterrows():281 k = f"{r['tier']}{r['level']}{r['anchor']}"282 macro(f"V{k}", f"{r['V_median']:,.0f}"); macro(f"Dmed{k}", fmt(r["D_median"], 2)); macro(f"landShare{k}", fmt(r["L_share_median"], 2))283 macro(f"priceRatio{k}", fmt(r["price_median_ratio"], 2)); macro(f"priceMdape{k}", fmt(r["price_mdape"], 1)); macro(f"priceWithinTwenty{k}", fmt(r["price_within_20pct"], 0, pct=True))284 macro(f"priceMdapeLo{k}", fmt(r["price_mdape_lo"], 1)); macro(f"priceMdapeHi{k}", fmt(r["price_mdape_hi"], 1))285 macro(f"assRatio{k}", fmt(r["assessor_median_ratio"], 2)); macro(f"assMdape{k}", fmt(r["assessor_mdape"], 1)); macro(f"assCod{k}", fmt(r["assessor_cod"], 1))286 macro(f"effAge{k}", fmt(r["eff_age_median"], 0)); macro(f"RCN{k}", f"{r['RCN_median']:,.0f}")287 # all tiers compact (anchor B)288 d2 = df[df.anchor == "B"]289 out2 = pd.DataFrame({"Extractor": d2["tier"], "Level": d2["level"], "Median $V$ (\\$)": d2["V_median"].map(lambda v: f"{v:,.0f}"), "Median $D$": d2["D_median"].map(lambda v: fmt(v, 2)),290 "Price ratio": d2["price_median_ratio"].map(lambda v: fmt(v, 2)), "Price MdAPE": d2["price_mdape"].map(lambda v: fmt(v, 1)), "Within 20\\%": d2["price_within_20pct"].map(lambda v: fmt(v, 0, pct=True)),291 "Assessor ratio": d2["assessor_median_ratio"].map(lambda v: fmt(v, 2)), "Assessor MdAPE": d2["assessor_mdape"].map(lambda v: fmt(v, 1))})292 write("levels_by_tier", tabular(out2, "llrrrrrrr"))293294295# ------------------------------------------------------------------ T11 sensitivity296def t_sensitivity():297 df = pd.read_csv(T / "exp7_8_sensitivity.csv")298 base = df.iloc[0]299 out = pd.DataFrame({"Scenario": df["scenario"], "Median $V$ (\\$)": df["V_median"].map(lambda v: f"{v:,.0f}"),300 "$\\Delta$ vs baseline (\\%)": ((df["V_median"] / base["V_median"] - 1) * 100).map(lambda v: f"{v:+.1f}"),301 "Median $D$": df["D_median"].map(lambda v: fmt(v, 2)), "Assessor ratio": df["assessor_median_ratio"].map(lambda v: fmt(v, 2)),302 "Price ratio": df["price_median_ratio"].map(lambda v: fmt(v, 2)), "Price MdAPE": df["price_mdape"].map(lambda v: fmt(v, 1))})303 write("sensitivity", tabular(out, "lrrrrrr"))304 for _, r in df.iterrows():305 k = key(r["scenario"].title())[:40]306 macro(f"sens{k}Delta", f"{(r['V_median'] / base['V_median'] - 1) * 100:+.1f}"); macro(f"sens{k}PriceRatio", fmt(r["price_median_ratio"], 2)); macro(f"sens{k}AssRatio", fmt(r["assessor_median_ratio"], 2))307 macro(f"sens{k}PriceMdape", fmt(r["price_mdape"], 1))308309310# ------------------------------------------------------------------ T12 calibration/holdout311def t_holdout():312 df = pd.read_csv(T / "exp10_calibration.csv")313 params = (df["parameters"].str.replace("→", "to", regex=False).str.replace("×", "x", regex=False).str.replace("≈", "approx.", regex=False)314 .str.replace("$", "", regex=False).str.replace("RCN level-only calibration", "level-only", regex=False).str.replace("life-only calibration", "life-only", regex=False)315 .str.replace("joint calibration", "joint", regex=False).str.replace("public parameters", "public", regex=False))316 out = pd.DataFrame({"Anchor": df["anchor"], "Level": df["level"], "Set": df["set"], "Parameters": params,317 "n": df["n"], "Median $D$": df["D_median"].map(lambda v: fmt(v, 2)), "Assessor ratio": df["assessor_median_ratio"].map(lambda v: fmt(v, 2)),318 "Assessor MdAPE": df["assessor_mdape"].map(lambda v: fmt(v, 1)), "Price ratio": df["price_median_ratio"].map(lambda v: fmt(v, 2)), "Price MdAPE": df["price_mdape"].map(lambda v: fmt(v, 1))})319 txt = tabular(out, "lllp{5.2cm}rrrrrr")320 write("calibration_holdout", txt)321 for _, r in df.iterrows():322 k = f"{r['anchor']}{r['level']}{r['set'].title()}" + ("Pub" if r["parameters"].startswith("public") else "Life" if r["parameters"].startswith("life") else "Level" if r["parameters"].startswith("RCN") else "Joint")323 macro(f"cal{k}AssRatio", fmt(r["assessor_median_ratio"], 2)); macro(f"cal{k}PriceRatio", fmt(r["price_median_ratio"], 2)); macro(f"cal{k}PriceMdape", fmt(r["price_mdape"], 1))324 macro(f"cal{k}LevelFactor", fmt(r["level_factor"], 2)); macro(f"cal{k}Life", fmt(r["economic_life"], 0)); macro(f"cal{k}N", f"{int(r['n'])}")325 macro(f"cal{k}ImpliedSqft", f"{r['implied_standard_per_sqft']:.0f}"); macro(f"cal{k}AssMdape", fmt(r["assessor_mdape"], 1))326327328# ------------------------------------------------------------------ T13 subgroups329def t_subgroups():330 df = pd.read_csv(T / "exp10_11_subgroups.csv")331 main = "sonnet" if "sonnet" in df.tier.values else df.tier.iloc[0]332 d = df[(df.tier == main) & (df.level == "B3") & (df.group.isin(["cma", "age_band", "price_tier", "roll_vintage"]))].copy()333 d0 = df[(df.tier == main) & (df.level == "B0")].set_index(["group", "value"])334 d["price_mdape_B0"] = [d0.loc[(g, v), "price_mdape"] if (g, v) in d0.index else np.nan for g, v in zip(d.group, d.value)]335 out = pd.DataFrame({"Group": d["group"].str.replace("_", " "), "Value": d["value"], "n": d["n"], "Assessor ratio": d["assessor_median_ratio"].map(lambda v: fmt(v, 2)),336 "Assessor COD": d["assessor_cod"].map(lambda v: fmt(v, 1)), "Price ratio": d["price_median_ratio"].map(lambda v: fmt(v, 2)),337 "Price MdAPE B3": d["price_mdape"].map(lambda v: fmt(v, 1)), "Price MdAPE B0": d["price_mdape_B0"].map(lambda v: fmt(v, 1))})338 write("subgroups", tabular(out, "llrrrrrr"))339 for _, r in d.iterrows():340 k = key(f"{r['group']}{str(r['value']).title()}")341 macro(f"sub{k}PriceMdape", fmt(r["price_mdape"], 1)); macro(f"sub{k}PriceRatio", fmt(r["price_median_ratio"], 2)); macro(f"sub{k}AssRatio", fmt(r["assessor_median_ratio"], 2)); macro(f"sub{k}N", f"{int(r['n'])}")342343344# ------------------------------------------------------------------ T14 scenarios345def t_scenarios():346 df = pd.read_csv(T / "exp13_scenarios.csv")347 out = pd.DataFrame({"Scenario (counterfactual, labelled)": df["scenario"], "Listings affected (\\%)": df["share_affected"].map(lambda v: fmt(v, 0, pct=True)),348 "Median $\\Delta V$, affected (\\%)": df["median_delta_V_pct_affected"].map(lambda v: f"{v:+.1f}"),349 "Median $\\Delta$ improvements, affected (\\%)": df["median_delta_improvements_pct_affected"].map(lambda v: f"{v:+.1f}"),350 "Median $\\Delta V$, affected (\\$)": df["median_delta_V_cad_affected"].map(lambda v: f"{v:+,.0f}")})351 write("scenarios", tabular(out, "lrrrr"))352 for _, r in df.iterrows():353 k = key(r["scenario"].title())354 macro(f"scen{k}V", f"{r['median_delta_V_pct_affected']:+.1f}"); macro(f"scen{k}Impr", f"{r['median_delta_improvements_pct_affected']:+.1f}")355 macro(f"scen{k}Cad", f"{r['median_delta_V_cad_affected']:+,.0f}"); macro(f"scen{k}Share", fmt(r["share_affected"], 0, pct=True))356357358# ------------------------------------------------------------------ T15 Monte Carlo359def t_mc():360 R = json.load(open(ROOT / "results" / "raw" / "exp_cost.json"))["exp14"]361 oat = pd.read_csv(T / "exp14_oat.csv", index_col=0)362 out = pd.DataFrame({"Factor (one at a time)": oat.index, "Median $|\\Delta V|$ (\\%)": oat.iloc[:, 0].map(lambda v: fmt(v, 1))})363 write("mc_oat", tabular(out, "lr"))364 macro("mcDraws", str(R["n_draws"])); macro("mcRelWidth", fmt(R["rel_width_median"], 0, pct=True)); macro("mcRelWidthLo", fmt(R["rel_width_p25_p75"][0], 0, pct=True)); macro("mcRelWidthHi", fmt(R["rel_width_p25_p75"][1], 0, pct=True))365 macro("mcPriceCoverage", fmt(R["price_coverage_80"], 0, pct=True)); macro("mcAssessorCoverage", fmt(R["assessor_total_coverage_80"], 0, pct=True))366 for k, v in R["one_at_a_time_median_abs_pct"].items():367 macro("oat" + key(k.title()), fmt(v, 1))368369370# ------------------------------------------------------------------ T16 market alignment & sales371def t_market():372 df = pd.read_csv(T / "exp15_market_alignment.csv")373 out = pd.DataFrame({"Indication": df["level"].str.replace("benchmark:", "Benchmark: ").str.replace("_", "\\_"), "n": df["n"],374 "Median ratio to price": df["price_median_ratio"].map(lambda v: fmt(v, 2)), "MdAPE": df["price_mdape"].map(lambda v: fmt(v, 1)),375 "P10": df["price_p10"].map(lambda v: fmt(v, 2)), "P90": df["price_p90"].map(lambda v: fmt(v, 2)), "Within 20\\%": df["price_within_20pct"].map(lambda v: fmt(v, 0, pct=True)),376 "Ratio to VP cost est.": df["vp_cost_est_median_ratio"].map(lambda v: fmt(v, 2)) if "vp_cost_est_median_ratio" in df else "--"})377 write("market_alignment", tabular(out, "lrrrrrrr"))378 for _, r in df.iterrows():379 k = key(r["level"].title())380 macro(f"mkt{k}Ratio", fmt(r["price_median_ratio"], 2)); macro(f"mkt{k}Mdape", fmt(r["price_mdape"], 1)); macro(f"mkt{k}Within", fmt(r["price_within_20pct"], 0, pct=True))381 if "vp_cost_est_median_ratio" in r and not np.isnan(r.get("vp_cost_est_median_ratio", np.nan)):382 macro(f"mkt{k}VpCostRatio", fmt(r["vp_cost_est_median_ratio"], 2))383 ds = pd.read_csv(T / "exp15_recorded_sales.csv")384 out = pd.DataFrame({"Set": ds["set"].str.replace("_", " "), "Level": ds["level"], "n": ds["n"], "Median ratio to sale": ds["median_ratio"].map(lambda v: fmt(v, 2)),385 "MdAPE": ds["mdape"].map(lambda v: fmt(v, 1)), "Within 20\\%": ds["within_20pct"].map(lambda v: fmt(v, 0, pct=True)), "Asking / sale (median)": ds["ask_vs_sale_median"].map(lambda v: fmt(v, 3))})386 write("recorded_sales", tabular(out, "llrrrrr"))387 for _, r in ds.iterrows():388 k = key(r['set'].title() + r['level'])389 macro(f"sale{k}Ratio", fmt(r["median_ratio"], 2)); macro(f"sale{k}Mdape", fmt(r["mdape"], 1)); macro(f"sale{k}N", f"{int(r['n'])}"); macro(f"sale{k}AskSale", fmt(r["ask_vs_sale_median"], 3))390391392# ------------------------------------------------------------------ T17 review load393def t_review():394 df = pd.read_csv(T / "exp16_review_load.csv")395 out = pd.DataFrame({"Extractor": df["tier"], "Rule": df["rule"], "Flagged (\\%)": df["share_flagged"].map(lambda v: fmt(v, 1, pct=True)),396 "No grade (\\%)": df["share_no_grade"].map(lambda v: fmt(v, 1, pct=True)), "Defects (\\%)": df["share_defects"].map(lambda v: fmt(v, 1, pct=True))})397 write("review_load", tabular(out, "llrrr"))398 for _, r in df.iterrows():399 macro(f"review{r['tier']}{re.sub(r'[^0-9]', '', r['rule'])}", fmt(r["share_flagged"], 0, pct=True))400401402# ------------------------------------------------------------------ T18 cost schedule403def t_schedule():404 cfg = yaml.safe_load(open(ROOT / "configs" / "cost_schedule.yaml"))405 rows = []406 for anc in ["B", "A"]:407 a = cfg["rcn"]["anchors"][anc]408 for g in ["basic", "standard", "upgraded", "luxury"]:409 lo, hi = a["grade_sensitivity_per_sqft"][g]410 rows.append({"Anchor": anc, "Grade": g, "\\$/sq ft (point)": a["grade_per_sqft"][g], "Band": f"{lo}–{hi}", "Reference storeys": a["reference_storeys"]})411 write("schedule_grades", tabular(pd.DataFrame(rows), "llrrr"))412 rows = []413 for anc in ["B", "A"]:414 a = cfg["rcn"]["anchors"][anc]415 rows.append({"Parameter": f"Storey multiplier, anchor {anc}", "Value": ", ".join(f"{k}: {v}" for k, v in a["storey_multiplier"].items()), "Source / status": "industry bungalow premium (B); assumption (A)"})416 r = cfg["rcn"]417 rows += [{"Parameter": "Attached-house multiplier", "Value": ", ".join(f"{k}: {v}" for k, v in r["attached_multiplier"].items()), "Source / status": "assumption"},418 {"Parameter": "Basement finish (\\$/sq ft of footprint)", "Value": ", ".join(f"{k}: {v}" for k, v in r["basement_finish_per_sqft"].items()), "Source / status": "assumption, $\\pm$30\\%"},419 {"Parameter": "Garage (\\$)", "Value": ", ".join(f"{k}: {v:,}" for k, v in r["garage"].items() if v), "Source / status": "assumption, $\\pm$30\\% (Dessins Drummond: single 25–50k)"},420 {"Parameter": "Soft costs / entrepreneurial incentive", "Value": f"{r['soft_cost_share']:.0%} / {r['entrepreneurial_incentive']:.0%}", "Source / status": "assumption; ranges 5–15\\% / 0–15\\%"},421 {"Parameter": "Site improvements", "Value": f"{cfg['site_improvements']['baseline_share_of_rcn']:.0%} of RCN + pool", "Source / status": "assumption"},422 {"Parameter": "Economic life / max physical depreciation", "Value": f"{cfg['depreciation']['economic_life_years']} y / {cfg['depreciation']['max_physical']:.0%}", "Source / status": "assumption; 50–75 y"},423 {"Parameter": "Condition factor on effective age", "Value": ", ".join(f"{k}: {v}" for k, v in cfg["depreciation"]["condition_factor"].items()), "Source / status": "assumption; strength 0.5–1.5"},424 {"Parameter": "Renovation (years removed)", "Value": f"extensive {cfg['depreciation']['renovation_extensive_years_removed']}, partial {cfg['depreciation']['renovation_partial_years_removed']}", "Source / status": "assumption"},425 {"Parameter": "Land", "Value": "roll land value $\\times$ market index (roll month $\\rightarrow$ 2026-07)", "Source / status": "MAMH roll; Vrai Prix index; share 0–1"},426 {"Parameter": "Temporal / regional index", "Value": "StatCan 18-10-0289 single-detached, by CMA", "Source / status": "official statistics"}]427 write("schedule_parameters", tabular(pd.DataFrame(rows), "p{4.2cm}p{6cm}p{4.3cm}").replace("\\\\$", "\\$"))428 b = cfg["rcn"]["anchors"]["B"]["grade_per_sqft"]; a_ = cfg["rcn"]["anchors"]["A"]["grade_per_sqft"]429 for g in b:430 macro(f"costB{g.title()}", str(b[g])); macro(f"costA{g.title()}", str(a_[g]))431 macro("econLife", str(cfg["depreciation"]["economic_life_years"])); macro("softShare", f"{cfg['rcn']['soft_cost_share'] * 100:.0f}"); macro("eiShare", f"{cfg['rcn']['entrepreneurial_incentive'] * 100:.0f}")432433434# ------------------------------------------------------------------ literature counts435def t_literature():436 v = pd.read_csv(ROOT / "docs" / "verification" / "verified.csv")437 macro("nRefsVerified", str(len(v))); macro("nRefsDoi", str(int(v["doi"].fillna("").astype(bool).sum())))438439440def all_tables():441 for fn in [t_exclusions, t_sample, t_run, t_agreement, t_channels, t_image_count, t_cross_model, t_scene_presence, t_mnar_sim, t_calibration,442 t_levels, t_sensitivity, t_holdout, t_subgroups, t_scenarios, t_mc, t_market, t_review, t_schedule, t_literature]:443 try:444 fn(); print("ok ", fn.__name__)445 except Exception as e: # noqa: BLE001446 print("FAIL", fn.__name__, repr(e))447 lines = [f"\\newcommand{{\\{k}}}{{{v}}}" for k, v in sorted(NUM.items())]448 (OUT / "numbers.tex").write_text("% generated by src/visualization/tables.py — do not edit\n" + "\n".join(lines) + "\n", encoding="utf-8")449 print(len(NUM), "macros")450451452if __name__ == "__main__":453 all_tables()454