MATH-1551: Add Weighted percentile features
diff --git a/src/main/java/org/apache/commons/math4/stat/descriptive/rank/Percentile.java b/src/main/java/org/apache/commons/math4/stat/descriptive/rank/Percentile.java
index 2015d7c..6836e79 100644
--- a/src/main/java/org/apache/commons/math4/stat/descriptive/rank/Percentile.java
+++ b/src/main/java/org/apache/commons/math4/stat/descriptive/rank/Percentile.java
@@ -21,7 +21,11 @@
import java.util.BitSet;
import org.apache.commons.math4.exception.MathIllegalArgumentException;
+import org.apache.commons.math4.exception.NotANumberException;
+import org.apache.commons.math4.exception.NotPositiveException;
+import org.apache.commons.math4.exception.NotStrictlyPositiveException;
import org.apache.commons.math4.exception.NullArgumentException;
+import org.apache.commons.math4.exception.NumberIsTooLargeException;
import org.apache.commons.math4.exception.OutOfRangeException;
import org.apache.commons.math4.exception.util.LocalizedFormats;
import org.apache.commons.math4.stat.descriptive.AbstractUnivariateStatistic;
@@ -120,6 +124,8 @@
/** Cached pivots. */
private int[] cachedPivots;
+ /** Weights*/
+ private double[] weights;
/**
* Constructs a Percentile with the following defaults.
* <ul>
@@ -212,6 +218,35 @@
}
super.setData(values);
}
+ /**
+ * Set the data array.
+ * @param values data array to store
+ * @param sampleWeights corresponding positive and non-NaN weights of values
+ * @throws MathIllegalArgumentException if lengths of values and weights are not equal or values or weights is null
+ * @throws NotANumberException if any weight is nan
+ * @throws NotStrictlyPositiveException if any weight is not positive
+ */
+ public void setData(final double[] values, final double[] sampleWeights)
+ throws MathIllegalArgumentException,
+ NotANumberException,
+ NotStrictlyPositiveException {
+ if (values == null || sampleWeights == null) {
+ throw new MathIllegalArgumentException(LocalizedFormats.NULL_NOT_ALLOWED);
+ }
+
+ /** Check length */
+ if (values.length != sampleWeights.length) {
+ throw new MathIllegalArgumentException(LocalizedFormats.LENGTH,
+ values, sampleWeights);
+ }
+ cachedPivots = new int[PIVOTS_HEAP_LENGTH];
+ Arrays.fill(cachedPivots, -1);
+
+ MathArrays.checkPositive(sampleWeights);
+ MathArrays.checkNotNaN(sampleWeights);
+ super.setData(values);
+ weights = sampleWeights.clone();
+ }
/** {@inheritDoc} */
@Override
@@ -225,20 +260,89 @@
}
super.setData(values, begin, length);
}
+ /**
+ * Set the data and weights arrays. The input array is copied, not referenced.
+ * @param values data array to store
+ * @param sampleWeights corresponding positive and non-NaN weights of values
+ * @param begin the index of the first element to include
+ * @param length the number of elements to include
+ * @throws MathIllegalArgumentException if lengths of values and weights are not equal or values or weights is null
+ * @throws NotPositiveException if begin or length is not positive
+ * @throws NumberIsTooLargeException if begin + length is greater than values.length
+ * @throws NotANumberException if any weight is nan
+ * @throws NotStrictlyPositiveException if any weight is not positive
+ */
+ public void setData(final double[] values,
+ final double[] sampleWeights,
+ final int begin,
+ final int length)
+ throws MathIllegalArgumentException,
+ NotPositiveException,
+ NumberIsTooLargeException,
+ NotANumberException,
+ NotStrictlyPositiveException{
+ if (begin < 0) {
+ throw new NotPositiveException(LocalizedFormats.START_POSITION, begin);
+ }
+
+ if (length < 0) {
+ throw new NotPositiveException(LocalizedFormats.LENGTH, length);
+ }
+
+ if (begin + length > values.length) {
+ throw new NumberIsTooLargeException(LocalizedFormats.SUBARRAY_ENDS_AFTER_ARRAY_END,
+ begin + length, values.length, true);
+ }
+
+ if (values == null || sampleWeights == null) {
+ throw new MathIllegalArgumentException(LocalizedFormats.NULL_NOT_ALLOWED);
+ }
+ cachedPivots = new int[PIVOTS_HEAP_LENGTH];
+ Arrays.fill(cachedPivots, -1);
+
+ /** Check length */
+ if (values.length != sampleWeights.length) {
+ throw new MathIllegalArgumentException(LocalizedFormats.LENGTH,
+ values, sampleWeights);
+ }
+ /** Check weights > 0 */
+ MathArrays.checkPositive(sampleWeights);
+ MathArrays.checkNotNaN(sampleWeights);
+
+ super.setData(values, begin, length);
+ weights = new double[length];
+ System.arraycopy(sampleWeights, begin, weights, 0, length);
+ }
/**
* Returns the result of evaluating the statistic over the stored data.
+ * If weights have been set, it will compute weighted percentile.
* <p>
* The stored array is the one which was set by previous calls to
- * {@link #setData(double[])}
+ * {@link #setData(double[])} or {@link #setData(double[], double[] sampleWeights, int begin, int length)}
* </p>
* @param p the percentile value to compute
* @return the value of the statistic applied to the stored data
- * @throws MathIllegalArgumentException if p is not a valid quantile value
+ * @throws MathIllegalArgumentException if lengths of values and weights are not equal or values or weights is null
+ * @throws NotPositiveException if begin, length is negative
+ * @throws NotStrictlyPositiveException if any weight is not positive
+ * @throws NotANumberException if any weight is nan
+ * @throws OutOfRangeException if p is invalid
+ * @throws NumberIsTooLargeException if begin + length is greater than values.length
* (p must be greater than 0 and less than or equal to 100)
*/
- public double evaluate(final double p) throws MathIllegalArgumentException {
- return evaluate(getDataRef(), p);
+ public double evaluate(final double p)
+ throws MathIllegalArgumentException,
+ NotPositiveException,
+ NotStrictlyPositiveException,
+ NotANumberException,
+ OutOfRangeException,
+ NumberIsTooLargeException {
+ if (weights == null) {
+ return evaluate(getDataRef(), p);
+ } else {
+ return evaluate(getDataRef(), weights, p);
+ }
}
/**
@@ -271,6 +375,35 @@
MathArrays.verifyValues(values, 0, 0);
return evaluate(values, 0, values.length, p);
}
+ /**
+ * Returns an estimate of the <code>p</code>th percentile of the values
+ * in the <code>values</code> array with their weights.
+ * <p>
+ * See {@link Percentile} for a description of the percentile estimation
+ * algorithm used.</p>
+ * @param values input array of values
+ * @param sampleWeights weights of values
+ * @param p the percentile value to compute
+ * @return the weighted percentile value or Double.NaN if the array is empty
+ * @throws MathIllegalArgumentException if lengths of values and weights are not equal or values or weights is null
+ * @throws NotPositiveException if begin, length is negative
+ * @throws NotStrictlyPositiveException if any weight is not positive
+ * @throws NotANumberException if any weight is not positive
+ * @throws OutOfRangeException if p is invalid
+ * @throws NumberIsTooLargeException if begin + length is greater than values.length
+ */
+ public double evaluate(final double[] values, final double[] sampleWeights, final double p)
+ throws MathIllegalArgumentException,
+ NotPositiveException,
+ NotStrictlyPositiveException,
+ NotANumberException,
+ OutOfRangeException,
+ NumberIsTooLargeException {
+
+ MathArrays.verifyValues(values, 0, 0);
+ MathArrays.verifyValues(sampleWeights, 0, 0);
+ return evaluate(values, sampleWeights, 0, values.length, p);
+ }
/**
* Returns an estimate of the <code>quantile</code>th percentile of the
@@ -299,6 +432,36 @@
throws MathIllegalArgumentException {
return evaluate(values, start, length, quantile);
}
+ /**
+ * Returns an estimate of the weighted <code>quantile</code>th percentile of the
+ * designated values in the <code>values</code> array. The quantile
+ * estimated is determined by the <code>quantile</code> property.
+ * <p>
+ * See {@link Percentile} for a description of the percentile estimation
+ * algorithm used.</p>
+ *
+ * @param values the input array
+ * @param sampleWeights the weights of values
+ * @param start index of the first array element to include
+ * @param length the number of elements to include
+ * @return the percentile value
+ * @throws MathIllegalArgumentException if lengths of values and weights are not equal or values or weights is null
+ * @throws NotPositiveException if begin, length is negative
+ * @throws NotStrictlyPositiveException if any weight is not positive
+ * @throws NotANumberException if any weight is not positive
+ * @throws OutOfRangeException if p is invalid
+ * @throws NumberIsTooLargeException if begin + length is greater than values.length
+ */
+ public double evaluate(final double[] values, final double[] sampleWeights,
+ final int start, final int length)
+ throws MathIllegalArgumentException,
+ NotPositiveException,
+ NotStrictlyPositiveException,
+ NotANumberException,
+ OutOfRangeException,
+ NumberIsTooLargeException {
+ return evaluate(values, sampleWeights, start, length, quantile);
+ }
/**
* Returns an estimate of the <code>p</code>th percentile of the values
@@ -349,7 +512,66 @@
return work.length == 0 ? Double.NaN :
estimationType.evaluate(work, pivotsHeap, p, kthSelector);
}
+ /**
+ * Returns an estimate of the <code>p</code>th percentile of the values
+ * in the <code>values</code> array with <code>sampleWeights</code>, starting with the element in (0-based)
+ * position <code>begin</code> in the array and including <code>length</code>
+ * values.
+ * <p>
+ * See {@link Percentile} for a description of the percentile estimation
+ * algorithm used.</p>
+ *
+ * @param values array of input values
+ * @param sampleWeights positive and non-NaN weights of values
+ * @param begin the first (0-based) element to include in the computation
+ * @param length the number of array elements to include
+ * @param p percentile to compute
+ * @return the weighted percentile value
+ * @throws MathIllegalArgumentException if lengths of values and weights are not equal or values or weights is null
+ * @throws NotPositiveException if begin, length is negative
+ * @throws NotStrictlyPositiveException if any weight is not positive
+ * @throws NotANumberException if any weight is not positive
+ * @throws OutOfRangeException if p is invalid
+ * @throws NumberIsTooLargeException if begin + length is greater than values.length
+ */
+ public double evaluate(final double[] values, final double[] sampleWeights, final int begin,
+ final int length, final double p)
+ throws MathIllegalArgumentException,
+ NotPositiveException,
+ NotStrictlyPositiveException,
+ NotANumberException,
+ OutOfRangeException,
+ NumberIsTooLargeException
+ {
+ /** Check length */
+ if (values.length != sampleWeights.length) {
+ throw new MathIllegalArgumentException(LocalizedFormats.LENGTH,
+ values, sampleWeights);
+ }
+ if (values == null || sampleWeights == null) {
+ throw new MathIllegalArgumentException(LocalizedFormats.NULL_NOT_ALLOWED);
+ }
+ MathArrays.verifyValues(values, begin, length);
+ MathArrays.verifyValues(sampleWeights, begin, length);
+ MathArrays.checkPositive(sampleWeights);
+ MathArrays.checkNotNaN(sampleWeights);
+
+ if (p > 100 || p <= 0) {
+ throw new OutOfRangeException(LocalizedFormats.OUT_OF_BOUNDS_QUANTILE_VALUE, p, 0, 100);
+ }
+ if (length == 0) {
+ return Double.NaN;
+ }
+ if (length == 1) {
+ return values[begin]; // always return single value for n = 1
+ }
+
+ final double[] work = getWorkArray(values, begin, length);
+ final double[] workWeights = getWorkArray(values, sampleWeights, begin, length);
+ return work.length == 0 ? Double.NaN :
+ estimationType.evaluate(work, workWeights, p);
+ }
/**
* Returns the value of the quantile field (determines what percentile is
* computed when evaluate() is called with no quantile argument).
@@ -423,7 +645,32 @@
}
return work;
}
-
+ /**
+ * Get the work arrays of weights to operate.
+ *
+ * @param values the array of numbers
+ * @param sampleWeights the array of weights
+ * @param begin index to start reading the array
+ * @param length the length of array to be read from the begin index
+ * @return work array sliced from values in the range [begin,begin+length)
+ */
+ protected double[] getWorkArray(final double[] values, final double[] sampleWeights,
+ final int begin, final int length) {
+ final double[] work;
+ if (values == getDataRef()) {
+ work = this.weights;
+ } else {
+ switch (nanStrategy) {
+ case REMOVED:// Drop weight if the data is NaN
+ work = removeAndSliceByRef(values, sampleWeights, begin, length, Double.NaN);
+ break;
+ default: //FIXED
+ work = copyOf(sampleWeights, begin, length);
+ break;
+ }
+ }
+ return work;
+ }
/**
* Make a copy of the array for the slice defined by array part from
* [begin, begin+length)
@@ -440,6 +687,7 @@
/**
* Replace every occurrence of a given value with a replacement value in a
* copied slice of array defined by array part from [begin, begin+length).
+ *
* @param values the input array
* @param begin start index of the array to include
* @param length number of elements to include from begin
@@ -458,7 +706,6 @@
}
return temp;
}
-
/**
* Remove the occurrence of a given value in a copied slice of array
* defined by the array part from [begin, begin+length).
@@ -504,7 +751,54 @@
}
return temp;
}
-
+ /**
+ * Remove weights element if the corresponding data is equal to the given value
+ * in [begin, begin+length)
+ *
+ * @param values the input array
+ * @param sampleWeights weights of the input array
+ * @param begin start index of the array to include
+ * @param length number of elements to include from begin
+ * @param removedValue the value to be removed from the sliced array
+ * @return the copy of the sliced array after removing weights
+ */
+ private static double[] removeAndSliceByRef(final double[] values,
+ final double[] sampleWeights,
+ final int begin, final int length,
+ final double removedValue) {
+ MathArrays.verifyValues(values, begin, length);
+ final double[] temp;
+ //BitSet(length) to indicate where the removedValue is located
+ final BitSet bits = new BitSet(length);
+ for (int i = begin; i < begin+length; i++) {
+ if (Precision.equalsIncludingNaN(removedValue, values[i])) {
+ bits.set(i - begin);
+ }
+ }
+ //Check if empty then create a new copy
+ if (bits.isEmpty()) {
+ temp = copyOf(sampleWeights, begin, length); // Nothing removed, just copy
+ } else if(bits.cardinality() == length) {
+ temp = new double[0]; // All removed, just empty
+ }else { // Some removable, so new
+ temp = new double[length - bits.cardinality()];
+ int start = begin; //start index from source array (i.e sampleWeights)
+ int dest = 0; //dest index in destination array(i.e temp)
+ int nextOne = -1; //nextOne is the index of bit set of next one
+ int bitSetPtr = 0; //bitSetPtr is start index pointer of bitset
+ while ((nextOne = bits.nextSetBit(bitSetPtr)) != -1) {
+ final int lengthToCopy = nextOne - bitSetPtr;
+ System.arraycopy(sampleWeights, start, temp, dest, lengthToCopy);
+ dest += lengthToCopy;
+ start = begin + (bitSetPtr = bits.nextClearBit(nextOne));
+ }
+ //Copy any residue past start index till begin+length
+ if (start < begin + length) {
+ System.arraycopy(sampleWeights,start,temp,dest,begin + length - start);
+ }
+ }
+ return temp;
+ }
/**
* Get pivots which is either cached or a newly created one
*
@@ -690,6 +984,11 @@
Double.compare(p, maxLimit) == 0 ?
length : p * (length + 1);
}
+ @Override
+ public double evaluate(final double[] work, final double[] sampleWeights,
+ final double p) throws MathIllegalArgumentException {
+ throw new MathIllegalArgumentException(LocalizedFormats.UNSUPPORTED_OPERATION);
+ }
},
/**
* The method R_1 has the following formulae for index and estimates<br>
@@ -716,7 +1015,11 @@
final int length, final KthSelector selector) {
return super.estimate(values, pivotsHeap, FastMath.ceil(pos - 0.5), length, selector);
}
-
+ @Override
+ public double evaluate(final double[] work, final double[] sampleWeights,
+ final double p) throws MathIllegalArgumentException {
+ throw new MathIllegalArgumentException(LocalizedFormats.UNSUPPORTED_OPERATION);
+ }
},
/**
* The method R_2 has the following formulae for index and estimates<br>
@@ -752,7 +1055,11 @@
super.estimate(values, pivotsHeap,FastMath.floor(pos + 0.5), length, selector);
return (low + high) / 2;
}
-
+ @Override
+ public double evaluate(final double[] work, final double[] sampleWeights,
+ final double p) throws MathIllegalArgumentException {
+ throw new MathIllegalArgumentException(LocalizedFormats.UNSUPPORTED_OPERATION);
+ }
},
/**
* The method R_3 has the following formulae for index and estimates<br>
@@ -769,7 +1076,11 @@
return Double.compare(p, minLimit) <= 0 ?
0 : FastMath.rint(length * p);
}
-
+ @Override
+ public double evaluate(final double[] work, final double[] sampleWeights,
+ final double p) throws MathIllegalArgumentException {
+ throw new MathIllegalArgumentException(LocalizedFormats.UNSUPPORTED_OPERATION);
+ }
},
/**
* The method R_4 has the following formulae for index and estimates<br>
@@ -790,7 +1101,11 @@
return Double.compare(p, minLimit) < 0 ? 0 :
Double.compare(p, maxLimit) == 0 ? length : length * p;
}
-
+ @Override
+ public double evaluate(final double[] work, final double[] sampleWeights,
+ final double p) throws MathIllegalArgumentException {
+ throw new MathIllegalArgumentException(LocalizedFormats.UNSUPPORTED_OPERATION);
+ }
},
/**
* The method R_5 has the following formulae for index and estimates<br>
@@ -813,6 +1128,11 @@
Double.compare(p, maxLimit) >= 0 ?
length : length * p + 0.5;
}
+ @Override
+ public double evaluate(final double[] work, final double[] sampleWeights,
+ final double p) throws MathIllegalArgumentException {
+ throw new MathIllegalArgumentException(LocalizedFormats.UNSUPPORTED_OPERATION);
+ }
},
/**
* The method R_6 has the following formulae for index and estimates<br>
@@ -841,6 +1161,11 @@
Double.compare(p, maxLimit) >= 0 ?
length : (length + 1) * p;
}
+ @Override
+ public double evaluate(final double[] work, final double[] sampleWeights,
+ final double p) throws MathIllegalArgumentException {
+ throw new MathIllegalArgumentException(LocalizedFormats.UNSUPPORTED_OPERATION);
+ }
},
/**
@@ -854,6 +1179,11 @@
* &minLimit = 0 \\
* &maxLimit = 1 \\
* \end{align}\)
+ * The formula to evaluate weighted percentiles is as following.<br>
+ * \( \begin{align}
+ * &S_k = (k-1)w_k + (n-1)\sum_{i=1}^{k-1}w_i
+ * &Then find k s.t. \frac{S_k}{S_n}\leq p \leq \frac{S_{k+1}}{S_n}
+ * \end{align}\)
*/
R_7("R-7") {
@Override
@@ -865,6 +1195,30 @@
length : 1 + (length - 1) * p;
}
+ @Override
+ public double evaluate(final double[] work, final double[] sampleWeights,
+ final double p) {
+ MathArrays.sortInPlace(work, sampleWeights);
+ double[] sk = new double[work.length];
+ for(int k = 0; k < work.length; k++) {
+ sk[k] = 0;
+ for (int j = 0; j < k; j++) {
+ sk[k] += sampleWeights[j];
+ }
+ sk[k] = k * sampleWeights[k] + (work.length - 1) * sk[k];
+ }
+
+ double qsn = (p / 100) * sk[sk.length-1];
+ int k = searchSk(qsn, sk, 0, work.length - 1);
+
+ double ret;
+ if (qsn == sk[k] && k == work.length - 1) {
+ ret = work[k] - (work[k] - work[k-1]) * (1 - (qsn - sk[k]) / (sk[k] - sk[k-1]));
+ } else {
+ ret = work[k] + (work[k+1] - work[k]) * (qsn - sk[k]) / (sk[k+1] - sk[k]);
+ }
+ return ret;
+ }
},
/**
@@ -891,6 +1245,11 @@
Double.compare(p, maxLimit) >= 0 ? length :
(length + 1d / 3) * p + 1d / 3;
}
+ @Override
+ public double evaluate(final double[] work, final double[] sampleWeights,
+ final double p) throws MathIllegalArgumentException {
+ throw new MathIllegalArgumentException(LocalizedFormats.UNSUPPORTED_OPERATION);
+ }
},
/**
@@ -913,7 +1272,11 @@
Double.compare(p, maxLimit) >= 0 ? length :
(length + 0.25) * p + 3d/8;
}
-
+ @Override
+ public double evaluate(final double[] work, final double[] sampleWeights,
+ final double p) throws MathIllegalArgumentException {
+ throw new MathIllegalArgumentException(LocalizedFormats.UNSUPPORTED_OPERATION);
+ }
},
;
@@ -1015,8 +1378,46 @@
public double evaluate(final double[] work, final double p, final KthSelector selector) {
return this.evaluate(work, null, p, selector);
}
+ /**
+ * Evaluate weighted percentile by estimation rule specified in {@link EstimationType}.
+ * @param work array of numbers to be used for finding the percentile
+ * @param sampleWeights the corresponding weights of data in work
+ * @param p the p<sup>th</sup> quantile to be computed
+ * @return estimated weighted percentile
+ * @throws MathIllegalArgumentException if weighted percentile is not supported by the current estimationType
+ */
+ public abstract double evaluate(final double[] work, final double[] sampleWeights,
+ final double p);
/**
+ * Search the interval q*sn locates in.
+ * @param qsn q*sn, where n refers to the data size
+ * @param sk the cumulative weights array
+ * @param lo start position to search qsn
+ * @param hi end position to search qsn
+ * @return the index of lower bound qsn locates in
+ */
+ private static int searchSk(double qsn, double[] sk, int lo, int hi) {
+ if (sk.length == 1) {
+ return 0;
+ }
+ if (hi - lo == 1) {
+ if (qsn == sk[hi]) {
+ return hi;
+ }
+ return lo;
+ } else {
+ int mid = (lo + hi) / 2;
+ if (qsn == sk[mid]) {
+ return mid;
+ } else if (qsn > sk[mid]) {
+ return searchSk(qsn, sk, mid, hi);
+ } else {
+ return searchSk(qsn, sk, lo, mid);
+ }
+ }
+ }
+ /**
* Gets the name of the enum
*
* @return the name
diff --git a/src/test/java/org/apache/commons/math4/stat/descriptive/rank/PercentileTest.java b/src/test/java/org/apache/commons/math4/stat/descriptive/rank/PercentileTest.java
index b56c610..0bf956b 100644
--- a/src/test/java/org/apache/commons/math4/stat/descriptive/rank/PercentileTest.java
+++ b/src/test/java/org/apache/commons/math4/stat/descriptive/rank/PercentileTest.java
@@ -23,7 +23,10 @@
import org.apache.commons.statistics.distribution.NormalDistribution;
import org.apache.commons.math4.exception.MathIllegalArgumentException;
import org.apache.commons.math4.exception.NotANumberException;
+import org.apache.commons.math4.exception.NotPositiveException;
+import org.apache.commons.math4.exception.NotStrictlyPositiveException;
import org.apache.commons.math4.exception.NullArgumentException;
+import org.apache.commons.math4.exception.NumberIsTooLargeException;
import org.apache.commons.math4.exception.OutOfRangeException;
import org.apache.commons.rng.simple.RandomSource;
import org.apache.commons.math4.stat.descriptive.UnivariateStatistic;
@@ -865,4 +868,179 @@
}
}
-}
+ // Test if weighted percentile got the same result with the non-weighted one
+ // when all weights are equal to each other.
+ @Test
+ public void testResultWithNonWeightedPercentile() {
+ double[] dataset =
+ new double[] { Double.NaN, Double.NaN, Double.NaN };
+ double[] weights =
+ new double[] { 1, 1, 1 };
+ Percentile p = new Percentile().
+ withEstimationType(Percentile.EstimationType.R_7).
+ withNaNStrategy(NaNStrategy.MAXIMAL);
+ Assert.assertEquals(p.evaluate(dataset, weights, 25d), p.evaluate(dataset, 25d), 0d);
+ Assert.assertEquals(p.evaluate(dataset, weights, 50d), p.evaluate(dataset, 50d), 0d);
+ Assert.assertEquals(p.evaluate(dataset, weights, 75d), p.evaluate(dataset, 75d), 0d);
+ p = new Percentile().
+ withEstimationType(Percentile.EstimationType.R_7).
+ withNaNStrategy(NaNStrategy.MINIMAL);
+ Assert.assertEquals(p.evaluate(dataset, weights, 25d), p.evaluate(dataset, 25d), 0d);
+ Assert.assertEquals(p.evaluate(dataset, weights, 50d), p.evaluate(dataset, 50d), 0d);
+ Assert.assertEquals(p.evaluate(dataset, weights, 75d), p.evaluate(dataset, 75d), 0d);
+ p = new Percentile().
+ withEstimationType(Percentile.EstimationType.R_7);
+ Assert.assertEquals(p.evaluate(dataset, weights, 25d), p.evaluate(dataset, 25d), 0d);
+ Assert.assertEquals(p.evaluate(dataset, weights, 50d), p.evaluate(dataset, 50d), 0d);
+ Assert.assertEquals(p.evaluate(dataset, weights, 75d), p.evaluate(dataset, 75d), 0d);
+ }
+
+ @Test(expected=MathIllegalArgumentException.class)
+ public void testDataAndWeightsLength() {
+ double[] dataset =
+ new double[] { 1d, 2d, 3d, 4d, 5d };
+ double[] weights =
+ new double[] { 1, 1, 1, 1 };
+ new Percentile().
+ withEstimationType(Percentile.EstimationType.R_7).
+ evaluate(dataset, weights, 50d);
+ }
+
+ @Test
+ public void testWeightedPercentileWithSpecialValues() {
+ double[] dataset = new double[] { 3, 4, 2, 9 };
+ double[] weights = new double[] { 2, 6, 4, 3};
+ Percentile p = new Percentile().
+ withEstimationType(Percentile.EstimationType.R_7);
+ Assert.assertEquals( 3.53125, p.evaluate(dataset, weights, 50d), 0d);
+ }
+
+ @Test(expected=MathIllegalArgumentException.class)
+ public void testsetDataInputLength() {
+ double[] dataset = new double[] { 3, 4, 2, 9 };
+ double[] weights = new double[] { 1, 1, 1 };
+ new Percentile().setData(dataset, weights);
+ new Percentile().setData(dataset, weights, 0, dataset.length);
+ }
+
+ @Test(expected=NotANumberException.class)
+ public void testsetDataNotANumber() {
+ double[] dataset = new double[] { 3, 4, 2, 9 };
+ double[] weights = new double[] { 1, 1, 1, Double.NaN };
+ new Percentile().setData(dataset, weights);
+ new Percentile().setData(dataset, weights, 0, dataset.length);
+ }
+
+ @Test(expected=NotStrictlyPositiveException.class)
+ public void testsetDataPositiveWeights() {
+ double[] dataset = new double[] { 3, 4, 2, 9 };
+ double[] weights = new double[] { -1, -1, -1, -1 };
+ new Percentile().setData(dataset, weights);
+ new Percentile().setData(dataset, weights, 0, dataset.length);
+ }
+
+ @Test(expected=NotPositiveException.class)
+ public void testsetDataPositivIndex() {
+ double[] dataset = new double[] { 3, 4, 2, 9 };
+ double[] weights = new double[] { 1, 1, 1, 1 };
+ new Percentile().setData(dataset, weights, -1, dataset.length);
+ new Percentile().setData(dataset, weights, 0, -1);
+ }
+
+ @Test(expected=NumberIsTooLargeException.class)
+ public void testsetDataIndexOutBound() {
+ double[] dataset = new double[] { 3, 4, 2, 9 };
+ double[] weights = new double[] { 1, 1, 1, 1 };
+ new Percentile().setData(dataset, weights, 0, dataset.length+1);
+ }
+
+ @Test(expected=MathIllegalArgumentException.class)
+ public void testsetDataInputNull() {
+ new Percentile().setData(null, null);
+ new Percentile().setData(null, null, 0, 0);
+ }
+
+ @Test(expected=MathIllegalArgumentException.class)
+ public void testevaluateInputLength() {
+ double[] dataset = new double[] { 3, 4, 2, 9 };
+ double[] weights = new double[] { 1, 1, 1 };
+ Percentile p = new Percentile().withEstimationType(Percentile.EstimationType.R_7);
+ p.setData(dataset, weights);
+ p.evaluate(50);
+ p.evaluate(dataset, weights, 50);
+ p.evaluate(dataset, weights, 0, dataset.length);
+ p.evaluate(dataset, weights, 0, dataset.length, 50);
+ }
+
+ @Test(expected=NotPositiveException.class)
+ public void testevaluatePositivIndex() {
+ double[] dataset = new double[] { 3, 4, 2, 9 };
+ double[] weights = new double[] { 1, 1, 1 ,1};
+ Percentile p = new Percentile().withEstimationType(Percentile.EstimationType.R_7);
+ p.setData(dataset, weights);
+ p.evaluate(50);
+ p.evaluate(dataset, weights, 50);
+ p.evaluate(dataset, weights, -1, dataset.length);
+ p.evaluate(dataset, weights, 0, -1, 50);
+ }
+
+ @Test(expected=NotStrictlyPositiveException.class)
+ public void testevaluatePositivWeights() {
+ double[] dataset = new double[] { 3, 4, 2, 9 };
+ double[] weights = new double[] { -1, -1, -1 , -1};
+ Percentile p = new Percentile().withEstimationType(Percentile.EstimationType.R_7);
+ p.setData(dataset, weights);
+ p.evaluate(50);
+ p.evaluate(dataset, weights, 50);
+ p.evaluate(dataset, weights, 0, dataset.length);
+ p.evaluate(dataset, weights, 0, dataset.length, 50);
+ }
+
+ @Test(expected=NotANumberException.class)
+ public void testevaluateNotANumber() {
+ double[] dataset = new double[] { 3, 4, 2, 9 };
+ double[] weights = new double[] { 1, 1, 1, Double.NaN};
+ Percentile p = new Percentile().withEstimationType(Percentile.EstimationType.R_7);
+ p.setData(dataset, weights);
+ p.evaluate(50);
+ p.evaluate(dataset, weights, 50);
+ p.evaluate(dataset, weights, 0, dataset.length);
+ p.evaluate(dataset, weights, 0, dataset.length, 50);
+ }
+
+ @Test(expected=NotStrictlyPositiveException.class)
+ public void testevaluatePositiveWeights() {
+ double[] dataset = new double[] { 3, 4, 2, 9 };
+ double[] weights = new double[] { -1, -1, -1, -1};
+ Percentile p = new Percentile().withEstimationType(Percentile.EstimationType.R_7);
+ p.setData(dataset, weights);
+ p.evaluate(50);
+ p.evaluate(dataset, weights, 50);
+ p.evaluate(dataset, weights, 0, dataset.length);
+ p.evaluate(dataset, weights, 0, dataset.length, 50);
+ }
+
+ @Test(expected=OutOfRangeException.class)
+ public void testevaluatep() {
+ double[] dataset = new double[] { 3, 4, 2, 9 };
+ double[] weights = new double[] { 1, 1, 1, 1};
+ Percentile p = new Percentile().withEstimationType(Percentile.EstimationType.R_7);
+ p.setData(dataset, weights);
+ p.evaluate(101);
+ p.evaluate(dataset, weights, 101);
+ p.evaluate(dataset, weights, 0, dataset.length);
+ p.evaluate(dataset, weights, 0, dataset.length, 101);
+ }
+
+ @Test(expected=NumberIsTooLargeException.class)
+ public void testevaluateIndexBound() {
+ double[] dataset = new double[] { 3, 4, 2, 9 };
+ double[] weights = new double[] { 1, 1, 1, 1};
+ Percentile p = new Percentile().withEstimationType(Percentile.EstimationType.R_7);
+ p.setData(dataset, weights);
+ p.evaluate(50);
+ p.evaluate(dataset, weights, 50);
+ p.evaluate(dataset, weights, 0, dataset.length + 1);
+ p.evaluate(dataset, weights, 0, dataset.length + 1, 50);
+ }
+}
\ No newline at end of file