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(
099: "Null 'values' argument.");
100: }
101:
102: List vlist;
103: if (stripNullAndNaNItems) {
104: vlist = new ArrayList(values.size());
105: Iterator iterator = values.listIterator();
106: while (iterator.hasNext()) {
107: Object obj = iterator.next();
108: if (obj instanceof Number) {
109: Number n = (Number) obj;
110: double v = n.doubleValue();
111: if (!Double.isNaN(v)) {
112: vlist.add(n);
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: } else if (value < lowerOutlierThreshold) {
150: outliers.add(number);
151: if (value < minOutlier && value >= lowerFaroutThreshold) {
152: minOutlier = value;
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(
163: median), new Double(q1), new Double(q3), new Double(
164: minRegularValue), new Double(maxRegularValue),
165: new Double(minOutlier), new Double(maxOutlier),
166: outliers);
167:
168: }
169:
170: /**
171: * Calculates the first quartile for a list of numbers in ascending order.
172: * If the items in the list are not in ascending order, the result is
173: * unspecified. If the list contains items that are <code>null</code>, not
174: * an instance of <code>Number</code>, or equivalent to
175: * <code>Double.NaN</code>, the result is unspecified.
176: *
177: * @param values the numbers in ascending order (<code>null</code> not
178: * permitted).
179: *
180: * @return The first quartile.
181: */
182: public static double calculateQ1(List values) {
183: if (values == null) {
184: throw new IllegalArgumentException(
185: "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,
194: count / 2);
195: } else {
196: result = Statistics.calculateMedian(values, 0, 0);
197: }
198: } else {
199: result = Statistics.calculateMedian(values, 0,
200: 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(
221: "Null 'values' argument.");
222: }
223: double result = Double.NaN;
224: int count = values.size();
225: if (count > 0) {
226: if (count % 2 == 1) {
227: if (count > 1) {
228: result = Statistics.calculateMedian(values,
229: count / 2, count - 1);
230: } else {
231: result = Statistics.calculateMedian(values, 0, 0);
232: }
233: } else {
234: result = Statistics.calculateMedian(values, count / 2,
235: count - 1);
236: }
237: }
238: return result;
239: }
240:
241: }
|