-
Notifications
You must be signed in to change notification settings - Fork 0
/
gap.py
183 lines (149 loc) · 5.31 KB
/
gap.py
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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
from __future__ import division
import sys
import argparse
class qrel:
"""a class for parsing trec qrels"""
def __init__(self, path, maxgrade=1):
"""constructor. Takes a path to a qrel file
and optionally the theoretically max grade,
which defaults to binary. Converts all neg.
grades to 0"""
#store the maxgrade
self.maxgrade = maxgrade
#compute the score associated w/ each grade
self.scores=[(2**x - 1)/(2**maxgrade) for x in range(maxgrade+1)]
#the actual qrel dict of dicts
#_qrel[query][doc] = grade
self._qrel = dict()
#the documents at each grade
#dict of dicts of lists
#R[query][grade] = count
self.R = dict()
#read the qrel one line at a time
IN = open(path)
for line in IN:
row = line.strip().split()
#cast stuff to ints
query = row[0]
doc = row[-2]
grade = int(row[-1])
#init dicts for that query
if query not in self.R:
self.R[query] = [0]*maxgrade
self._qrel[query] = dict()
#make sure grade is non-neg
if grade < 0:
grade = 0
#if its rel add it to R
if grade > 0:
self.R[query][grade-1] += 1
#add it to the qrel
self._qrel[query][doc] = grade
IN.close()
def getR(self, query):
"""returns that queries dict of the docs at each grade"""
return self.R[query]
def getQueries(self):
"""returns the set of queries"""
return self.R.keys()
def judge(self, query, doc):
"""returns the grade of the doc for that query."""
try:
return self._qrel[query][doc]
except KeyError:
return 0
def getScore(self, grade):
"""returns the score associated w/ a rel grade"""
return self.scores[grade]
def getMaxgrade(self):
"""returns the maxgrade in the qrel"""
return self.maxgrade
def parserun(runpath, maxrank=20):
"""constructs a run from a trec run"""
#initialize
#ranked lists go in a dict of lists
#rl[query]=[doc1,doc2,...]
rl = dict()
name = None
#read the ranked list into a dict of dicts
#rawlist[query][doc]=score
rawlist = dict()
#open the run
IN = open(runpath)
#and read it line by line
for line in IN.readlines():
#chomp it and split it by white space
row = line.strip().split()
#make sure the row wasn't empty
if len(row)==0:
continue
if name == None:
name = row[-1]
#read the query, doc, score, etc
query = row[0]
if row[-2] == 'NaN':
score = 0.0
else:
score = float(row[-2])
#make sure the query is in the dict
if not query in rawlist:
rawlist[query]=dict()
#store the doc in the presorted list
#if the doc shows up more than once in a query,
#that's not my problem
doc = row[2]
rawlist[query][doc] = score
IN.close()
#for each query
for query in sorted(rawlist):
#sort the list by score and then by name
rl[query] = sorted(rawlist[query],key=lambda x: (rawlist[query][x],x),reverse=True)[:maxrank]
return name, rl
def gap(query,run,qrel):
"""gap of a run on a query given a qrel"""
totalp=0
for n in range(len(run[query])):
docn = run[query][n]
grade = qrel.judge(query,docn)
if grade > 0:
p = 0
for m in range(n+1):
i = min(grade,qrel.judge(query,run[query][m]))
for j in range(1,i+1):
p += qrel.getScore(j)
totalp += p / (n+1)
denom = 0
for i in range(1,qrel.getMaxgrade()+1):
rel=0
for j in range(1, i+1):
rel += qrel.getScore(j)
denom += rel*qrel.R[query][i-1]
return totalp/denom
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument('qrel_path', help='path to qrel file')
parser.add_argument('run_path', help='path to run file')
parser.add_argument('-v', '--verbose', help='display score for each query', action='store_true')
parser.add_argument('-r', '--rank', type=int, help='evaluation rank. Defaults to 1000')
parser.add_argument('-m', '--maxgrade', type=int, help='specify maxgrade. Defaults to 4')
args = parser.parse_args()
#get the maxgrade
MAXGRADE = 4
if args.maxgrade != None:
MAXGRADE = args.maxgrade
#so we can make the qrel
theqrel = qrel(args.qrel_path, MAXGRADE)
#now get the rank
RANK = 1000
if args.rank != None:
RANK = args.rank
runname, therun = parserun(args.run_path, RANK)
#finally, do the evaluation
mgap = 0
for query in sorted(theqrel.getQueries()):
qgap = gap(query, therun, theqrel)
if args.verbose:
print("\t".join([runname, query, str(qgap)]))
mgap += qgap
mgap /= len(theqrel.getQueries())
print("\t".join([runname,'avg',str(mgap)]))