001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-present, by David Gilbert 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 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 025 * Other names may be trademarks of their respective owners.] 026 * 027 * ---------------------------- 028 * BoxAndWhiskerCalculator.java 029 * ---------------------------- 030 * (C) Copyright 2003-present, by David Gilbert and Contributors. 031 * 032 * Original Author: David Gilbert; 033 * Contributor(s): -; 034 * 035 */ 036 037package org.jfree.data.statistics; 038 039import java.util.ArrayList; 040import java.util.Collections; 041import java.util.Iterator; 042import java.util.List; 043import org.jfree.chart.util.Args; 044 045/** 046 * A utility class that calculates the mean, median, quartiles Q1 and Q3, plus 047 * a list of outlier values...all from an arbitrary list of 048 * {@code Number} objects. 049 */ 050public abstract class BoxAndWhiskerCalculator { 051 052 /** 053 * Calculates the statistics required for a {@link BoxAndWhiskerItem} 054 * from a list of {@code Number} objects. Any items in the list 055 * that are {@code null}, not an instance of {@code Number}, or 056 * equivalent to {@code Double.NaN}, will be ignored. 057 * 058 * @param values a list of numbers (a {@code null} list is not 059 * permitted). 060 * 061 * @return A box-and-whisker item. 062 */ 063 public static BoxAndWhiskerItem calculateBoxAndWhiskerStatistics( 064 List values) { 065 return calculateBoxAndWhiskerStatistics(values, true); 066 } 067 068 /** 069 * Calculates the statistics required for a {@link BoxAndWhiskerItem} 070 * from a list of {@code Number} objects. Any items in the list 071 * that are {@code null}, not an instance of {@code Number}, or 072 * equivalent to {@code Double.NaN}, will be ignored. 073 * 074 * @param values a list of numbers (a {@code null} list is not 075 * permitted). 076 * @param stripNullAndNaNItems a flag that controls the handling of null 077 * and NaN items. 078 * 079 * @return A box-and-whisker item. 080 */ 081 public static BoxAndWhiskerItem calculateBoxAndWhiskerStatistics( 082 List values, boolean stripNullAndNaNItems) { 083 084 Args.nullNotPermitted(values, "values"); 085 086 List vlist; 087 if (stripNullAndNaNItems) { 088 vlist = new ArrayList(values.size()); 089 Iterator iterator = values.listIterator(); 090 while (iterator.hasNext()) { 091 Object obj = iterator.next(); 092 if (obj instanceof Number) { 093 Number n = (Number) obj; 094 double v = n.doubleValue(); 095 if (!Double.isNaN(v)) { 096 vlist.add(n); 097 } 098 } 099 } 100 } 101 else { 102 vlist = values; 103 } 104 Collections.sort(vlist); 105 106 double mean = Statistics.calculateMean(vlist, false); 107 double median = Statistics.calculateMedian(vlist, false); 108 double q1 = calculateQ1(vlist); 109 double q3 = calculateQ3(vlist); 110 111 double interQuartileRange = q3 - q1; 112 113 double upperOutlierThreshold = q3 + (interQuartileRange * 1.5); 114 double lowerOutlierThreshold = q1 - (interQuartileRange * 1.5); 115 116 double upperFaroutThreshold = q3 + (interQuartileRange * 2.0); 117 double lowerFaroutThreshold = q1 - (interQuartileRange * 2.0); 118 119 double minRegularValue = Double.POSITIVE_INFINITY; 120 double maxRegularValue = Double.NEGATIVE_INFINITY; 121 double minOutlier = Double.POSITIVE_INFINITY; 122 double maxOutlier = Double.NEGATIVE_INFINITY; 123 List outliers = new ArrayList(); 124 125 Iterator iterator = vlist.iterator(); 126 while (iterator.hasNext()) { 127 Number number = (Number) iterator.next(); 128 double value = number.doubleValue(); 129 if (value > upperOutlierThreshold) { 130 outliers.add(number); 131 if (value > maxOutlier && value <= upperFaroutThreshold) { 132 maxOutlier = value; 133 } 134 } 135 else if (value < lowerOutlierThreshold) { 136 outliers.add(number); 137 if (value < minOutlier && value >= lowerFaroutThreshold) { 138 minOutlier = value; 139 } 140 } 141 else { 142 minRegularValue = Math.min(minRegularValue, value); 143 maxRegularValue = Math.max(maxRegularValue, value); 144 } 145 minOutlier = Math.min(minOutlier, minRegularValue); 146 maxOutlier = Math.max(maxOutlier, maxRegularValue); 147 } 148 149 return new BoxAndWhiskerItem(mean, median, q1, q3, minRegularValue, 150 maxRegularValue, minOutlier, maxOutlier, outliers); 151 152 } 153 154 /** 155 * Calculates the first quartile for a list of numbers in ascending order. 156 * If the items in the list are not in ascending order, the result is 157 * unspecified. If the list contains items that are {@code null}, not 158 * an instance of {@code Number}, or equivalent to 159 * {@code Double.NaN}, the result is unspecified. 160 * 161 * @param values the numbers in ascending order ({@code null} not 162 * permitted). 163 * 164 * @return The first quartile. 165 */ 166 public static double calculateQ1(List values) { 167 Args.nullNotPermitted(values, "values"); 168 169 double result = Double.NaN; 170 int count = values.size(); 171 if (count > 0) { 172 if (count % 2 == 1) { 173 if (count > 1) { 174 result = Statistics.calculateMedian(values, 0, count / 2); 175 } 176 else { 177 result = Statistics.calculateMedian(values, 0, 0); 178 } 179 } 180 else { 181 result = Statistics.calculateMedian(values, 0, count / 2 - 1); 182 } 183 184 } 185 return result; 186 } 187 188 /** 189 * Calculates the third quartile for a list of numbers in ascending order. 190 * If the items in the list are not in ascending order, the result is 191 * unspecified. If the list contains items that are {@code null}, not 192 * an instance of {@code Number}, or equivalent to 193 * {@code Double.NaN}, the result is unspecified. 194 * 195 * @param values the list of values ({@code null} not permitted). 196 * 197 * @return The third quartile. 198 */ 199 public static double calculateQ3(List values) { 200 Args.nullNotPermitted(values, "values"); 201 double result = Double.NaN; 202 int count = values.size(); 203 if (count > 0) { 204 if (count % 2 == 1) { 205 if (count > 1) { 206 result = Statistics.calculateMedian(values, count / 2, 207 count - 1); 208 } 209 else { 210 result = Statistics.calculateMedian(values, 0, 0); 211 } 212 } 213 else { 214 result = Statistics.calculateMedian(values, count / 2, 215 count - 1); 216 } 217 } 218 return result; 219 } 220 221}