"""LaTeX tables and number macros for WP19, generated only from results/processed/*.csv and config/baseline.yaml (Phase 5). Writes results/tables/*.tex and copies to paper/tables/. Run: python -m src.tables """ from __future__ import annotations import json import shutil from pathlib import Path import numpy as np import pandas as pd import yaml ROOT = Path(__file__).resolve().parents[1] PRO = ROOT / "results" / "processed"; TAB = ROOT / "results" / "tables"; PTAB = ROOT / "paper" / "tables" TAB.mkdir(parents=True, exist_ok=True); PTAB.mkdir(parents=True, exist_ok=True) MACROS = {} def rd(name): return pd.read_csv(PRO / name) def write(name: str, body: str): (TAB / f"{name}.tex").write_text(body) shutil.copy(TAB / f"{name}.tex", PTAB / f"{name}.tex") print("table", name) def fmt(x, d=3): if isinstance(x, (bool, np.bool_)): return "yes" if x else "no" if x is None or (isinstance(x, float) and np.isnan(x)): return "--" return f"{x:.{d}f}" def macro(name, value, d=2, pct=False): """Register a number macro \\name{...}; names must be letters only.""" assert name.isalpha(), name if isinstance(value, str): MACROS[name] = value else: MACROS[name] = f"{100 * value:.{d}f}" if pct else f"{value:.{d}f}" def tabular(cols, rows, align=None): align = align or "l" + "r" * (len(cols) - 1) out = [f"\\begin{{tabular}}{{{align}}}", "\\toprule", " & ".join(cols) + " \\\\", "\\midrule"] out += [" & ".join(r) + " \\\\" for r in rows] out += ["\\bottomrule", "\\end{tabular}"] return "\n".join(out) + "\n" # ------------------------------------------------------------------------------------------------ def tab01_parameters(): cfg = yaml.safe_load((ROOT / "config" / "baseline.yaml").read_text()) rows = [ ("$r$", "discount rate", fmt(cfg["preferences"]["r"], 2), "per year", "real rate of 4\\%; standard in housing search models"), ("$s$", "listing inflow", fmt(cfg["sellers"]["s"], 1), "listings per year", "normalisation"), ("$\\delta$", "withdrawal hazard", fmt(cfg["sellers"]["delta"], 2), "per year", "10--20\\% of listings withdrawn at baseline hazards"), ("$w_0$", "own-use value on withdrawal", fmt(cfg["sellers"]["w0"], 2), "units of $q$", "gains from trade of about 20\\% plus match component"), ("$[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"), ("$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"), ("$k$", "buyer entry cost", fmt(cfg["buyers"]["k"], 2), "units of $q$", "search set-up, inspections"), ("$\\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"), ("$\\phi$", "qualification probability", fmt(cfg["buyers"]["phi"], 2), "per meeting", "pre-approval screen"), ("$A$", "matching efficiency", fmt(cfg["matching"]["A"], 1), "meetings per year at $\\theta=1$", "meetings = serious offers, not viewings"), ("$\\alpha$", "matching elasticity (buyers)", fmt(cfg["matching"]["alpha"], 2), "--", "symmetric Cobb--Douglas"), ("$\\beta$", "seller Nash share", fmt(cfg["bargaining"]["beta"], 2), "--", "symmetric; Hosios value $1-\\alpha$"), ("$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"), ("$\\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"), ] 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}")) def tab02_baseline(): m = rd("exp01_baseline_moments.csv").iloc[0]; t = rd("exp01_baseline_types.csv") rows = [ ("Tightness $\\theta^*$ (buyers per listing)", fmt(m.theta, 2)), ("Effective seller contact rate $\\Lambda_s$ (per year)", fmt(m.Lam_s, 2)), ("Effective buyer contact rate $\\Lambda_b$ (per year)", fmt(m.Lam_b, 2)), ("Inventory of listings $S$ (inflow $=1$)", fmt(m.S, 3)), ("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)), ("Mean buyer search duration (months)", fmt(12 * m.buyer_mean_duration, 2)), ("Transaction-weighted mean price", fmt(m.price_mean, 4)), ("Price s.d. among identical houses", fmt(m.price_sd_total, 4)), ("Between-type share of price variance", fmt(m.price_between_share, 3)), ("Expected price, most patient / most urgent type", f"{m.pbar_patient:.4f} / {m.pbar_urgent:.4f}"), ("Urgency discount $D$, most urgent type / transaction-weighted (\\%)", f"{100 * m.D_urgent:.2f} / {100 * m.D_mean:.2f}"), ("Sale hazard, most patient / most urgent type (per year)", f"{m.eta_patient:.2f} / {m.eta_urgent:.2f}"), ("Walrasian free-entry benchmark $p^*$; gap $\\Gamma$ (\\%)", f"{m.p_star:.3f}; {100 * m.Gamma:.1f}"), ("Welfare flow: total / sellers / holding costs", f"{m.welfare_total:.4f} / {m.welfare_sellers:.4f} / {m.welfare_holding_costs:.4f}"), ("Max.\\ seller residual / free-entry residual", f"{m.seller_resid_max:.1e} / {abs(m.free_entry_resid):.1e}"), ] write("tab02_baseline_moments", tabular(["Moment", "Value"], [list(r) for r in rows], "lr")) 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()] 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)) for k_, v in (("thetaBase", m.theta), ("tomBase", m.mean_tom_sold_months), ("DurgentBase", 100 * m.D_urgent), ("DmeanBase", 100 * m.D_mean), ("sdPriceBase", 100 * m.price_sd_total), ("betweenShareBase", 100 * m.price_between_share), ("pstarBase", m.p_star), ("GammaBase", 100 * m.Gamma), ("buyerDurBase", 12 * m.buyer_mean_duration), ("etaPatientBase", m.eta_patient), ("etaUrgentBase", m.eta_urgent), ("soldShareBase", 100 * m.Q), ("LamSBase", m.Lam_s), ("LamBBase", m.Lam_b), ("inventoryBase", m.S), ("accPatientBase", 100 * m.acc_patient), ("accUrgentBase", 100 * m.acc_urgent)): macro(k_, v, 2) 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) macro("nTypes", f"{len(t)}") def tab03_comparative_statics(): a = rd("exp03_arrival_shock.csv") sel = [0.5, 0.7, 0.85, 1.0, 1.2, 1.5, 2.0] rows = [] for mult in sel: for kind in ("GE", "PE"): r = a[(np.isclose(a.A_mult, mult)) & (a.kind == kind)].iloc[0] 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), f"{100 * r.D_urgent:.2f}", f"{100 * r.D_mean:.2f}", fmt(r.price_sd_total, 4), f"{100 * r.Gamma:.1f}"]) 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)) g = a[a.kind == "GE"] lo = g[np.isclose(g.A_mult, 0.5)].iloc[0]; hi = g[np.isclose(g.A_mult, 1.0)].iloc[0] 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) macro("thetaHalfA", lo.theta, 2); macro("inventoryHalfA", lo.S, 3) pe = a[(a.kind == "PE") & np.isclose(a.A_mult, 0.5)].iloc[0] macro("priceDropHalfAPE", 100 * (1 - pe.price_mean / hi.price_mean), 1); macro("tomHalfAPE", pe.mean_tom_sold_months, 1) amp = rd("exp07_amplification.csv") for r in amp.itertuples(): 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) gs = rd("exp03_gamma_sign_change.csv"); cross = gs[gs.Gamma < 0].Lam_s.min() macro("GammaCrossLam", cross, 0) def tab04_financing(): q = rd("exp04_qualification_screen.csv") 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)] write("tab04a_qualification_screen", tabular(["$\\phi$", "$\\theta$", "$\\Lambda_s$", "price", "TOM (mo.)", "$D_{urg}$ (\\%)", "$S$"], rows)) d = rd("exp04_financing_phi.csv") rows = [] for phi, g in d.groupby("phi_m"): wmean = lambda col: (g[col] * g.f).sum() / g.f.sum() 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), 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])]) 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)) b = d[np.isclose(d.phi_m, 0.85)] wm = lambda col: (b[col] * b.f).sum() / b.f.sum() 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) 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) 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) nu = rd("exp04_financing_nu.csv"); rows = [] for v, g in nu.groupby("nu"): wmean = lambda col: (g[col] * g.f).sum() / g.f.sum() 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}"]) write("tab04c_financing_delay", tabular(["$\\nu$", "delay (mo.)", "$\\theta$", "$\\Delta_b$ (\\%)", "selection (\\%)", "gap (\\%)", "TOM (mo.)", "cash sales (\\%)"], rows)) bt = rd("exp04_financing_beta.csv"); rows = [] for v, g in bt.groupby("beta"): wmean = lambda col: (g[col] * g.f).sum() / g.f.sum() 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}"]) write("tab04d_financing_bargaining", tabular(["$\\beta$", "$\\theta$", "$\\Delta_b$ (\\%)", "outside (\\%)", "selection (\\%)", "gap (\\%)", "$\\Delta_b$ patient", "$\\Delta_b$ urgent"], rows)) m6 = rd("exp06_matching_vs_approval.csv") 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()] write("tab04e_matching_vs_approval", tabular(["shock", "value", "agreement hazard", "$\\theta$", "price", "listed (mo.)", "to closing (mo.)", "$S$", "$Q$", "gap (\\%)", "$U_m\\ge0$"], rows)) 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] macro("expSixPriceA", 100 * (aa.price_mean / b0.price_mean - 1), 2); macro("expSixPricePhi", 100 * (pp.price_mean / b0.price_mean - 1), 2) 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) macro("expSixSA", aa.S, 3); macro("expSixSPhi", pp.S, 3); macro("expSixQA", aa.Q, 3); macro("expSixQPhi", pp.Q, 3) macro("phiMinParticipation", f"{d[d.buyer_participation_ok].phi_m.min():.2f}") g7 = d[np.isclose(d.phi_m, 0.70)]; macro("gapPhiSeventy", 100 * (g7.gap * g7.f).sum() / g7.f.sum(), 2) nw = nu[np.isclose(nu.nu, 52.0)]; macro("gapNuWeek", 100 * (nw.gap * nw.f).sum() / nw.f.sum(), 1) nf = nu[np.isclose(nu.nu, 3.0)]; macro("gapNuFourMonths", 100 * (nf.gap * nf.f).sum() / nf.f.sum(), 1) macro("expSixPriceA", abs(100 * (aa.price_mean / b0.price_mean - 1)), 2); macro("expSixPricePhi", abs(100 * (pp.price_mean / b0.price_mean - 1)), 2) def tab05_heterogeneity(): h = rd("exp08_heterogeneity_spread.csv") 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()] 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)) b = rd("exp08_dispersion_vs_beta.csv") 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()] write("tab05b_dispersion_beta", tabular(["$\\beta$", "$\\theta$", "within s.d.", "between s.d.", "between share (\\%)", "$D_{urg}$ (\\%)"], rows)) 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] macro("DurgentCmaxSix", 100 * r06.D_urgent, 1); macro("betweenShareCmaxSix", 100 * r06.price_between_share, 1); macro("betweenShareCmaxThree", 100 * r03.price_between_share, 1) lo = h[(np.isclose(h.c_max, 0.3)) & np.isclose(h.A_mult, 0.7)].iloc[0] macro("betweenShareLowA", 100 * lo.price_between_share, 1); macro("sdPriceLowA", 100 * lo.price_sd_total, 2) def tab06_selection(): d = rd("exp10_selection_decomposition.csv"); d = d[(d.kind == "GE") & d.A_mult.isin([0.7, 1.3])] rows = [] for r in d.itertuples(): 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")]) 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)) mx = d.loc[d.composition_impact_log.abs().idxmax()] 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) macro("ampRatioMax", d.amplification_ratio_impact.max(), 3); macro("ampRatioMin", d.amplification_ratio_impact.min(), 3) tn = d[(d.G == "truncnorm") & np.isclose(d.c_max, 0.3) & np.isclose(d.A_mult, 0.7)].iloc[0] macro("withinTN", 100 * tn.within_log, 2); macro("compTN", 100 * tn.composition_impact_log, 3) ex = d[(d.G == "exponential") & np.isclose(d.c_max, 0.3) & np.isclose(d.A_mult, 0.7)].iloc[0] macro("withinExp", 100 * ex.within_log, 2); macro("compExp", 100 * ex.composition_impact_log, 3) # dynamics summary rows = [] for sc in ("perm_A_minus20", "perm_A_plus20", "temp_A_minus20_1y", "perm_phi_minus20", "perm_A_minus20_exponential", "perm_A_minus20_delta0"): t = rd(f"exp09_transition_{sc}.csv"); j = json.load(open(ROOT / "results" / "raw" / f"exp09_transition_{sc}.json")) P0 = 0.0 b0 = rd("exp01_baseline_moments.csv").iloc[0].price_mean if "exponential" not in sc and "delta0" not in sc else np.nan 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), 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"])]) 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)) t = rd("exp09_transition_perm_A_minus20.csv"); base = rd("exp01_baseline_moments.csv").iloc[0] 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) 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) 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) tt = rd("exp09_transition_temp_A_minus20_1y.csv"); macro("dynTempQRebound", 100 * (tt.Q.max() / base.Q - 1), 1) def tab07_robustness(): d = rd("exp12_sensitivity_oat.csv") 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()] write("tab07_sensitivity", tabular(["parameter", "value", "$\\theta$", "price", "TOM (mo.)", "$D_{urg}$ (\\%)", "between (\\%)", "$Q$", "conv."], rows)) a = rd("exp12_alternative_distributions.csv") 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()] write("tab07b_distributions", tabular(["$G$", "IFR", "$\\theta$", "price", "TOM (mo.)", "$D_{urg}$ (\\%)", "between (\\%)", "s.d.\\ price", "conv."], rows)) c = rd("exp12_convergence_types.csv") 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()] write("tab07c_convergence_types", tabular(["types", "$\\theta$", "price", "TOM (mo.)", "$\\bar D$ (\\%)", "seconds"], rows)) dt = rd("exp12_convergence_dt.csv") 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()] write("tab07d_convergence_dt", tabular(["$\\Delta t$", "$\\theta_{0^+}$", "$P_{0^+}$", "$P_{1y}$", "$Q_{1y}$", "$S_{1y}$", "conv.", "iter.", "seconds"], rows)) g = rd("exp12_global_sensitivity.csv") 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) 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) e7 = rd("exp07_entry_costs.csv") 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()] write("tab07e_entry_costs", tabular(["$k/k_0$", "$\\kappa_b/\\kappa_{b0}$", "$\\theta$", "$\\Lambda_s$", "price", "TOM (mo.)", "$D_{urg}$ (\\%)", "$S$", "$Q$"], rows)) u = rd("exp07_uniqueness_draws.csv"); macro("uniqDraws", f"{len(u)}") 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()))], ["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}]"], ["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}]"]] 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())}") ct = c[c.n_types == c.n_types.max()].iloc[0]; c9 = c[c.n_types == 9].iloc[0] macro("convTypesRelErr", f"{100 * abs(c9.price_mean - ct.price_mean) / ct.price_mean:.1e}"); macro("convTypesMax", f"{int(c.n_types.max())}") val = json.load(open(ROOT / "results" / "logs" / "validation.json")) 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) pm = rd("val_montecarlo.csv"); macro("mcN", f"{int(pm.n.max()):,}".replace(",", "{,}")) def tab08_welfare(): b = rd("exp05_bargaining_power.csv"); pl = rd("exp05_planner_benchmark.csv") rows = [] for beta in (0.2, 0.35, 0.5, 0.55, 0.65, 0.8): for lab in ("heterogeneous", "homogeneous"): r = b[np.isclose(b.beta, beta) & (b.sellers == lab)].iloc[0] 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}"]) write("tab08_welfare", tabular(["$\\beta$", "sellers", "$\\theta$", "price", "TOM (mo.)", "welfare", "sellers' value", "holding costs", "$D_{urg}$ (\\%)"], rows)) 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()] write("tab08b_planner", tabular(["sellers", "$\\theta$ decentralised", "$\\theta$ planner", "welfare decentralised", "welfare planner", "gain (\\%)"], rows)) for lab, tag in (("heterogeneous", "Het"), ("homogeneous", "Hom")): g = b[b.sellers == lab]; best = g.loc[g.welfare_total.idxmax()] 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) 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) 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] hi_ = b[np.isclose(b.beta, 0.8) & (b.sellers == "heterogeneous")].iloc[0] 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) macro("welfareLossBetaTwenty", 100 * (1 - lo_.welfare_total / hos.welfare_total), 1) def tab09_benchmarks(): pc = rd("exp11_protocol_comparison.csv") 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()] write("tab09_protocols", tabular(["protocol", "$A/A_0$", "$\\theta$", "price", "TOM (mo.)", "s.d.\\ price", "between (\\%)", "$D_{urg}$ (\\%)", "welfare", "$Q$"], rows)) 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] 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) macro("postedSd", 100 * p_.price_sd_total, 2) def write_numbers(): lines = ["% generated by src/tables.py from results/ — do not edit"] + [f"\\newcommand{{\\{k}}}{{{v}}}" for k, v in sorted(MACROS.items())] write("numbers", "\n".join(lines) + "\n") if __name__ == "__main__": import sys failed = [] for f in (tab01_parameters, tab02_baseline, tab03_comparative_statics, tab04_financing, tab05_heterogeneity, tab06_selection, tab07_robustness, tab08_welfare, tab09_benchmarks): try: f() except Exception as ex: failed.append((f.__name__, repr(ex))); print("FAILED", f.__name__, repr(ex)) write_numbers() if failed: sys.exit(1)