/*
* RapidMiner
*
* Copyright (C) 2001-2008 by Rapid-I and the contributors
*
* Complete list of developers available at our web site:
*
* http://rapid-i.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
package com.rapidminer.tools.math;
import java.awt.Component;
import java.awt.FlowLayout;
import java.awt.Font;
import javax.swing.BorderFactory;
import javax.swing.JPanel;
import javax.swing.JTextPane;
import com.rapidminer.operator.IOContainer;
import com.rapidminer.operator.ResultObjectAdapter;
import com.rapidminer.report.Readable;
import com.rapidminer.report.Reportable;
import com.rapidminer.tools.Tools;
/**
* Superclass for all objects which can be averaged. Averagable objects can be
* stored in a average vector.
*
* @author Ingo Mierswa
* @version $Id: Averagable.java,v 1.8 2008/07/31 17:43:41 ingomierswa Exp $
*/
public abstract class Averagable extends ResultObjectAdapter implements Cloneable, Reportable, Readable {
/**
*
*/
private static final long serialVersionUID = 3193522429555690641L;
/** The averages are summed up each time buildAverage is called. */
private double meanSum;
/** The squared averages are summed up each time buildAverage is called. */
private double meanSquaredSum;
/** Counts the number of times, build average was executed. */
private int averageCount;
public Averagable() {
this.meanSum = Double.NaN;
this.meanSquaredSum = Double.NaN;
this.averageCount = 0;
}
public Averagable(Averagable o) {
this.meanSum = o.meanSum;
this.meanSquaredSum = o.meanSquaredSum;
this.averageCount = o.averageCount;
}
/** Returns the name of this averagable. The returned string should only contain
* lowercase letters and underscore (RapidMiner parameter format) since the names
* will be automatically used for GUI purposes. */
public abstract String getName();
/** Returns the (current) value of the averagable (the average itself). If the
* method {@link #buildSingleAverage(Averagable)} was used, this method must return the
* micro average from both (or more) criteria. This is usually achieved by
* correctly implementing {@link #buildSingleAverage(Averagable)}. */
public abstract double getMikroAverage();
/**
* Returns the variance of the averagable. The returned value must not be
* negative. If the averagable does not define a variance this method should
* return Double.NaN.
*/
public abstract double getMikroVariance();
/**
* Must be implemented by subclasses such that it copies all values of
* <code>other</code> to <code>this</code>. When this method is called,
* it is guaranteed, that <code>other</code> is a subclass of the class of
* the object it is called on.
*
* @deprecated Please use copy constructors instead
*/
@Deprecated
protected final void cloneAveragable(Averagable other) { }
/**
* This method should build the average of this and another averagable of
* the same type. The next invocation of {@link #getMikroAverage()} should return the
* average of this and the given averagable. Hence, this method is used to build
* the actual micro average value of two criteria. Please refer to
* {@link com.rapidminer.operator.performance.SimpleCriterion} for a simple
* implementation example.
*/
protected abstract void buildSingleAverage(Averagable averagable);
// ================================================================================
/**
* This method builds the makro average of two averagables of the same type.
* First this method checks if the classes of <code>this</code> and
* <code>performance</code> are the same and if the {@link #getName()}
* methods return the same String. Otherwise a RuntimeException is thrown.
* <br>
* The value of <code>averagable.</code>{@link #getMikroAverage()} is
* added to {@link #meanSum}, its square is added to
* {@link #meanSquaredSum} and {@link #averageCount} is increased by one.
* These values are used in the {@link #getMakroAverage()} and
* {@link #getMakroVariance()} methods. <br>
* Subclasses should implement the method buildSingleAverage() to build the
* mikro (weighted) average of <code>this</code> averagable and the given
* <code>averagable</code>. They must be weighted by the number of
* examples used for calculating the averagables.
*/
public final void buildAverage(Averagable averagable) {
if (!averagable.getClass().equals(this.getClass()))
throw new RuntimeException("Cannot build average of different averagable types (" + this.getClass().getName() + "/" + averagable.getClass().getName() + ").");
if (!averagable.getName().equals(this.getName()))
throw new RuntimeException("Cannot build average of different averagable types (" + this.getName() + "/" + averagable.getName() + ").");
if (averageCount == 0) { // count yourself
double value = this.getMikroAverage();
meanSum = value;
meanSquaredSum = value * value;
averageCount = 1;
}
double value = averagable.getMikroAverage();
meanSum += value;
meanSquaredSum += value * value;
averageCount++;
buildSingleAverage(averagable);
}
/**
* This method returns the makro average if it was defined and the mikro
* average (the current value) otherwise. This method should be used instead
* of {@link #getMikroAverage()} for optimization purposes, i.e. by methods like
* <code>getFitness()</code> of performance criteria.
*/
public final double getAverage() {
double average = Double.NaN;
if (averageCount > 0)
average = getMakroAverage();
if (Double.isNaN(average))
average = getMikroAverage();
return average;
}
/**
* This method returns the makro variance if it was defined and the mikro
* variance otherwise.
*/
public final double getVariance() {
double variance = Double.NaN;
if (averageCount > 0)
variance = getMakroVariance();
if (Double.isNaN(variance))
variance = getMikroVariance();
return variance;
}
/**
* This method returns the makro standard deviation if it was defined and
* the mikro standard deviation otherwise.
*/
public final double getStandardDeviation() {
double sd = Double.NaN;
if (averageCount > 0)
sd = getMakroStandardDeviation();
if (Double.isNaN(sd))
sd = getMikroStandardDeviation();
return sd;
}
/** Returns the standard deviation of the performance criterion. */
public final double getMikroStandardDeviation() {
double variance = getMikroVariance();
if (Double.isNaN(variance))
return Double.NaN;
else if (variance < 0.0d)
return 0.0d;
else
return Math.sqrt(variance);
}
/**
* Returns the average value of all performance criteria average by using
* the {@link #buildAverage(Averagable)} method.
*/
public final double getMakroAverage() {
return meanSum / averageCount;
}
/**
* Returns the variance of all performance criteria average by using the
* {@link #buildAverage(Averagable)} method.
*/
public final double getMakroVariance() {
double mean = getMakroAverage();
double result = meanSquaredSum / averageCount - mean * mean;
if (Double.isNaN(result))
return Double.NaN;
else if (result < 0.0d)
return 0.0d;
else
return result;
}
/**
* Returns the standard deviation of all performance criteria average by
* using the {@link #buildAverage(Averagable)} method.
*/
public final double getMakroStandardDeviation() {
return Math.sqrt(getMakroVariance());
}
/** Returns the number of averagables used to create this averagable. */
public int getAverageCount() {
return this.averageCount;
}
/** Returns a (deep) clone of this averagable. */
public Object clone() throws CloneNotSupportedException {
try {
Class<?> clazz = this.getClass();
java.lang.reflect.Constructor cloneConstructor = clazz.getConstructor(new Class[] { clazz });
Averagable result = (Averagable)cloneConstructor.newInstance(new Object[] { this });
return result;
} catch (IllegalAccessException e) {
throw new CloneNotSupportedException("Cannot clone averagable: " + e.getMessage());
} catch (InstantiationException e) {
throw new CloneNotSupportedException("Cannot clone averagable: " + e.getMessage());
} catch (java.lang.reflect.InvocationTargetException e) {
throw new CloneNotSupportedException("Cannot clone averagable: " + e.getMessage());
} catch (NoSuchMethodException e) {
throw new CloneNotSupportedException("Cannot clone averagable: " + e.getMessage());
}
}
// ================================================================================
/** This default implementation returns a framed text pane containing the string
* delivered by toResultString(). Hence, each performance
* criterion should either overwrite this method or should overwrite toResultString()
* in order to ensure a nice visual representation of this criterion. */
public Component getVisualizationComponent(IOContainer ioContainer) {
JPanel infoPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
JTextPane infoText = new JTextPane();
infoText.setEditable(false);
infoText.setBackground(infoPanel.getBackground());
infoText.setFont(infoText.getFont().deriveFont(Font.BOLD));
infoText.setText(toResultString());
infoPanel.add(infoText);
infoPanel.setBorder(BorderFactory.createEtchedBorder());
return infoPanel;
}
/**
* Indicates wether or not percentage format should be used in the
* {@link #toString} method. The default implementation returns false.
*/
public boolean formatPercent() {
return false;
}
/** Formats the value for the {@link #toString()} method. */
private final String formatValue(double value) {
if (Double.isNaN(value))
return "unknown";
else {
if (formatPercent()) {
return Tools.formatPercent(value);
} else {
return Tools.formatNumber(value);
}
}
}
/** Formats the standard deviation for the {@link #toString()} method. */
private final String formatDeviation(double dev) {
if (formatPercent()) {
return Tools.formatPercent(dev);
} else {
return Tools.formatNumber(dev);
}
}
public String getExtension() {
return "avg";
}
public String getFileDescription() {
return "averagable";
}
public String toString() {
StringBuffer result = new StringBuffer(getName() + ": ");
boolean makroUsable = false;
if (averageCount > 0) {
double makroAverage = getMakroAverage();
if (!Double.isNaN(makroAverage)) {
makroUsable = true;
result.append(formatValue(makroAverage));
double sd = getMakroStandardDeviation();
if (!Double.isNaN(sd))
result.append(" +/- " + formatDeviation(sd));
}
}
if (makroUsable) {
result.append(" (mikro: ");
}
result.append(formatValue(getMikroAverage()));
double sd = getMikroStandardDeviation();
if (!Double.isNaN(sd))
result.append(" +/- " + formatDeviation(sd));
if (makroUsable) {
result.append(")");
}
return result.toString();
}
}