czty's picture
Add files using upload-large-folder tool
a071401 verified
Raw
History Blame Contribute Delete
8.23 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_parallel_warm")
METRIC = "prompt_tokens"
METHOD_ORDER = [
"No MCP",
"Biomni-100",
"Biomni-500",
"Biomni-1k",
"Biomni-2k",
"BioManus",
]
METHOD_STYLE = {
"No MCP": {
"color": "#8A8F98",
"linewidth": 1.7,
"alpha": 0.72,
"zorder": 2,
},
"Biomni-100": {
"color": "#C77C2A",
"linewidth": 1.8,
"alpha": 0.78,
"zorder": 3,
},
"Biomni-500": {
"color": "#B66D18",
"linewidth": 1.8,
"alpha": 0.80,
"zorder": 3,
},
"Biomni-1k": {
"color": "#9A5200",
"linewidth": 1.9,
"alpha": 0.86,
"zorder": 4,
},
"Biomni-2k": {
"color": "#7A3E00",
"linewidth": 2.1,
"alpha": 0.92,
"zorder": 5,
},
"BioManus": {
"color": "#D62728",
"linewidth": 2.8,
"alpha": 1.00,
"zorder": 7,
},
}
# =========================================================
# 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_label(task: str) -> str:
mapping = {
"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 mapping.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 Excel
# =========================================================
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 containing '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]
common_tasks = [
t for t in pivot.columns
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.")
# Sort tasks from low to high context.
log_pivot = np.log10(pivot.astype(float))
task_score = log_pivot.mean(axis=0).sort_values(ascending=True)
tasks = task_score.index.tolist()
log_pivot = log_pivot.loc[available_methods, tasks]
# =========================================================
# Plot
# =========================================================
plt.rcParams.update(
{
"font.family": "DejaVu Serif",
"font.size": 10.5,
"axes.titlesize": 14.5,
"axes.labelsize": 11,
"legend.fontsize": 9.2,
"xtick.labelsize": 9.0,
"ytick.labelsize": 9.5,
"pdf.fonttype": 42,
"ps.fonttype": 42,
}
)
fig, ax = plt.subplots(figsize=(10.2, 4.8))
x = np.arange(len(tasks))
y_min = 5.0
y_max = max(7.2, float(np.nanmax(log_pivot.values)) + 0.15)
# Warm background band similar to the reference style.
ax.axhspan(y_min, y_max, color="#F6EFE7", alpha=0.55, zorder=0)
# Vertical translucent bars at each task axis.
for xi in x:
ax.vlines(
xi,
y_min,
y_max,
color="#D99A5B",
linewidth=5.0,
alpha=0.16,
zorder=1,
)
# Horizontal subtle grid.
for y in [5, 6, 7]:
if y_min <= y <= y_max:
ax.axhline(
y,
color="#C9C1B8",
linewidth=0.8,
linestyle=":",
alpha=0.75,
zorder=1,
)
# Plot angular polylines with visible breakpoints.
for method in available_methods:
style = METHOD_STYLE[method]
y = log_pivot.loc[method, tasks].values.astype(float)
ax.plot(
x,
y,
color=style["color"],
linewidth=style["linewidth"],
alpha=style["alpha"],
marker="o",
markersize=4.0 if method != "BioManus" else 5.0,
markeredgecolor="white",
markeredgewidth=0.6,
label=method,
zorder=style["zorder"],
)
# Optional: light area under BioManus.
if "BioManus" in available_methods:
y_bio = log_pivot.loc["BioManus", tasks].values.astype(float)
ax.fill_between(
x,
y_bio,
y_min,
color=METHOD_STYLE["BioManus"]["color"],
alpha=0.055,
zorder=2,
)
# Axes and labels.
ax.set_xlim(-0.25, len(tasks) - 0.75)
ax.set_ylim(y_min, y_max)
ax.set_xticks(x)
ax.set_xticklabels([wrap_label(t) for t in tasks], rotation=30, ha="right")
ax.set_yticks([5, 6, 7])
ax.set_yticklabels([r"$10^5$", r"$10^6$", r"$10^7$"])
ax.set_ylabel("Prompt tokens per task (log scale)")
ax.set_title(
"Context Consumption Across BioAgentBench Tasks",
pad=12,
fontweight="bold",
)
# Clean spines.
ax.spines["top"].set_visible(False)
ax.spines["right"].set_visible(False)
ax.spines["left"].set_color("#7A6A5A")
ax.spines["bottom"].set_color("#7A6A5A")
ax.tick_params(axis="x", length=0, pad=8)
ax.tick_params(axis="y", colors="#4B423A")
# Legend.
ax.legend(
loc="upper center",
bbox_to_anchor=(0.5, -0.25),
ncol=6,
frameon=False,
handlelength=2.0,
columnspacing=1.10,
)
fig.tight_layout(rect=[0, 0.10, 1, 1])
# 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("\nTask order from low to high context:")
for i, task in enumerate(tasks, 1):
print(f"{i:02d}. {task}")
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)}")