Source for org.jfree.data.statistics.BoxAndWhiskerCalculator

   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: }