blob: a9b7872994cbfe18598bce31588960b54af6b116 [file] [log] [blame]
/*
* Copyright (C) 2014 The Guava Authors
*
* Licensed 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 com.google.common.math;
import static com.google.common.math.Quantiles.median;
import static com.google.common.math.Quantiles.percentiles;
import static com.google.common.math.Quantiles.quartiles;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static java.lang.Double.NEGATIVE_INFINITY;
import static java.lang.Double.NaN;
import static java.lang.Double.POSITIVE_INFINITY;
import static java.math.RoundingMode.CEILING;
import static java.math.RoundingMode.FLOOR;
import static java.math.RoundingMode.UNNECESSARY;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Ordering;
import com.google.common.math.Quantiles.ScaleAndIndexes;
import com.google.common.primitives.Doubles;
import com.google.common.primitives.Ints;
import com.google.common.primitives.Longs;
import com.google.common.truth.Correspondence;
import com.google.common.truth.Correspondence.BinaryPredicate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import junit.framework.TestCase;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Tests for {@link Quantiles}.
*
* @author Pete Gillin
*/
public class QuantilesTest extends TestCase {
/*
* Since Quantiles provides a fluent-style API, each test covers a chain of methods resulting in
* the computation of one or more quantiles (or in an error) rather than individual methods. The
* tests are divided into three sections:
* 1. Tests on a hardcoded dataset for chains starting with median(), quartiles(), and scale(10);
* 2. Tests on hardcoded datasets include non-finite values for chains starting with scale(10);
* 3. Tests on a mechanically generated dataset for chains starting with percentiles();
* 4. Tests of illegal usages of the API.
*/
/*
* Covering every combination would lead to an explosion in the number of tests. So we cover only:
* - median with compute taking a double-collection and with computeInPlace;
* - quartiles with index and with indexes taking int-varargs, and with compute taking a
* double-collection and with computeInPlace;
* - scale with index and with indexes taking int-varargs, and with all overloads of compute
* taking a double-collection and with computeInPlace;
* - scale with indexes taking integer-collection with compute taking a double-collection and with
* computeInPlace;
* - (except that, for non-finite values, we don't do all combinations exhaustively);
* - percentiles with index and with indexes taking int-varargs, and with compute taking a
* double-collection and with computeInPlace.
*/
private static final double ALLOWED_ERROR = 1.0e-10;
/**
* A {@link Correspondence} which accepts finite values within {@link #ALLOWED_ERROR} of each
* other.
*/
private static final Correspondence<Number, Number> FINITE_QUANTILE_CORRESPONDENCE =
Correspondence.tolerance(ALLOWED_ERROR);
/**
* A {@link Correspondence} which accepts either finite values within {@link #ALLOWED_ERROR} of
* each other or identical non-finite values.
*/
private static final Correspondence<Double, Double> QUANTILE_CORRESPONDENCE =
Correspondence.from(
new BinaryPredicate<Double, Double>() {
@Override
public boolean apply(@Nullable Double actual, @Nullable Double expected) {
// Test for equality to allow non-finite values to match; otherwise, use the finite
// test.
return actual.equals(expected)
|| FINITE_QUANTILE_CORRESPONDENCE.compare(actual, expected);
}
},
"is identical to or " + FINITE_QUANTILE_CORRESPONDENCE);
// 1. Tests on a hardcoded dataset for chains starting with median(), quartiles(), and scale(10):
/** The squares of the 16 integers from 0 to 15, in an arbitrary order. */
private static final ImmutableList<Double> SIXTEEN_SQUARES_DOUBLES =
ImmutableList.of(
25.0, 100.0, 0.0, 144.0, 9.0, 121.0, 4.0, 225.0, 169.0, 64.0, 49.0, 16.0, 36.0, 1.0, 81.0,
196.0);
private static final ImmutableList<Long> SIXTEEN_SQUARES_LONGS =
ImmutableList.of(
25L, 100L, 0L, 144L, 9L, 121L, 4L, 225L, 169L, 64L, 49L, 16L, 36L, 1L, 81L, 196L);
private static final ImmutableList<Integer> SIXTEEN_SQUARES_INTEGERS =
ImmutableList.of(25, 100, 0, 144, 9, 121, 4, 225, 169, 64, 49, 16, 36, 1, 81, 196);
private static final double SIXTEEN_SQUARES_MIN = 0.0;
private static final double SIXTEEN_SQUARES_DECILE_1 = 0.5 * (1.0 + 4.0);
private static final double SIXTEEN_SQUARES_QUARTILE_1 = 0.25 * 9.0 + 0.75 * 16.0;
private static final double SIXTEEN_SQUARES_MEDIAN = 0.5 * (49.0 + 64.0);
private static final double SIXTEEN_SQUARES_QUARTILE_3 = 0.75 * 121.0 + 0.25 * 144.0;
private static final double SIXTEEN_SQUARES_DECILE_8 = 144.0;
private static final double SIXTEEN_SQUARES_MAX = 225.0;
public void testMedian_compute_doubleCollection() {
assertThat(median().compute(SIXTEEN_SQUARES_DOUBLES))
.isWithin(ALLOWED_ERROR)
.of(SIXTEEN_SQUARES_MEDIAN);
}
public void testMedian_computeInPlace() {
double[] dataset = Doubles.toArray(SIXTEEN_SQUARES_DOUBLES);
assertThat(median().computeInPlace(dataset)).isWithin(ALLOWED_ERROR).of(SIXTEEN_SQUARES_MEDIAN);
assertThat(dataset).usingExactEquality().containsExactlyElementsIn(SIXTEEN_SQUARES_DOUBLES);
}
public void testQuartiles_index_compute_doubleCollection() {
assertThat(quartiles().index(1).compute(SIXTEEN_SQUARES_DOUBLES))
.isWithin(ALLOWED_ERROR)
.of(SIXTEEN_SQUARES_QUARTILE_1);
}
public void testQuartiles_index_computeInPlace() {
double[] dataset = Doubles.toArray(SIXTEEN_SQUARES_DOUBLES);
assertThat(quartiles().index(1).computeInPlace(dataset))
.isWithin(ALLOWED_ERROR)
.of(SIXTEEN_SQUARES_QUARTILE_1);
assertThat(dataset).usingExactEquality().containsExactlyElementsIn(SIXTEEN_SQUARES_DOUBLES);
}
public void testQuartiles_indexes_varargs_compute_doubleCollection() {
assertThat(quartiles().indexes(1, 3).compute(SIXTEEN_SQUARES_DOUBLES))
.comparingValuesUsing(QUANTILE_CORRESPONDENCE)
.containsExactly(1, SIXTEEN_SQUARES_QUARTILE_1, 3, SIXTEEN_SQUARES_QUARTILE_3);
}
public void testQuartiles_indexes_varargs_computeInPlace() {
double[] dataset = Doubles.toArray(SIXTEEN_SQUARES_DOUBLES);
assertThat(quartiles().indexes(1, 3).computeInPlace(dataset))
.comparingValuesUsing(QUANTILE_CORRESPONDENCE)
.containsExactly(
1, SIXTEEN_SQUARES_QUARTILE_1,
3, SIXTEEN_SQUARES_QUARTILE_3);
assertThat(dataset).usingExactEquality().containsExactlyElementsIn(SIXTEEN_SQUARES_DOUBLES);
}
public void testScale_index_compute_doubleCollection() {
assertThat(Quantiles.scale(10).index(1).compute(SIXTEEN_SQUARES_DOUBLES))
.isWithin(ALLOWED_ERROR)
.of(SIXTEEN_SQUARES_DECILE_1);
}
public void testScale_index_compute_longCollection() {
assertThat(Quantiles.scale(10).index(1).compute(SIXTEEN_SQUARES_LONGS))
.isWithin(ALLOWED_ERROR)
.of(SIXTEEN_SQUARES_DECILE_1);
}
public void testScale_index_compute_integerCollection() {
assertThat(Quantiles.scale(10).index(1).compute(SIXTEEN_SQUARES_INTEGERS))
.isWithin(ALLOWED_ERROR)
.of(SIXTEEN_SQUARES_DECILE_1);
}
public void testScale_index_compute_doubleVarargs() {
double[] dataset = Doubles.toArray(SIXTEEN_SQUARES_DOUBLES);
assertThat(Quantiles.scale(10).index(1).compute(dataset))
.isWithin(ALLOWED_ERROR)
.of(SIXTEEN_SQUARES_DECILE_1);
assertThat(dataset)
.usingExactEquality()
.containsExactlyElementsIn(SIXTEEN_SQUARES_DOUBLES)
.inOrder();
}
public void testScale_index_compute_longVarargs() {
long[] dataset = Longs.toArray(SIXTEEN_SQUARES_LONGS);
assertThat(Quantiles.scale(10).index(1).compute(dataset))
.isWithin(ALLOWED_ERROR)
.of(SIXTEEN_SQUARES_DECILE_1);
assertThat(dataset).asList().isEqualTo(SIXTEEN_SQUARES_LONGS);
}
public void testScale_index_compute_intVarargs() {
int[] dataset = Ints.toArray(SIXTEEN_SQUARES_INTEGERS);
assertThat(Quantiles.scale(10).index(1).compute(dataset))
.isWithin(ALLOWED_ERROR)
.of(SIXTEEN_SQUARES_DECILE_1);
assertThat(dataset).asList().isEqualTo(SIXTEEN_SQUARES_INTEGERS);
}
public void testScale_index_computeInPlace() {
double[] dataset = Doubles.toArray(SIXTEEN_SQUARES_DOUBLES);
assertThat(Quantiles.scale(10).index(1).computeInPlace(dataset))
.isWithin(ALLOWED_ERROR)
.of(SIXTEEN_SQUARES_DECILE_1);
assertThat(dataset).usingExactEquality().containsExactlyElementsIn(SIXTEEN_SQUARES_DOUBLES);
}
public void testScale_index_computeInPlace_explicitVarargs() {
assertThat(Quantiles.scale(10).index(5).computeInPlace(78.9, 12.3, 45.6))
.isWithin(ALLOWED_ERROR)
.of(45.6);
}
public void testScale_indexes_varargs_compute_doubleCollection() {
// Note that we specify index 1 twice, which by the method contract should be ignored.
assertThat(Quantiles.scale(10).indexes(0, 10, 5, 1, 8, 1).compute(SIXTEEN_SQUARES_DOUBLES))
.comparingValuesUsing(QUANTILE_CORRESPONDENCE)
.containsExactly(
0, SIXTEEN_SQUARES_MIN,
10, SIXTEEN_SQUARES_MAX,
5, SIXTEEN_SQUARES_MEDIAN,
1, SIXTEEN_SQUARES_DECILE_1,
8, SIXTEEN_SQUARES_DECILE_8);
}
public void testScale_indexes_varargs_compute_doubleCollection_snapshotsIndexes() {
// This test is the same as testScale_indexes_varargs_compute_doubleCollection except that the
// array of indexes to be calculated is modified between the calls to indexes and compute: since
// the contract is that it is snapshotted, this shouldn't make any difference to the result.
int[] indexes = {0, 10, 5, 1, 8, 10};
ScaleAndIndexes intermediate = Quantiles.scale(10).indexes(indexes);
indexes[0] = 3;
assertThat(intermediate.compute(SIXTEEN_SQUARES_DOUBLES))
.comparingValuesUsing(QUANTILE_CORRESPONDENCE)
.containsExactly(
0, SIXTEEN_SQUARES_MIN,
10, SIXTEEN_SQUARES_MAX,
5, SIXTEEN_SQUARES_MEDIAN,
1, SIXTEEN_SQUARES_DECILE_1,
8, SIXTEEN_SQUARES_DECILE_8);
}
public void testScale_indexes_largeVarargs_compute_doubleCollection() {
int scale = Integer.MAX_VALUE;
int otherIndex = (Integer.MAX_VALUE - 1) / 3; // this divides exactly
// For the otherIndex calculation, we have q=Integer.MAX_VALUE, k=(Integer.MAX_VALUE-1)/3, and
// N=16. Therefore k*(N-1)/q = 5-5/Integer.MAX_VALUE, which has floor 4 and fractional part
// (1-5/Integer.MAX_VALUE).
double otherValue = 16.0 * 5.0 / Integer.MAX_VALUE + 25.0 * (1.0 - 5.0 / Integer.MAX_VALUE);
assertThat(
Quantiles.scale(scale).indexes(0, scale, otherIndex).compute(SIXTEEN_SQUARES_DOUBLES))
.comparingValuesUsing(QUANTILE_CORRESPONDENCE)
.containsExactly(
0, SIXTEEN_SQUARES_MIN, scale, SIXTEEN_SQUARES_MAX, otherIndex, otherValue);
}
public void testScale_indexes_varargs_compute_longCollection() {
// Note that we specify index 1 twice, which by the method contract should be ignored.
assertThat(Quantiles.scale(10).indexes(0, 10, 5, 1, 8, 1).compute(SIXTEEN_SQUARES_LONGS))
.comparingValuesUsing(QUANTILE_CORRESPONDENCE)
.containsExactly(
0, SIXTEEN_SQUARES_MIN,
10, SIXTEEN_SQUARES_MAX,
5, SIXTEEN_SQUARES_MEDIAN,
1, SIXTEEN_SQUARES_DECILE_1,
8, SIXTEEN_SQUARES_DECILE_8);
}
public void testScale_indexes_varargs_compute_integerCollection() {
// Note that we specify index 1 twice, which by the method contract should be ignored.
assertThat(Quantiles.scale(10).indexes(0, 10, 5, 1, 8, 1).compute(SIXTEEN_SQUARES_INTEGERS))
.comparingValuesUsing(QUANTILE_CORRESPONDENCE)
.containsExactly(
0, SIXTEEN_SQUARES_MIN,
10, SIXTEEN_SQUARES_MAX,
5, SIXTEEN_SQUARES_MEDIAN,
1, SIXTEEN_SQUARES_DECILE_1,
8, SIXTEEN_SQUARES_DECILE_8);
}
public void testScale_indexes_varargs_compute_indexOrderIsMaintained() {
assertThat(Quantiles.scale(10).indexes(0, 10, 5, 1, 8, 1).compute(SIXTEEN_SQUARES_INTEGERS))
.comparingValuesUsing(QUANTILE_CORRESPONDENCE)
.containsExactly(
0, SIXTEEN_SQUARES_MIN,
10, SIXTEEN_SQUARES_MAX,
5, SIXTEEN_SQUARES_MEDIAN,
1, SIXTEEN_SQUARES_DECILE_1,
8, SIXTEEN_SQUARES_DECILE_8)
.inOrder();
}
public void testScale_indexes_varargs_compute_doubleVarargs() {
double[] dataset = Doubles.toArray(SIXTEEN_SQUARES_DOUBLES);
assertThat(Quantiles.scale(10).indexes(0, 10, 5, 1, 8, 1).compute(dataset))
.comparingValuesUsing(QUANTILE_CORRESPONDENCE)
.containsExactly(
0, SIXTEEN_SQUARES_MIN,
10, SIXTEEN_SQUARES_MAX,
5, SIXTEEN_SQUARES_MEDIAN,
1, SIXTEEN_SQUARES_DECILE_1,
8, SIXTEEN_SQUARES_DECILE_8);
assertThat(dataset)
.usingExactEquality()
.containsExactlyElementsIn(SIXTEEN_SQUARES_DOUBLES)
.inOrder();
}
public void testScale_indexes_varargs_compute_longVarargs() {
long[] dataset = Longs.toArray(SIXTEEN_SQUARES_LONGS);
assertThat(Quantiles.scale(10).indexes(0, 10, 5, 1, 8, 1).compute(dataset))
.comparingValuesUsing(QUANTILE_CORRESPONDENCE)
.containsExactly(
0, SIXTEEN_SQUARES_MIN,
10, SIXTEEN_SQUARES_MAX,
5, SIXTEEN_SQUARES_MEDIAN,
1, SIXTEEN_SQUARES_DECILE_1,
8, SIXTEEN_SQUARES_DECILE_8);
assertThat(dataset).asList().isEqualTo(SIXTEEN_SQUARES_LONGS);
}
public void testScale_indexes_varargs_compute_intVarargs() {
int[] dataset = Ints.toArray(SIXTEEN_SQUARES_INTEGERS);
assertThat(Quantiles.scale(10).indexes(0, 10, 5, 1, 8, 1).compute(dataset))
.comparingValuesUsing(QUANTILE_CORRESPONDENCE)
.containsExactly(
0, SIXTEEN_SQUARES_MIN,
10, SIXTEEN_SQUARES_MAX,
5, SIXTEEN_SQUARES_MEDIAN,
1, SIXTEEN_SQUARES_DECILE_1,
8, SIXTEEN_SQUARES_DECILE_8);
assertThat(dataset).asList().isEqualTo(SIXTEEN_SQUARES_INTEGERS);
}
public void testScale_indexes_varargs_computeInPlace() {
double[] dataset = Doubles.toArray(SIXTEEN_SQUARES_DOUBLES);
assertThat(Quantiles.scale(10).indexes(0, 10, 5, 1, 8, 1).computeInPlace(dataset))
.comparingValuesUsing(QUANTILE_CORRESPONDENCE)
.containsExactly(
0, SIXTEEN_SQUARES_MIN,
10, SIXTEEN_SQUARES_MAX,
5, SIXTEEN_SQUARES_MEDIAN,
1, SIXTEEN_SQUARES_DECILE_1,
8, SIXTEEN_SQUARES_DECILE_8);
assertThat(dataset).usingExactEquality().containsExactlyElementsIn(SIXTEEN_SQUARES_DOUBLES);
}
public void testScale_indexes_varargs_computeInPlace_explicitVarargs() {
assertThat(Quantiles.scale(10).indexes(0, 10).computeInPlace(78.9, 12.3, 45.6))
.comparingValuesUsing(QUANTILE_CORRESPONDENCE)
.containsExactly(
0, 12.3,
10, 78.9);
}
public void testScale_indexes_collection_compute_doubleCollection() {
// Note that we specify index 1 twice, which by the method contract should be ignored.
assertThat(
Quantiles.scale(10)
.indexes(ImmutableList.of(0, 10, 5, 1, 8, 1))
.compute(SIXTEEN_SQUARES_DOUBLES))
.comparingValuesUsing(QUANTILE_CORRESPONDENCE)
.containsExactly(
0, SIXTEEN_SQUARES_MIN,
10, SIXTEEN_SQUARES_MAX,
5, SIXTEEN_SQUARES_MEDIAN,
1, SIXTEEN_SQUARES_DECILE_1,
8, SIXTEEN_SQUARES_DECILE_8);
}
public void testScale_indexes_collection_computeInPlace() {
double[] dataset = Doubles.toArray(SIXTEEN_SQUARES_DOUBLES);
assertThat(
Quantiles.scale(10)
.indexes(ImmutableList.of(0, 10, 5, 1, 8, 1))
.computeInPlace(dataset))
.comparingValuesUsing(QUANTILE_CORRESPONDENCE)
.containsExactly(
0, SIXTEEN_SQUARES_MIN,
10, SIXTEEN_SQUARES_MAX,
5, SIXTEEN_SQUARES_MEDIAN,
1, SIXTEEN_SQUARES_DECILE_1,
8, SIXTEEN_SQUARES_DECILE_8);
assertThat(dataset).usingExactEquality().containsExactlyElementsIn(SIXTEEN_SQUARES_DOUBLES);
}
// 2. Tests on hardcoded datasets include non-finite values for chains starting with scale(10):
private static final ImmutableList<Double> ONE_TO_FIVE_AND_POSITIVE_INFINITY =
ImmutableList.of(3.0, 5.0, POSITIVE_INFINITY, 1.0, 4.0, 2.0);
private static final ImmutableList<Double> ONE_TO_FIVE_AND_NEGATIVE_INFINITY =
ImmutableList.of(3.0, 5.0, NEGATIVE_INFINITY, 1.0, 4.0, 2.0);
private static final ImmutableList<Double> NEGATIVE_INFINITY_AND_FIVE_POSITIVE_INFINITIES =
ImmutableList.of(
POSITIVE_INFINITY,
POSITIVE_INFINITY,
NEGATIVE_INFINITY,
POSITIVE_INFINITY,
POSITIVE_INFINITY,
POSITIVE_INFINITY);
private static final ImmutableList<Double> ONE_TO_FIVE_AND_NAN =
ImmutableList.of(3.0, 5.0, NaN, 1.0, 4.0, 2.0);
public void testScale_indexes_varargs_compute_doubleCollection_positiveInfinity() {
assertThat(
Quantiles.scale(10)
.indexes(0, 1, 2, 8, 9, 10)
.compute(ONE_TO_FIVE_AND_POSITIVE_INFINITY))
.comparingValuesUsing(QUANTILE_CORRESPONDENCE)
.containsExactly(
0, 1.0,
1, 1.5,
2, 2.0,
8, 5.0,
9, POSITIVE_INFINITY, // interpolating between 5.0 and POSITIVE_INFNINITY
10, POSITIVE_INFINITY);
}
public void testScale_index_compute_doubleCollection_positiveInfinity() {
// interpolating between 5.0 and POSITIVE_INFNINITY
assertThat(Quantiles.scale(10).index(9).compute(ONE_TO_FIVE_AND_POSITIVE_INFINITY))
.isPositiveInfinity();
}
public void testScale_indexes_varargs_compute_doubleCollection_negativeInfinity() {
assertThat(
Quantiles.scale(10)
.indexes(0, 1, 2, 8, 9, 10)
.compute(ONE_TO_FIVE_AND_NEGATIVE_INFINITY))
.comparingValuesUsing(QUANTILE_CORRESPONDENCE)
.containsExactly(
0, NEGATIVE_INFINITY,
1, NEGATIVE_INFINITY, // interpolating between NEGATIVE_INFNINITY and 1.0
2, 1.0,
8, 4.0,
9, 4.5,
10, 5.0);
}
public void testScale_index_compute_doubleCollection_negativeInfinity() {
// interpolating between NEGATIVE_INFNINITY and 1.0
assertThat(Quantiles.scale(10).index(1).compute(ONE_TO_FIVE_AND_NEGATIVE_INFINITY))
.isNegativeInfinity();
}
public void testScale_indexes_varargs_compute_doubleCollection_bothInfinities() {
assertThat(
Quantiles.scale(10)
.indexes(0, 1, 2, 8, 9, 10)
.compute(NEGATIVE_INFINITY_AND_FIVE_POSITIVE_INFINITIES))
.comparingValuesUsing(QUANTILE_CORRESPONDENCE)
.containsExactly(
0, NEGATIVE_INFINITY,
1, NaN, // interpolating between NEGATIVE_ and POSITIVE_INFINITY values
2, POSITIVE_INFINITY,
8, POSITIVE_INFINITY,
9, POSITIVE_INFINITY, // interpolating between two POSITIVE_INFINITY values
10, POSITIVE_INFINITY);
}
public void testScale_indexes_varargs_compute_doubleCollection_nan() {
assertThat(Quantiles.scale(10).indexes(0, 1, 2, 8, 9, 10).compute(ONE_TO_FIVE_AND_NAN))
.comparingValuesUsing(QUANTILE_CORRESPONDENCE)
.containsExactly(
0, NaN,
1, NaN,
2, NaN,
8, NaN,
9, NaN,
10, NaN);
}
public void testScale_index_compute_doubleCollection_nan() {
assertThat(Quantiles.scale(10).index(5).compute(ONE_TO_FIVE_AND_NAN)).isNaN();
}
// 3. Tests on a mechanically generated dataset for chains starting with percentiles():
private static final int PSEUDORANDOM_DATASET_SIZE = 9951;
private static final ImmutableList<Double> PSEUDORANDOM_DATASET = generatePseudorandomDataset();
private static final ImmutableList<Double> PSEUDORANDOM_DATASET_SORTED =
Ordering.natural().immutableSortedCopy(PSEUDORANDOM_DATASET);
private static ImmutableList<Double> generatePseudorandomDataset() {
Random random = new Random(2211275185798966364L);
ImmutableList.Builder<Double> largeDatasetBuilder = ImmutableList.builder();
for (int i = 0; i < PSEUDORANDOM_DATASET_SIZE; i++) {
largeDatasetBuilder.add(random.nextGaussian());
}
return largeDatasetBuilder.build();
}
private static double expectedLargeDatasetPercentile(int index) {
// We have q=100, k=index, and N=9951. Therefore k*(N-1)/q is 99.5*index. If index is even, that
// is an integer 199*index/2. If index is odd, that is halfway between floor(199*index/2) and
// ceil(199*index/2).
if (index % 2 == 0) {
int position = IntMath.divide(199 * index, 2, UNNECESSARY);
return PSEUDORANDOM_DATASET_SORTED.get(position);
} else {
int positionFloor = IntMath.divide(199 * index, 2, FLOOR);
int positionCeil = IntMath.divide(199 * index, 2, CEILING);
double lowerValue = PSEUDORANDOM_DATASET_SORTED.get(positionFloor);
double upperValue = PSEUDORANDOM_DATASET_SORTED.get(positionCeil);
return (lowerValue + upperValue) / 2.0;
}
}
public void testPercentiles_index_compute_doubleCollection() {
for (int index = 0; index <= 100; index++) {
assertWithMessage("quantile at index " + index)
.that(percentiles().index(index).compute(PSEUDORANDOM_DATASET))
.isWithin(ALLOWED_ERROR)
.of(expectedLargeDatasetPercentile(index));
}
}
@AndroidIncompatible // slow
public void testPercentiles_index_computeInPlace() {
// Assert that the computation gives the correct result for all possible percentiles.
for (int index = 0; index <= 100; index++) {
double[] dataset = Doubles.toArray(PSEUDORANDOM_DATASET);
assertWithMessage("quantile at index " + index)
.that(percentiles().index(index).computeInPlace(dataset))
.isWithin(ALLOWED_ERROR)
.of(expectedLargeDatasetPercentile(index));
}
// Assert that the dataset contains the same elements after the in-place computation (although
// they may be reordered). We only do this for one index rather than for all indexes, as it is
// quite expensives (quadratic in the size of PSEUDORANDOM_DATASET).
double[] dataset = Doubles.toArray(PSEUDORANDOM_DATASET);
@SuppressWarnings("unused")
double actual = percentiles().index(33).computeInPlace(dataset);
assertThat(dataset).usingExactEquality().containsExactlyElementsIn(PSEUDORANDOM_DATASET);
}
public void testPercentiles_indexes_varargsPairs_compute_doubleCollection() {
for (int index1 = 0; index1 <= 100; index1++) {
for (int index2 = 0; index2 <= 100; index2++) {
ImmutableMap.Builder<Integer, Double> expectedBuilder = ImmutableMap.builder();
expectedBuilder.put(index1, expectedLargeDatasetPercentile(index1));
if (index2 != index1) {
expectedBuilder.put(index2, expectedLargeDatasetPercentile(index2));
}
assertThat(percentiles().indexes(index1, index2).compute(PSEUDORANDOM_DATASET))
.comparingValuesUsing(QUANTILE_CORRESPONDENCE)
.containsExactlyEntriesIn(expectedBuilder.build());
}
}
}
public void testPercentiles_indexes_varargsAll_compute_doubleCollection() {
ArrayList<Integer> indexes = new ArrayList<>();
ImmutableMap.Builder<Integer, Double> expectedBuilder = ImmutableMap.builder();
for (int index = 0; index <= 100; index++) {
indexes.add(index);
expectedBuilder.put(index, expectedLargeDatasetPercentile(index));
}
Random random = new Random(770683168895677741L);
Collections.shuffle(indexes, random);
assertThat(percentiles().indexes(Ints.toArray(indexes)).compute(PSEUDORANDOM_DATASET))
.comparingValuesUsing(QUANTILE_CORRESPONDENCE)
.containsExactlyEntriesIn(expectedBuilder.build());
}
@AndroidIncompatible // slow
public void testPercentiles_indexes_varargsAll_computeInPlace() {
double[] dataset = Doubles.toArray(PSEUDORANDOM_DATASET);
List<Integer> indexes = new ArrayList<>();
ImmutableMap.Builder<Integer, Double> expectedBuilder = ImmutableMap.builder();
for (int index = 0; index <= 100; index++) {
indexes.add(index);
expectedBuilder.put(index, expectedLargeDatasetPercentile(index));
}
Random random = new Random(770683168895677741L);
Collections.shuffle(indexes, random);
assertThat(percentiles().indexes(Ints.toArray(indexes)).computeInPlace(dataset))
.comparingValuesUsing(QUANTILE_CORRESPONDENCE)
.containsExactlyEntriesIn(expectedBuilder.build());
assertThat(dataset).usingExactEquality().containsExactlyElementsIn(PSEUDORANDOM_DATASET);
}
// 4. Tests of illegal usages of the API:
private static final ImmutableList<Double> EMPTY_DATASET = ImmutableList.of();
public void testScale_zero() {
try {
Quantiles.scale(0);
fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException expected) {
}
}
public void testScale_negative() {
try {
Quantiles.scale(-4);
fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException expected) {
}
}
public void testScale_index_negative() {
Quantiles.Scale intermediate = Quantiles.scale(10);
try {
intermediate.index(-1);
fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException expected) {
}
}
public void testScale_index_tooHigh() {
Quantiles.Scale intermediate = Quantiles.scale(10);
try {
intermediate.index(11);
fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException expected) {
}
}
public void testScale_indexes_varargs_negative() {
Quantiles.Scale intermediate = Quantiles.scale(10);
try {
intermediate.indexes(1, -1, 3);
fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException expected) {
}
}
public void testScale_indexes_varargs_tooHigh() {
Quantiles.Scale intermediate = Quantiles.scale(10);
try {
intermediate.indexes(1, 11, 3);
fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException expected) {
}
}
public void testScale_indexes_collection_negative() {
Quantiles.Scale intermediate = Quantiles.scale(10);
try {
intermediate.indexes(ImmutableList.of(1, -1, 3));
fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException expected) {
}
}
public void testScale_indexes_collection_tooHigh() {
Quantiles.Scale intermediate = Quantiles.scale(10);
try {
intermediate.indexes(ImmutableList.of(1, 11, 3));
fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException expected) {
}
}
public void testScale_index_compute_doubleCollection_empty() {
Quantiles.ScaleAndIndex intermediate = Quantiles.scale(10).index(3);
try {
intermediate.compute(EMPTY_DATASET);
fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException expected) {
}
}
public void testScale_index_compute_doubleVarargs_empty() {
Quantiles.ScaleAndIndex intermediate = Quantiles.scale(10).index(3);
try {
intermediate.compute(new double[] {});
fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException expected) {
}
}
public void testScale_index_compute_longVarargs_empty() {
Quantiles.ScaleAndIndex intermediate = Quantiles.scale(10).index(3);
try {
intermediate.compute(new long[] {});
fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException expected) {
}
}
public void testScale_index_compute_intVarargs_empty() {
Quantiles.ScaleAndIndex intermediate = Quantiles.scale(10).index(3);
try {
intermediate.compute(new int[] {});
fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException expected) {
}
}
public void testScale_index_computeInPlace_empty() {
Quantiles.ScaleAndIndex intermediate = Quantiles.scale(10).index(3);
try {
intermediate.computeInPlace(new double[] {});
fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException expected) {
}
}
public void testScale_indexes_varargs_compute_doubleCollection_empty() {
Quantiles.ScaleAndIndexes intermediate = Quantiles.scale(10).indexes(1, 3, 5);
try {
intermediate.compute(EMPTY_DATASET);
fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException expected) {
}
}
public void testScale_indexes_varargs_compute_doubleVarargs_empty() {
Quantiles.ScaleAndIndexes intermediate = Quantiles.scale(10).indexes(1, 3, 5);
try {
intermediate.compute(new double[] {});
fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException expected) {
}
}
public void testScale_indexes_varargs_compute_longVarargs_empty() {
Quantiles.ScaleAndIndexes intermediate = Quantiles.scale(10).indexes(1, 3, 5);
try {
intermediate.compute(new long[] {});
fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException expected) {
}
}
public void testScale_indexes_varargs_compute_intVarargs_empty() {
Quantiles.ScaleAndIndexes intermediate = Quantiles.scale(10).indexes(1, 3, 5);
try {
intermediate.compute(new int[] {});
fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException expected) {
}
}
public void testScale_indexes_varargs_computeInPlace_empty() {
Quantiles.ScaleAndIndexes intermediate = Quantiles.scale(10).indexes(1, 3, 5);
try {
intermediate.computeInPlace(new double[] {});
fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException expected) {
}
}
public void testScale_indexes_indexes_computeInPlace_empty() {
int[] emptyIndexes = {};
try {
Quantiles.ScaleAndIndexes unused = Quantiles.scale(10).indexes(emptyIndexes);
fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException expected) {
}
}
}