org.apache.openejb.math.stat.descriptive.rank.Percentile Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.openejb.math.stat.descriptive.rank;
import java.io.Serializable;
import java.util.Arrays;
import org.apache.openejb.math.MathRuntimeException;
import org.apache.openejb.math.stat.descriptive.AbstractUnivariateStatistic;
/**
* Provides percentile computation.
*
* There are several commonly used methods for estimating percentiles (a.k.a.
* quantiles) based on sample data. For large samples, the different methods
* agree closely, but when sample sizes are small, different methods will give
* significantly different results. The algorithm implemented here works as follows:
*
* - Let
n
be the length of the (sorted) array and
* 0 < p <= 100
be the desired percentile.
* - If
n = 1
return the unique array element (regardless of
* the value of p
); otherwise
* - Compute the estimated percentile position
*
pos = p * (n + 1) / 100
and the difference, d
* between pos
and floor(pos)
(i.e. the fractional
* part of pos
). If pos >= n
return the largest
* element in the array; otherwise
* - Let
lower
be the element in position
* floor(pos)
in the array and let upper
be the
* next element in the array. Return lower + d * (upper - lower)
*
*
*
* To compute percentiles, the data must be (totally) ordered. Input arrays
* are copied and then sorted using {@link java.util.Arrays#sort(double[])}.
* The ordering used by Arrays.sort(double[])
is the one determined
* by {@link java.lang.Double#compareTo(Double)}. This ordering makes
* Double.NaN
larger than any other value (including
* Double.POSITIVE_INFINITY
). Therefore, for example, the median
* (50th percentile) of
* {0, 1, 2, 3, 4, Double.NaN}
evaluates to 2.5.
*
* Since percentile estimation usually involves interpolation between array
* elements, arrays containing NaN
or infinite values will often
* result in NaN or infinite values returned.
*
* Note that this implementation is not synchronized. If
* multiple threads access an instance of this class concurrently, and at least
* one of the threads invokes the increment()
or
* clear()
method, it must be synchronized externally.
*
* @version $Revision: 811685 $ $Date: 2009-09-05 10:36:48 -0700 (Sat, 05 Sep 2009) $
*/
public class Percentile extends AbstractUnivariateStatistic implements Serializable {
/** Serializable version identifier */
private static final long serialVersionUID = -1231216485095130416L;
/** Determines what percentile is computed when evaluate() is activated
* with no quantile argument */
private double quantile = 0.0;
/**
* Constructs a Percentile with a default quantile
* value of 50.0.
*/
public Percentile() {
this(50.0);
}
/**
* Constructs a Percentile with the specific quantile value.
* @param p the quantile
* @throws IllegalArgumentException if p is not greater than 0 and less
* than or equal to 100
*/
public Percentile(final double p) {
setQuantile(p);
}
/**
* Copy constructor, creates a new {@code Percentile} identical
* to the {@code original}
*
* @param original the {@code Percentile} instance to copy
*/
public Percentile(Percentile original) {
copy(original, this);
}
/**
* Returns an estimate of the p
th percentile of the values
* in the values
array.
*
* Calls to this method do not modify the internal quantile
* state of this statistic.
*
*
* - Returns
Double.NaN
if values
has length
* 0
* - Returns (for any value of
p
) values[0]
* if values
has length 1
* - Throws
IllegalArgumentException
if values
* is null or p is not a valid quantile value (p must be greater than 0
* and less than or equal to 100)
*
*
* See {@link Percentile} for a description of the percentile estimation
* algorithm used.
*
* @param values input array of values
* @param p the percentile value to compute
* @return the percentile value or Double.NaN if the array is empty
* @throws IllegalArgumentException if values
is null
* or p is invalid
*/
public double evaluate(final double[] values, final double p) {
test(values, 0, 0);
return evaluate(values, 0, values.length, p);
}
/**
* Returns an estimate of the quantile
th percentile of the
* designated values in the values
array. The quantile
* estimated is determined by the quantile
property.
*
*
* - Returns
Double.NaN
if length = 0
* - Returns (for any value of
quantile
)
* values[begin]
if length = 1
* - Throws
IllegalArgumentException
if values
* is null, or start
or length
* is invalid
*
*
* See {@link Percentile} for a description of the percentile estimation
* algorithm used.
*
* @param values the input array
* @param start index of the first array element to include
* @param length the number of elements to include
* @return the percentile value
* @throws IllegalArgumentException if the parameters are not valid
*
*/
@Override
public double evaluate( final double[] values, final int start, final int length) {
return evaluate(values, start, length, quantile);
}
/**
* Returns an estimate of the p
th percentile of the values
* in the values
array, starting with the element in (0-based)
* position begin
in the array and including length
* values.
*
* Calls to this method do not modify the internal quantile
* state of this statistic.
*
*
* - Returns
Double.NaN
if length = 0
* - Returns (for any value of
p
) values[begin]
* if length = 1
* - Throws
IllegalArgumentException
if values
* is null , begin
or length
is invalid, or
* p
is not a valid quantile value (p must be greater than 0
* and less than or equal to 100)
*
*
* See {@link Percentile} for a description of the percentile estimation
* algorithm used.
*
* @param values array of input values
* @param p the percentile to compute
* @param begin the first (0-based) element to include in the computation
* @param length the number of array elements to include
* @return the percentile value
* @throws IllegalArgumentException if the parameters are not valid or the
* input array is null
*/
public double evaluate(final double[] values, final int begin,
final int length, final double p) {
test(values, begin, length);
if ((p > 100) || (p <= 0)) {
throw MathRuntimeException.createIllegalArgumentException(
"out of bounds quantile value: {0}, must be in (0, 100]", p);
}
if (length == 0) {
return Double.NaN;
}
if (length == 1) {
return values[begin]; // always return single value for n = 1
}
double n = length;
double pos = p * (n + 1) / 100;
double fpos = Math.floor(pos);
int intPos = (int) fpos;
double dif = pos - fpos;
double[] sorted = new double[length];
System.arraycopy(values, begin, sorted, 0, length);
Arrays.sort(sorted);
if (pos < 1) {
return sorted[0];
}
if (pos >= n) {
return sorted[length - 1];
}
double lower = sorted[intPos - 1];
double upper = sorted[intPos];
return lower + dif * (upper - lower);
}
/**
* Returns the value of the quantile field (determines what percentile is
* computed when evaluate() is called with no quantile argument).
*
* @return quantile
*/
public double getQuantile() {
return quantile;
}
/**
* Sets the value of the quantile field (determines what percentile is
* computed when evaluate() is called with no quantile argument).
*
* @param p a value between 0 < p <= 100
* @throws IllegalArgumentException if p is not greater than 0 and less
* than or equal to 100
*/
public void setQuantile(final double p) {
if (p <= 0 || p > 100) {
throw MathRuntimeException.createIllegalArgumentException(
"out of bounds quantile value: {0}, must be in (0, 100]", p);
}
quantile = p;
}
/**
* {@inheritDoc}
*/
@Override
public Percentile copy() {
Percentile result = new Percentile();
copy(this, result);
return result;
}
/**
* Copies source to dest.
* Neither source nor dest can be null.
*
* @param source Percentile to copy
* @param dest Percentile to copy to
* @throws NullPointerException if either source or dest is null
*/
public static void copy(Percentile source, Percentile dest) {
dest.quantile = source.quantile;
}
}