SPB Git forge

spb/wp19_uqo

Public
0commits 0branches 0releases
0 Bsize
maindefault branch
—last push
25.9 KB · 285 lines python
Raw Blame History
1"""LaTeX tables and number macros for WP19, generated only from results/processed/*.csv and config/baseline.yaml (Phase 5).2Writes results/tables/*.tex and copies to paper/tables/.  Run: python -m src.tables3"""4from __future__ import annotations56import json7import shutil8from pathlib import Path910import numpy as np11import pandas as pd12import yaml1314ROOT = Path(__file__).resolve().parents[1]15PRO = ROOT / "results" / "processed"; TAB = ROOT / "results" / "tables"; PTAB = ROOT / "paper" / "tables"16TAB.mkdir(parents=True, exist_ok=True); PTAB.mkdir(parents=True, exist_ok=True)17MACROS = {}181920def rd(name):21    return pd.read_csv(PRO / name)222324def write(name: str, body: str):25    (TAB / f"{name}.tex").write_text(body)26    shutil.copy(TAB / f"{name}.tex", PTAB / f"{name}.tex")27    print("table", name)282930def fmt(x, d=3):31    if isinstance(x, (bool, np.bool_)):32        return "yes" if x else "no"33    if x is None or (isinstance(x, float) and np.isnan(x)):34        return "--"35    return f"{x:.{d}f}"363738def macro(name, value, d=2, pct=False):39    """Register a number macro \\name{...}; names must be letters only."""40    assert name.isalpha(), name41    if isinstance(value, str):42        MACROS[name] = value43    else:44        MACROS[name] = f"{100 * value:.{d}f}" if pct else f"{value:.{d}f}"454647def tabular(cols, rows, align=None):48    align = align or "l" + "r" * (len(cols) - 1)49    out = [f"\\begin{{tabular}}{{{align}}}", "\\toprule", " & ".join(cols) + " \\\\", "\\midrule"]50    out += [" & ".join(r) + " \\\\" for r in rows]51    out += ["\\bottomrule", "\\end{tabular}"]52    return "\n".join(out) + "\n"535455# ------------------------------------------------------------------------------------------------56def tab01_parameters():57    cfg = yaml.safe_load((ROOT / "config" / "baseline.yaml").read_text())58    rows = [59        ("$r$", "discount rate", fmt(cfg["preferences"]["r"], 2), "per year", "real rate of 4\\%; standard in housing search models"),60        ("$s$", "listing inflow", fmt(cfg["sellers"]["s"], 1), "listings per year", "normalisation"),61        ("$\\delta$", "withdrawal hazard", fmt(cfg["sellers"]["delta"], 2), "per year", "10--20\\% of listings withdrawn at baseline hazards"),62        ("$w_0$", "own-use value on withdrawal", fmt(cfg["sellers"]["w0"], 2), "units of $q$", "gains from trade of about 20\\% plus match component"),63        ("$[c_L,c_H]$", "carrying-cost support", f"[{cfg['sellers']['c_min']:.2f}, {cfg['sellers']['c_max']:.2f}]", "share of value per year", "maintenance and taxes to double housing / distress"),64        ("$F$", "carrying-cost distribution", f"Beta({cfg['sellers']['beta_a']:.0f},{cfg['sellers']['beta_b']:.0f}), {cfg['sellers']['n_types']} types", "--", "right-skewed: most sellers patient, a minority urgent"),65        ("$k$", "buyer entry cost", fmt(cfg["buyers"]["k"], 2), "units of $q$", "search set-up, inspections"),66        ("$\\kappa_b$", "buyer flow search cost", fmt(cfg["buyers"]["kappa_b"], 2), "units of $q$ per year", "temporary housing, time; comparable to seller carrying costs"),67        ("$\\phi$", "qualification probability", fmt(cfg["buyers"]["phi"], 2), "per meeting", "pre-approval screen"),68        ("$A$", "matching efficiency", fmt(cfg["matching"]["A"], 1), "meetings per year at $\\theta=1$", "meetings = serious offers, not viewings"),69        ("$\\alpha$", "matching elasticity (buyers)", fmt(cfg["matching"]["alpha"], 2), "--", "symmetric Cobb--Douglas"),70        ("$\\beta$", "seller Nash share", fmt(cfg["bargaining"]["beta"], 2), "--", "symmetric; Hosios value $1-\\alpha$"),71        ("$G$", "match-value distribution", f"$N({cfg['match_values']['mu']:.1f},{cfg['match_values']['sigma']:.2f}^2)$ on $[{cfg['match_values']['lo']:.1f},{cfg['match_values']['hi']:.1f}]$", "units of $q$", "log-concave (IFR); 10\\% dispersion of buyer valuations"),72        ("$\\pi,\\nu,\\phi_m$", "financing extension", f"{cfg['financing_extension']['pi']:.2f}, {cfg['financing_extension']['nu']:.0f}, {cfg['financing_extension']['phi']:.2f}", "--, per year, --", "cash share; one-month approval; approval probability"),73    ]74    write("tab01_parameters", tabular(["Symbol", "Parameter", "Value", "Units", "Illustrative justification"], [list(r) for r in rows], "lp{2.7cm}lp{2.3cm}p{4.6cm}"))757677def tab02_baseline():78    m = rd("exp01_baseline_moments.csv").iloc[0]; t = rd("exp01_baseline_types.csv")79    rows = [80        ("Tightness $\\theta^*$ (buyers per listing)", fmt(m.theta, 2)), ("Effective seller contact rate $\\Lambda_s$ (per year)", fmt(m.Lam_s, 2)),81        ("Effective buyer contact rate $\\Lambda_b$ (per year)", fmt(m.Lam_b, 2)), ("Inventory of listings $S$ (inflow $=1$)", fmt(m.S, 3)),82        ("Sales flow $Q$ (share of listings eventually sold)", fmt(m.Q, 3)), ("Mean time on market of sold listings (months)", fmt(m.mean_tom_sold_months, 2)),83        ("Mean buyer search duration (months)", fmt(12 * m.buyer_mean_duration, 2)), ("Transaction-weighted mean price", fmt(m.price_mean, 4)),84        ("Price s.d. among identical houses", fmt(m.price_sd_total, 4)), ("Between-type share of price variance", fmt(m.price_between_share, 3)),85        ("Expected price, most patient / most urgent type", f"{m.pbar_patient:.4f} / {m.pbar_urgent:.4f}"),86        ("Urgency discount $D$, most urgent type / transaction-weighted (\\%)", f"{100 * m.D_urgent:.2f} / {100 * m.D_mean:.2f}"),87        ("Sale hazard, most patient / most urgent type (per year)", f"{m.eta_patient:.2f} / {m.eta_urgent:.2f}"),88        ("Walrasian free-entry benchmark $p^*$; gap $\\Gamma$ (\\%)", f"{m.p_star:.3f}; {100 * m.Gamma:.1f}"),89        ("Welfare flow: total / sellers / holding costs", f"{m.welfare_total:.4f} / {m.welfare_sellers:.4f} / {m.welfare_holding_costs:.4f}"),90        ("Max.\\ seller residual / free-entry residual", f"{m.seller_resid_max:.1e} / {abs(m.free_entry_resid):.1e}"),91    ]92    write("tab02_baseline_moments", tabular(["Moment", "Value"], [list(r) for r in rows], "lr"))93    rows = [[fmt(r.c, 3), fmt(r.f, 3), fmt(r.V, 4), fmt(r.vstar, 4), fmt(r.acc, 3), fmt(r.eta, 2), fmt(r.E_tau_months, 2), fmt(r.pbar, 4), f"{100 * r.D:.2f}", fmt(r.stock_share, 3), fmt(r.sales_share, 3)] for r in t.itertuples()]94    write("tab02b_baseline_types", tabular(["$c$", "$f(c)$", "$V(c)$", "$v^*(c)$", "$1-G(v^*)$", "$\\eta(c)$", "E[$\\tau$] (mo.)", "$\\bar p(c)$", "$D(c)$ (\\%)", "stock share", "sales share"], rows))95    for k_, v in (("thetaBase", m.theta), ("tomBase", m.mean_tom_sold_months), ("DurgentBase", 100 * m.D_urgent), ("DmeanBase", 100 * m.D_mean),96                  ("sdPriceBase", 100 * m.price_sd_total), ("betweenShareBase", 100 * m.price_between_share), ("pstarBase", m.p_star), ("GammaBase", 100 * m.Gamma),97                  ("buyerDurBase", 12 * m.buyer_mean_duration), ("etaPatientBase", m.eta_patient), ("etaUrgentBase", m.eta_urgent), ("soldShareBase", 100 * m.Q),98                  ("LamSBase", m.Lam_s), ("LamBBase", m.Lam_b), ("inventoryBase", m.S), ("accPatientBase", 100 * m.acc_patient), ("accUrgentBase", 100 * m.acc_urgent)):99        macro(k_, v, 2)100    macro("priceBase", m.price_mean, 3); macro("pbarPatientBase", m.pbar_patient, 3); macro("pbarUrgentBase", m.pbar_urgent, 3); macro("VPatientBase", m.V_max, 3); macro("VUrgentBase", m.V_min, 3)101    macro("nTypes", f"{len(t)}")102103104def tab03_comparative_statics():105    a = rd("exp03_arrival_shock.csv")106    sel = [0.5, 0.7, 0.85, 1.0, 1.2, 1.5, 2.0]107    rows = []108    for mult in sel:109        for kind in ("GE", "PE"):110            r = a[(np.isclose(a.A_mult, mult)) & (a.kind == kind)].iloc[0]111            rows.append([f"{mult:.2f}", kind, fmt(r.theta, 2), fmt(r.Lam_s, 2), fmt(r.price_mean, 4), fmt(r.mean_tom_sold_months, 2), fmt(r.S, 3), fmt(r.Q, 3),112                         f"{100 * r.D_urgent:.2f}", f"{100 * r.D_mean:.2f}", fmt(r.price_sd_total, 4), f"{100 * r.Gamma:.1f}"])113    write("tab03_comparative_statics", tabular(["$A/A_0$", "Eq.", "$\\theta$", "$\\Lambda_s$", "price", "TOM (mo.)", "$S$", "$Q$", "$D_{urg}$ (\\%)", "$\\bar D$ (\\%)", "s.d.\\ price", "$\\Gamma$ (\\%)"], rows))114    g = a[a.kind == "GE"]115    lo = g[np.isclose(g.A_mult, 0.5)].iloc[0]; hi = g[np.isclose(g.A_mult, 1.0)].iloc[0]116    macro("priceDropHalfA", 100 * (1 - lo.price_mean / hi.price_mean), 1); macro("tomHalfA", lo.mean_tom_sold_months, 1); macro("DurgentHalfA", 100 * lo.D_urgent, 1)117    macro("thetaHalfA", lo.theta, 2); macro("inventoryHalfA", lo.S, 3)118    pe = a[(a.kind == "PE") & np.isclose(a.A_mult, 0.5)].iloc[0]119    macro("priceDropHalfAPE", 100 * (1 - pe.price_mean / hi.price_mean), 1); macro("tomHalfAPE", pe.mean_tom_sold_months, 1)120    amp = rd("exp07_amplification.csv")121    for r in amp.itertuples():122        macro(f"el{r.shock.capitalize()}{r.kind}Lam", r.el_Lam_s, 2); macro(f"el{r.shock.capitalize()}{r.kind}Price", r.el_price, 3); macro(f"el{r.shock.capitalize()}{r.kind}Tom", r.el_tom, 2)123    gs = rd("exp03_gamma_sign_change.csv"); cross = gs[gs.Gamma < 0].Lam_s.min()124    macro("GammaCrossLam", cross, 0)125126127def tab04_financing():128    q = rd("exp04_qualification_screen.csv")129    rows = [[fmt(r.phi, 2), fmt(r.theta, 2), fmt(r.Lam_s, 2), fmt(r.price_mean, 4), fmt(r.mean_tom_sold_months, 2), f"{100 * r.D_urgent:.2f}", fmt(r.S, 3)] for r in q.itertuples() if np.isclose(r.phi % 0.1, 0, atol=1e-6) or np.isclose(r.phi, 0.85)]130    write("tab04a_qualification_screen", tabular(["$\\phi$", "$\\theta$", "$\\Lambda_s$", "price", "TOM (mo.)", "$D_{urg}$ (\\%)", "$S$"], rows))131    d = rd("exp04_financing_phi.csv")132    rows = []133    for phi, g in d.groupby("phi_m"):134        wmean = lambda col: (g[col] * g.f).sum() / g.f.sum()135        rows.append([fmt(phi, 2), fmt(g.theta.iloc[0], 2), fmt(g.U_c.iloc[0], 4), fmt(g.U_m.iloc[0], 4), fmt(g.pi_tilde.iloc[0], 3),136                     f"{100 * wmean('Delta_b'):.2f}", f"{100 * g.outside_term.iloc[0]:.2f}", f"{100 * wmean('sel_term'):.2f}", f"{100 * wmean('gap'):.2f}", fmt(wmean("E_tau_listed_months"), 2), fmt(g.E_total_duration_months.iloc[0], 2), fmt(g.S.iloc[0], 3), fmt(g.buyer_participation_ok.iloc[0])])137    write("tab04b_financing_extension", tabular(["$\\phi_m$", "$\\theta$", "$U_c$", "$U_m$", "$\\tilde\\pi$", "$\\Delta_b$ (\\%)", "outside (\\%)", "selection (\\%)", "gap (\\%)", "listed (mo.)", "to closing (mo.)", "$S$", "$U_m\\ge0$"], rows))138    b = d[np.isclose(d.phi_m, 0.85)]139    wm = lambda col: (b[col] * b.f).sum() / b.f.sum()140    macro("gapMeanBase", 100 * wm("gap"), 2); macro("deltaBMeanBase", 100 * wm("Delta_b"), 2); macro("selMeanBase", 100 * wm("sel_term"), 2); macro("outsideBase", 100 * b.outside_term.iloc[0], 2)141    macro("gapPatientBase", 100 * b.gap.iloc[0], 2); macro("gapUrgentBase", 100 * b.gap.iloc[-1], 2); macro("deltaBPatientBase", 100 * b.Delta_b.iloc[0], 2); macro("deltaBUrgentBase", 100 * b.Delta_b.iloc[-1], 2)142    macro("cashShareBase", 100 * b.cash_share_of_sales.iloc[0], 1); macro("UcBase", b.U_c.iloc[0], 4); macro("UmBase", b.U_m.iloc[0], 4); macro("piTildeBase", b.pi_tilde.iloc[0], 3)143    nu = rd("exp04_financing_nu.csv"); rows = []144    for v, g in nu.groupby("nu"):145        wmean = lambda col: (g[col] * g.f).sum() / g.f.sum()146        rows.append([("$\\infty$" if v > 1e3 else fmt(v, 0)), (fmt(12 / v, 2) if v < 1e3 else "0"), fmt(g.theta.iloc[0], 2), f"{100 * wmean('Delta_b'):.2f}", f"{100 * wmean('sel_term'):.2f}", f"{100 * wmean('gap'):.2f}", fmt(wmean("E_tau_listed_months"), 2), f"{100 * g.cash_share_of_sales.iloc[0]:.1f}"])147    write("tab04c_financing_delay", tabular(["$\\nu$", "delay (mo.)", "$\\theta$", "$\\Delta_b$ (\\%)", "selection (\\%)", "gap (\\%)", "TOM (mo.)", "cash sales (\\%)"], rows))148    bt = rd("exp04_financing_beta.csv"); rows = []149    for v, g in bt.groupby("beta"):150        wmean = lambda col: (g[col] * g.f).sum() / g.f.sum()151        rows.append([fmt(v, 2), fmt(g.theta.iloc[0], 2), f"{100 * wmean('Delta_b'):.2f}", f"{100 * g.outside_term.iloc[0]:.2f}", f"{100 * wmean('sel_term'):.2f}", f"{100 * wmean('gap'):.2f}", f"{100 * g.Delta_b.iloc[0]:.2f}", f"{100 * g.Delta_b.iloc[-1]:.2f}"])152    write("tab04d_financing_bargaining", tabular(["$\\beta$", "$\\theta$", "$\\Delta_b$ (\\%)", "outside (\\%)", "selection (\\%)", "gap (\\%)", "$\\Delta_b$ patient", "$\\Delta_b$ urgent"], rows))153    m6 = rd("exp06_matching_vs_approval.csv")154    rows = [[r.shock.replace("phi_m", "$\\phi_m$"), fmt(r.value, 2), fmt(r.agg_hazard, 3), fmt(r.theta, 2), fmt(r.price_mean, 4), fmt(r.mean_tom_months, 2), fmt(r.total_duration_months, 2), fmt(r.S, 3), fmt(r.Q, 3), f"{100 * r.gap_mean:.2f}", fmt(r.buyer_participation_ok)] for r in m6.itertuples()]155    write("tab04e_matching_vs_approval", tabular(["shock", "value", "agreement hazard", "$\\theta$", "price", "listed (mo.)", "to closing (mo.)", "$S$", "$Q$", "gap (\\%)", "$U_m\\ge0$"], rows))156    ok = m6[m6.buyer_participation_ok]; aa = ok[(ok.shock == "A") & np.isclose(ok.value, 0.7)].iloc[0]; pp = ok[(ok.shock == "phi_m") & np.isclose(ok.value, 0.7)].iloc[0]; b0 = ok[(ok.shock == "A") & np.isclose(ok.value, 1.0)].iloc[0]157    macro("expSixPriceA", 100 * (aa.price_mean / b0.price_mean - 1), 2); macro("expSixPricePhi", 100 * (pp.price_mean / b0.price_mean - 1), 2)158    macro("expSixGapA", 100 * aa.gap_mean, 2); macro("expSixGapPhi", 100 * pp.gap_mean, 2); macro("expSixClosingA", aa.total_duration_months, 2); macro("expSixClosingPhi", pp.total_duration_months, 2); macro("expSixClosingBase", b0.total_duration_months, 2)159    macro("expSixSA", aa.S, 3); macro("expSixSPhi", pp.S, 3); macro("expSixQA", aa.Q, 3); macro("expSixQPhi", pp.Q, 3)160    macro("phiMinParticipation", f"{d[d.buyer_participation_ok].phi_m.min():.2f}")161    g7 = d[np.isclose(d.phi_m, 0.70)]; macro("gapPhiSeventy", 100 * (g7.gap * g7.f).sum() / g7.f.sum(), 2)162    nw = nu[np.isclose(nu.nu, 52.0)]; macro("gapNuWeek", 100 * (nw.gap * nw.f).sum() / nw.f.sum(), 1)163    nf = nu[np.isclose(nu.nu, 3.0)]; macro("gapNuFourMonths", 100 * (nf.gap * nf.f).sum() / nf.f.sum(), 1)164    macro("expSixPriceA", abs(100 * (aa.price_mean / b0.price_mean - 1)), 2); macro("expSixPricePhi", abs(100 * (pp.price_mean / b0.price_mean - 1)), 2)165166167def tab05_heterogeneity():168    h = rd("exp08_heterogeneity_spread.csv")169    rows = [[fmt(r.c_max, 2), fmt(r.c_sd, 3), fmt(r.A_mult, 1), fmt(r.theta, 2), fmt(r.price_mean, 4), fmt(r.price_sd_total, 4), f"{100 * r.price_between_share:.1f}", f"{100 * r.D_urgent:.2f}", fmt(r.pbar_range, 4), fmt(r.mean_tom_sold_months, 2)] for r in h.itertuples()]170    write("tab05_heterogeneity", tabular(["$c_H$", "s.d.$(c)$", "$A/A_0$", "$\\theta$", "price", "s.d.\\ price", "between (\\%)", "$D_{urg}$ (\\%)", "$\\bar p$ range", "TOM (mo.)"], rows))171    b = rd("exp08_dispersion_vs_beta.csv")172    rows = [[fmt(r.beta, 2), fmt(r.theta, 2), fmt(np.sqrt(r.price_var_within), 4), fmt(np.sqrt(r.price_var_between), 4), f"{100 * r.price_between_share:.1f}", f"{100 * r.D_urgent:.2f}"] for r in b.itertuples()]173    write("tab05b_dispersion_beta", tabular(["$\\beta$", "$\\theta$", "within s.d.", "between s.d.", "between share (\\%)", "$D_{urg}$ (\\%)"], rows))174    r06 = h[(np.isclose(h.c_max, 0.6)) & np.isclose(h.A_mult, 1.0)].iloc[0]; r03 = h[(np.isclose(h.c_max, 0.3)) & np.isclose(h.A_mult, 1.0)].iloc[0]175    macro("DurgentCmaxSix", 100 * r06.D_urgent, 1); macro("betweenShareCmaxSix", 100 * r06.price_between_share, 1); macro("betweenShareCmaxThree", 100 * r03.price_between_share, 1)176    lo = h[(np.isclose(h.c_max, 0.3)) & np.isclose(h.A_mult, 0.7)].iloc[0]177    macro("betweenShareLowA", 100 * lo.price_between_share, 1); macro("sdPriceLowA", 100 * lo.price_sd_total, 2)178179180def tab06_selection():181    d = rd("exp10_selection_decomposition.csv"); d = d[(d.kind == "GE") & d.A_mult.isin([0.7, 1.3])]182    rows = []183    for r in d.itertuples():184        rows.append([r.G, fmt(r.c_max, 1), fmt(r.A_mult, 1), f"{100 * r.within_log:.2f}", f"{100 * r.composition_impact_log:.3f}", f"{100 * r.composition_ss_log:.3f}", fmt(r.amplification_ratio_impact, 4), fmt(r.eps_patient, 3), fmt(r.eps_urgent, 3), ("amplifies" if r.composition_impact_log * r.within_log > 0 else "dampens")])185    write("tab06_selection_bias", tabular(["$G$", "$c_H$", "$A/A_0$", "within (\\%)", "comp.\\ impact (\\%)", "comp.\\ s.s.\\ (\\%)", "ratio", "$\\epsilon(c_L)$", "$\\epsilon(c_H)$", "selection"], rows))186    mx = d.loc[d.composition_impact_log.abs().idxmax()]187    macro("compMaxAbs", 100 * abs(mx.composition_impact_log), 2); macro("compMaxG", mx.G); macro("compMaxCmax", mx.c_max, 1); macro("compMaxA", mx.A_mult, 1)188    macro("ampRatioMax", d.amplification_ratio_impact.max(), 3); macro("ampRatioMin", d.amplification_ratio_impact.min(), 3)189    tn = d[(d.G == "truncnorm") & np.isclose(d.c_max, 0.3) & np.isclose(d.A_mult, 0.7)].iloc[0]190    macro("withinTN", 100 * tn.within_log, 2); macro("compTN", 100 * tn.composition_impact_log, 3)191    ex = d[(d.G == "exponential") & np.isclose(d.c_max, 0.3) & np.isclose(d.A_mult, 0.7)].iloc[0]192    macro("withinExp", 100 * ex.within_log, 2); macro("compExp", 100 * ex.composition_impact_log, 3)193    # dynamics summary194    rows = []195    for sc in ("perm_A_minus20", "perm_A_plus20", "temp_A_minus20_1y", "perm_phi_minus20", "perm_A_minus20_exponential", "perm_A_minus20_delta0"):196        t = rd(f"exp09_transition_{sc}.csv"); j = json.load(open(ROOT / "results" / "raw" / f"exp09_transition_{sc}.json"))197        P0 = 0.0198        b0 = rd("exp01_baseline_moments.csv").iloc[0].price_mean if "exponential" not in sc and "delta0" not in sc else np.nan199        rows.append([sc.replace("_", "\\_"), fmt(t.theta.iloc[0], 2), fmt(t.theta.iloc[-1], 2), fmt(t.P_obs.iloc[0], 4), fmt(t.P_obs.iloc[-1], 4),200                     f"{1e4 * np.log(t.P_obs.iloc[0] / t.P_fix.iloc[0]):.1f}", f"{1e4 * np.log(t.P_obs / t.P_fix).abs().max():.1f}", fmt(t.Q.iloc[0], 3), fmt(t.Q.min(), 3), fmt(t.Q.iloc[-1], 3), fmt(t.S.max(), 3), fmt(j["converged"]), str(j["iterations"])])201    write("tab06b_dynamics", tabular(["scenario", "$\\theta_{0^+}$", "$\\theta_\\infty$", "$P_{0^+}$", "$P_\\infty$", "comp.\\ $0^+$ (bp)", "max $|$comp.$|$ (bp)", "$Q_{0^+}$", "$Q_{min}$", "$Q_\\infty$", "$S_{max}$", "conv.", "iter."], rows))202    t = rd("exp09_transition_perm_A_minus20.csv"); base = rd("exp01_baseline_moments.csv").iloc[0]203    macro("dynPriceImpact", 100 * (t.P_obs.iloc[0] / base.price_mean - 1), 2); macro("dynQImpact", 100 * (t.Q.iloc[0] / base.Q - 1), 1); macro("dynQEnd", 100 * (t.Q.iloc[-1] / base.Q - 1), 1)204    macro("dynSEnd", 100 * (t.S.iloc[-1] / base.S - 1), 1); macro("dynCompImpactBp", 1e4 * np.log(t.P_obs.iloc[0] / t.P_fix.iloc[0]), 2); macro("dynCompMaxBp", 1e4 * np.log(t.P_obs / t.P_fix).abs().max(), 2)205    half = t[t.S >= t.S.iloc[0] + 0.5 * (t.S.iloc[-1] - t.S.iloc[0])].t.iloc[0]; macro("dynSHalfLifeMonths", 12 * half, 1)206    tt = rd("exp09_transition_temp_A_minus20_1y.csv"); macro("dynTempQRebound", 100 * (tt.Q.max() / base.Q - 1), 1)207208209def tab07_robustness():210    d = rd("exp12_sensitivity_oat.csv")211    rows = [[r.parameter.replace("_", "\\_"), fmt(r.value, 3), fmt(r.theta, 2), fmt(r.price_mean, 4), fmt(r.mean_tom_sold_months, 2), f"{100 * r.D_urgent:.2f}", f"{100 * r.price_between_share:.1f}", fmt(r.Q, 3), fmt(r.converged)] for r in d.itertuples()]212    write("tab07_sensitivity", tabular(["parameter", "value", "$\\theta$", "price", "TOM (mo.)", "$D_{urg}$ (\\%)", "between (\\%)", "$Q$", "conv."], rows))213    a = rd("exp12_alternative_distributions.csv")214    rows = [[r.G.replace("_", "\\_"), fmt(r.is_ifr), fmt(r.theta, 2), fmt(r.price_mean, 4), fmt(r.mean_tom_sold_months, 2), f"{100 * r.D_urgent:.2f}", f"{100 * r.price_between_share:.1f}", fmt(r.price_sd_total, 4), fmt(r.converged)] for r in a.itertuples()]215    write("tab07b_distributions", tabular(["$G$", "IFR", "$\\theta$", "price", "TOM (mo.)", "$D_{urg}$ (\\%)", "between (\\%)", "s.d.\\ price", "conv."], rows))216    c = rd("exp12_convergence_types.csv")217    rows = [[str(int(r.n_types)), fmt(r.theta, 5), fmt(r.price_mean, 5), fmt(r.mean_tom_sold_months, 4), f"{100 * r.D_mean:.3f}", fmt(r.seconds, 2)] for r in c.itertuples()]218    write("tab07c_convergence_types", tabular(["types", "$\\theta$", "price", "TOM (mo.)", "$\\bar D$ (\\%)", "seconds"], rows))219    dt = rd("exp12_convergence_dt.csv")220    rows = [[fmt(r.dt, 2), fmt(r.theta_impact, 5), fmt(r.P_obs_impact, 5), fmt(r.P_obs_1y, 5), fmt(r.Q_1y, 5), fmt(r.S_1y, 5), fmt(r.converged), str(int(r.iterations)), fmt(r.seconds, 1)] for r in dt.itertuples()]221    write("tab07d_convergence_dt", tabular(["$\\Delta t$", "$\\theta_{0^+}$", "$P_{0^+}$", "$P_{1y}$", "$Q_{1y}$", "$S_{1y}$", "conv.", "iter.", "seconds"], rows))222    g = rd("exp12_global_sensitivity.csv")223    macro("globalN", f"{len(g)}"); macro("globalConvShare", 100 * g.converged.mean(), 1); macro("globalSignD", 100 * g.sign_D_urgent_pos.mean(), 1); macro("globalSignEta", 100 * g.sign_eta_increasing.mean(), 1); macro("globalSignP", 100 * g.sign_pbar_decreasing.mean(), 1)224    macro("globalTomMin", g.mean_tom_sold_months.min(), 1); macro("globalTomMax", g.mean_tom_sold_months.max(), 1); macro("globalDurgMin", 100 * g.D_urgent.min(), 1); macro("globalDurgMax", 100 * g.D_urgent.max(), 1)225    e7 = rd("exp07_entry_costs.csv")226    rows = [[fmt(r.k_mult, 2), fmt(r.kappa_b_mult, 2), fmt(r.theta, 2), fmt(r.Lam_s, 2), fmt(r.price_mean, 4), fmt(r.mean_tom_sold_months, 2), f"{100 * r.D_urgent:.2f}", fmt(r.S, 3), fmt(r.Q, 3)] for r in e7.itertuples()]227    write("tab07e_entry_costs", tabular(["$k/k_0$", "$\\kappa_b/\\kappa_{b0}$", "$\\theta$", "$\\Lambda_s$", "price", "TOM (mo.)", "$D_{urg}$ (\\%)", "$S$", "$Q$"], rows))228    u = rd("exp07_uniqueness_draws.csv"); macro("uniqDraws", f"{len(u)}")229    rows = [["draws", str(len(u))], ["draws with exactly one root", str(int((u.n_roots == 1).sum()))], ["draws with several roots", str(int((u.n_roots > 1).sum()))],230            ["draws without a root", str(int((u.n_roots == 0).sum()))], ["range of $A$", f"[{u.A.min():.2f}, {u.A.max():.2f}]"], ["range of $\\beta$", f"[{u.beta.min():.2f}, {u.beta.max():.2f}]"],231            ["range of $\\delta$", f"[{u.delta.min():.2f}, {u.delta.max():.2f}]"], ["range of $\\phi$", f"[{u.phi.min():.2f}, {u.phi.max():.2f}]"], ["range of $\\theta^*$", f"[{u.theta.min():.2f}, {u.theta.max():.2f}]"]]232    write("tab07f_uniqueness", tabular(["Item", "Value"], rows, "lr")); macro("uniqMultiple", f"{int((u.n_roots > 1).sum())}"); macro("uniqNone", f"{int((u.n_roots == 0).sum())}")233    ct = c[c.n_types == c.n_types.max()].iloc[0]; c9 = c[c.n_types == 9].iloc[0]234    macro("convTypesRelErr", f"{100 * abs(c9.price_mean - ct.price_mean) / ct.price_mean:.1e}"); macro("convTypesMax", f"{int(c.n_types.max())}")235    val = json.load(open(ROOT / "results" / "logs" / "validation.json"))236    macro("mcMaxRelErr", 100 * max(v["max_abs_rel_err"] for v in val["montecarlo"].values()), 2); macro("mcTypesRelErr", 100 * val["montecarlo_types_max_rel_err_price"], 2)237    pm = rd("val_montecarlo.csv"); macro("mcN", f"{int(pm.n.max()):,}".replace(",", "{,}"))238239240def tab08_welfare():241    b = rd("exp05_bargaining_power.csv"); pl = rd("exp05_planner_benchmark.csv")242    rows = []243    for beta in (0.2, 0.35, 0.5, 0.55, 0.65, 0.8):244        for lab in ("heterogeneous", "homogeneous"):245            r = b[np.isclose(b.beta, beta) & (b.sellers == lab)].iloc[0]246            rows.append([fmt(beta, 2), lab, fmt(r.theta, 2), fmt(r.price_mean, 4), fmt(r.mean_tom_sold_months, 2), fmt(r.welfare_total, 4), fmt(r.welfare_sellers, 4), fmt(r.welfare_holding_costs, 4), f"{100 * r.D_urgent:.2f}"])247    write("tab08_welfare", tabular(["$\\beta$", "sellers", "$\\theta$", "price", "TOM (mo.)", "welfare", "sellers' value", "holding costs", "$D_{urg}$ (\\%)"], rows))248    rows = [[r.sellers, fmt(r.theta_decentralised, 3), fmt(r.theta_planner, 3), fmt(r.welfare_decentralised, 5), fmt(r.welfare_planner, 5), f"{100 * (r.welfare_planner / r.welfare_decentralised - 1):.3f}"] for r in pl.itertuples()]249    write("tab08b_planner", tabular(["sellers", "$\\theta$ decentralised", "$\\theta$ planner", "welfare decentralised", "welfare planner", "gain (\\%)"], rows))250    for lab, tag in (("heterogeneous", "Het"), ("homogeneous", "Hom")):251        g = b[b.sellers == lab]; best = g.loc[g.welfare_total.idxmax()]252        macro(f"betaStar{tag}", best.beta, 2); macro(f"welfareLoss{tag}", 100 * (1 - g[np.isclose(g.beta, 0.5)].welfare_total.iloc[0] / best.welfare_total), 2)253        r = pl[pl.sellers == lab].iloc[0]; macro(f"plannerGain{tag}", 100 * (r.welfare_planner / r.welfare_decentralised - 1), 2); macro(f"plannerTheta{tag}", r.theta_planner, 2)254    hos = b[np.isclose(b.beta, 0.5) & (b.sellers == "heterogeneous")].iloc[0]; lo_ = b[np.isclose(b.beta, 0.2) & (b.sellers == "heterogeneous")].iloc[0]255    hi_ = b[np.isclose(b.beta, 0.8) & (b.sellers == "heterogeneous")].iloc[0]256    macro("thetaBetaTwenty", lo_.theta, 1); macro("thetaBetaEighty", hi_.theta, 1); macro("tomBetaTwenty", lo_.mean_tom_sold_months, 1); macro("tomBetaEighty", hi_.mean_tom_sold_months, 1)257    macro("welfareLossBetaTwenty", 100 * (1 - lo_.welfare_total / hos.welfare_total), 1)258259260def tab09_benchmarks():261    pc = rd("exp11_protocol_comparison.csv")262    rows = [[r.protocol, fmt(r.A_mult, 1), fmt(r.theta, 2), fmt(r.price_mean, 4), fmt(r.mean_tom_sold_months, 2), fmt(r.price_sd_total, 4), f"{100 * r.price_between_share:.1f}", f"{100 * r.D_urgent:.2f}", fmt(r.welfare_total, 4), fmt(r.Q, 3)] for r in pc.itertuples()]263    write("tab09_protocols", tabular(["protocol", "$A/A_0$", "$\\theta$", "price", "TOM (mo.)", "s.d.\\ price", "between (\\%)", "$D_{urg}$ (\\%)", "welfare", "$Q$"], rows))264    n = pc[(pc.protocol == "nash") & np.isclose(pc.A_mult, 1.0)].iloc[0]; p_ = pc[(pc.protocol == "posted") & np.isclose(pc.A_mult, 1.0)].iloc[0]265    macro("postedTheta", p_.theta, 2); macro("postedPrice", p_.price_mean, 4); macro("postedTom", p_.mean_tom_sold_months, 2); macro("postedDurg", 100 * p_.D_urgent, 2); macro("postedWelfareGap", 100 * (p_.welfare_total / n.welfare_total - 1), 1)266    macro("postedSd", 100 * p_.price_sd_total, 2)267268269def write_numbers():270    lines = ["% generated by src/tables.py from results/ — do not edit"] + [f"\\newcommand{{\\{k}}}{{{v}}}" for k, v in sorted(MACROS.items())]271    write("numbers", "\n".join(lines) + "\n")272273274if __name__ == "__main__":275    import sys276    failed = []277    for f in (tab01_parameters, tab02_baseline, tab03_comparative_statics, tab04_financing, tab05_heterogeneity, tab06_selection, tab07_robustness, tab08_welfare, tab09_benchmarks):278        try:279            f()280        except Exception as ex:281            failed.append((f.__name__, repr(ex))); print("FAILED", f.__name__, repr(ex))282    write_numbers()283    if failed:284        sys.exit(1)285