File size: 3,351 Bytes
0c025bc
 
 
 
 
 
 
 
 
 
6be2d5b
0c025bc
 
 
56325ab
 
 
0c025bc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""Re-ranker v2: candidate-EC features from the merged hit list (train + UniProt references).

The same function builds the training rows (official-label proxy, see experiments/external) and the
inference rows, so features cannot drift. Candidates are the top 10 ECs of the weighted vote.
"""
import math

FEATURES = ['v12w', 'v12', 'v8', 'v4', 'v1', 'rank', 's_tr', 's_sp', 's_un', 's_u2', 'best_bits_rel', 'best_pid',
            'best_rank', 'best_src', 'qcov', 'scov', 'n10', 'n50', 'l3', 'l2', 'lfreq', 'top_pid', 'top_bits',
            'top_src', 'n_hits', 'n_cand', 'margin', 'pid_gap', 'nec_best']
SRC = {'sp_': 1, 'cp_': 1, 'br_': 1, 'dk_': 1, 'bo_': 1, 'pd_': 1, 'un_': 2, 'es_': 2, 'e2_': 2, 'u2_': 3}   # curated -> 1, unreviewed -> 2/3


def source(s):
    """0 = training protein (ids tr_<digits>), 1 = curated external (Swiss-Prot, BRENDA, CatPred, DLKcat),
    2 = unreviewed with protein-level evidence or ESP, 3 = TrEMBL transcript-level."""
    return 0 if (s.startswith('tr_') and s[3:].isdigit()) else SRC.get(s[:3], 1)


def candidate_features(hits, labels, freq, weights, k=50, power=12):
    """hits: (sseqid, pid, qlen, slen, evalue, bits, qstart, qend, sstart, send) tuples, any order.
    Returns (candidate ECs ordered by weighted vote, feature rows)."""
    h = sorted(hits, key=lambda x: -x[5])[:k]
    if not h:
        return [], []
    top = h[0][5]
    st, l3, l2 = {}, {}, {}
    tot = {'w': 0.0, '12': 0.0, '8': 0.0, '4': 0.0, '1': 0.0}
    for r, (s, pid, ql, sl, ev, b, qs, qe, ss, se) in enumerate(h):
        ecs = labels[s]
        n = len(ecs)
        rb = b / top
        src = source(s)
        w = rb ** power * (1.0 if src == 0 else weights.get(s[:3], 1.0))
        tot['w'] += w
        tot['12'] += rb ** 12
        tot['8'] += rb ** 8
        tot['4'] += rb ** 4
        tot['1'] += rb
        for e in ecs:
            d = st.get(e)
            if d is None:
                d = st[e] = dict(w=0.0, p12=0.0, p8=0.0, p4=0.0, p1=0.0, src=[0.0, 0.0, 0.0, 0.0], bb=b, bp=pid, br=r,
                                 bs=src, qc=(qe - qs + 1) / ql, sc=(se - ss + 1) / sl, n10=0, n50=0, nec=n)
            d['w'] += w / n
            d['p12'] += rb ** 12 / n
            d['p8'] += rb ** 8 / n
            d['p4'] += rb ** 4 / n
            d['p1'] += rb / n
            d['src'][src] += w / n
            d['n10'] += r < 10
            d['n50'] += 1
            p3 = e.rsplit('.', 1)[0]
            p2 = '.'.join(e.split('.')[:2])
            l3[p3] = l3.get(p3, 0.0) + w / n
            l2[p2] = l2.get(p2, 0.0) + w / n
    cands = sorted(st, key=lambda e: (-st[e]['w'], -freq.get(e, 0), e))[:10]
    ws = [st[e]['w'] for e in cands]
    margin = (ws[0] - (ws[1] if len(ws) > 1 else 0.0)) / tot['w']
    rows = []
    for i, e in enumerate(cands):
        d = st[e]
        rows.append([d['w'] / tot['w'], d['p12'] / tot['12'], d['p8'] / tot['8'], d['p4'] / tot['4'], d['p1'] / tot['1'], i,
                     *[x / tot['w'] for x in d['src']], d['bb'] / top, d['bp'], d['br'], d['bs'], d['qc'], d['sc'],
                     d['n10'], d['n50'], l3[e.rsplit('.', 1)[0]] / tot['w'], l2['.'.join(e.split('.')[:2])] / tot['w'],
                     math.log1p(freq.get(e, 0)), h[0][1], top, source(h[0][0]), len(h), len(st), margin,
                     h[0][1] - d['bp'], d['nec']])
    return cands, rows