-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmake_assignment_misdirection_table.py
More file actions
executable file
·116 lines (93 loc) · 3.62 KB
/
Copy pathmake_assignment_misdirection_table.py
File metadata and controls
executable file
·116 lines (93 loc) · 3.62 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
#!/usr/bin/env python3
import argparse
import pandas as pd
def safe_div(a, b):
return a / b if b else None
def prf(tp, fp, fn):
p = safe_div(tp, tp + fp)
r = safe_div(tp, tp + fn)
f1 = safe_div(2 * p * r, p + r) if (p is not None and r is not None and (p + r)) else None
return p, r, f1
def aggregate_assign(df):
cols = set(df.columns)
mis = int(df["assign_misdirected"].sum()) if "assign_misdirected" in cols else 0
# WITH-legend schema: explicit TP/FP/FN
if "assign_tp" in cols:
tp = int(df["assign_tp"].sum())
fp = int(df["assign_fp"].sum())
fn = int(df["assign_fn"].sum())
gt = int(df["assign_gt"].sum())
# WO-legend schema: no TP, reconstruct from GT - FN
else:
required = {"assign_gt", "assign_fn", "assign_fp", "assign_pred"}
missing = required - cols
if missing:
raise KeyError(
f"Missing required columns: {sorted(missing)}. Available columns={sorted(cols)}"
)
gt = int(df["assign_gt"].sum())
fn = int(df["assign_fn"].sum())
tp = gt - fn
fp_explicit = int(df["assign_fp"].sum())
pred = int(df["assign_pred"].sum())
fp_from_pred = pred - tp
# Keep explicit FP if present; this also preserves prior behavior
fp = fp_explicit if fp_explicit == fp_from_pred else fp_explicit
p, r, f1 = prf(tp, fp, fn)
return {
"N": int(len(df)),
"assign_gt": int(gt),
"assign_tp": int(tp),
"assign_fp": int(fp),
"assign_fn": int(fn),
"assign_precision": p,
"assign_recall": r,
"assign_f1": f1,
"assign_misdirected": int(mis),
"mis_fn": safe_div(mis, fn),
}
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--with_sag", required=True, help="CSV for SAG with legend")
ap.add_argument("--with_mag", required=True, help="CSV for MAG with legend")
ap.add_argument("--with_dag", required=True, help="CSV for DAG with legend")
ap.add_argument("--wo_sag", required=True, help="CSV for SAG without legend")
ap.add_argument("--wo_mag", required=True, help="CSV for MAG without legend")
ap.add_argument("--wo_dag", required=True, help="CSV for DAG without legend")
ap.add_argument("--out_csv", required=True, help="Output merged CSV path")
args = ap.parse_args()
inputs = [
("SAG", "With", args.with_sag),
("MAG", "With", args.with_mag),
("DAG", "With", args.with_dag),
("SAG", "Without", args.wo_sag),
("MAG", "Without", args.wo_mag),
("DAG", "Without", args.wo_dag),
]
rows = []
for regime, legend, path in inputs:
df = pd.read_csv(path)
stats = aggregate_assign(df)
rows.append({
"Regime": regime,
"Legend": legend,
"N": stats["N"],
"Assign_GT": stats["assign_gt"],
"Assign_TP": stats["assign_tp"],
"Assign_FP": stats["assign_fp"],
"Assign_FN": stats["assign_fn"],
"Assign_P": stats["assign_precision"],
"Assign_R": stats["assign_recall"],
"Assign_F1": stats["assign_f1"],
"Misdir": stats["assign_misdirected"],
"Mis_FN": stats["mis_fn"],
})
regime_order = {"SAG": 0, "MAG": 1, "DAG": 2}
legend_order = {"With": 0, "Without": 1}
rows = sorted(rows, key=lambda r: (regime_order[r["Regime"]], legend_order[r["Legend"]]))
out_df = pd.DataFrame(rows)
out_df.to_csv(args.out_csv, index=False)
print(out_df.to_string(index=False))
print(f"\nWrote: {args.out_csv}")
if __name__ == "__main__":
main()