content: /* ===========================================================
   * JFreeChart : a free chart library for the Java(tm) platform
   * ===========================================================
   *
   * (C) Copyright 2000-2011, by Object Refinery Limited and Contributors.
   *
   * Project Info:  http://www.jfree.org/jfreechart/index.html
   *
   * This library is free software; you can redistribute it and/or modify it
   * under the terms of the GNU Lesser General Public License as published by
   * the Free Software Foundation; either version 2.1 of the License, or
   * (at your option) any later version.
   *
   * This library is distributed in the hope that it will be useful, but
   * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
   * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
   * License for more details.
   *
   * You should have received a copy of the GNU Lesser General Public
   * License along with this library; if not, write to the Free Software
   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
   * USA.
   *
   * [Oracle and Java are registered trademarks of Oracle and/or its affiliates.
   * Other names may be trademarks of their respective owners.]
   *
   * ----------------------------
   * BoxAndWhiskerCalculator.java
   * ----------------------------
   * (C) Copyright 2003-2008,  by Object Refinery Limited and Contributors.
   *
   * Original Author:  David Gilbert (for Object Refinery Limited);
   * Contributor(s):   -;
   *
   * Changes
   * -------
   * 28-Aug-2003 : Version 1 (DG);
   * 17-Nov-2003 : Fixed bug in calculations of outliers and median (DG);
   * 10-Jan-2005 : Removed deprecated methods in preparation for 1.0.0
   *               release (DG);
   * ------------- JFREECHART 1.0.x ---------------------------------------------
   * 15-Nov-2006 : Cleaned up handling of null arguments, and null or NaN items
   *               in the list (DG);
   *
   */
  
  package org.jfree.data.statistics;
  
  import org.jfree.chart.axis.NumberComparator;
  
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.List;
  
  /**
   * A utility class that calculates the mean, median, quartiles Q1 and Q3, plus
   * a list of outlier values...all from an arbitrary list of
   * <code>Number</code> objects.
   */
  public abstract class BoxAndWhiskerCalculator {
  
      /**
       * Calculates the statistics required for a {@link BoxAndWhiskerItem}
       * from a list of <code>Number</code> objects.  Any items in the list
       * that are <code>null</code>, not an instance of <code>Number</code>, or
       * equivalent to <code>Double.NaN</code>, will be ignored.
       *
       * @param values  a list of numbers (a <code>null</code> list is not
       *                permitted).
       *
       * @return A box-and-whisker item.
       */
      public static BoxAndWhiskerItem calculateBoxAndWhiskerStatistics(
                                          List<Number> values) {
          return calculateBoxAndWhiskerStatistics(values, true);
      }
  
      /**
       * Calculates the statistics required for a {@link BoxAndWhiskerItem}
       * from a list of <code>Number</code> objects.  Any items in the list
       * that are <code>null</code>, not an instance of <code>Number</code>, or
       * equivalent to <code>Double.NaN</code>, will be ignored.
       *
       * @param values  a list of numbers (a <code>null</code> list is not
       *                permitted).
       * @param stripNullAndNaNItems  a flag that controls the handling of null
       *     and NaN items.
       *
       * @return A box-and-whisker item.
       *
       * @since 1.0.3
       */
      public static BoxAndWhiskerItem calculateBoxAndWhiskerStatistics(
              List<Number> values, boolean stripNullAndNaNItems) {
  
          if (values == null) {
              throw new IllegalArgumentException("Null 'values' argument.");
          }
  
          List<Number> vlist;
          if (stripNullAndNaNItems) {
              vlist = new ArrayList<Number>(values.size());
              for (Number n : values) {
                  double v = n.doubleValue();
                  if (!Double.isNaN(v)) {
                      vlist.add(n);
                  }
              }
          }
          else {
              vlist = values;
          }
          Collections.sort(vlist, new NumberComparator());
  
          double mean = Statistics.calculateMean(vlist, false);
          double median = Statistics.calculateMedian(vlist, false);
          double q1 = calculateQ1(vlist);
          double q3 = calculateQ3(vlist);
  
          double interQuartileRange = q3 - q1;
  
          double upperOutlierThreshold = q3 + (interQuartileRange * 1.5);
          double lowerOutlierThreshold = q1 - (interQuartileRange * 1.5);
  
          double upperFaroutThreshold = q3 + (interQuartileRange * 2.0);
          double lowerFaroutThreshold = q1 - (interQuartileRange * 2.0);
  
          double minRegularValue = Double.POSITIVE_INFINITY;
          double maxRegularValue = Double.NEGATIVE_INFINITY;
          double minOutlier = Double.POSITIVE_INFINITY;
          double maxOutlier = Double.NEGATIVE_INFINITY;
          List<Number> outliers = new ArrayList<Number>();
  
          for (Number number : vlist) {
              double value = number.doubleValue();
              if (value > upperOutlierThreshold) {
                  outliers.add(number);
                  if (value > maxOutlier && value <= upperFaroutThreshold) {
                      maxOutlier = value;
                  }
              } else if (value < lowerOutlierThreshold) {
                  outliers.add(number);
                  if (value < minOutlier && value >= lowerFaroutThreshold) {
                      minOutlier = value;
                  }
              } else {
                  minRegularValue = Math.min(minRegularValue, value);
                  maxRegularValue = Math.max(maxRegularValue, value);
              }
              minOutlier = Math.min(minOutlier, minRegularValue);
              maxOutlier = Math.max(maxOutlier, maxRegularValue);
          }
  
          return new BoxAndWhiskerItem(mean, median,
                  q1, q3, minRegularValue,
                  maxRegularValue, minOutlier,
                  maxOutlier, outliers);
  
      }
  
      /**
       * Calculates the first quartile for a list of numbers in ascending order.
       * If the items in the list are not in ascending order, the result is
       * unspecified.  If the list contains items that are <code>null</code>, not
       * an instance of <code>Number</code>, or equivalent to
       * <code>Double.NaN</code>, the result is unspecified.
       *
       * @param values  the numbers in ascending order (<code>null</code> not
       *     permitted).
       *
       * @return The first quartile.
       */
      public static double calculateQ1(List<Number> values) {
          if (values == null) {
              throw new IllegalArgumentException("Null 'values' argument.");
          }
  
          double result = Double.NaN;
          int count = values.size();
          if (count > 0) {
              if (count % 2 == 1) {
                  if (count > 1) {
                      result = Statistics.calculateMedian(values, 0, count / 2);
                  }
                  else {
                      result = Statistics.calculateMedian(values, 0, 0);
                  }
              }
              else {
                  result = Statistics.calculateMedian(values, 0, count / 2 - 1);
              }
  
          }
          return result;
      }
  
      /**
       * Calculates the third quartile for a list of numbers in ascending order.
       * If the items in the list are not in ascending order, the result is
       * unspecified.  If the list contains items that are <code>null</code>, not
       * an instance of <code>Number</code>, or equivalent to
       * <code>Double.NaN</code>, the result is unspecified.
       *
       * @param values  the list of values (<code>null</code> not permitted).
       *
       * @return The third quartile.
       */
      public static double calculateQ3(List<Number> values) {
          if (values == null) {
              throw new IllegalArgumentException("Null 'values' argument.");
          }
          double result = Double.NaN;
          int count = values.size();
          if (count > 0) {
              if (count % 2 == 1) {
                  if (count > 1) {
                      result = Statistics.calculateMedian(values, count / 2,
                              count - 1);
                  }
                  else {
                      result = Statistics.calculateMedian(values, 0, 0);
                  }
              }
              else {
                  result = Statistics.calculateMedian(values, count / 2,
                          count - 1);
              }
          }
          return result;
      }
  
  }
  