    001    /* ===========================================================
    002     * JFreeChart : a free chart library for the Java(tm) platform
    003     * ===========================================================
    004     *
    005     * (C) Copyright 2000-2005, 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-2005,  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.1 2005/10/25 21:34:46 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     *
    044     */
    045
    046    package org.jfree.data.statistics;
    047
    048    import java.util.ArrayList;
    049    import java.util.Collections;
    050    import java.util.Iterator;
    051    import java.util.List;
    052
    053    /**
    054     * A utility class that calculates the mean, median, quartiles Q1 and Q3, plus
    055     * a list of outlier values...all from an arbitrary list of
    056     * <code>Number</code> objects.
    057     */
    058    public abstract class BoxAndWhiskerCalculator {
    059
    060        /**
    061         * Calculates the statistics required for a {@link BoxAndWhiskerItem}.
    062         * <P>
    063         * Any items in the list that are not instances of the <code>Number</code>
    064         * class are ignored. Likewise, <code>null</code> values are ignored.
    065         *
    066         * @param values  a list of numbers (a <code>null</code> list is not
    067         *                permitted).
    068         *
    069         * @return Box-and-whisker statistics.
    070         */
    071        public static BoxAndWhiskerItem calculateBoxAndWhiskerStatistics(
    072                                            List values) {
    073
    074            Collections.sort(values);
    075
    076            double mean = Statistics.calculateMean(values);
    077            double median = Statistics.calculateMedian(values, false);
    078            double q1 = calculateQ1(values);
    079            double q3 = calculateQ3(values);
    080
    081            double interQuartileRange = q3 - q1;
    082
    083            double upperOutlierThreshold = q3 + (interQuartileRange * 1.5);
    084            double lowerOutlierThreshold = q1 - (interQuartileRange * 1.5);
    085
    086            double upperFaroutThreshold = q3 + (interQuartileRange * 2.0);
    087            double lowerFaroutThreshold = q1 - (interQuartileRange * 2.0);
    088
    089            double minRegularValue = Double.POSITIVE_INFINITY;
    090            double maxRegularValue = Double.NEGATIVE_INFINITY;
    091            double minOutlier = Double.POSITIVE_INFINITY;
    092            double maxOutlier = Double.NEGATIVE_INFINITY;
    093            List outliers = new ArrayList();
    094
    095            Iterator iterator = values.iterator();
    096            while (iterator.hasNext()) {
    097                Object object = iterator.next();
    098                if (object != null && object instanceof Number) {
    099                    Number number = (Number) object;
    100                    double value = number.doubleValue();
    101                    if (value > upperOutlierThreshold) {
    102                        outliers.add(number);
    103                        if (value > maxOutlier && value <= upperFaroutThreshold) {
    104                            maxOutlier = value;
    105                        }
    106                    }
    107                    else if (value < lowerOutlierThreshold) {
    108                        outliers.add(number);
    109                        if (value < minOutlier && value >= lowerFaroutThreshold) {
    110                            minOutlier = value;
    111                        }
    112                    }
    113                    else {
    114                        if (minRegularValue == Double.NaN) {
    115                            minRegularValue = value;
    116                        }
    117                        else {
    118                            minRegularValue = Math.min(minRegularValue, value);
    119                        }
    120                        if (maxRegularValue == Double.NaN) {
    121                            maxRegularValue = value;
    122                        }
    123                        else {
    124                            maxRegularValue = Math.max(maxRegularValue, value);
    125                        }
    126                    }
    127
    128                }
    129            }
    130            minOutlier = Math.min(minOutlier, minRegularValue);
    131            maxOutlier = Math.max(maxOutlier, maxRegularValue);
    132
    133            return new BoxAndWhiskerItem(
    134                new Double(mean),
    135                new Double(median),
    136                new Double(q1),
    137                new Double(q3),
    138                new Double(minRegularValue),
    139                new Double(maxRegularValue),
    140                new Double(minOutlier),
    141                new Double(maxOutlier),
    142                outliers
    143            );
    144
    145        }
    146
    147        /**
    148         * Calculates the first quartile for a list of numbers in ascending order.
    149         *
    150         * @param values  the numbers in ascending order.
    151         *
    152         * @return The first quartile.
    153         */
    154        public static double calculateQ1(List values) {
    155            double result = Double.NaN;
    156            int count = values.size();
    157            if (count > 0) {
    158                if (count % 2 == 1) {
    159                    if (count > 1) {
    160                        result = Statistics.calculateMedian(values, 0, count / 2);
    161                    }
    162                    else {
    163                        result = Statistics.calculateMedian(values, 0, 0);
    164                    }
    165                }
    166                else {
    167                    result = Statistics.calculateMedian(values, 0, count / 2 - 1);
    168                }
    169
    170            }
    171            return result;
    172        }
    173
    174        /**
    175         * Calculates the third quartile for a list of numbers in ascending order.
    176         *
    177         * @param values  the list of values.
    178         *
    179         * @return The third quartile.
    180         */
    181        public static double calculateQ3(List values) {
    182            double result = Double.NaN;
    183            int count = values.size();
    184            if (count > 0) {
    185                if (count % 2 == 1) {
    186                    if (count > 1) {
    187                        result = Statistics.calculateMedian(
    188                            values, count / 2, count - 1
    189                        );
    190                    }
    191                    else {
    192                        result = Statistics.calculateMedian(values, 0, 0);
    193                    }
    194                }
    195                else {
    196                    result = Statistics.calculateMedian(
    197                        values, count / 2, count - 1
    198                    );
    199                }
    200
    201            }
    202            return result;
    203        }
    204
    205    }