001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors. 006 * 007 * Project Info: http://www.jfree.org/jfreechart/index.html 008 * 009 * This library is free software; you can redistribute it and/or modify it 010 * under the terms of the GNU Lesser General Public License as published by 011 * the Free Software Foundation; either version 2.1 of the License, or 012 * (at your option) any later version. 013 * 014 * This library is distributed in the hope that it will be useful, but 015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 017 * License for more details. 018 * 019 * You should have received a copy of the GNU Lesser General Public 020 * License along with this library; if not, write to the Free Software 021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 022 * USA. 023 * 024 * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 025 * in the United States and other countries.] 026 * 027 * ---------------------------- 028 * BoxAndWhiskerCalculator.java 029 * ---------------------------- 030 * (C) Copyright 2003-2005, by Object Refinery Limited and Contributors. 031 * 032 * Original Author: David Gilbert (for Object Refinery Limited); 033 * Contributor(s): -; 034 * 035 * $Id: BoxAndWhiskerCalculator.java,v 1.3.2.1 2005/10/25 21:34:46 mungady Exp $ 036 * 037 * Changes 038 * ------- 039 * 28-Aug-2003 : Version 1 (DG); 040 * 17-Nov-2003 : Fixed bug in calculations of outliers and median (DG); 041 * 10-Jan-2005 : Removed deprecated methods in preparation for 1.0.0 042 * release (DG); 043 * 044 */ 045 046 package org.jfree.data.statistics; 047 048 import java.util.ArrayList; 049 import java.util.Collections; 050 import java.util.Iterator; 051 import java.util.List; 052 053 /** 054 * A utility class that calculates the mean, median, quartiles Q1 and Q3, plus 055 * a list of outlier values...all from an arbitrary list of 056 * <code>Number</code> objects. 057 */ 058 public abstract class BoxAndWhiskerCalculator { 059 060 /** 061 * Calculates the statistics required for a {@link BoxAndWhiskerItem}. 062 * <P> 063 * Any items in the list that are not instances of the <code>Number</code> 064 * class are ignored. Likewise, <code>null</code> values are ignored. 065 * 066 * @param values a list of numbers (a <code>null</code> list is not 067 * permitted). 068 * 069 * @return Box-and-whisker statistics. 070 */ 071 public static BoxAndWhiskerItem calculateBoxAndWhiskerStatistics( 072 List values) { 073 074 Collections.sort(values); 075 076 double mean = Statistics.calculateMean(values); 077 double median = Statistics.calculateMedian(values, false); 078 double q1 = calculateQ1(values); 079 double q3 = calculateQ3(values); 080 081 double interQuartileRange = q3 - q1; 082 083 double upperOutlierThreshold = q3 + (interQuartileRange * 1.5); 084 double lowerOutlierThreshold = q1 - (interQuartileRange * 1.5); 085 086 double upperFaroutThreshold = q3 + (interQuartileRange * 2.0); 087 double lowerFaroutThreshold = q1 - (interQuartileRange * 2.0); 088 089 double minRegularValue = Double.POSITIVE_INFINITY; 090 double maxRegularValue = Double.NEGATIVE_INFINITY; 091 double minOutlier = Double.POSITIVE_INFINITY; 092 double maxOutlier = Double.NEGATIVE_INFINITY; 093 List outliers = new ArrayList(); 094 095 Iterator iterator = values.iterator(); 096 while (iterator.hasNext()) { 097 Object object = iterator.next(); 098 if (object != null && object instanceof Number) { 099 Number number = (Number) object; 100 double value = number.doubleValue(); 101 if (value > upperOutlierThreshold) { 102 outliers.add(number); 103 if (value > maxOutlier && value <= upperFaroutThreshold) { 104 maxOutlier = value; 105 } 106 } 107 else if (value < lowerOutlierThreshold) { 108 outliers.add(number); 109 if (value < minOutlier && value >= lowerFaroutThreshold) { 110 minOutlier = value; 111 } 112 } 113 else { 114 if (minRegularValue == Double.NaN) { 115 minRegularValue = value; 116 } 117 else { 118 minRegularValue = Math.min(minRegularValue, value); 119 } 120 if (maxRegularValue == Double.NaN) { 121 maxRegularValue = value; 122 } 123 else { 124 maxRegularValue = Math.max(maxRegularValue, value); 125 } 126 } 127 128 } 129 } 130 minOutlier = Math.min(minOutlier, minRegularValue); 131 maxOutlier = Math.max(maxOutlier, maxRegularValue); 132 133 return new BoxAndWhiskerItem( 134 new Double(mean), 135 new Double(median), 136 new Double(q1), 137 new Double(q3), 138 new Double(minRegularValue), 139 new Double(maxRegularValue), 140 new Double(minOutlier), 141 new Double(maxOutlier), 142 outliers 143 ); 144 145 } 146 147 /** 148 * Calculates the first quartile for a list of numbers in ascending order. 149 * 150 * @param values the numbers in ascending order. 151 * 152 * @return The first quartile. 153 */ 154 public static double calculateQ1(List values) { 155 double result = Double.NaN; 156 int count = values.size(); 157 if (count > 0) { 158 if (count % 2 == 1) { 159 if (count > 1) { 160 result = Statistics.calculateMedian(values, 0, count / 2); 161 } 162 else { 163 result = Statistics.calculateMedian(values, 0, 0); 164 } 165 } 166 else { 167 result = Statistics.calculateMedian(values, 0, count / 2 - 1); 168 } 169 170 } 171 return result; 172 } 173 174 /** 175 * Calculates the third quartile for a list of numbers in ascending order. 176 * 177 * @param values the list of values. 178 * 179 * @return The third quartile. 180 */ 181 public static double calculateQ3(List values) { 182 double result = Double.NaN; 183 int count = values.size(); 184 if (count > 0) { 185 if (count % 2 == 1) { 186 if (count > 1) { 187 result = Statistics.calculateMedian( 188 values, count / 2, count - 1 189 ); 190 } 191 else { 192 result = Statistics.calculateMedian(values, 0, 0); 193 } 194 } 195 else { 196 result = Statistics.calculateMedian( 197 values, count / 2, count - 1 198 ); 199 } 200 201 } 202 return result; 203 } 204 205 }