1 /* 2 * Copyright (C) 2014 The Guava Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.common.math; 18 19 import static com.google.common.math.Quantiles.median; 20 import static com.google.common.math.Quantiles.percentiles; 21 import static com.google.common.math.Quantiles.quartiles; 22 import static com.google.common.truth.Truth.assertThat; 23 import static java.lang.Double.NEGATIVE_INFINITY; 24 import static java.lang.Double.NaN; 25 import static java.lang.Double.POSITIVE_INFINITY; 26 import static java.math.RoundingMode.CEILING; 27 import static java.math.RoundingMode.FLOOR; 28 import static java.math.RoundingMode.UNNECESSARY; 29 30 import com.google.common.collect.ImmutableList; 31 import com.google.common.collect.ImmutableMap; 32 import com.google.common.collect.Ordering; 33 import com.google.common.math.Quantiles.ScaleAndIndexes; 34 import com.google.common.primitives.Doubles; 35 import com.google.common.primitives.Ints; 36 import com.google.common.primitives.Longs; 37 import com.google.common.truth.Correspondence; 38 import java.util.ArrayList; 39 import java.util.Collections; 40 import java.util.List; 41 import java.util.Random; 42 import junit.framework.TestCase; 43 import org.checkerframework.checker.nullness.compatqual.NullableDecl; 44 45 /** 46 * Tests for {@link Quantiles}. 47 * 48 * @author Pete Gillin 49 */ 50 public class QuantilesTest extends TestCase { 51 52 /* 53 * Since Quantiles provides a fluent-style API, each test covers a chain of methods resulting in 54 * the computation of one or more quantiles (or in an error) rather than individual methods. The 55 * tests are divided into three sections: 56 * 1. Tests on a hardcoded dataset for chains starting with median(), quartiles(), and scale(10); 57 * 2. Tests on hardcoded datasets include non-finite values for chains starting with scale(10); 58 * 3. Tests on a mechanically generated dataset for chains starting with percentiles(); 59 * 4. Tests of illegal usages of the API. 60 */ 61 62 /* 63 * Covering every combination would lead to an explosion in the number of tests. So we cover only: 64 * - median with compute taking a double-collection and with computeInPlace; 65 * - quartiles with index and with indexes taking int-varargs, and with compute taking a 66 * double-collection and with computeInPlace; 67 * - scale with index and with indexes taking int-varargs, and with all overloads of compute 68 * taking a double-collection and with computeInPlace; 69 * - scale with indexes taking integer-collection with compute taking a double-collection and with 70 * computeInPlace; 71 * - (except that, for non-finite values, we don't do all combinations exhaustively); 72 * - percentiles with index and with indexes taking int-varargs, and with compute taking a 73 * double-collection and with computeInPlace. 74 */ 75 76 private static final double ALLOWED_ERROR = 1.0e-10; 77 78 /** 79 * A {@link Correspondence} which accepts finite values within {@link #ALLOWED_ERROR} of each 80 * other. 81 */ 82 private static final Correspondence<Number, Number> FINITE_QUANTILE_CORRESPONDENCE = 83 Correspondence.tolerance(ALLOWED_ERROR); 84 85 /** 86 * A {@link Correspondence} which accepts either finite values within {@link #ALLOWED_ERROR} of 87 * each other or identical non-finite values. 88 */ 89 private static final Correspondence<Double, Double> QUANTILE_CORRESPONDENCE = 90 new Correspondence<Double, Double>() { 91 92 @Override 93 public boolean compare(@NullableDecl Double actual, @NullableDecl Double expected) { 94 // Test for equality to allow non-finite values to match; otherwise, use the finite test. 95 return actual.equals(expected) 96 || FINITE_QUANTILE_CORRESPONDENCE.compare(actual, expected); 97 } 98 99 @Override 100 public String toString() { 101 return "is identical to or " + FINITE_QUANTILE_CORRESPONDENCE; 102 } 103 }; 104 105 // 1. Tests on a hardcoded dataset for chains starting with median(), quartiles(), and scale(10): 106 107 /** The squares of the 16 integers from 0 to 15, in an arbitrary order. */ 108 private static final ImmutableList<Double> SIXTEEN_SQUARES_DOUBLES = 109 ImmutableList.of( 110 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, 111 196.0); 112 113 private static final ImmutableList<Long> SIXTEEN_SQUARES_LONGS = 114 ImmutableList.of( 115 25L, 100L, 0L, 144L, 9L, 121L, 4L, 225L, 169L, 64L, 49L, 16L, 36L, 1L, 81L, 196L); 116 private static final ImmutableList<Integer> SIXTEEN_SQUARES_INTEGERS = 117 ImmutableList.of(25, 100, 0, 144, 9, 121, 4, 225, 169, 64, 49, 16, 36, 1, 81, 196); 118 private static final double SIXTEEN_SQUARES_MIN = 0.0; 119 private static final double SIXTEEN_SQUARES_DECILE_1 = 0.5 * (1.0 + 4.0); 120 private static final double SIXTEEN_SQUARES_QUARTILE_1 = 0.25 * 9.0 + 0.75 * 16.0; 121 private static final double SIXTEEN_SQUARES_MEDIAN = 0.5 * (49.0 + 64.0); 122 private static final double SIXTEEN_SQUARES_QUARTILE_3 = 0.75 * 121.0 + 0.25 * 144.0; 123 private static final double SIXTEEN_SQUARES_DECILE_8 = 144.0; 124 private static final double SIXTEEN_SQUARES_MAX = 225.0; 125 testMedian_compute_doubleCollection()126 public void testMedian_compute_doubleCollection() { 127 assertThat(median().compute(SIXTEEN_SQUARES_DOUBLES)) 128 .isWithin(ALLOWED_ERROR) 129 .of(SIXTEEN_SQUARES_MEDIAN); 130 } 131 testMedian_computeInPlace()132 public void testMedian_computeInPlace() { 133 double[] dataset = Doubles.toArray(SIXTEEN_SQUARES_DOUBLES); 134 assertThat(median().computeInPlace(dataset)).isWithin(ALLOWED_ERROR).of(SIXTEEN_SQUARES_MEDIAN); 135 assertThat(dataset).usingExactEquality().containsExactlyElementsIn(SIXTEEN_SQUARES_DOUBLES); 136 } 137 testQuartiles_index_compute_doubleCollection()138 public void testQuartiles_index_compute_doubleCollection() { 139 assertThat(quartiles().index(1).compute(SIXTEEN_SQUARES_DOUBLES)) 140 .isWithin(ALLOWED_ERROR) 141 .of(SIXTEEN_SQUARES_QUARTILE_1); 142 } 143 testQuartiles_index_computeInPlace()144 public void testQuartiles_index_computeInPlace() { 145 double[] dataset = Doubles.toArray(SIXTEEN_SQUARES_DOUBLES); 146 assertThat(quartiles().index(1).computeInPlace(dataset)) 147 .isWithin(ALLOWED_ERROR) 148 .of(SIXTEEN_SQUARES_QUARTILE_1); 149 assertThat(dataset).usingExactEquality().containsExactlyElementsIn(SIXTEEN_SQUARES_DOUBLES); 150 } 151 testQuartiles_indexes_varargs_compute_doubleCollection()152 public void testQuartiles_indexes_varargs_compute_doubleCollection() { 153 assertThat(quartiles().indexes(1, 3).compute(SIXTEEN_SQUARES_DOUBLES)) 154 .comparingValuesUsing(QUANTILE_CORRESPONDENCE) 155 .containsExactly(1, SIXTEEN_SQUARES_QUARTILE_1, 3, SIXTEEN_SQUARES_QUARTILE_3); 156 } 157 testQuartiles_indexes_varargs_computeInPlace()158 public void testQuartiles_indexes_varargs_computeInPlace() { 159 double[] dataset = Doubles.toArray(SIXTEEN_SQUARES_DOUBLES); 160 assertThat(quartiles().indexes(1, 3).computeInPlace(dataset)) 161 .comparingValuesUsing(QUANTILE_CORRESPONDENCE) 162 .containsExactly( 163 1, SIXTEEN_SQUARES_QUARTILE_1, 164 3, SIXTEEN_SQUARES_QUARTILE_3); 165 assertThat(dataset).usingExactEquality().containsExactlyElementsIn(SIXTEEN_SQUARES_DOUBLES); 166 } 167 testScale_index_compute_doubleCollection()168 public void testScale_index_compute_doubleCollection() { 169 assertThat(Quantiles.scale(10).index(1).compute(SIXTEEN_SQUARES_DOUBLES)) 170 .isWithin(ALLOWED_ERROR) 171 .of(SIXTEEN_SQUARES_DECILE_1); 172 } 173 testScale_index_compute_longCollection()174 public void testScale_index_compute_longCollection() { 175 assertThat(Quantiles.scale(10).index(1).compute(SIXTEEN_SQUARES_LONGS)) 176 .isWithin(ALLOWED_ERROR) 177 .of(SIXTEEN_SQUARES_DECILE_1); 178 } 179 testScale_index_compute_integerCollection()180 public void testScale_index_compute_integerCollection() { 181 assertThat(Quantiles.scale(10).index(1).compute(SIXTEEN_SQUARES_INTEGERS)) 182 .isWithin(ALLOWED_ERROR) 183 .of(SIXTEEN_SQUARES_DECILE_1); 184 } 185 testScale_index_compute_doubleVarargs()186 public void testScale_index_compute_doubleVarargs() { 187 double[] dataset = Doubles.toArray(SIXTEEN_SQUARES_DOUBLES); 188 assertThat(Quantiles.scale(10).index(1).compute(dataset)) 189 .isWithin(ALLOWED_ERROR) 190 .of(SIXTEEN_SQUARES_DECILE_1); 191 assertThat(dataset) 192 .usingExactEquality() 193 .containsExactlyElementsIn(SIXTEEN_SQUARES_DOUBLES) 194 .inOrder(); 195 } 196 testScale_index_compute_longVarargs()197 public void testScale_index_compute_longVarargs() { 198 long[] dataset = Longs.toArray(SIXTEEN_SQUARES_LONGS); 199 assertThat(Quantiles.scale(10).index(1).compute(dataset)) 200 .isWithin(ALLOWED_ERROR) 201 .of(SIXTEEN_SQUARES_DECILE_1); 202 assertThat(dataset).asList().isEqualTo(SIXTEEN_SQUARES_LONGS); 203 } 204 testScale_index_compute_intVarargs()205 public void testScale_index_compute_intVarargs() { 206 int[] dataset = Ints.toArray(SIXTEEN_SQUARES_INTEGERS); 207 assertThat(Quantiles.scale(10).index(1).compute(dataset)) 208 .isWithin(ALLOWED_ERROR) 209 .of(SIXTEEN_SQUARES_DECILE_1); 210 assertThat(dataset).asList().isEqualTo(SIXTEEN_SQUARES_INTEGERS); 211 } 212 testScale_index_computeInPlace()213 public void testScale_index_computeInPlace() { 214 double[] dataset = Doubles.toArray(SIXTEEN_SQUARES_DOUBLES); 215 assertThat(Quantiles.scale(10).index(1).computeInPlace(dataset)) 216 .isWithin(ALLOWED_ERROR) 217 .of(SIXTEEN_SQUARES_DECILE_1); 218 assertThat(dataset).usingExactEquality().containsExactlyElementsIn(SIXTEEN_SQUARES_DOUBLES); 219 } 220 testScale_index_computeInPlace_explicitVarargs()221 public void testScale_index_computeInPlace_explicitVarargs() { 222 assertThat(Quantiles.scale(10).index(5).computeInPlace(78.9, 12.3, 45.6)) 223 .isWithin(ALLOWED_ERROR) 224 .of(45.6); 225 } 226 testScale_indexes_varargs_compute_doubleCollection()227 public void testScale_indexes_varargs_compute_doubleCollection() { 228 // Note that we specify index 1 twice, which by the method contract should be ignored. 229 assertThat(Quantiles.scale(10).indexes(0, 10, 5, 1, 8, 1).compute(SIXTEEN_SQUARES_DOUBLES)) 230 .comparingValuesUsing(QUANTILE_CORRESPONDENCE) 231 .containsExactly( 232 0, SIXTEEN_SQUARES_MIN, 233 10, SIXTEEN_SQUARES_MAX, 234 5, SIXTEEN_SQUARES_MEDIAN, 235 1, SIXTEEN_SQUARES_DECILE_1, 236 8, SIXTEEN_SQUARES_DECILE_8); 237 } 238 testScale_indexes_varargs_compute_doubleCollection_snapshotsIndexes()239 public void testScale_indexes_varargs_compute_doubleCollection_snapshotsIndexes() { 240 // This test is the same as testScale_indexes_varargs_compute_doubleCollection except that the 241 // array of indexes to be calculated is modified between the calls to indexes and compute: since 242 // the contract is that it is snapshotted, this shouldn't make any difference to the result. 243 int[] indexes = {0, 10, 5, 1, 8, 10}; 244 ScaleAndIndexes intermediate = Quantiles.scale(10).indexes(indexes); 245 indexes[0] = 3; 246 assertThat(intermediate.compute(SIXTEEN_SQUARES_DOUBLES)) 247 .comparingValuesUsing(QUANTILE_CORRESPONDENCE) 248 .containsExactly( 249 0, SIXTEEN_SQUARES_MIN, 250 10, SIXTEEN_SQUARES_MAX, 251 5, SIXTEEN_SQUARES_MEDIAN, 252 1, SIXTEEN_SQUARES_DECILE_1, 253 8, SIXTEEN_SQUARES_DECILE_8); 254 } 255 testScale_indexes_largeVarargs_compute_doubleCollection()256 public void testScale_indexes_largeVarargs_compute_doubleCollection() { 257 int scale = Integer.MAX_VALUE; 258 int otherIndex = (Integer.MAX_VALUE - 1) / 3; // this divides exactly 259 // For the otherIndex calculation, we have q=Integer.MAX_VALUE, k=(Integer.MAX_VALUE-1)/3, and 260 // N=16. Therefore k*(N-1)/q = 5-5/Integer.MAX_VALUE, which has floor 4 and fractional part 261 // (1-5/Integer.MAX_VALUE). 262 double otherValue = 16.0 * 5.0 / Integer.MAX_VALUE + 25.0 * (1.0 - 5.0 / Integer.MAX_VALUE); 263 assertThat( 264 Quantiles.scale(scale).indexes(0, scale, otherIndex).compute(SIXTEEN_SQUARES_DOUBLES)) 265 .comparingValuesUsing(QUANTILE_CORRESPONDENCE) 266 .containsExactly( 267 0, SIXTEEN_SQUARES_MIN, scale, SIXTEEN_SQUARES_MAX, otherIndex, otherValue); 268 } 269 testScale_indexes_varargs_compute_longCollection()270 public void testScale_indexes_varargs_compute_longCollection() { 271 // Note that we specify index 1 twice, which by the method contract should be ignored. 272 assertThat(Quantiles.scale(10).indexes(0, 10, 5, 1, 8, 1).compute(SIXTEEN_SQUARES_LONGS)) 273 .comparingValuesUsing(QUANTILE_CORRESPONDENCE) 274 .containsExactly( 275 0, SIXTEEN_SQUARES_MIN, 276 10, SIXTEEN_SQUARES_MAX, 277 5, SIXTEEN_SQUARES_MEDIAN, 278 1, SIXTEEN_SQUARES_DECILE_1, 279 8, SIXTEEN_SQUARES_DECILE_8); 280 } 281 testScale_indexes_varargs_compute_integerCollection()282 public void testScale_indexes_varargs_compute_integerCollection() { 283 // Note that we specify index 1 twice, which by the method contract should be ignored. 284 assertThat(Quantiles.scale(10).indexes(0, 10, 5, 1, 8, 1).compute(SIXTEEN_SQUARES_INTEGERS)) 285 .comparingValuesUsing(QUANTILE_CORRESPONDENCE) 286 .containsExactly( 287 0, SIXTEEN_SQUARES_MIN, 288 10, SIXTEEN_SQUARES_MAX, 289 5, SIXTEEN_SQUARES_MEDIAN, 290 1, SIXTEEN_SQUARES_DECILE_1, 291 8, SIXTEEN_SQUARES_DECILE_8); 292 } 293 testScale_indexes_varargs_compute_doubleVarargs()294 public void testScale_indexes_varargs_compute_doubleVarargs() { 295 double[] dataset = Doubles.toArray(SIXTEEN_SQUARES_DOUBLES); 296 assertThat(Quantiles.scale(10).indexes(0, 10, 5, 1, 8, 1).compute(dataset)) 297 .comparingValuesUsing(QUANTILE_CORRESPONDENCE) 298 .containsExactly( 299 0, SIXTEEN_SQUARES_MIN, 300 10, SIXTEEN_SQUARES_MAX, 301 5, SIXTEEN_SQUARES_MEDIAN, 302 1, SIXTEEN_SQUARES_DECILE_1, 303 8, SIXTEEN_SQUARES_DECILE_8); 304 assertThat(dataset) 305 .usingExactEquality() 306 .containsExactlyElementsIn(SIXTEEN_SQUARES_DOUBLES) 307 .inOrder(); 308 } 309 testScale_indexes_varargs_compute_longVarargs()310 public void testScale_indexes_varargs_compute_longVarargs() { 311 long[] dataset = Longs.toArray(SIXTEEN_SQUARES_LONGS); 312 assertThat(Quantiles.scale(10).indexes(0, 10, 5, 1, 8, 1).compute(dataset)) 313 .comparingValuesUsing(QUANTILE_CORRESPONDENCE) 314 .containsExactly( 315 0, SIXTEEN_SQUARES_MIN, 316 10, SIXTEEN_SQUARES_MAX, 317 5, SIXTEEN_SQUARES_MEDIAN, 318 1, SIXTEEN_SQUARES_DECILE_1, 319 8, SIXTEEN_SQUARES_DECILE_8); 320 assertThat(dataset).asList().isEqualTo(SIXTEEN_SQUARES_LONGS); 321 } 322 testScale_indexes_varargs_compute_intVarargs()323 public void testScale_indexes_varargs_compute_intVarargs() { 324 int[] dataset = Ints.toArray(SIXTEEN_SQUARES_INTEGERS); 325 assertThat(Quantiles.scale(10).indexes(0, 10, 5, 1, 8, 1).compute(dataset)) 326 .comparingValuesUsing(QUANTILE_CORRESPONDENCE) 327 .containsExactly( 328 0, SIXTEEN_SQUARES_MIN, 329 10, SIXTEEN_SQUARES_MAX, 330 5, SIXTEEN_SQUARES_MEDIAN, 331 1, SIXTEEN_SQUARES_DECILE_1, 332 8, SIXTEEN_SQUARES_DECILE_8); 333 assertThat(dataset).asList().isEqualTo(SIXTEEN_SQUARES_INTEGERS); 334 } 335 testScale_indexes_varargs_computeInPlace()336 public void testScale_indexes_varargs_computeInPlace() { 337 double[] dataset = Doubles.toArray(SIXTEEN_SQUARES_DOUBLES); 338 assertThat(Quantiles.scale(10).indexes(0, 10, 5, 1, 8, 1).computeInPlace(dataset)) 339 .comparingValuesUsing(QUANTILE_CORRESPONDENCE) 340 .containsExactly( 341 0, SIXTEEN_SQUARES_MIN, 342 10, SIXTEEN_SQUARES_MAX, 343 5, SIXTEEN_SQUARES_MEDIAN, 344 1, SIXTEEN_SQUARES_DECILE_1, 345 8, SIXTEEN_SQUARES_DECILE_8); 346 assertThat(dataset).usingExactEquality().containsExactlyElementsIn(SIXTEEN_SQUARES_DOUBLES); 347 } 348 testScale_indexes_varargs_computeInPlace_explicitVarargs()349 public void testScale_indexes_varargs_computeInPlace_explicitVarargs() { 350 assertThat(Quantiles.scale(10).indexes(0, 10).computeInPlace(78.9, 12.3, 45.6)) 351 .comparingValuesUsing(QUANTILE_CORRESPONDENCE) 352 .containsExactly( 353 0, 12.3, 354 10, 78.9); 355 } 356 testScale_indexes_collection_compute_doubleCollection()357 public void testScale_indexes_collection_compute_doubleCollection() { 358 // Note that we specify index 1 twice, which by the method contract should be ignored. 359 assertThat( 360 Quantiles.scale(10) 361 .indexes(ImmutableList.of(0, 10, 5, 1, 8, 1)) 362 .compute(SIXTEEN_SQUARES_DOUBLES)) 363 .comparingValuesUsing(QUANTILE_CORRESPONDENCE) 364 .containsExactly( 365 0, SIXTEEN_SQUARES_MIN, 366 10, SIXTEEN_SQUARES_MAX, 367 5, SIXTEEN_SQUARES_MEDIAN, 368 1, SIXTEEN_SQUARES_DECILE_1, 369 8, SIXTEEN_SQUARES_DECILE_8); 370 } 371 testScale_indexes_collection_computeInPlace()372 public void testScale_indexes_collection_computeInPlace() { 373 double[] dataset = Doubles.toArray(SIXTEEN_SQUARES_DOUBLES); 374 assertThat( 375 Quantiles.scale(10) 376 .indexes(ImmutableList.of(0, 10, 5, 1, 8, 1)) 377 .computeInPlace(dataset)) 378 .comparingValuesUsing(QUANTILE_CORRESPONDENCE) 379 .containsExactly( 380 0, SIXTEEN_SQUARES_MIN, 381 10, SIXTEEN_SQUARES_MAX, 382 5, SIXTEEN_SQUARES_MEDIAN, 383 1, SIXTEEN_SQUARES_DECILE_1, 384 8, SIXTEEN_SQUARES_DECILE_8); 385 assertThat(dataset).usingExactEquality().containsExactlyElementsIn(SIXTEEN_SQUARES_DOUBLES); 386 } 387 388 // 2. Tests on hardcoded datasets include non-finite values for chains starting with scale(10): 389 390 private static final ImmutableList<Double> ONE_TO_FIVE_AND_POSITIVE_INFINITY = 391 ImmutableList.of(3.0, 5.0, POSITIVE_INFINITY, 1.0, 4.0, 2.0); 392 private static final ImmutableList<Double> ONE_TO_FIVE_AND_NEGATIVE_INFINITY = 393 ImmutableList.of(3.0, 5.0, NEGATIVE_INFINITY, 1.0, 4.0, 2.0); 394 private static final ImmutableList<Double> NEGATIVE_INFINITY_AND_FIVE_POSITIVE_INFINITIES = 395 ImmutableList.of( 396 POSITIVE_INFINITY, 397 POSITIVE_INFINITY, 398 NEGATIVE_INFINITY, 399 POSITIVE_INFINITY, 400 POSITIVE_INFINITY, 401 POSITIVE_INFINITY); 402 private static final ImmutableList<Double> ONE_TO_FIVE_AND_NAN = 403 ImmutableList.of(3.0, 5.0, NaN, 1.0, 4.0, 2.0); 404 testScale_indexes_varargs_compute_doubleCollection_positiveInfinity()405 public void testScale_indexes_varargs_compute_doubleCollection_positiveInfinity() { 406 assertThat( 407 Quantiles.scale(10) 408 .indexes(0, 1, 2, 8, 9, 10) 409 .compute(ONE_TO_FIVE_AND_POSITIVE_INFINITY)) 410 .comparingValuesUsing(QUANTILE_CORRESPONDENCE) 411 .containsExactly( 412 0, 1.0, 413 1, 1.5, 414 2, 2.0, 415 8, 5.0, 416 9, POSITIVE_INFINITY, // interpolating between 5.0 and POSITIVE_INFNINITY 417 10, POSITIVE_INFINITY); 418 } 419 testScale_index_compute_doubleCollection_positiveInfinity()420 public void testScale_index_compute_doubleCollection_positiveInfinity() { 421 // interpolating between 5.0 and POSITIVE_INFNINITY 422 assertThat(Quantiles.scale(10).index(9).compute(ONE_TO_FIVE_AND_POSITIVE_INFINITY)) 423 .isPositiveInfinity(); 424 } 425 testScale_indexes_varargs_compute_doubleCollection_negativeInfinity()426 public void testScale_indexes_varargs_compute_doubleCollection_negativeInfinity() { 427 assertThat( 428 Quantiles.scale(10) 429 .indexes(0, 1, 2, 8, 9, 10) 430 .compute(ONE_TO_FIVE_AND_NEGATIVE_INFINITY)) 431 .comparingValuesUsing(QUANTILE_CORRESPONDENCE) 432 .containsExactly( 433 0, NEGATIVE_INFINITY, 434 1, NEGATIVE_INFINITY, // interpolating between NEGATIVE_INFNINITY and 1.0 435 2, 1.0, 436 8, 4.0, 437 9, 4.5, 438 10, 5.0); 439 } 440 testScale_index_compute_doubleCollection_negativeInfinity()441 public void testScale_index_compute_doubleCollection_negativeInfinity() { 442 // interpolating between NEGATIVE_INFNINITY and 1.0 443 assertThat(Quantiles.scale(10).index(1).compute(ONE_TO_FIVE_AND_NEGATIVE_INFINITY)) 444 .isNegativeInfinity(); 445 } 446 testScale_indexes_varargs_compute_doubleCollection_bothInfinities()447 public void testScale_indexes_varargs_compute_doubleCollection_bothInfinities() { 448 assertThat( 449 Quantiles.scale(10) 450 .indexes(0, 1, 2, 8, 9, 10) 451 .compute(NEGATIVE_INFINITY_AND_FIVE_POSITIVE_INFINITIES)) 452 .comparingValuesUsing(QUANTILE_CORRESPONDENCE) 453 .containsExactly( 454 0, NEGATIVE_INFINITY, 455 1, NaN, // interpolating between NEGATIVE_ and POSITIVE_INFINITY values 456 2, POSITIVE_INFINITY, 457 8, POSITIVE_INFINITY, 458 9, POSITIVE_INFINITY, // interpolating between two POSITIVE_INFINITY values 459 10, POSITIVE_INFINITY); 460 } 461 testScale_indexes_varargs_compute_doubleCollection_nan()462 public void testScale_indexes_varargs_compute_doubleCollection_nan() { 463 assertThat(Quantiles.scale(10).indexes(0, 1, 2, 8, 9, 10).compute(ONE_TO_FIVE_AND_NAN)) 464 .comparingValuesUsing(QUANTILE_CORRESPONDENCE) 465 .containsExactly( 466 0, NaN, 467 1, NaN, 468 2, NaN, 469 8, NaN, 470 9, NaN, 471 10, NaN); 472 } 473 testScale_index_compute_doubleCollection_nan()474 public void testScale_index_compute_doubleCollection_nan() { 475 assertThat(Quantiles.scale(10).index(5).compute(ONE_TO_FIVE_AND_NAN)).isNaN(); 476 } 477 478 // 3. Tests on a mechanically generated dataset for chains starting with percentiles(): 479 480 private static final int PSEUDORANDOM_DATASET_SIZE = 9951; 481 private static final ImmutableList<Double> PSEUDORANDOM_DATASET = generatePseudorandomDataset(); 482 private static final ImmutableList<Double> PSEUDORANDOM_DATASET_SORTED = 483 Ordering.natural().immutableSortedCopy(PSEUDORANDOM_DATASET); 484 generatePseudorandomDataset()485 private static ImmutableList<Double> generatePseudorandomDataset() { 486 Random random = new Random(2211275185798966364L); 487 ImmutableList.Builder<Double> largeDatasetBuilder = ImmutableList.builder(); 488 for (int i = 0; i < PSEUDORANDOM_DATASET_SIZE; i++) { 489 largeDatasetBuilder.add(random.nextGaussian()); 490 } 491 return largeDatasetBuilder.build(); 492 } 493 expectedLargeDatasetPercentile(int index)494 private static double expectedLargeDatasetPercentile(int index) { 495 // We have q=100, k=index, and N=9951. Therefore k*(N-1)/q is 99.5*index. If index is even, that 496 // is an integer 199*index/2. If index is odd, that is halfway between floor(199*index/2) and 497 // ceil(199*index/2). 498 if (index % 2 == 0) { 499 int position = IntMath.divide(199 * index, 2, UNNECESSARY); 500 return PSEUDORANDOM_DATASET_SORTED.get(position); 501 } else { 502 int positionFloor = IntMath.divide(199 * index, 2, FLOOR); 503 int positionCeil = IntMath.divide(199 * index, 2, CEILING); 504 double lowerValue = PSEUDORANDOM_DATASET_SORTED.get(positionFloor); 505 double upperValue = PSEUDORANDOM_DATASET_SORTED.get(positionCeil); 506 return (lowerValue + upperValue) / 2.0; 507 } 508 } 509 testPercentiles_index_compute_doubleCollection()510 public void testPercentiles_index_compute_doubleCollection() { 511 for (int index = 0; index <= 100; index++) { 512 assertThat(percentiles().index(index).compute(PSEUDORANDOM_DATASET)) 513 .named("quantile at index " + index) 514 .isWithin(ALLOWED_ERROR) 515 .of(expectedLargeDatasetPercentile(index)); 516 } 517 } 518 519 @AndroidIncompatible // slow testPercentiles_index_computeInPlace()520 public void testPercentiles_index_computeInPlace() { 521 // Assert that the computation gives the correct result for all possible percentiles. 522 for (int index = 0; index <= 100; index++) { 523 double[] dataset = Doubles.toArray(PSEUDORANDOM_DATASET); 524 assertThat(percentiles().index(index).computeInPlace(dataset)) 525 .named("quantile at index " + index) 526 .isWithin(ALLOWED_ERROR) 527 .of(expectedLargeDatasetPercentile(index)); 528 } 529 530 // Assert that the dataset contains the same elements after the in-place computation (although 531 // they may be reordered). We only do this for one index rather than for all indexes, as it is 532 // quite expensives (quadratic in the size of PSEUDORANDOM_DATASET). 533 double[] dataset = Doubles.toArray(PSEUDORANDOM_DATASET); 534 @SuppressWarnings("unused") 535 double actual = percentiles().index(33).computeInPlace(dataset); 536 assertThat(dataset).usingExactEquality().containsExactlyElementsIn(PSEUDORANDOM_DATASET); 537 } 538 testPercentiles_indexes_varargsPairs_compute_doubleCollection()539 public void testPercentiles_indexes_varargsPairs_compute_doubleCollection() { 540 for (int index1 = 0; index1 <= 100; index1++) { 541 for (int index2 = 0; index2 <= 100; index2++) { 542 ImmutableMap.Builder<Integer, Double> expectedBuilder = ImmutableMap.builder(); 543 expectedBuilder.put(index1, expectedLargeDatasetPercentile(index1)); 544 if (index2 != index1) { 545 expectedBuilder.put(index2, expectedLargeDatasetPercentile(index2)); 546 } 547 assertThat(percentiles().indexes(index1, index2).compute(PSEUDORANDOM_DATASET)) 548 .comparingValuesUsing(QUANTILE_CORRESPONDENCE) 549 .containsExactlyEntriesIn(expectedBuilder.build()); 550 } 551 } 552 } 553 testPercentiles_indexes_varargsAll_compute_doubleCollection()554 public void testPercentiles_indexes_varargsAll_compute_doubleCollection() { 555 ArrayList<Integer> indexes = new ArrayList<>(); 556 ImmutableMap.Builder<Integer, Double> expectedBuilder = ImmutableMap.builder(); 557 for (int index = 0; index <= 100; index++) { 558 indexes.add(index); 559 expectedBuilder.put(index, expectedLargeDatasetPercentile(index)); 560 } 561 Random random = new Random(770683168895677741L); 562 Collections.shuffle(indexes, random); 563 assertThat(percentiles().indexes(Ints.toArray(indexes)).compute(PSEUDORANDOM_DATASET)) 564 .comparingValuesUsing(QUANTILE_CORRESPONDENCE) 565 .containsExactlyEntriesIn(expectedBuilder.build()); 566 } 567 568 @AndroidIncompatible // slow testPercentiles_indexes_varargsAll_computeInPlace()569 public void testPercentiles_indexes_varargsAll_computeInPlace() { 570 double[] dataset = Doubles.toArray(PSEUDORANDOM_DATASET); 571 List<Integer> indexes = new ArrayList<>(); 572 ImmutableMap.Builder<Integer, Double> expectedBuilder = ImmutableMap.builder(); 573 for (int index = 0; index <= 100; index++) { 574 indexes.add(index); 575 expectedBuilder.put(index, expectedLargeDatasetPercentile(index)); 576 } 577 Random random = new Random(770683168895677741L); 578 Collections.shuffle(indexes, random); 579 assertThat(percentiles().indexes(Ints.toArray(indexes)).computeInPlace(dataset)) 580 .comparingValuesUsing(QUANTILE_CORRESPONDENCE) 581 .containsExactlyEntriesIn(expectedBuilder.build()); 582 assertThat(dataset).usingExactEquality().containsExactlyElementsIn(PSEUDORANDOM_DATASET); 583 } 584 585 // 4. Tests of illegal usages of the API: 586 587 private static final ImmutableList<Double> EMPTY_DATASET = ImmutableList.of(); 588 testScale_zero()589 public void testScale_zero() { 590 try { 591 Quantiles.scale(0); 592 fail("Expected IllegalArgumentException"); 593 } catch (IllegalArgumentException expected) { 594 } 595 } 596 testScale_negative()597 public void testScale_negative() { 598 try { 599 Quantiles.scale(-4); 600 fail("Expected IllegalArgumentException"); 601 } catch (IllegalArgumentException expected) { 602 } 603 } 604 testScale_index_negative()605 public void testScale_index_negative() { 606 Quantiles.Scale intermediate = Quantiles.scale(10); 607 try { 608 intermediate.index(-1); 609 fail("Expected IllegalArgumentException"); 610 } catch (IllegalArgumentException expected) { 611 } 612 } 613 testScale_index_tooHigh()614 public void testScale_index_tooHigh() { 615 Quantiles.Scale intermediate = Quantiles.scale(10); 616 try { 617 intermediate.index(11); 618 fail("Expected IllegalArgumentException"); 619 } catch (IllegalArgumentException expected) { 620 } 621 } 622 testScale_indexes_varargs_negative()623 public void testScale_indexes_varargs_negative() { 624 Quantiles.Scale intermediate = Quantiles.scale(10); 625 try { 626 intermediate.indexes(1, -1, 3); 627 fail("Expected IllegalArgumentException"); 628 } catch (IllegalArgumentException expected) { 629 } 630 } 631 testScale_indexes_varargs_tooHigh()632 public void testScale_indexes_varargs_tooHigh() { 633 Quantiles.Scale intermediate = Quantiles.scale(10); 634 try { 635 intermediate.indexes(1, 11, 3); 636 fail("Expected IllegalArgumentException"); 637 } catch (IllegalArgumentException expected) { 638 } 639 } 640 testScale_indexes_collection_negative()641 public void testScale_indexes_collection_negative() { 642 Quantiles.Scale intermediate = Quantiles.scale(10); 643 try { 644 intermediate.indexes(ImmutableList.of(1, -1, 3)); 645 fail("Expected IllegalArgumentException"); 646 } catch (IllegalArgumentException expected) { 647 } 648 } 649 testScale_indexes_collection_tooHigh()650 public void testScale_indexes_collection_tooHigh() { 651 Quantiles.Scale intermediate = Quantiles.scale(10); 652 try { 653 intermediate.indexes(ImmutableList.of(1, 11, 3)); 654 fail("Expected IllegalArgumentException"); 655 } catch (IllegalArgumentException expected) { 656 } 657 } 658 testScale_index_compute_doubleCollection_empty()659 public void testScale_index_compute_doubleCollection_empty() { 660 Quantiles.ScaleAndIndex intermediate = Quantiles.scale(10).index(3); 661 try { 662 intermediate.compute(EMPTY_DATASET); 663 fail("Expected IllegalArgumentException"); 664 } catch (IllegalArgumentException expected) { 665 } 666 } 667 testScale_index_compute_doubleVarargs_empty()668 public void testScale_index_compute_doubleVarargs_empty() { 669 Quantiles.ScaleAndIndex intermediate = Quantiles.scale(10).index(3); 670 try { 671 intermediate.compute(new double[] {}); 672 fail("Expected IllegalArgumentException"); 673 } catch (IllegalArgumentException expected) { 674 } 675 } 676 testScale_index_compute_longVarargs_empty()677 public void testScale_index_compute_longVarargs_empty() { 678 Quantiles.ScaleAndIndex intermediate = Quantiles.scale(10).index(3); 679 try { 680 intermediate.compute(new long[] {}); 681 fail("Expected IllegalArgumentException"); 682 } catch (IllegalArgumentException expected) { 683 } 684 } 685 testScale_index_compute_intVarargs_empty()686 public void testScale_index_compute_intVarargs_empty() { 687 Quantiles.ScaleAndIndex intermediate = Quantiles.scale(10).index(3); 688 try { 689 intermediate.compute(new int[] {}); 690 fail("Expected IllegalArgumentException"); 691 } catch (IllegalArgumentException expected) { 692 } 693 } 694 testScale_index_computeInPlace_empty()695 public void testScale_index_computeInPlace_empty() { 696 Quantiles.ScaleAndIndex intermediate = Quantiles.scale(10).index(3); 697 try { 698 intermediate.computeInPlace(new double[] {}); 699 fail("Expected IllegalArgumentException"); 700 } catch (IllegalArgumentException expected) { 701 } 702 } 703 testScale_indexes_varargs_compute_doubleCollection_empty()704 public void testScale_indexes_varargs_compute_doubleCollection_empty() { 705 Quantiles.ScaleAndIndexes intermediate = Quantiles.scale(10).indexes(1, 3, 5); 706 try { 707 intermediate.compute(EMPTY_DATASET); 708 fail("Expected IllegalArgumentException"); 709 } catch (IllegalArgumentException expected) { 710 } 711 } 712 testScale_indexes_varargs_compute_doubleVarargs_empty()713 public void testScale_indexes_varargs_compute_doubleVarargs_empty() { 714 Quantiles.ScaleAndIndexes intermediate = Quantiles.scale(10).indexes(1, 3, 5); 715 try { 716 intermediate.compute(new double[] {}); 717 fail("Expected IllegalArgumentException"); 718 } catch (IllegalArgumentException expected) { 719 } 720 } 721 testScale_indexes_varargs_compute_longVarargs_empty()722 public void testScale_indexes_varargs_compute_longVarargs_empty() { 723 Quantiles.ScaleAndIndexes intermediate = Quantiles.scale(10).indexes(1, 3, 5); 724 try { 725 intermediate.compute(new long[] {}); 726 fail("Expected IllegalArgumentException"); 727 } catch (IllegalArgumentException expected) { 728 } 729 } 730 testScale_indexes_varargs_compute_intVarargs_empty()731 public void testScale_indexes_varargs_compute_intVarargs_empty() { 732 Quantiles.ScaleAndIndexes intermediate = Quantiles.scale(10).indexes(1, 3, 5); 733 try { 734 intermediate.compute(new int[] {}); 735 fail("Expected IllegalArgumentException"); 736 } catch (IllegalArgumentException expected) { 737 } 738 } 739 testScale_indexes_varargs_computeInPlace_empty()740 public void testScale_indexes_varargs_computeInPlace_empty() { 741 Quantiles.ScaleAndIndexes intermediate = Quantiles.scale(10).indexes(1, 3, 5); 742 try { 743 intermediate.computeInPlace(new double[] {}); 744 fail("Expected IllegalArgumentException"); 745 } catch (IllegalArgumentException expected) { 746 } 747 } 748 } 749