Frames | No Frames |
1: /* =========================================================== 2: * JFreeChart : a free chart library for the Java(tm) platform 3: * =========================================================== 4: * 5: * (C) Copyright 2000-2007, 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-2007, by Object Refinery Limited and Contributors. 31: * 32: * Original Author: David Gilbert (for Object Refinery Limited); 33: * Contributor(s): -; 34: * 35: * Changes 36: * ------- 37: * 28-Aug-2003 : Version 1 (DG); 38: * 17-Nov-2003 : Fixed bug in calculations of outliers and median (DG); 39: * 10-Jan-2005 : Removed deprecated methods in preparation for 1.0.0 40: * release (DG); 41: * ------------- JFREECHART 1.0.x --------------------------------------------- 42: * 15-Nov-2006 : Cleaned up handling of null arguments, and null or NaN items 43: * in the list (DG); 44: * 45: */ 46: 47: package org.jfree.data.statistics; 48: 49: import java.util.ArrayList; 50: import java.util.Collections; 51: import java.util.Iterator; 52: import java.util.List; 53: 54: /** 55: * A utility class that calculates the mean, median, quartiles Q1 and Q3, plus 56: * a list of outlier values...all from an arbitrary list of 57: * <code>Number</code> objects. 58: */ 59: public abstract class BoxAndWhiskerCalculator { 60: 61: /** 62: * Calculates the statistics required for a {@link BoxAndWhiskerItem} 63: * from a list of <code>Number</code> objects. Any items in the list 64: * that are <code>null</code>, not an instance of <code>Number</code>, or 65: * equivalent to <code>Double.NaN</code>, will be ignored. 66: * 67: * @param values a list of numbers (a <code>null</code> list is not 68: * permitted). 69: * 70: * @return A box-and-whisker item. 71: */ 72: public static BoxAndWhiskerItem calculateBoxAndWhiskerStatistics( 73: List values) { 74: return calculateBoxAndWhiskerStatistics(values, true); 75: } 76: 77: /** 78: * Calculates the statistics required for a {@link BoxAndWhiskerItem} 79: * from a list of <code>Number</code> objects. Any items in the list 80: * that are <code>null</code>, not an instance of <code>Number</code>, or 81: * equivalent to <code>Double.NaN</code>, will be ignored. 82: * 83: * @param values a list of numbers (a <code>null</code> list is not 84: * permitted). 85: * @param stripNullAndNaNItems a flag that controls the handling of null 86: * and NaN items. 87: * 88: * @return A box-and-whisker item. 89: * 90: * @since 1.0.3 91: */ 92: public static BoxAndWhiskerItem calculateBoxAndWhiskerStatistics( 93: List values, boolean stripNullAndNaNItems) { 94: 95: if (values == null) { 96: throw new IllegalArgumentException("Null 'values' argument."); 97: } 98: 99: List vlist; 100: if (stripNullAndNaNItems) { 101: vlist = new ArrayList(values.size()); 102: Iterator iterator = values.listIterator(); 103: while (iterator.hasNext()) { 104: Object obj = iterator.next(); 105: if (obj instanceof Number) { 106: Number n = (Number) obj; 107: double v = n.doubleValue(); 108: if (!Double.isNaN(v)) { 109: vlist.add(n); 110: } 111: } 112: } 113: } 114: else { 115: vlist = values; 116: } 117: Collections.sort(vlist); 118: 119: double mean = Statistics.calculateMean(vlist, false); 120: double median = Statistics.calculateMedian(vlist, false); 121: double q1 = calculateQ1(vlist); 122: double q3 = calculateQ3(vlist); 123: 124: double interQuartileRange = q3 - q1; 125: 126: double upperOutlierThreshold = q3 + (interQuartileRange * 1.5); 127: double lowerOutlierThreshold = q1 - (interQuartileRange * 1.5); 128: 129: double upperFaroutThreshold = q3 + (interQuartileRange * 2.0); 130: double lowerFaroutThreshold = q1 - (interQuartileRange * 2.0); 131: 132: double minRegularValue = Double.POSITIVE_INFINITY; 133: double maxRegularValue = Double.NEGATIVE_INFINITY; 134: double minOutlier = Double.POSITIVE_INFINITY; 135: double maxOutlier = Double.NEGATIVE_INFINITY; 136: List outliers = new ArrayList(); 137: 138: Iterator iterator = vlist.iterator(); 139: while (iterator.hasNext()) { 140: Number number = (Number) iterator.next(); 141: double value = number.doubleValue(); 142: if (value > upperOutlierThreshold) { 143: outliers.add(number); 144: if (value > maxOutlier && value <= upperFaroutThreshold) { 145: maxOutlier = value; 146: } 147: } 148: else if (value < lowerOutlierThreshold) { 149: outliers.add(number); 150: if (value < minOutlier && value >= lowerFaroutThreshold) { 151: minOutlier = value; 152: } 153: } 154: else { 155: minRegularValue = Math.min(minRegularValue, value); 156: maxRegularValue = Math.max(maxRegularValue, value); 157: } 158: minOutlier = Math.min(minOutlier, minRegularValue); 159: maxOutlier = Math.max(maxOutlier, maxRegularValue); 160: } 161: 162: return new BoxAndWhiskerItem(new Double(mean), new Double(median), 163: new Double(q1), new Double(q3), new Double(minRegularValue), 164: new Double(maxRegularValue), new Double(minOutlier), 165: new Double(maxOutlier), outliers); 166: 167: } 168: 169: /** 170: * Calculates the first quartile for a list of numbers in ascending order. 171: * If the items in the list are not in ascending order, the result is 172: * unspecified. If the list contains items that are <code>null</code>, not 173: * an instance of <code>Number</code>, or equivalent to 174: * <code>Double.NaN</code>, the result is unspecified. 175: * 176: * @param values the numbers in ascending order (<code>null</code> not 177: * permitted). 178: * 179: * @return The first quartile. 180: */ 181: public static double calculateQ1(List values) { 182: if (values == null) { 183: throw new IllegalArgumentException("Null 'values' argument."); 184: } 185: 186: double result = Double.NaN; 187: int count = values.size(); 188: if (count > 0) { 189: if (count % 2 == 1) { 190: if (count > 1) { 191: result = Statistics.calculateMedian(values, 0, count / 2); 192: } 193: else { 194: result = Statistics.calculateMedian(values, 0, 0); 195: } 196: } 197: else { 198: result = Statistics.calculateMedian(values, 0, count / 2 - 1); 199: } 200: 201: } 202: return result; 203: } 204: 205: /** 206: * Calculates the third quartile for a list of numbers in ascending order. 207: * If the items in the list are not in ascending order, the result is 208: * unspecified. If the list contains items that are <code>null</code>, not 209: * an instance of <code>Number</code>, or equivalent to 210: * <code>Double.NaN</code>, the result is unspecified. 211: * 212: * @param values the list of values (<code>null</code> not permitted). 213: * 214: * @return The third quartile. 215: */ 216: public static double calculateQ3(List values) { 217: if (values == null) { 218: throw new IllegalArgumentException("Null 'values' argument."); 219: } 220: double result = Double.NaN; 221: int count = values.size(); 222: if (count > 0) { 223: if (count % 2 == 1) { 224: if (count > 1) { 225: result = Statistics.calculateMedian(values, count / 2, 226: count - 1); 227: } 228: else { 229: result = Statistics.calculateMedian(values, 0, 0); 230: } 231: } 232: else { 233: result = Statistics.calculateMedian(values, count / 2, 234: count - 1); 235: } 236: } 237: return result; 238: } 239: 240: }