czty's picture
Add files using upload-large-folder tool
a071401 verified
Raw
History Blame Contribute Delete
7.71 kB
#!/usr/bin/env python3
from __future__ import annotations
import re
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
# =========================
# Config
# =========================
XLSX_PATH = Path("Context_Consuming.xlsx")
OUT_PREFIX = Path("bioagentbench_context_radar_final")
METRIC = "prompt_tokens"
# 正文图建议只保留这 4 个;如果要完整 6 条线,见下方注释。
# METHOD_ORDER = [
# "No MCP",
# "Biomni-100",
# "Biomni-2k",
# "BioManus",
# ]
# 附录完整版本可改成:
METHOD_ORDER = [
"No MCP",
"Biomni-100",
"Biomni-500",
"Biomni-1k",
"Biomni-2k",
"BioManus",
]
TASK_ORDER = [
"alzheimer-mouse",
"cystic-fibrosis",
"deseq",
"transcript-quant",
"single-cell",
"metagenomics",
"viral-metagenomics",
"comparative-genomics",
"evolution",
"giab",
]
METHOD_STYLE = {
"No MCP": {
"color": "#6B7280",
"linewidth": 1.8,
"linestyle": "-",
"fill_alpha": 0.00,
},
"Biomni-100": {
"color": "#2563EB",
"linewidth": 1.8,
"linestyle": "-",
"fill_alpha": 0.00,
},
"Biomni-500": {
"color": "#059669",
"linewidth": 1.6,
"linestyle": "-",
"fill_alpha": 0.00,
},
"Biomni-1k": {
"color": "#D97706",
"linewidth": 1.6,
"linestyle": "-",
"fill_alpha": 0.00,
},
"Biomni-2k": {
"color": "#7C3AED",
"linewidth": 2.0,
"linestyle": "-",
"fill_alpha": 0.00,
},
"BioManus": {
"color": "#DC2626",
"linewidth": 2.6,
"linestyle": "-",
"fill_alpha": 0.04,
},
}
# =========================
# Utilities
# =========================
def clean_text(x) -> str:
if pd.isna(x):
return ""
s = str(x)
s = s.replace("\ufeff", "").replace("\u200b", "").replace("\u200c", "")
s = re.sub(r"\s+", " ", s)
return s.strip()
def method_name(x) -> str | None:
s = clean_text(x)
if not s:
return None
lower = s.lower()
if "biomanus" in lower:
return "BioManus"
try:
scale = int(float(s))
except ValueError:
return None
if scale == 0:
return "No MCP"
if scale == 100:
return "Biomni-100"
if scale == 500:
return "Biomni-500"
if scale == 1000:
return "Biomni-1k"
if scale == 2000:
return "Biomni-2k"
# Ignore 250 and other scales.
return None
def wrap_task_label(task: str) -> str:
labels = {
"alzheimer-mouse": "alzheimer\nmouse",
"cystic-fibrosis": "cystic\nfibrosis",
"deseq": "deseq",
"transcript-quant": "transcript\nquant",
"single-cell": "single-cell",
"metagenomics": "metagenomics",
"viral-metagenomics": "viral\nmetagenomics",
"comparative-genomics": "comparative\ngenomics",
"evolution": "evolution",
"giab": "giab",
}
return labels.get(task, task.replace("-", "\n"))
def geometric_mean(values: np.ndarray) -> float:
values = np.asarray(values, dtype=float)
values = values[values > 0]
return float(np.exp(np.mean(np.log(values)))) if len(values) else np.nan
def format_token(x: float) -> str:
if x >= 1_000_000:
return f"{x / 1_000_000:.1f}M"
if x >= 1_000:
return f"{x / 1_000:.0f}K"
return f"{x:.0f}"
# =========================
# Load data
# =========================
raw = pd.read_excel(XLSX_PATH, sheet_name=0, header=None)
header_candidates = raw.index[
raw.iloc[:, 1].astype(str).map(clean_text).eq("Tasks")
].tolist()
if not header_candidates:
raise ValueError("Cannot find header row with 'Tasks' in column B.")
header_idx = header_candidates[0]
df = raw.iloc[header_idx + 1 :, :6].copy()
df.columns = [
"group",
"task",
"results_match",
"prompt_tokens",
"completion_tokens",
"total_tokens",
]
df["group"] = df["group"].ffill()
df["method"] = df["group"].map(method_name)
df["task"] = df["task"].map(clean_text)
df = df[df["task"].ne("")]
df = df[df["method"].notna()]
for col in ["prompt_tokens", "completion_tokens", "total_tokens"]:
df[col] = pd.to_numeric(df[col], errors="coerce")
df = df.dropna(subset=[METRIC])
df = df[df["method"].isin(METHOD_ORDER)]
df["method"] = pd.Categorical(df["method"], categories=METHOD_ORDER, ordered=True)
pivot = df.pivot_table(
index="method",
columns="task",
values=METRIC,
aggfunc="mean",
)
available_methods = [m for m in METHOD_ORDER if m in pivot.index]
task_order = [t for t in TASK_ORDER if t in pivot.columns]
common_tasks = [
t for t in task_order
if pivot.loc[available_methods, t].notna().all()
]
pivot = pivot.loc[available_methods, common_tasks]
if pivot.empty:
raise ValueError("No valid method-task matrix found for radar plotting.")
# =========================
# Plot
# =========================
plt.rcParams.update(
{
"font.family": "DejaVu Sans",
"font.size": 10,
"axes.titlesize": 14,
"legend.fontsize": 9,
"pdf.fonttype": 42,
"ps.fonttype": 42,
}
)
tasks = list(pivot.columns)
task_labels = [wrap_task_label(t) for t in tasks]
n_tasks = len(tasks)
angles = np.linspace(0, 2 * np.pi, n_tasks, endpoint=False)
angles_closed = np.concatenate([angles, [angles[0]]])
fig, ax = plt.subplots(
figsize=(7.6, 6.6),
subplot_kw={"projection": "polar"},
)
ax.set_theta_offset(np.pi / 2)
ax.set_theta_direction(-1)
values_log = np.log10(pivot.values.astype(float))
# Fixed range keeps the figure visually stable across reruns.
r_min = 5.0
r_max = max(7.2, float(np.nanmax(values_log)) + 0.15)
ax.set_ylim(r_min, r_max)
# Axis labels.
ax.set_xticks(angles)
ax.set_xticklabels(task_labels, fontsize=9.5)
# Radial ticks.
radial_ticks = [5, 6, 7]
radial_ticks = [t for t in radial_ticks if r_min <= t <= r_max]
ax.set_yticks(radial_ticks)
ax.set_yticklabels([rf"$10^{t}$" for t in radial_ticks], fontsize=9, color="#4B5563")
ax.set_rlabel_position(90)
# Grid style.
ax.grid(True, color="#CBD5E1", linewidth=0.75, alpha=0.75)
ax.spines["polar"].set_color("#94A3B8")
ax.spines["polar"].set_linewidth(0.8)
# Plot methods.
for method in available_methods:
values = np.log10(pivot.loc[method, tasks].values.astype(float))
values_closed = np.concatenate([values, [values[0]]])
style = METHOD_STYLE[method]
ax.plot(
angles_closed,
values_closed,
color=style["color"],
linewidth=style["linewidth"],
linestyle=style["linestyle"],
label=method,
zorder=4 if method == "BioManus" else 3,
)
if style["fill_alpha"] > 0:
ax.fill(
angles_closed,
values_closed,
color=style["color"],
alpha=style["fill_alpha"],
zorder=2,
)
# Title.
ax.set_title("BioAgentBench Context Consumption", pad=22, fontweight="bold")
# Legend.
ax.legend(
loc="upper left",
bbox_to_anchor=(1.05, 1.03),
frameon=False,
handlelength=2.4,
borderaxespad=0.0,
)
fig.subplots_adjust(left=0.05, right=0.78, top=0.90, bottom=0.06)
# Save.
fig.savefig(f"{OUT_PREFIX}.pdf", bbox_inches="tight")
fig.savefig(f"{OUT_PREFIX}.png", dpi=400, bbox_inches="tight")
fig.savefig(f"{OUT_PREFIX}.svg", bbox_inches="tight")
print("Saved:")
print(f" {OUT_PREFIX}.pdf")
print(f" {OUT_PREFIX}.png")
print(f" {OUT_PREFIX}.svg")
print("\nGeometric mean prompt tokens:")
for method in available_methods:
gm = geometric_mean(pivot.loc[method, tasks].values.astype(float))
print(f"{method:12s}: {format_token(gm)}")