001 /* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2006, 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-2006, 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.2 2006/11/16 11:19:47 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 * ------------- JFREECHART 1.0.x ---------------------------------------------
044 * 15-Nov-2006 : Cleaned up handling of null arguments, and null or NaN items
045 * in the list (DG);
046 *
047 */
048
049 package org.jfree.data.statistics;
050
051 import java.util.ArrayList;
052 import java.util.Collections;
053 import java.util.Iterator;
054 import java.util.List;
055
056 /**
057 * A utility class that calculates the mean, median, quartiles Q1 and Q3, plus
058 * a list of outlier values...all from an arbitrary list of
059 * <code>Number</code> objects.
060 */
061 public abstract class BoxAndWhiskerCalculator {
062
063 /**
064 * Calculates the statistics required for a {@link BoxAndWhiskerItem}
065 * from a list of <code>Number</code> objects. Any items in the list
066 * that are <code>null</code>, not an instance of <code>Number</code>, or
067 * equivalent to <code>Double.NaN</code>, will be ignored.
068 *
069 * @param values a list of numbers (a <code>null</code> list is not
070 * permitted).
071 *
072 * @return A box-and-whisker item.
073 */
074 public static BoxAndWhiskerItem calculateBoxAndWhiskerStatistics(
075 List values) {
076 return calculateBoxAndWhiskerStatistics(values, true);
077 }
078
079 /**
080 * Calculates the statistics required for a {@link BoxAndWhiskerItem}
081 * from a list of <code>Number</code> objects. Any items in the list
082 * that are <code>null</code>, not an instance of <code>Number</code>, or
083 * equivalent to <code>Double.NaN</code>, will be ignored.
084 *
085 * @param values a list of numbers (a <code>null</code> list is not
086 * permitted).
087 * @param stripNullAndNaNItems a flag that controls the handling of null
088 * and NaN items.
089 *
090 * @return A box-and-whisker item.
091 *
092 * @since 1.0.3
093 */
094 public static BoxAndWhiskerItem calculateBoxAndWhiskerStatistics(
095 List values, boolean stripNullAndNaNItems) {
096
097 if (values == null) {
098 throw new IllegalArgumentException("Null 'values' argument.");
099 }
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 }