001/**
002 * Copyright (c) 2011, The University of Southampton and the individual contributors.
003 * All rights reserved.
004 *
005 * Redistribution and use in source and binary forms, with or without modification,
006 * are permitted provided that the following conditions are met:
007 *
008 *   *  Redistributions of source code must retain the above copyright notice,
009 *      this list of conditions and the following disclaimer.
010 *
011 *   *  Redistributions in binary form must reproduce the above copyright notice,
012 *      this list of conditions and the following disclaimer in the documentation
013 *      and/or other materials provided with the distribution.
014 *
015 *   *  Neither the name of the University of Southampton nor the names of its
016 *      contributors may be used to endorse or promote products derived from this
017 *      software without specific prior written permission.
018 *
019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029 */
030package org.openimaj.experiment.evaluation.retrieval.analysers;
031
032import gnu.trove.map.hash.TObjectDoubleHashMap;
033import gnu.trove.procedure.TDoubleProcedure;
034import gnu.trove.procedure.TObjectDoubleProcedure;
035
036import java.io.File;
037import java.io.FileWriter;
038import java.io.IOException;
039
040import net.sf.jasperreports.engine.JRException;
041import net.sf.jasperreports.engine.JasperPrint;
042
043import org.apache.commons.math.stat.descriptive.DescriptiveStatistics;
044import org.openimaj.data.identity.Identifiable;
045import org.openimaj.experiment.evaluation.AnalysisResult;
046
047import com.googlecode.jatl.Html;
048
049/**
050 * {@link AnalysisResult} used with {@link PrecisionAtN} to hold the P@N
051 * precision after N documents have been retrieved.
052 * <p>
053 * Provides both per-query precisions and descriptive statistics over all
054 * queries.
055 * 
056 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
057 * 
058 * @param <QUERY>
059 *            Type of query
060 */
061public class PrecisionAtNResult<QUERY> implements AnalysisResult {
062        TObjectDoubleHashMap<QUERY> allScores = new TObjectDoubleHashMap<QUERY>();
063        int N;
064
065        /**
066         * Construct with the given N.
067         * 
068         * @param N
069         *            number of retrieved documents at which precision is calculated
070         */
071        public PrecisionAtNResult(int N) {
072                this.N = N;
073        }
074
075        private DescriptiveStatistics computeStats() {
076                final DescriptiveStatistics ds = new DescriptiveStatistics();
077
078                allScores.forEachValue(new TDoubleProcedure() {
079                        @Override
080                        public boolean execute(double value) {
081                                ds.addValue(value);
082                                return true;
083                        }
084                });
085
086                return ds;
087        }
088
089        void writeHTML(File file, final String title, final String info) throws IOException {
090                FileWriter fw = null;
091                try {
092                        fw = new FileWriter(file);
093
094                        new Html(fw) {
095                                {
096                                        html();
097                                        head();
098                                        title(title);
099                                        end();
100                                        body();
101                                        h1().text(title).end();
102                                        div().text(info).end();
103                                        hr();
104                                        pre().text(PrecisionAtNResult.this.toString()).end();
105                                        endAll();
106                                }
107                        };
108                } finally {
109                        if (fw != null)
110                                fw.close();
111                }
112        }
113
114        @Override
115        public String toString() {
116                return getSummaryReport();
117        }
118
119        @Override
120        public JasperPrint getSummaryReport(String title, String info) throws JRException {
121                // FIXME
122                throw new UnsupportedOperationException();
123        }
124
125        @Override
126        public JasperPrint getDetailReport(String title, String info) throws JRException {
127                // FIXME
128                throw new UnsupportedOperationException();
129        }
130
131        @Override
132        public String getSummaryReport() {
133                final StringBuilder outBuffer = new StringBuilder();
134
135                final DescriptiveStatistics ds = computeStats();
136                outBuffer.append("Aggregate P@" + N + " Statistics:\n");
137                outBuffer.append(String.format("%-15s\t%6d\n", "num_q", ds.getN()));
138                outBuffer.append(String.format("%-15s\t%6.4f\n", "min", ds.getMin()));
139                outBuffer.append(String.format("%-15s\t%6.4f\n", "max", ds.getMax()));
140                outBuffer.append(String.format("%-15s\t%6.4f\n", "mean", ds.getMean()));
141                outBuffer.append(String.format("%-15s\t%6.4f\n", "std dev", ds.getStandardDeviation()));
142                outBuffer.append(String.format("%-15s\t%6.4f\n", "median", ds.getPercentile(50)));
143                outBuffer.append(String.format("%-15s\t%6.4f\n", "skewness", ds.getSkewness()));
144                outBuffer.append(String.format("%-15s\t%6.4f\n", "kurtosis", ds.getKurtosis()));
145
146                return outBuffer.toString();
147        }
148
149        @Override
150        public String getDetailReport() {
151                final StringBuilder outBuffer = new StringBuilder();
152
153                allScores.forEachEntry(new TObjectDoubleProcedure<QUERY>() {
154                        @Override
155                        public boolean execute(QUERY a, double b) {
156                                String id;
157                                if (a instanceof Identifiable)
158                                        id = ((Identifiable) a).getID();
159                                else
160                                        id = a.toString();
161
162                                outBuffer.append(String.format("P@%-11s\t%10s\t%6.4f\n", N, id, b));
163
164                                return true;
165                        }
166                });
167                outBuffer.append("\n");
168
169                outBuffer.append(getSummaryReport());
170
171                return outBuffer.toString();
172        }
173}