/*******************************************************************************
* Copyright 2013
* Ubiquitous Knowledge Processing (UKP) Lab
* Technische Universität Darmstadt
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package de.tudarmstadt.ukp.csniper.webapp.statistics.page;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.io.IOUtils;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.ChoiceRenderer;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.ListMultipleChoice;
import org.apache.wicket.markup.html.form.NumberTextField;
import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.markup.html.list.ListView;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.model.Model;
import org.apache.wicket.model.PropertyModel;
import org.apache.wicket.spring.injection.annot.SpringBean;
import de.tudarmstadt.ukp.csniper.webapp.DefaultValues;
import de.tudarmstadt.ukp.csniper.webapp.evaluation.EvaluationRepository;
import de.tudarmstadt.ukp.csniper.webapp.evaluation.model.EvaluationItem;
import de.tudarmstadt.ukp.csniper.webapp.evaluation.model.Query;
import de.tudarmstadt.ukp.csniper.webapp.search.CorpusService;
import de.tudarmstadt.ukp.csniper.webapp.search.PreparedQuery;
import de.tudarmstadt.ukp.csniper.webapp.search.SearchEngine;
import de.tudarmstadt.ukp.csniper.webapp.statistics.SortableAggregatedEvaluationResultDataProvider;
import de.tudarmstadt.ukp.csniper.webapp.support.wicket.ExtendedIndicatingAjaxButton;
import de.tudarmstadt.ukp.csniper.webapp.support.wicket.ThresholdLink;
/**
* Statistics Page
*/
public class StatisticsPage2
extends StatisticsPageBase
{
private static final long serialVersionUID = 1L;
@SpringBean(name = "evaluationRepository")
private EvaluationRepository repository;
@SpringBean(name = "corpusService")
private CorpusService corpusService;
private SortableAggregatedEvaluationResultDataProvider dataProvider;
private StatsTable statsTable;
private SettingsForm settings;
public StatisticsPage2()
{
super();
settings = new SettingsForm("settingsForm");
add(settings);
statsTable = new StatsTable("statsTable");
statsTable.setOutputMarkupId(true);
add(statsTable);
}
private class StatsTable
extends Panel
{
private static final long serialVersionUID = 1L;
ListView<QueryStatistics> list;
public StatsTable(String aId)
{
super(aId);
list = new ListView<QueryStatistics>("list", new ArrayList<QueryStatistics>())
{
private static final long serialVersionUID = 1L;
@Override
protected void populateItem(ListItem<QueryStatistics> item)
{
QueryStatistics e = item.getModelObject();
item.add(new Label("type", e.type));
item.add(new Label("collectionId", e.collectionId));
item.add(new Label("query", e.query));
item.add(new Label("userCount", String.valueOf(e.itemStats.users)));
item.add(new Label("itemCount", String.valueOf(e.itemStats.items)));
item.add(new Label("itemComplete", String.format("%.1f%%",
((double) e.itemStats.getComplete() / e.itemStats.items) * 100.0)));
item.add(new Label("fleissKappa", String
.format("%.2f", e.itemStats.fleissKappa)));
item.add(new Label("correctCount", String.valueOf(e.itemStats.correctVotes)));
item.add(new Label("wrongCount", String.valueOf(e.itemStats.wrongVotes)));
item.add(new Label("TP", String.valueOf(e.itemStats.truePositives)));
item.add(new Label("FP", String.valueOf(e.itemStats.falsePositive)));
item.add(new Label("UNK", String.valueOf(e.itemStats.unknown)));
item.add(new Label("precision", String.format("%.4f",
e.itemStats.getPrecision(true))));
item.add(new Label("precision2", String.format("%.4f",
e.itemStats.getPrecision(false))));
}
};
add(list);
}
}
private class SettingsForm
extends Form
{
private static final long serialVersionUID = 1L;
private double userThreshold = DefaultValues.DEFAULT_USER_THRESHOLD;
private double confidenceThreshold = DefaultValues.DEFAULT_CONFIDENCE_THRESHOLD;
private Set<String> users = new HashSet<String>();
private List<Query> queries;
public SettingsForm(String aId)
{
super(aId);
ListMultipleChoice<String> userSelect = new ListMultipleChoice<String>("userSelect",
new PropertyModel<Set<String>>(this, "users"), repository.listUsers());
userSelect.setRequired(true);
add(userSelect);
ListMultipleChoice<Query> querySelect = new ListMultipleChoice<Query>("querySelect",
new PropertyModel<List<Query>>(this, "queries"), repository.listUniqueQueries());
querySelect.setChoiceRenderer(new ChoiceRenderer<Query>() {
private static final long serialVersionUID = 1L;
@Override
public Object getDisplayValue(Query aObject)
{
if (aObject.getType() == null) {
return aObject.getQuery();
}
else {
return String.format("%s - %s", aObject.getType(), aObject.getQuery());
}
}
});
querySelect.setRequired(true);
add(querySelect);
add(new NumberTextField<Double>("userThreshold", new PropertyModel<Double>(this,
"userThreshold")).setMinimum(0.0).setMaximum(1.0));
add(new NumberTextField<Double>("confidenceThreshold", new PropertyModel<Double>(this,
"confidenceThreshold")).setMinimum(0.0).setMaximum(1.0));
add(new ThresholdLink("thresholdHelp"));
add(new ExtendedIndicatingAjaxButton("thresholdButton", new Model<String>("Apply"),
new Model<String>("Calculating..."))
{
private static final long serialVersionUID = 1L;
@Override
protected void onSubmit(AjaxRequestTarget aTarget, Form<?> aForm)
{
statsTable.list.setModelObject(calculate());
aTarget.add(getFeedbackPanel(), statsTable);
}
@Override
public void onError(AjaxRequestTarget aTarget, Form<?> aForm)
{
super.onError(aTarget, aForm);
// Make sure the feedback messages are rendered
aTarget.add(getFeedbackPanel());
}
});
}
private List<QueryStatistics> calculate()
{
List<QueryStatistics> someList = new ArrayList<QueryStatistics>();
for (Query q : queries) {
List<EvaluationItem> items = new ArrayList<EvaluationItem>();
for (SearchEngine engine : corpusService.listEngines(q.getCollectionId())) {
if (engine.getName().equals(q.getEngine())) {
PreparedQuery query = engine.createQuery(q.getType(), q.getCollectionId(), q.getQuery());
query.setMaxResults(Integer.MAX_VALUE);
try {
items = query.execute();
}
finally {
IOUtils.closeQuietly(query);
}
break;
}
}
// Remember the total number of items because the following call filters out all
// items that are not peristed yet
int totalItems = items.size();
items = repository.writeEvaluationItems(items, false);
dataProvider = new SortableAggregatedEvaluationResultDataProvider(
repository.listAggregatedResults(items, users, userThreshold,
confidenceThreshold), users);
QueryStatistics qstats = new QueryStatistics();
qstats.type = q.getType();
qstats.collectionId = q.getCollectionId();
qstats.query = q.getQuery();
qstats.itemStats = dataProvider.getItemStatistics();
// Calculate based on all items, not only on the persisted items
qstats.itemStats.items = totalItems;
qstats.itemStats.unknown = qstats.itemStats.items - qstats.itemStats.truePositives
- qstats.itemStats.falsePositive;
someList.add(qstats);
}
return someList;
}
}
private static class QueryStatistics
implements Serializable
{
private static final long serialVersionUID = 1L;
public String type;
public String collectionId;
public String query;
public ItemStatistics itemStats;
}
public static class ItemStatistics
implements Serializable
{
private static final long serialVersionUID = 1L;
public int users;
public int items;
public int correctVotes;
public int wrongVotes;
public int truePositives;
public int falsePositive;
public int unknown;
public double fleissKappa;
public double getPrecision(boolean aUnknownAsFalsePositives)
{
if (aUnknownAsFalsePositives) {
return (double) truePositives / (falsePositive + truePositives + unknown);
}
else {
return (double) truePositives / (truePositives + falsePositive);
}
}
public int getComplete()
{
return truePositives + falsePositive;
}
@Override
public String toString()
{
return "ItemStatistics [users=" + users + ", items=" + items + ", correctVotes="
+ correctVotes + ", wrongVotes=" + wrongVotes + ", truePositives="
+ truePositives + ", falsePositive=" + falsePositive + ", unknown=" + unknown
+ ", fleissKappa=" + fleissKappa + "]";
}
}
}