File Coverage

blib/lib/RePrec.pm
Criterion Covered Total %
statement 9 108 8.3
branch 0 32 0.0
condition 0 12 0.0
subroutine 3 11 27.2
pod 7 7 100.0
total 19 170 11.1


line stmt bran cond sub pod time code
1             ######################### -*- Mode: Perl -*- #########################
2             ##
3             ## File : $RCSfile: RePrec.pm,v $
4             ##
5             ## Author : Norbert Goevert
6             ## Created On : Wed Feb 5 11:19:51 1997
7             ## Last Modified : Time-stamp: <2003-01-19 16:59:06 goevert>
8             ##
9             ## Description : calculate and print recall precision functions
10             ##
11             ## $Id: RePrec.pm,v 1.29 2003/06/13 12:29:30 goevert Exp $
12             ##
13             ######################################################################
14              
15              
16 1     1   595 use strict;
  1         2  
  1         55  
17              
18              
19             =pod #---------------------------------------------------------------#
20              
21             =head1 NAME
22              
23             RePrec - compute recall precision curves
24              
25             =head1 SYNOPSIS
26              
27             require RePrec::;
28              
29             =head1 DESCRIPTION
30              
31             B is an abstract class for computing recall precision curves.
32             Subclasses implement different recall-precision curve interpretation
33             measures. Theoretical background is given in detail by the
34             I by Norbert Fuhr (chapter 3,
35             Evaluation). Web address:
36             F.
37              
38             =head1 METHODS
39              
40             =over
41              
42             =cut #---------------------------------------------------------------#
43              
44              
45             package RePrec;
46              
47              
48 1     1   5 use Carp;
  1         2  
  1         63  
49 1     1   788 use IO::File;
  1         11069  
  1         1382  
50             require RePrec::Tools;
51              
52              
53             our $VERSION;
54             '$Name: release_0_32 $ 0_0' =~ /(\d+)[-_](\d+)/; $VERSION = sprintf '%d.%03d', $1, $2;
55              
56              
57             ## public ############################################################
58              
59             =pod #---------------------------------------------------------------#
60              
61             =item $rp = RePrec::->new($distribution)
62              
63             constructor. Takes as argument a distribution. $distribution is a
64             reference to an array containing a two element array reference for
65             each rank (top most rank first). The first element within the
66             references contains the number of relevant documents while the second
67             one contains the number of non-relevant documents.
68              
69             =cut #---------------------------------------------------------------#
70              
71             sub new {
72              
73 0     0 1   my $proto = shift;
74 0   0       my $class = ref($proto) || $proto;
75 0           my $self = {};
76              
77 0           my $distribution = shift;
78 0 0         croak 'distribution: wrong format' unless ref $distribution eq 'ARRAY';
79              
80 0           my($rels, $nrels) = (0, 0);
81 0           foreach (@$distribution) {
82 0 0 0       croak 'distribution: wrong format' unless ref $_ eq 'ARRAY' and @$_ == 2;
83 0           $rels += $_->[0];
84 0           $nrels += $_->[1];
85             }
86              
87 0           $self->{rels} = $rels;
88 0           $self->{nrels} = $nrels;
89 0           $self->{numdocs} = $rels + $nrels;
90              
91 0           bless $self => $class;
92              
93 0           $self->_sortrfdata($distribution);
94              
95 0           $self;
96             }
97              
98              
99             =pod #---------------------------------------------------------------#
100              
101             =item $visual = $rp->visual
102              
103             returns a textual representation of the searchresult.
104              
105             =cut #---------------------------------------------------------------#
106              
107             sub visual {
108              
109 0     0 1   my $self = shift;
110 0           return $self->{resultstring};
111             }
112              
113              
114             =cut #---------------------------------------------------------------#
115              
116             =item ($graph, $average) = $rp->calculate([$points])
117              
118             calculates precision values for $points. $points may be an integer
119             (specifying for how many recall points precision is to be computed),
120             an reference to a list of recall points, the string I (implying
121             the recall points 0.25, 0.50, and 0.75), the string I (implying
122             recall points 0, 0.1, 0.2, ..., 1), or the string I (implying
123             one recall point computed after each rank). If argument $points is
124             omitted precision will be computed for ten recall points (i. e., 0.1,
125             0.2, ..., 1).
126              
127             As a result you get a list of (recall, precision) pairs (array of
128             array references with two elements each) and the averaged precision
129             (over all recall points computed).
130              
131             =cut #---------------------------------------------------------------#
132              
133             sub calculate {
134              
135 0     0 1   my $self = shift;
136 0           my $points = shift;
137              
138 0 0         return undef unless $self->{rels};
139              
140 0 0         $points = 10 unless defined $points;
141              
142             # calculate recall points for which precision is to be computed
143 0           my @points;
144 0 0         if ($points =~ /rank/i) {
    0          
    0          
    0          
    0          
    0          
145             # calculate precision at the end of each rank
146 0           @points = 'rank';
147             } elsif ($points =~ /smart/i) {
148             # calculate precision for smart default recall points
149 0           @points = (0.25, 0.50, 0.75);
150             } elsif ($points =~ /trec/i) {
151             # calculate precision for smart default recall points
152 0           @points = (0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1);
153             } elsif ($points =~ /simple/i) {
154             # calculate precision at each simple recall point
155 0           foreach (1 .. $self->{rels}) {
156 0           push @points, $_ / $self->{rels};
157             }
158             } elsif (ref $points eq 'ARRAY') {
159             # calculate precision at the given list of recall points
160 0           @points = @{$points};
  0            
161             } elsif ($points = int $points) {
162             # calculate precision at a given number of recall points
163 0           @points = map { $_ / $points } (1 .. $points);
  0            
164             } else {
165             # don't know what to do
166 0           return undef;
167             }
168              
169 0           my @rp;
170 0 0         if ($points[0] eq 'rank') {
171 0           @rp = $self->precision_rank;
172             } else {
173 0           @rp = $self->precision(@points)
174             }
175              
176 0           my $sum = 0;
177 0           foreach (@rp) {
178 0           my($recall, $precision) = @$_;
179 0           $sum += $precision;
180             }
181              
182 0           my $average = $sum / @rp;
183 0           $self->{rpgraph} = [ \@rp, $average ];
184             }
185              
186              
187             =pod #---------------------------------------------------------------#
188              
189             =item @precision = $rp->precision(@recall)
190              
191             calculate precision for recall points in @recall. Returned is an array
192             of (recall, precision) pairs (array of array references with two
193             elements each). This method is abstract within this class, you need to
194             choose the proper implementation from the subclasses (or overwrite it
195             in your own RePrec subclass).
196              
197             =cut #---------------------------------------------------------------#
198              
199             sub precision {
200              
201 0     0 1   my $self = shift;
202 0           croak ref($self) . '::precision: abstract class method';
203             }
204              
205              
206             =pod #---------------------------------------------------------------#
207              
208             =item @precision = $rp->precision_rank
209              
210             calculate precision after each rank. Returned is an array
211             of (recall, precision) pairs (array of array references with two
212             elements each).
213              
214             =cut #---------------------------------------------------------------#
215              
216             sub precision_rank {
217              
218 0     0 1   my $self = shift;
219              
220 0           my @result;
221 0           foreach (@{$self->{rank_rels_nrels}}) {
  0            
222 0           my $recall = $_->[0] / $self->{rels};
223 0 0 0       next if @result and $result[$#result]->[0] == $recall;
224 0           my $total = $_->[0] + $_->[1];
225 0           push @result, [ $recall, $self->precision($recall) ];
226             }
227              
228 0           @result;
229             }
230              
231              
232             =cut #---------------------------------------------------------------#
233              
234             =item $rp->gnuplot([$gnuplot])
235              
236             plot curve with gnuplot(1). $gnuplot is a hash reference where
237             parameters for gnuplot can be set.
238              
239             =cut #---------------------------------------------------------------#
240              
241             sub gnuplot {
242              
243 0     0 1   my $self = shift;
244 0           my %gnuplot = @_;
245              
246 0 0         return undef unless $self->{rpgraph};
247              
248 0           RePrec::Tools::gnuplot(@{$self->{rpgraph}}, \%gnuplot);
  0            
249             }
250              
251              
252             =pod #---------------------------------------------------------------#
253              
254             =item $rp->write_rpdata($file, [$average]);
255              
256             Write the recall-precision data to file(s). Writes data for average
257             precision if $average is true.
258              
259             =cut #---------------------------------------------------------------#
260              
261             sub write_rpdata {
262              
263 0     0 1   my $self = shift;
264 0           my $file = shift;
265 0           my $average = shift;
266              
267 0 0         return undef unless $self->{rpgraph};
268              
269 0 0         if ($average) {
270 0           RePrec::Tools::write_rpdata($file, @{$self->{rpgraph}});
  0            
271             } else {
272 0           RePrec::Tools::write_rpdata($file, $self->{rpgraph}->[0]);
273             }
274             }
275              
276              
277             ## private ###########################################################
278              
279             =pod #---------------------------------------------------------------#
280              
281             =begin private
282              
283             =item $rp->_sortrfdata($distribution)
284              
285             Takes a distribution (as given to the constructor) and computes some
286             internal data structures needed for the various Recall-Precision
287             measures.
288              
289             =end private
290              
291             =cut #---------------------------------------------------------------#
292              
293             sub _sortrfdata {
294              
295 0     0     my $self = shift;
296 0           my $distribution = shift;
297              
298             ## data structures:
299              
300             # for each rank (index) denote number of relevant and
301             # nonrelevant documents in this rank and in ranks before
302             # (note that the first rank is at index 0!)
303 0           my @rank; # $self->{rank_rels_nrels}
304              
305             # for each relevant document denote number of rank where it occurs
306             # (note that the index for the first relevant document is 0 and,
307             # again, that the first rank index is 0!)
308             my @rels; # $self->{rels_rank}
309              
310             # for each document denote number of rank where it occurs (note
311             # that the index for the first relevant document is 0 and, again,
312             # that the first rank index is 0!)
313 0           my @docs; # $self->{docs_rank}
314              
315             # string representation of ranking; for each rank
316             # add a tuple with number of relevant and non relevant documents
317 0           my $resultstring = ''; # $self->{resultstring}
318              
319 0           my $rels = 0; # number of relevant documents in current and earlier ranks
320 0           my $nrels = 0; # number of non-relevant documents in current and earlier ranks
321 0           my $rank = 0; # current rank
322 0           my $visual = '';
323 0           my $part_rels = 0;
324 0           my $part_nrels = 0;
325 0           foreach (@$distribution) {
326 0           my($rank_rels, $rank_nrels) = @$_;
327 0           $visual .= "($rank_rels, $rank_nrels) ";
328 0           $rels += $rank_rels;
329 0           $nrels += $rank_nrels;
330 0           push @rank, [ $rels, $nrels ];
331 0           push @rels, ($rank) x int($rank_rels + $part_rels);
332 0           push @docs, ($rank) x int($rank_rels + $rank_nrels + $part_rels + $rank_nrels);
333 0           $part_rels = $rank_rels + $part_rels - int($rank_rels + $part_rels);
334 0           $part_nrels = $rank_nrels + $part_nrels - int($rank_nrels + $part_nrels);
335 0           $rank++;
336             }
337 0 0 0       if ($part_rels or $part_nrels) {
338 0           push @rank, [ $rels + $part_rels, $nrels + $part_nrels ];
339 0           push @rels, ($rank) x 1;
340 0           push @docs, ($rank) x 1;
341             }
342 0           $visual =~ s/ $//;
343              
344 0           $self->{rels_rank} = \@rels;
345 0           $self->{docs_rank} = \@docs;
346 0           $self->{rank_rels_nrels} = \@rank;
347 0           $self->{resultstring} = $visual;
348             }
349              
350              
351             =pod #---------------------------------------------------------------#
352              
353             =back
354              
355             =head1 BUGS
356              
357             Yes. Please let me know!
358              
359             =head1 SEE ALSO
360              
361             =over
362              
363             =item Different recall-precision measures:
364              
365             RePrec::Ceiling(3),
366             RePrec::EP(3),
367             RePrec::EP_ND(3),
368             RePrec::PRR(3),
369             RePrec::Raw(3),
370             RePrec::Salton(3)
371              
372             =item Parsing of searchresults and relevance judgements
373              
374             RePrec::Collection(3),
375             RePrec::Collection::FERMI(3),
376             RePrec::Collection::Paris(3),
377             RePrec::Searchresult(3),
378             RePrec::Searchresult::HySpirit(3)
379              
380             =item Miscellaneous tools
381              
382             RePrec::Average(3),
383             RePrec::Tools(3),
384             reprec(1)
385              
386             =item Other
387              
388             gnuplot(1),
389             perl(1)
390              
391             =back
392              
393             =head1 AUTHOR
394              
395             Norbert GEvert EFE
396              
397             =cut #---------------------------------------------------------------#
398              
399              
400             1;