snr_bias / code /scripts /grl_plot_revision_figures.py
cangyeone's picture
Upload GRL reproducibility package
7170296 verified
Raw
History Blame Contribute Delete
5.15 kB
#!/usr/bin/env python3
"""Generate GRL revision figures from bootstrap and SNR-stratified tables."""
from __future__ import annotations
import argparse
import csv
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
DEFAULT_REVISION_DIR = Path("outputs/grl_revision_20260610")
DEFAULT_FIG_DIR = Path("grl_overleaf/figures/grl_revision")
def read_rows(path: Path) -> list[dict[str, str]]:
if not path.exists():
raise FileNotFoundError(f"Missing required table: {path}")
with path.open(newline="", encoding="utf-8") as f:
return list(csv.DictReader(f))
def style(ax):
ax.spines["top"].set_visible(False)
ax.spines["right"].set_visible(False)
ax.grid(axis="y", color="#e6e6e6")
ax.set_axisbelow(True)
def lookup(rows: list[dict[str, str]], condition: str, metric: str) -> dict[str, float]:
row = next(r for r in rows if r["condition"] == condition and r["metric"] == metric)
return {k: float(row[k]) for k in ("estimate", "ci_low", "ci_high")}
def fig2(revision_dir: Path, fig_dir: Path) -> None:
phase = read_rows(revision_dir / "tables" / "phase_bootstrap_ci.csv")
disp = read_rows(revision_dir / "tables" / "dispersion_bootstrap_ci.csv")
fig, axs = plt.subplots(1, 2, figsize=(7.2, 3.1), constrained_layout=True)
conds = ["full", "snr5", "snr10"]
labels = ["Full", "SNR>5", "SNR>10"]
x = np.arange(len(conds))
ax = axs[0]
for metric, color, marker in [("P_f1", "#4477AA", "o"), ("S_f1", "#CC6677", "s"), ("mean_f1", "#228833", "^")]:
vals = [lookup(phase, c, metric) for c in conds]
y = np.array([v["estimate"] for v in vals])
yerr = np.array([[v["estimate"] - v["ci_low"] for v in vals], [v["ci_high"] - v["estimate"] for v in vals]])
ax.errorbar(x, y, yerr=yerr, marker=marker, linewidth=2, capsize=3, label=metric.replace("_", " "), color=color)
ax.set_xticks(x, labels)
ax.set_ylabel("F1")
ax.set_title("Phase picking")
ax.legend(frameon=False, fontsize=8)
style(ax)
ax = axs[1]
conds_d = ["full", "snr_q1", "snr_q2"]
labels_d = ["Full", "SNR>3.04", "SNR>6.77"]
x = np.arange(len(conds_d))
for metric, color, marker in [("mae", "#4477AA", "o"), ("rmse", "#CC6677", "s")]:
vals = [lookup(disp, c, metric) for c in conds_d]
y = np.array([v["estimate"] for v in vals])
yerr = np.array([[v["estimate"] - v["ci_low"] for v in vals], [v["ci_high"] - v["estimate"] for v in vals]])
ax.errorbar(x, y, yerr=yerr, marker=marker, linewidth=2, capsize=3, label=metric.upper(), color=color)
ax.set_xticks(x, labels_d)
ax.set_ylabel("Velocity error (km/s)")
ax.set_title("Dispersion")
ax.legend(frameon=False, fontsize=8)
style(ax)
fig_dir.mkdir(parents=True, exist_ok=True)
for ext in ("pdf", "png"):
fig.savefig(fig_dir / f"fig2_bootstrap_ci.{ext}", dpi=300, bbox_inches="tight")
plt.close(fig)
def si_stratified(revision_dir: Path, fig_dir: Path) -> None:
phase = read_rows(revision_dir / "tables" / "phase_snr_stratified_metrics.csv")
disp = read_rows(revision_dir / "tables" / "dispersion_snr_stratified_metrics.csv")
fig, axs = plt.subplots(1, 2, figsize=(7.2, 3.1), constrained_layout=True)
ax = axs[0]
for condition, color in [("full", "#4477AA"), ("snr5", "#CC6677"), ("snr10", "#228833")]:
rows = [r for r in phase if r["condition"] == condition and r["phase"] == "P_S_mean"]
rows.sort(key=lambda r: r["test_snr_bin"])
ax.plot(range(len(rows)), [float(r["f1"]) for r in rows], marker="o", label=condition, color=color)
ax.set_xticks(range(len({r["test_snr_bin"] for r in phase})))
ax.set_xticklabels(sorted({r["test_snr_bin"] for r in phase}), rotation=25, ha="right", fontsize=7)
ax.set_ylabel("Mean F1")
ax.set_title("Phase picking by test SNR")
ax.legend(frameon=False, fontsize=8)
style(ax)
ax = axs[1]
for condition, color in [("full", "#4477AA"), ("snr_q1", "#CC6677"), ("snr_q2", "#228833")]:
rows = [r for r in disp if r["condition"] == condition]
rows.sort(key=lambda r: r["test_snr_bin"])
ax.plot(range(len(rows)), [float(r["mae"]) for r in rows], marker="o", label=condition, color=color)
ax.set_xticks(range(len({r["test_snr_bin"] for r in disp})))
ax.set_xticklabels(sorted({r["test_snr_bin"] for r in disp}), rotation=25, ha="right", fontsize=7)
ax.set_ylabel("MAE (km/s)")
ax.set_title("Dispersion by test SNR")
ax.legend(frameon=False, fontsize=8)
style(ax)
fig_dir.mkdir(parents=True, exist_ok=True)
for ext in ("pdf", "png"):
fig.savefig(fig_dir / f"figS_snr_stratified.{ext}", dpi=300, bbox_inches="tight")
plt.close(fig)
def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--revision-dir", type=Path, default=DEFAULT_REVISION_DIR)
parser.add_argument("--fig-dir", type=Path, default=DEFAULT_FIG_DIR)
args = parser.parse_args()
fig2(args.revision_dir, args.fig_dir)
si_stratified(args.revision_dir, args.fig_dir)
if __name__ == "__main__":
main()