Frames | No Frames |
1: /* =========================================================== 2: * JFreeChart : a free chart library for the Java(tm) platform 3: * =========================================================== 4: * 5: * (C) Copyright 2000-2006, by Object Refinery Limited and Contributors. 6: * 7: * Project Info: http://www.jfree.org/jfreechart/index.html 8: * 9: * This library is free software; you can redistribute it and/or modify it 10: * under the terms of the GNU Lesser General Public License as published by 11: * the Free Software Foundation; either version 2.1 of the License, or 12: * (at your option) any later version. 13: * 14: * This library is distributed in the hope that it will be useful, but 15: * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 16: * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 17: * License for more details. 18: * 19: * You should have received a copy of the GNU Lesser General Public 20: * License along with this library; if not, write to the Free Software 21: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 22: * USA. 23: * 24: * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 25: * in the United States and other countries.] 26: * 27: * ---------------------------- 28: * BoxAndWhiskerCalculator.java 29: * ---------------------------- 30: * (C) Copyright 2003-2006, by Object Refinery Limited and Contributors. 31: * 32: * Original Author: David Gilbert (for Object Refinery Limited); 33: * Contributor(s): -; 34: * 35: * $Id: BoxAndWhiskerCalculator.java,v 1.3.2.2 2006/11/16 11:19:47 mungady Exp $ 36: * 37: * Changes 38: * ------- 39: * 28-Aug-2003 : Version 1 (DG); 40: * 17-Nov-2003 : Fixed bug in calculations of outliers and median (DG); 41: * 10-Jan-2005 : Removed deprecated methods in preparation for 1.0.0 42: * release (DG); 43: * ------------- JFREECHART 1.0.x --------------------------------------------- 44: * 15-Nov-2006 : Cleaned up handling of null arguments, and null or NaN items 45: * in the list (DG); 46: * 47: */ 48: 49: package org.jfree.data.statistics; 50: 51: import java.util.ArrayList; 52: import java.util.Collections; 53: import java.util.Iterator; 54: import java.util.List; 55: 56: /** 57: * A utility class that calculates the mean, median, quartiles Q1 and Q3, plus 58: * a list of outlier values...all from an arbitrary list of 59: * <code>Number</code> objects. 60: */ 61: public abstract class BoxAndWhiskerCalculator { 62: 63: /** 64: * Calculates the statistics required for a {@link BoxAndWhiskerItem} 65: * from a list of <code>Number</code> objects. Any items in the list 66: * that are <code>null</code>, not an instance of <code>Number</code>, or 67: * equivalent to <code>Double.NaN</code>, will be ignored. 68: * 69: * @param values a list of numbers (a <code>null</code> list is not 70: * permitted). 71: * 72: * @return A box-and-whisker item. 73: */ 74: public static BoxAndWhiskerItem calculateBoxAndWhiskerStatistics( 75: List values) { 76: return calculateBoxAndWhiskerStatistics(values, true); 77: } 78: 79: /** 80: * Calculates the statistics required for a {@link BoxAndWhiskerItem} 81: * from a list of <code>Number</code> objects. Any items in the list 82: * that are <code>null</code>, not an instance of <code>Number</code>, or 83: * equivalent to <code>Double.NaN</code>, will be ignored. 84: * 85: * @param values a list of numbers (a <code>null</code> list is not 86: * permitted). 87: * @param stripNullAndNaNItems a flag that controls the handling of null 88: * and NaN items. 89: * 90: * @return A box-and-whisker item. 91: * 92: * @since 1.0.3 93: */ 94: public static BoxAndWhiskerItem calculateBoxAndWhiskerStatistics( 95: List values, boolean stripNullAndNaNItems) { 96: 97: if (values == null) { 98: throw new IllegalArgumentException("Null 'values' argument."); 99: } 100: 101: List vlist; 102: if (stripNullAndNaNItems) { 103: vlist = new ArrayList(values.size()); 104: Iterator iterator = values.listIterator(); 105: while (iterator.hasNext()) { 106: Object obj = iterator.next(); 107: if (obj instanceof Number) { 108: Number n = (Number) obj; 109: double v = n.doubleValue(); 110: if (!Double.isNaN(v)) { 111: vlist.add(n); 112: } 113: } 114: } 115: } 116: else { 117: vlist = values; 118: } 119: Collections.sort(vlist); 120: 121: double mean = Statistics.calculateMean(vlist, false); 122: double median = Statistics.calculateMedian(vlist, false); 123: double q1 = calculateQ1(vlist); 124: double q3 = calculateQ3(vlist); 125: 126: double interQuartileRange = q3 - q1; 127: 128: double upperOutlierThreshold = q3 + (interQuartileRange * 1.5); 129: double lowerOutlierThreshold = q1 - (interQuartileRange * 1.5); 130: 131: double upperFaroutThreshold = q3 + (interQuartileRange * 2.0); 132: double lowerFaroutThreshold = q1 - (interQuartileRange * 2.0); 133: 134: double minRegularValue = Double.POSITIVE_INFINITY; 135: double maxRegularValue = Double.NEGATIVE_INFINITY; 136: double minOutlier = Double.POSITIVE_INFINITY; 137: double maxOutlier = Double.NEGATIVE_INFINITY; 138: List outliers = new ArrayList(); 139: 140: Iterator iterator = vlist.iterator(); 141: while (iterator.hasNext()) { 142: Number number = (Number) iterator.next(); 143: double value = number.doubleValue(); 144: if (value > upperOutlierThreshold) { 145: outliers.add(number); 146: if (value > maxOutlier && value <= upperFaroutThreshold) { 147: maxOutlier = value; 148: } 149: } 150: else if (value < lowerOutlierThreshold) { 151: outliers.add(number); 152: if (value < minOutlier && value >= lowerFaroutThreshold) { 153: minOutlier = value; 154: } 155: } 156: else { 157: minRegularValue = Math.min(minRegularValue, value); 158: maxRegularValue = Math.max(maxRegularValue, value); 159: } 160: minOutlier = Math.min(minOutlier, minRegularValue); 161: maxOutlier = Math.max(maxOutlier, maxRegularValue); 162: } 163: 164: return new BoxAndWhiskerItem(new Double(mean), new Double(median), 165: new Double(q1), new Double(q3), new Double(minRegularValue), 166: new Double(maxRegularValue), new Double(minOutlier), 167: new Double(maxOutlier), outliers); 168: 169: } 170: 171: /** 172: * Calculates the first quartile for a list of numbers in ascending order. 173: * If the items in the list are not in ascending order, the result is 174: * unspecified. If the list contains items that are <code>null</code>, not 175: * an instance of <code>Number</code>, or equivalent to 176: * <code>Double.NaN</code>, the result is unspecified. 177: * 178: * @param values the numbers in ascending order (<code>null</code> not 179: * permitted). 180: * 181: * @return The first quartile. 182: */ 183: public static double calculateQ1(List values) { 184: if (values == null) { 185: throw new IllegalArgumentException("Null 'values' argument."); 186: } 187: 188: double result = Double.NaN; 189: int count = values.size(); 190: if (count > 0) { 191: if (count % 2 == 1) { 192: if (count > 1) { 193: result = Statistics.calculateMedian(values, 0, count / 2); 194: } 195: else { 196: result = Statistics.calculateMedian(values, 0, 0); 197: } 198: } 199: else { 200: result = Statistics.calculateMedian(values, 0, count / 2 - 1); 201: } 202: 203: } 204: return result; 205: } 206: 207: /** 208: * Calculates the third quartile for a list of numbers in ascending order. 209: * If the items in the list are not in ascending order, the result is 210: * unspecified. If the list contains items that are <code>null</code>, not 211: * an instance of <code>Number</code>, or equivalent to 212: * <code>Double.NaN</code>, the result is unspecified. 213: * 214: * @param values the list of values (<code>null</code> not permitted). 215: * 216: * @return The third quartile. 217: */ 218: public static double calculateQ3(List values) { 219: if (values == null) { 220: throw new IllegalArgumentException("Null 'values' argument."); 221: } 222: double result = Double.NaN; 223: int count = values.size(); 224: if (count > 0) { 225: if (count % 2 == 1) { 226: if (count > 1) { 227: result = Statistics.calculateMedian(values, count / 2, 228: count - 1); 229: } 230: else { 231: result = Statistics.calculateMedian(values, 0, 0); 232: } 233: } 234: else { 235: result = Statistics.calculateMedian(values, count / 2, 236: count - 1); 237: } 238: } 239: return result; 240: } 241: 242: }