| /* |
| * 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) { |
| } |
| } |
| } |