-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathplot_ber_with_labels.py
More file actions
132 lines (104 loc) · 3.93 KB
/
plot_ber_with_labels.py
File metadata and controls
132 lines (104 loc) · 3.93 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
#!/usr/bin/env python3
import argparse
from pathlib import Path
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
def load_one_csv(path: str, snr_col: str, ber_col: str) -> pd.DataFrame:
df = pd.read_csv(path)
df[snr_col] = pd.to_numeric(df[snr_col], errors="coerce")
df[ber_col] = pd.to_numeric(df[ber_col], errors="coerce")
df = df.dropna(subset=[snr_col, ber_col]).copy()
df = df.rename(columns={snr_col: "snr_db", ber_col: "ber"})
return df
def aggregate_df(df: pd.DataFrame, how: str) -> pd.DataFrame:
if how == "none":
return df.sort_values("snr_db")
if how == "mean":
return df.groupby("snr_db", as_index=False)["ber"].mean()
if how == "median":
return df.groupby("snr_db", as_index=False)["ber"].median()
raise ValueError("Unknown aggregate mode")
def main():
ap = argparse.ArgumentParser(
description="Plot BER vs SNR for multiple CSV files on the same semilog-y graph."
)
# multiple inputs
ap.add_argument(
"--csv",
nargs="+",
required=True,
help="One or more CSV paths. Example: --csv metrics_a.csv metrics_b.csv",
)
ap.add_argument(
"--labels",
nargs="*",
default=[],
help="Optional labels (same count as --csv). If omitted, filenames are used.",
)
# column selection (in case different files differ)
ap.add_argument("--snr_col", default="snr_db", help="SNR column name (default snr_db)")
ap.add_argument("--ber_col", default="ber", help="BER column name (default ber)")
# axis + behavior
ap.add_argument("--out", default="", help="Optional output image path (e.g., ber_vs_snr.png)")
ap.add_argument("--xmax", type=float, default=15.0, help="Max SNR on x-axis (default 10 dB)")
ap.add_argument("--ymin", type=float, default=1e-5, help="Min BER on y-axis (default 1e-5)")
ap.add_argument("--ymax", type=float, default=1.0, help="Max BER on y-axis (default 1)")
ap.add_argument(
"--clip_zero_to",
type=float,
default=1e-5,
help="Replace BER<=0 with this value so it shows on log scale (default 1e-5)",
)
ap.add_argument(
"--aggregate",
choices=["none", "mean", "median"],
default="mean",
help="Aggregate multiple points per SNR (default mean). Use 'none' to plot all points.",
)
args = ap.parse_args()
csv_paths = args.csv
labels = args.labels
if labels and (len(labels) != len(csv_paths)):
raise SystemExit(
f"--labels count ({len(labels)}) must match --csv count ({len(csv_paths)}) "
f"or be omitted."
)
if not labels:
labels = [Path(p).stem for p in csv_paths]
plt.figure()
xmins = []
for path, label in zip(csv_paths, labels):
df = load_one_csv(path, args.snr_col, args.ber_col)
# limit x-range
df = df[df["snr_db"] <= args.xmax].copy()
# handle zeros/negatives for log scale
df.loc[df["ber"] <= 0, "ber"] = args.clip_zero_to
# aggregate
df_plot = aggregate_df(df, args.aggregate).sort_values("snr_db")
if len(df_plot) == 0:
print(f"[WARN] No valid rows after filtering for: {path}")
continue
xmins.append(float(df_plot["snr_db"].min()))
plt.semilogy(
df_plot["snr_db"],
df_plot["ber"],
marker="o",
linestyle="-",
label=label,
)
if not xmins:
raise SystemExit("No data to plot (all files empty after parsing/filtering).")
plt.xlabel("SNR (dB)")
plt.ylabel("BER")
plt.xlim(min(xmins), args.xmax)
plt.ylim(args.ymin, args.ymax)
plt.grid(True, which="both", linestyle="--", alpha=0.5)
plt.title("BER vs SNR (E5-Large Attention Block Study)")
plt.legend()
if args.out:
plt.savefig(args.out, dpi=200, bbox_inches="tight")
else:
plt.show()
if __name__ == "__main__":
main()