SPB Git

spb/wp46_uqo

Public

WP46 exhibits: representative-spec heatmaps, trimmed Pareto axis with materiality band

Simon-Pierre Boucher committed (Nov 9, 2026) parent b752125 Browse files

2 changed files +21 −5

modified scripts/build_paper.py +13 −3
@@ -112,9 +112,19 @@ def main() -> None:
112 112 if cc.exists():
113 113 TB.crosscheck_table(json.load(open(cc)), tdir / "tab_crosscheck.tex")
114 114 # --- figures
115 − F.ratio_heatmap(per_symbol, 1, tierA, F.FIG / "fig_heatmap_h1_tierA.pdf", "Tier A, h = 1")
116 − F.ratio_heatmap(per_symbol, 22, tierA, F.FIG / "fig_heatmap_h22_tierA.pdf", "Tier A, h = 22")
117 − F.ratio_heatmap(per_symbol, 1, tierB + [s for s in specs_all if s.startswith("garch11__normal")], F.FIG / "fig_heatmap_h1_tierB.pdf", "Tier B, h = 1")
115 + # representative specifications (Student-t where available) ordered by family: readable heatmaps (mandate §5-6)
116 + rep0 = {}
117 + for sp in specs_all:
118 + mid, dist, _ = sp.split("__"); pref = {"t": 0, "normal": 1, "empirical": 2, "ged": 3, "skewt": 4}[dist]
119 + if mid not in rep0 or pref < rep0[mid][0]:
120 + rep0[mid] = (pref, sp)
121 + fam_order = ["baseline", "classical", "asymmetric", "power", "fractional", "component", "regime-switching", "score-driven", "realized"]
122 + repA = sorted([v[1] for v in rep0.values() if F.fam(v[1]) != "realized" and v[1].split("__")[0] not in F.EXT_PARENT], key=lambda s: (fam_order.index(F.fam(s)), s))
123 + repB = sorted([v[1] for v in rep0.values() if F.fam(v[1]) == "realized" and v[1].split("__")[0] not in F.EXT_PARENT], key=lambda s: s)
124 + F.ratio_heatmap(per_symbol, 1, repA, F.FIG / "fig_heatmap_h1_tierA.pdf", "Tier A (representative specifications), h = 1")
125 + F.ratio_heatmap(per_symbol, 22, repA, F.FIG / "fig_heatmap_h22_tierA.pdf", "Tier A (representative specifications), h = 22")
126 + F.ratio_heatmap(per_symbol, 1, repB + [s for s in specs_all if s.startswith("garch11__normal")], F.FIG / "fig_heatmap_h1_tierB.pdf", "Tier B (+ GARCH(1,1)-N reference), h = 1")
127 + F.ratio_heatmap(per_symbol, 1, tierA, F.FIG / "fig_heatmap_h1_tierA_full.pdf", "Tier A, all innovation laws, h = 1", vmin=0.7, vmax=1.3)
118 128 F.mcs_matrix(per_symbol, 1, "A", F.FIG / "fig_mcs_h1_tierA.pdf", "90% MCS, Tier A, h = 1")
119 129 F.mcs_matrix(per_symbol, 22, "A", F.FIG / "fig_mcs_h22_tierA.pdf", "90% MCS, Tier A, h = 22")
120 130 F.mcs_matrix(per_symbol, 1, "B", F.FIG / "fig_mcs_h1_tierB.pdf", "90% MCS, Tier B (+ GARCH(1,1)-N), h = 1")
modified src/wp46/reporting/figures.py +8 −2
@@ -172,10 +172,15 @@ def horizon_profile(pooled: pd.DataFrame, specs: list[str], path: Path, cls: str
172 172 # ----------------------------------------------------------------------------- 6. Pareto: accuracy vs cost / failure
173 173 def pareto(pooled_h1: pd.DataFrame, accounting: pd.DataFrame, path: Path, xcol: str = "fit_time_median", xlabel: str = "Median refit time (s, log scale)") -> Path:
174 174 d = pooled_h1.merge(accounting.drop(columns=[c for c in ("family",) if c in accounting]), on="spec_id", how="inner")
175 + d = d[np.isfinite(d.mean_ratio)]
176 + ymax = min(float(np.nanpercentile(d.mean_ratio, 95)) * 1.05, 2.5) # degenerate outliers never dictate the axis (mandate §6)
177 + n_clip = int((d.mean_ratio > ymax).sum()); d = d.assign(mean_ratio_plot=np.minimum(d.mean_ratio, ymax))
175 178 fig, ax = plt.subplots(figsize=(ps.W2, 3.2))
176 179 for f in sorted(d.family.unique()):
177 180 s = d[d.family == f]
178 − ax.scatter(s[xcol], s.mean_ratio, marker=ps.FAMILY_MARKER.get(f, "o"), color=ps.FAMILY_COLOR.get(f, ps.INK3), s=22, label=ps.FAMILY_LABEL.get(f, f), zorder=3, edgecolor="white", linewidth=0.4)
181 + ax.scatter(s[xcol], s.mean_ratio_plot, marker=ps.FAMILY_MARKER.get(f, "o"), color=ps.FAMILY_COLOR.get(f, ps.INK3), s=22, label=ps.FAMILY_LABEL.get(f, f), zorder=3, edgecolor="white", linewidth=0.4)
182 + if n_clip:
183 + ax.text(0.01, 0.98, f"{n_clip} specification(s) above {ymax:.2f} shown at the top edge", transform=ax.transAxes, fontsize=6, va="top", color=ps.INK2)
179 184 # Pareto frontier (min ratio for cost <= x)
180 185 dd = d.sort_values(xcol); best = np.minimum.accumulate(dd.mean_ratio.values)
181 186 ax.step(dd[xcol], best, where="post", color=ps.INK, lw=0.8, ls="--", label="Pareto frontier")
@@ -183,7 +188,8 @@ def pareto(pooled_h1: pd.DataFrame, accounting: pd.DataFrame, path: Path, xcol:
183 188 ax.annotate(spec_label(r.spec_id), (r[xcol], r.mean_ratio), xytext=(3, 3), textcoords="offset points", fontsize=5.5, color=ps.INK2)
184 189 if "time" in xcol:
185 190 ax.set_xscale("log")
186 − ax.axhline(1, color=ps.INK3, lw=0.7); ax.set_xlabel(xlabel); ax.set_ylabel("Pooled QLIKE ratio to GARCH(1,1)-N, h = 1")
191 + ax.axhline(1, color=ps.INK3, lw=0.7); ax.axhspan(0.98, 1.02, color="#f2f2f0", zorder=0) # ±2 % materiality band
192 + ax.set_xlabel(xlabel); ax.set_ylabel("Pooled QLIKE ratio to GARCH(1,1)-N, h = 1"); ax.set_ylim(min(0.8, float(d.mean_ratio_plot.min()) * 0.98), ymax * 1.02)
187 193 ax.legend(ncol=3, fontsize=6, loc="upper right")
188 194 ps.save(fig, path); _audit(path.name, ["results/tables/pooled_*.csv", "results/tables/accounting_*.csv"], len(d))
189 195 return path
190 196
Menu

Theme