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