/* * 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(); } }