SPB Git forge

spb/wp20_uqo

Public
1commits 1branches 0releases
2.8 MBsize
maindefault branch
yesterdaylast push
TeX 59% Python 40.8%
33.1 KB · 454 lines python
Raw Blame History
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