1. /* ===========================================================
  2. * JFreeChart : a free chart library for the Java(tm) platform
  3. * ===========================================================
  4. *
  5. * (C) Copyright 2000-2005, 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 License
  20. * along with this library; if not, write to the Free Software Foundation,
  21. * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
  22. *
  23. * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
  24. * in the United States and other countries.]
  25. *
  26. * ----------------------------
  27. * BoxAndWhiskerCalculator.java
  28. * ----------------------------
  29. * (C) Copyright 2003-2005, by Object Refinery Limited and Contributors.
  30. *
  31. * Original Author: David Gilbert (for Object Refinery Limited);
  32. * Contributor(s): -;
  33. *
  34. * $Id: BoxAndWhiskerCalculator.java,v 1.3 2005/02/09 13:51:57 mungady Exp $
  35. *
  36. * Changes
  37. * -------
  38. * 28-Aug-2003 : Version 1 (DG);
  39. * 17-Nov-2003 : Fixed bug in calculations of outliers and median (DG);
  40. * 10-Jan-2005 : Removed deprecated methods in preparation for 1.0.0
  41. * release (DG);
  42. *
  43. */
  44. package org.jfree.data.statistics;
  45. import java.util.ArrayList;
  46. import java.util.Collections;
  47. import java.util.Iterator;
  48. import java.util.List;
  49. /**
  50. * A utility class that calculates the mean, median, quartiles Q1 and Q3, plus
  51. * a list of outlier values...all from an arbitrary list of
  52. * <code>Number</code> objects.
  53. */
  54. public abstract class BoxAndWhiskerCalculator {
  55. /**
  56. * Calculates the statistics required for a {@link BoxAndWhiskerItem}.
  57. * <P>
  58. * Any items in the list that are not instances of the <code>Number</code>
  59. * class are ignored. Likewise, <code>null</code> values are ignored.
  60. *
  61. * @param values a list of numbers (a <code>null</code> list is not
  62. * permitted).
  63. *
  64. * @return Box-and-whisker statistics.
  65. */
  66. public static BoxAndWhiskerItem calculateBoxAndWhiskerStatistics(
  67. List values) {
  68. Collections.sort(values);
  69. double mean = Statistics.calculateMean(values);
  70. double median = Statistics.calculateMedian(values, false);
  71. double q1 = calculateQ1(values);
  72. double q3 = calculateQ3(values);
  73. double interQuartileRange = q3 - q1;
  74. double upperOutlierThreshold = q3 + (interQuartileRange * 1.5);
  75. double lowerOutlierThreshold = q1 - (interQuartileRange * 1.5);
  76. double upperFaroutThreshold = q3 + (interQuartileRange * 2.0);
  77. double lowerFaroutThreshold = q1 - (interQuartileRange * 2.0);
  78. double minRegularValue = Double.POSITIVE_INFINITY;
  79. double maxRegularValue = Double.NEGATIVE_INFINITY;
  80. double minOutlier = Double.POSITIVE_INFINITY;
  81. double maxOutlier = Double.NEGATIVE_INFINITY;
  82. List outliers = new ArrayList();
  83. Iterator iterator = values.iterator();
  84. while (iterator.hasNext()) {
  85. Object object = iterator.next();
  86. if (object != null && object instanceof Number) {
  87. Number number = (Number) object;
  88. double value = number.doubleValue();
  89. if (value > upperOutlierThreshold) {
  90. outliers.add(number);
  91. if (value > maxOutlier && value <= upperFaroutThreshold) {
  92. maxOutlier = value;
  93. }
  94. }
  95. else if (value < lowerOutlierThreshold) {
  96. outliers.add(number);
  97. if (value < minOutlier && value >= lowerFaroutThreshold) {
  98. minOutlier = value;
  99. }
  100. }
  101. else {
  102. if (minRegularValue == Double.NaN) {
  103. minRegularValue = value;
  104. }
  105. else {
  106. minRegularValue = Math.min(minRegularValue, value);
  107. }
  108. if (maxRegularValue == Double.NaN) {
  109. maxRegularValue = value;
  110. }
  111. else {
  112. maxRegularValue = Math.max(maxRegularValue, value);
  113. }
  114. }
  115. }
  116. }
  117. minOutlier = Math.min(minOutlier, minRegularValue);
  118. maxOutlier = Math.max(maxOutlier, maxRegularValue);
  119. return new BoxAndWhiskerItem(
  120. new Double(mean),
  121. new Double(median),
  122. new Double(q1),
  123. new Double(q3),
  124. new Double(minRegularValue),
  125. new Double(maxRegularValue),
  126. new Double(minOutlier),
  127. new Double(maxOutlier),
  128. outliers
  129. );
  130. }
  131. /**
  132. * Calculates the first quartile for a list of numbers in ascending order.
  133. *
  134. * @param values the numbers in ascending order.
  135. *
  136. * @return The first quartile.
  137. */
  138. public static double calculateQ1(List values) {
  139. double result = Double.NaN;
  140. int count = values.size();
  141. if (count > 0) {
  142. if (count % 2 == 1) {
  143. if (count > 1) {
  144. result = Statistics.calculateMedian(values, 0, count / 2);
  145. }
  146. else {
  147. result = Statistics.calculateMedian(values, 0, 0);
  148. }
  149. }
  150. else {
  151. result = Statistics.calculateMedian(values, 0, count / 2 - 1);
  152. }
  153. }
  154. return result;
  155. }
  156. /**
  157. * Calculates the third quartile for a list of numbers in ascending order.
  158. *
  159. * @param values the list of values.
  160. *
  161. * @return The third quartile.
  162. */
  163. public static double calculateQ3(List values) {
  164. double result = Double.NaN;
  165. int count = values.size();
  166. if (count > 0) {
  167. if (count % 2 == 1) {
  168. if (count > 1) {
  169. result = Statistics.calculateMedian(
  170. values, count / 2, count - 1
  171. );
  172. }
  173. else {
  174. result = Statistics.calculateMedian(values, 0, 0);
  175. }
  176. }
  177. else {
  178. result = Statistics.calculateMedian(
  179. values, count / 2, count - 1
  180. );
  181. }
  182. }
  183. return result;
  184. }
  185. }