-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcheck_example.py
More file actions
119 lines (101 loc) · 5.43 KB
/
Copy pathcheck_example.py
File metadata and controls
119 lines (101 loc) · 5.43 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
#!/usr/bin/env python3
"""
check_example.py — validate a finished run of the shipped example dataset.
This is EXAMPLE-SPECIFIC: it knows the five "Set A" signal pairs and compares a
freshly produced run against the golden tables in ``example_data/expected/``.
``run_example.sh`` calls it as its last step. If you adapt the runbook to your
own data, drop this step (there is no golden reference for arbitrary data).
For each known pair it prints the single-screen call (δ′ and P per screen) and
the joint call, asserts the joint dominant class is correct, and — when the
golden table is present — checks the joint probabilities agree within a
tolerance (MCMC is stochastic, so never exact equality).
Usage:
python check_example.py --single1 S1.tsv --single2 S2.tsv --merged MERGED.tsv \\
[--golden-dir example_data/expected] [--tol 0.15]
"""
import argparse
import glob
import os
import sys
import pandas as pd
# Known Set A interactions: (orfA, orfB, expected dominant class, label).
CHECKS = [
("RVBD1854c", "RVBD0392c", "aggravating", "ndh-ndhA"),
("RVBD0050", "RVBD3682", "aggravating", "ponA1-ponA2"),
("RVBD2193", "RVBD2200c", "alleviating", "ctaE-ctaC"),
("RVBD1304", "RVBD3795", "alleviating", "atpB-embB"),
]
CLASS_COL = {"aggravating": "prob_aggravating",
"alleviating": "prob_alleviating",
"discordant": "prob_discordant"}
PROB_COLS = ["prob_interaction", "prob_aggravating", "prob_alleviating",
"prob_discordant", "prob_no_interaction", "prob_concordant"]
def get_pair(frame, o1, o2):
a, b = sorted([o1, o2]) # canonical orf1 < orf2
row = frame[(frame["orf1"] == a) & (frame["orf2"] == b)]
if len(row) != 1:
raise SystemExit(f"ERROR: pair {a}_{b} matched {len(row)} rows")
return row.iloc[0]
def one_screen(frame, o1, o2):
try:
r = get_pair(frame, o1, o2)
return f"δ′={float(r['delta_prime_median']):+.2f} P={float(r['prob_interaction_median']):.3f}"
except SystemExit:
return "n/a"
def main():
ap = argparse.ArgumentParser(description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument("--single1", required=True, help="exp1 per-screen gene-pair table")
ap.add_argument("--single2", required=True, help="exp2 per-screen gene-pair table")
ap.add_argument("--merged", required=True, help="merged 20-column joint table")
ap.add_argument("--golden-dir", default="example_data/expected",
help="dir with the golden merged_*.tsv (default: example_data/expected)")
ap.add_argument("--tol", type=float, default=0.15, help="abs tolerance on probs (default 0.15)")
args = ap.parse_args()
s1 = pd.read_csv(args.single1, sep="\t")
s2 = pd.read_csv(args.single2, sep="\t")
m = pd.read_csv(args.merged, sep="\t")
print("Known Set A pairs — single-screen call vs joint call:")
print(" pass = correct interaction DIRECTION (expected class dominates the other two")
print(" interaction classes). Clearing the 0.50 hit threshold is reported separately:")
print(" borderline pairs can fall short when the global mixture is re-fit on only ~120")
print(" pairs (see README, 'Testing and validation').")
ok = True
n_confident = 0
for o1, o2, klass, label in CHECKS:
r = get_pair(m, o1, o2)
col = CLASS_COL[klass]
p = float(r[col])
other = max(float(r[c]) for c in CLASS_COL.values() if c != col)
direction_ok = p > other # expected class dominates the other interaction classes
confident = p >= 0.5 # clears the 0.50 hit threshold
n_confident += int(confident)
ok = ok and direction_ok
print(f" {label:13s} expected {klass}")
print(f" single-screen exp1[{one_screen(s1, o1, o2)}] exp2[{one_screen(s2, o1, o2)}]")
print(f" joint {col}={p:.3f} (next interaction class={other:.3f}) "
f"direction={'OK' if direction_ok else 'WRONG'}, "
f"{'confident hit (>=0.5)' if confident else 'sub-threshold (<0.5)'}")
golden = sorted(glob.glob(os.path.join(args.golden_dir, "merged_*.tsv")))
if golden:
g = pd.read_csv(golden[0], sep="\t")
print(f"\nComparison vs the PUBLISHED (golden) values in {golden[0]}:")
print(" NOTE: these are *expected to differ*. The joint mixture is a global fit;")
print(" the toy re-estimates it from ~120 interaction-enriched pairs, whereas the")
print(" published probabilities were fit across all ~290k pairs. This comparison")
print(" is informational and does NOT affect pass/fail (the class calls above do).")
for o1, o2, klass, label in CHECKS:
r = get_pair(m, o1, o2)
e = get_pair(g, o1, o2)
col = CLASS_COL[klass]
print(f" {label:13s} {col}: toy={float(r[col]):.3f} published={float(e[col]):.3f} "
f"(Δ={abs(float(r[col]) - float(e[col])):.3f})")
else:
print(f"\n(no golden table in {args.golden_dir}/; skipped published-value comparison)")
print()
if not ok:
sys.exit("EXAMPLE CHECK: FAILED — a known pair has the wrong interaction class")
print(f"EXAMPLE CHECK: PASSED — all {len(CHECKS)} known pairs have the correct interaction "
f"class\n ({n_confident}/{len(CHECKS)} also clear the 0.50 hit threshold)")
if __name__ == "__main__":
main()