/** * Copyright (C) 2001-2017 by RapidMiner and the contributors * * Complete list of developers available at our web site: * * http://rapidminer.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.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import com.rapidminer.operator.ResultObjectAdapter; import com.rapidminer.report.Readable; 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 */ public abstract class Averagable extends ResultObjectAdapter implements Cloneable, 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. */ @Override 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. */ @Override public Object clone() throws CloneNotSupportedException { try { Class<? extends Averagable> clazz = this.getClass(); Constructor<? extends Averagable> cloneConstructor = clazz.getConstructor(clazz); Averagable result = cloneConstructor.newInstance(this); return result; } catch (IllegalAccessException | InstantiationException | InvocationTargetException | NoSuchMethodException e) { throw new CloneNotSupportedException("Cannot clone averagable: " + e.getMessage()); } } // ================================================================================ /** * 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"; } @Override 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(); } @Override public boolean isInTargetEncoding() { return false; } public void setAverageCount(int averageCount) { this.averageCount = averageCount; } }