foto / validation_and_analysis /summarize_grid.py
Hurum Maksora Tohfa
validation tests
45383d0 unverified
Raw
History Blame Contribute Delete
6.4 kB
"""R@k tables and plots from benchmark_grid.py output (Table 4)."""
import argparse, json
from pathlib import Path
import numpy as np
import pandas as pd
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
KS = [5, 10, 20, 50]
STYLES = ["terse", "casual", "vague", "detailed", "notation"]
METHOD_COLORS = {"ours": "#1a6faa", "pathfinder": "#c98a1e", "s2": "#7a4fa3"}
FORM_STYLES = {"orig": "-", "expanded": "--", "keywords": ":", "fused": "-."}
def load_tidy(path):
rows = []
for line in open(path):
rec = json.loads(line)
for style, cells in rec["cells"].items():
for cell_key, c in cells.items():
method, form = cell_key.split("/")
rows.append({
"arxiv_id": rec["arxiv_id"],
"fig_index": rec["fig_index"],
"target_in_pf": rec.get("target_in_pf"),
"style": style, "method": method, "form": form,
"fig_rank": c.get("fig_rank"),
"paper_rank": c.get("paper_rank"),
"pool_size": c.get("pool_size"),
"n_arxiv_ids": c.get("n_arxiv_ids"),
})
return pd.DataFrame(rows)
def rank_column(df, level):
if level == "fig":
return df.fig_rank
if level == "paper":
return df.paper_rank
# task: figure rank for ours, paper rank for baselines
return df.fig_rank.where(df.method == "ours", df.paper_rank)
def recall_table(df, level):
df = df.copy()
df["rank"] = rank_column(df, level)
out = []
for (style, method, form), g in df.groupby(["style", "method", "form"]):
entry = {"style": style, "method": method, "form": form,
"n": len(g)}
for k in KS:
entry[f"R@{k}"] = round(
float((g["rank"].notna() & (g["rank"] <= k)).mean()), 3)
if g.n_arxiv_ids.notna().any():
entry["s2_empty"] = round(
float((g.n_arxiv_ids == 0).mean()), 3)
out.append(entry)
t = pd.DataFrame(out)
t["style"] = pd.Categorical(t["style"], STYLES)
return t.sort_values(["style", "method", "form"]).reset_index(drop=True)
def print_blocks(t):
for style in STYLES:
block = t[t["style"] == style].drop(columns="style")
if block.empty:
continue
print(f"\n=== {style} ===")
print(block.to_string(index=False))
def plot_heatmap(t, level, k, stem):
col = f"R@{k}"
piv = t.pivot_table(index=["method", "form"], columns="style",
values=col, observed=True)
piv = piv.reindex(columns=[s for s in STYLES if s in piv.columns])
fig, ax = plt.subplots(
figsize=(1.3 * len(piv.columns) + 3, 0.42 * len(piv) + 1.5))
im = ax.imshow(piv.to_numpy(), vmin=0, vmax=1, cmap="viridis",
aspect="auto")
ax.set_xticks(range(len(piv.columns)), piv.columns)
ax.set_yticks(range(len(piv)),
[f"{m}/{f}" for m, f in piv.index])
for i in range(piv.shape[0]):
for j in range(piv.shape[1]):
v = piv.iloc[i, j]
if not np.isnan(v):
ax.text(j, i, f"{v:.2f}", ha="center", va="center",
fontsize=8,
color="white" if v < 0.55 else "black")
ax.set_title(f"{col}, level={level}")
fig.colorbar(im, ax=ax, shrink=0.8)
fig.tight_layout()
out = f"{stem}_heatmap_{level}_r{k}.png"
fig.savefig(out, dpi=200)
plt.close(fig)
return out
def plot_curves(t, level, stem):
fig, axes = plt.subplots(1, len(STYLES), figsize=(3.2 * len(STYLES), 3.4),
sharey=True)
for ax, style in zip(axes, STYLES):
block = t[t["style"] == style]
for _, r in block.iterrows():
ys = [r[f"R@{k}"] for k in KS]
ax.plot(KS, ys, color=METHOD_COLORS.get(r.method, "gray"),
linestyle=FORM_STYLES.get(r.form, "-"),
marker="o", ms=3,
label=f"{r.method}/{r.form}")
ax.set_title(style)
ax.set_xscale("log")
ax.set_xticks(KS, [str(k) for k in KS])
ax.set_xlabel("k")
ax.set_ylim(-0.03, 1.03)
ax.grid(alpha=0.25)
axes[0].set_ylabel(f"recall (level={level})")
handles, labels = axes[0].get_legend_handles_labels()
fig.legend(handles, labels, loc="center left",
bbox_to_anchor=(1.0, 0.5), fontsize=8, frameon=False)
fig.tight_layout()
out = f"{stem}_curves_{level}.png"
fig.savefig(out, dpi=200, bbox_inches="tight")
plt.close(fig)
return out
def main():
ap = argparse.ArgumentParser()
ap.add_argument("jsonl")
ap.add_argument("--level", default="task",
choices=["task", "fig", "paper"])
ap.add_argument("--k", type=int, default=20,
help="k for the heatmap")
ap.add_argument("--forms", default=None,
help="comma list to restrict, e.g. orig,keywords")
ap.add_argument("--covered-only", action="store_true",
help="restrict pathfinder rows to figures whose target "
"paper exists in the pathfinder corpus")
args = ap.parse_args()
stem = str(Path(args.jsonl).with_suffix(""))
df = load_tidy(args.jsonl)
n_figs = df[["arxiv_id", "fig_index"]].drop_duplicates().shape[0]
print(f"{n_figs} figures, {len(df)} cells, level={args.level}")
if args.forms:
keep = [f.strip() for f in args.forms.split(",")]
df = df[df.form.isin(keep)]
if args.covered_only:
drop = (df.method == "pathfinder") & (df.target_in_pf == False) # noqa: E712
print(f"covered-only: dropping {int(drop.sum())} pathfinder cells "
"whose target is absent from the pathfinder corpus")
df = df[~drop]
if df.target_in_pf.notna().any():
cov = df.drop_duplicates(["arxiv_id", "fig_index"]).target_in_pf
print(f"pathfinder corpus covers {cov.mean():.1%} of sampled targets")
t = recall_table(df, args.level)
print_blocks(t)
csv = f"{stem}_recall_{args.level}.csv"
t.to_csv(csv, index=False)
h = plot_heatmap(t, args.level, args.k, stem)
c = plot_curves(t, args.level, stem)
print(f"\nwrote: {csv}\n {h}\n {c}")
if __name__ == "__main__":
main()