/*
Copyright 2008-2010 Gephi
Authors : Eduardo Ramos <eduramiba@gmail.com>
Website : http://www.gephi.org
This file is part of Gephi.
Gephi is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
Gephi 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Gephi. If not, see <http://www.gnu.org/licenses/>.
*/
package org.gephi.utils;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Arrays;
import java.util.Collection;
/**
* Class with some statistics methods for calculating values such as the average, median, sum, max and min of a list of numbers.
* @author Eduardo Ramos <eduramiba@gmail.com>
*/
public class StatisticsUtils {
/**
* <p>Get average calculation of various numbers as a BigDecimal</p>
* <p>Null values will not be counted.</p>
* @param numbers Numbers to calculate average
* @return Average as a BigDecimal
*/
public static BigDecimal average(Number[] numbers) {
if (numbers == null || numbers.length == 0) {
return null;
}
BigDecimal sum = new BigDecimal(0);
int numbersCount = 0;
for (Number number : numbers) {
if (number != null) {
sum = sum.add(new BigDecimal(number.toString()));
++numbersCount;
}
}
BigDecimal result;
try {
result = sum.divide(new BigDecimal(numbersCount));
} catch (ArithmeticException ex) {
result = sum.divide(new BigDecimal(numbersCount), 10, RoundingMode.HALF_EVEN);//Maximum of 10 decimal digits to avoid periodic number exception.
}
return result;
}
/**
* <p>Get average calculation of various numbers as a BigDecimal</p>
* <p>Null values will not be counted.</p>
* @param numbers Numbers to calculate average
* @return Average as a BigDecimal
*/
public static BigDecimal average(Collection<Number> numbers) {
return average(numbers.toArray(new Number[0]));
}
/**
* <p>Calculate median of various numbers as a BigDecimal.</p>
* <p>The elements can't be null.</p>
* <p>The elements don't need to be sorted.</p>
* @param numbers Not null numbers to calculate median
* @return Median as a BigDecimal
*/
public static BigDecimal median(Number[] numbers) {
if (numbers == null || numbers.length == 0) {
return null;
}
BigDecimal[] bigDecimalNumbers = numbersArrayToSortedBigDecimalArray(numbers);
return median(bigDecimalNumbers);
}
/**
* <p>Calculate median of various numbers as a BigDecimal.</p>
* <p>The elements can't be null.</p>
* <p>The elements don't need to be sorted.</p>
* @param numbers Not null numbers to calculate median
* @return Median as a BigDecimal
*/
public static BigDecimal median(Collection<Number> numbers) {
return median(numbers.toArray(new Number[0]));
}
/**
* <p>Calculate first quartile (Q1) of various numbers as a BigDecimal.</p>
* <p>The elements can't be null.</p>
* <p>The elements don't need to be sorted.</p>
* @param numbers Not null numbers to calculate Q1
* @return Q1 as a BigDecimal
*/
public static BigDecimal quartile1(Number[] numbers) {
if (numbers == null || numbers.length == 0) {
return null;
}
BigDecimal[] bigDecimalNumbers = numbersArrayToSortedBigDecimalArray(numbers);
return quartile1(bigDecimalNumbers);
}
/**
* <p>Calculate first quartile (Q1) of various numbers as a BigDecimal.</p>
* <p>The elements can't be null.</p>
* <p>The elements don't need to be sorted.</p>
* @param numbers Not null numbers to calculate Q1
* @return Q1 as a BigDecimal
*/
public static BigDecimal quartile1(Collection<Number> numbers) {
return quartile1(numbers.toArray(new Number[0]));
}
/**
* <p>Calculate third quartile (Q3) of various numbers as a BigDecimal.</p>
* <p>The elements can't be null.</p>
* <p>The elements don't need to be sorted.</p>
* @param numbers Not null numbers to calculate Q3
* @return Q3 as a BigDecimal
*/
public static BigDecimal quartile3(Number[] numbers) {
if (numbers == null || numbers.length == 0) {
return null;
}
BigDecimal[] bigDecimalNumbers = numbersArrayToSortedBigDecimalArray(numbers);
return quartile3(bigDecimalNumbers);
}
/**
* <p>Calculate third quartile (Q3) of various numbers as a BigDecimal.</p>
* <p>The elements can't be null.</p>
* <p>The elements don't need to be sorted.</p>
* @param numbers Not null numbers to calculate Q3
* @return Q3 as a BigDecimal
*/
public static BigDecimal quartile3(Collection<Number> numbers) {
return quartile3(numbers.toArray(new Number[0]));
}
/**
* <p>Get sum of various numbers as a BigDecimal</p>
* <p>Null values will not be counted.</p>
* @param numbers Numbers to calculate sum
* @return Sum as a BigDecimal
*/
public static BigDecimal sum(Number[] numbers) {
if (numbers == null || numbers.length == 0) {
return null;
}
BigDecimal sum = new BigDecimal(0);
for (Number number : numbers) {
if (number != null) {
sum = sum.add(new BigDecimal(number.toString()));
}
}
return sum;
}
/**
* <p>Get sum of various numbers as a BigDecimal</p>
* <p>Null values will not be counted.</p>
* @param numbers Numbers to calculate sum
* @return Sum as a BigDecimal
*/
public static BigDecimal sum(Collection<Number> numbers) {
return sum(numbers.toArray(new Number[0]));
}
/**
* <p>Get the minimum value of an array of Number elements as a BigDecimal.</p>
* <p>The elements can't be null.</p>
* <p>The elements don't need to be sorted.</p>
* @param numbers Numbers to get min
* @return Minimum value as a BigDecimal
*/
public static BigDecimal minValue(Number[] numbers) {
if (numbers == null || numbers.length == 0) {
return null;
}
BigDecimal[] bigDecimalNumbers = numbersArrayToSortedBigDecimalArray(numbers);
return bigDecimalNumbers[0];
}
/**
* <p>Get the minimum value of a collection of Number elements as a BigDecimal.</p>
* <p>The elements can't be null.</p>
* <p>The elements don't need to be sorted.</p>
* @param numbers Numbers to get min
* @return Minimum value as a BigDecimal
*/
public static BigDecimal minValue(Collection<Number> numbers) {
return minValue(numbers.toArray(new Number[0]));
}
/**
* <p>Get the maximum value of an array of Number elements as a BigDecimal.</p>
* <p>The elements can't be null.</p>
* <p>The elements don't need to be sorted.</p>
* @param numbers Numbers to get max
* @return Maximum value as a BigDecimal
*/
public static BigDecimal maxValue(Number[] numbers) {
if (numbers == null || numbers.length == 0) {
return null;
}
BigDecimal[] bigDecimalNumbers = numbersArrayToSortedBigDecimalArray(numbers);
return bigDecimalNumbers[bigDecimalNumbers.length - 1];
}
/**
* <p>Get the maximum value of a collection of Number elements as a BigDecimal.</p>
* <p>The elements can't be null.</p>
* <p>The elements don't need to be sorted.</p>
* @param numbers Numbers to get max
* @return Maximum value as a BigDecimal
*/
public static BigDecimal maxValue(Collection<Number> numbers) {
return maxValue(numbers.toArray(new Number[0]));
}
/**
* <p>Calculates all statistics and returns them in a <code>BigDecimal</code> numbers array.</p>
* <p>Using this will be faster than calling all statistics separately.</p>
* <p>Returns an array of <b>length=8</b> of <code>BigDecimal</code> numbers with the results in the following order:
* <ol>
* <li>average</li>
* <li>first quartile (Q1)</li>
* <li>median</li>
* <li>third quartile (Q3)</li>
* <li>interquartile range (IQR)</li>
* <li>sum</li>
* <li>minimumValue</li>
* <li>maximumValue</li>
* </ol>
* </p>
* <p>The elements can't be null.</p>
* <p>The elements don't need to be sorted.</p>
* @param numbers Numbers to get all statistics
* @return Array with all statisctis
*/
public static BigDecimal[] getAllStatistics(Number[] numbers) {
if (numbers == null || numbers.length == 0) {
return null;
}
BigDecimal[] bigDecimalNumbers = numbersArrayToSortedBigDecimalArray(numbers);
BigDecimal sum = sum(bigDecimalNumbers);
BigDecimal[] statistics = new BigDecimal[8];
statistics[0] = average(sum, new BigDecimal(bigDecimalNumbers.length));
statistics[1] = quartile1(bigDecimalNumbers);
statistics[2] = median(bigDecimalNumbers);
statistics[3] = quartile3(bigDecimalNumbers);
statistics[4] = statistics[3].subtract(statistics[1]);
statistics[5] = sum;
statistics[6] = minValue(bigDecimalNumbers);
statistics[7] = maxValue(bigDecimalNumbers);
return statistics;
}
/**
* <p>Calculates all statistics and returns them in a <code>BigDecimal</code> numbers array.</p>
* <p>Using this will be faster than calling all statistics separately.</p>
* <p>Returns an array of <b>length=8</b> of <code>BigDecimal</code> numbers with the results in the following order:
* <ol>
* <li>average</li>
* <li>first quartile (Q1)</li>
* <li>median</li>
* <li>third quartile (Q3)</li>
* <li>interquartile range (IQR)</li>
* <li>sum</li>
* <li>minimumValue</li>
* <li>maximumValue</li>
* </ol>
* </p>
* <p>The elements can't be null.</p>
* <p>The elements don't need to be sorted.</p>
* @param numbers Numbers to get all statistics
* @return Array with all statisctis
*/
public static BigDecimal[] getAllStatistics(Collection<Number> numbers) {
return getAllStatistics(numbers.toArray(new Number[0]));
}
/**
* <p>Takes an array of numbers of any type combination and returns
* an array with their BigDecimal equivalent numbers.</p>
* @return BigDecimal array
*/
public static BigDecimal[] numbersArrayToSortedBigDecimalArray(Number[] numbers) {
if (numbers == null) {
return null;
}
BigDecimal[] result = new BigDecimal[numbers.length];
Number number;
for (int i = 0; i < result.length; i++) {
number = numbers[i];
if (number != null) {
result[i] = new BigDecimal(number.toString());
}
}
Arrays.sort(result);
return result;
}
/***********Private methods:***********/
//Next methods need the number array already converted to BigDecimal and sorted.
//Used for faster calculating of all statistics, not repeating the sorting and conversion to BigDecimal array.
private static BigDecimal average(final BigDecimal sum, final BigDecimal numbersCount) {
BigDecimal result;
try {
result = sum.divide(numbersCount);
} catch (ArithmeticException ex) {
result = sum.divide(numbersCount, 10, RoundingMode.HALF_EVEN);//Maximum of 10 decimal digits to avoid periodic number exception.
}
return result;
}
private static BigDecimal median(final BigDecimal[] bigDecimalNumbers) {
return median(bigDecimalNumbers, 0, bigDecimalNumbers.length);
}
private static BigDecimal median(final BigDecimal[] bigDecimalNumbers, final int start, final int end) {
final int size = end - start;
if (size % 2 == 1) {
return bigDecimalNumbers[start + (size + 1) / 2 - 1];
} else {
BigDecimal result = bigDecimalNumbers[start + (size) / 2 - 1];
result = result.add(bigDecimalNumbers[start + (size) / 2]);
return result.divide(BigDecimal.valueOf(2));
}
}
private static BigDecimal quartile1(BigDecimal[] bigDecimalNumbers) {
final int size = bigDecimalNumbers.length;
if (size % 2 == 1) {
if (size > 1) {
return median(bigDecimalNumbers, 0, size / 2 + 1);
} else {
return median(bigDecimalNumbers, 0, 1);
}
} else {
return median(bigDecimalNumbers, 0, size / 2);
}
}
private static BigDecimal quartile3(BigDecimal[] bigDecimalNumbers) {
final int size = bigDecimalNumbers.length;
if (size % 2 == 1) {
if (size > 1) {
return median(bigDecimalNumbers, size / 2, size);
} else {
return median(bigDecimalNumbers, 0, 1);
}
} else {
return median(bigDecimalNumbers, size / 2, size);
}
}
private static BigDecimal sum(BigDecimal[] bigDecimalNumbers) {
BigDecimal sum = new BigDecimal(0);
for (BigDecimal number : bigDecimalNumbers) {
if (number != null) {
sum = sum.add(number);
}
}
return sum;
}
private static BigDecimal minValue(BigDecimal[] bigDecimalNumbers) {
return bigDecimalNumbers[0];
}
private static BigDecimal maxValue(BigDecimal[] bigDecimalNumbers) {
return bigDecimalNumbers[bigDecimalNumbers.length - 1];
}
}