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 com.google.common.truth.Truth.assertWithMessage; 24 import static java.lang.Double.NEGATIVE_INFINITY; 25 import static java.lang.Double.NaN; 26 import static java.lang.Double.POSITIVE_INFINITY; 27 import static java.math.RoundingMode.CEILING; 28 import static java.math.RoundingMode.FLOOR; 29 import static java.math.RoundingMode.UNNECESSARY; 30 import static org.junit.Assert.assertThrows; 31 32 import com.google.common.collect.ImmutableList; 33 import com.google.common.collect.ImmutableMap; 34 import com.google.common.collect.Ordering; 35 import com.google.common.math.Quantiles.ScaleAndIndexes; 36 import com.google.common.primitives.Doubles; 37 import com.google.common.primitives.Ints; 38 import com.google.common.primitives.Longs; 39 import com.google.common.truth.Correspondence; 40 import com.google.common.truth.Correspondence.BinaryPredicate; 41 import java.util.ArrayList; 42 import java.util.Collections; 43 import java.util.List; 44 import java.util.Random; 45 import junit.framework.TestCase; 46 import org.checkerframework.checker.nullness.qual.Nullable; 47 48 /** 49 * Tests for {@link Quantiles}. 50 * 51 * @author Pete Gillin 52 */ 53 public class QuantilesTest extends TestCase { 54 55 /* 56 * Since Quantiles provides a fluent-style API, each test covers a chain of methods resulting in 57 * the computation of one or more quantiles (or in an error) rather than individual methods. The 58 * tests are divided into three sections: 59 * 1. Tests on a hardcoded dataset for chains starting with median(), quartiles(), and scale(10); 60 * 2. Tests on hardcoded datasets include non-finite values for chains starting with scale(10); 61 * 3. Tests on a mechanically generated dataset for chains starting with percentiles(); 62 * 4. Tests of illegal usages of the API. 63 */ 64 65 /* 66 * Covering every combination would lead to an explosion in the number of tests. So we cover only: 67 * - median with compute taking a double-collection and with computeInPlace; 68 * - quartiles with index and with indexes taking int-varargs, and with compute taking a 69 * double-collection and with computeInPlace; 70 * - scale with index and with indexes taking int-varargs, and with all overloads of compute 71 * taking a double-collection and with computeInPlace; 72 * - scale with indexes taking integer-collection with compute taking a double-collection and with 73 * computeInPlace; 74 * - (except that, for non-finite values, we don't do all combinations exhaustively); 75 * - percentiles with index and with indexes taking int-varargs, and with compute taking a 76 * double-collection and with computeInPlace. 77 */ 78 79 private static final double ALLOWED_ERROR = 1.0e-10; 80 81 /** 82 * A {@link Correspondence} which accepts finite values within {@link #ALLOWED_ERROR} of each 83 * other. 84 */ 85 private static final Correspondence<Number, Number> FINITE_QUANTILE_CORRESPONDENCE = 86 Correspondence.tolerance(ALLOWED_ERROR); 87 88 /** 89 * A {@link Correspondence} which accepts either finite values within {@link #ALLOWED_ERROR} of 90 * each other or identical non-finite values. 91 */ 92 private static final Correspondence<Double, Double> QUANTILE_CORRESPONDENCE = 93 Correspondence.from( 94 new BinaryPredicate<Double, Double>() { 95 @Override 96 public boolean apply(@Nullable Double actual, @Nullable Double expected) { 97 // Test for equality to allow non-finite values to match; otherwise, use the finite 98 // test. 99 return actual.equals(expected) 100 || FINITE_QUANTILE_CORRESPONDENCE.compare(actual, expected); 101 } 102 }, 103 "is identical to or " + FINITE_QUANTILE_CORRESPONDENCE); 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_indexOrderIsMaintained()294 public void testScale_indexes_varargs_compute_indexOrderIsMaintained() { 295 assertThat(Quantiles.scale(10).indexes(0, 10, 5, 1, 8, 1).compute(SIXTEEN_SQUARES_INTEGERS)) 296 .comparingValuesUsing(QUANTILE_CORRESPONDENCE) 297 .containsExactly( 298 0, SIXTEEN_SQUARES_MIN, 299 10, SIXTEEN_SQUARES_MAX, 300 5, SIXTEEN_SQUARES_MEDIAN, 301 1, SIXTEEN_SQUARES_DECILE_1, 302 8, SIXTEEN_SQUARES_DECILE_8) 303 .inOrder(); 304 } 305 testScale_indexes_varargs_compute_doubleVarargs()306 public void testScale_indexes_varargs_compute_doubleVarargs() { 307 double[] dataset = Doubles.toArray(SIXTEEN_SQUARES_DOUBLES); 308 assertThat(Quantiles.scale(10).indexes(0, 10, 5, 1, 8, 1).compute(dataset)) 309 .comparingValuesUsing(QUANTILE_CORRESPONDENCE) 310 .containsExactly( 311 0, SIXTEEN_SQUARES_MIN, 312 10, SIXTEEN_SQUARES_MAX, 313 5, SIXTEEN_SQUARES_MEDIAN, 314 1, SIXTEEN_SQUARES_DECILE_1, 315 8, SIXTEEN_SQUARES_DECILE_8); 316 assertThat(dataset) 317 .usingExactEquality() 318 .containsExactlyElementsIn(SIXTEEN_SQUARES_DOUBLES) 319 .inOrder(); 320 } 321 testScale_indexes_varargs_compute_longVarargs()322 public void testScale_indexes_varargs_compute_longVarargs() { 323 long[] dataset = Longs.toArray(SIXTEEN_SQUARES_LONGS); 324 assertThat(Quantiles.scale(10).indexes(0, 10, 5, 1, 8, 1).compute(dataset)) 325 .comparingValuesUsing(QUANTILE_CORRESPONDENCE) 326 .containsExactly( 327 0, SIXTEEN_SQUARES_MIN, 328 10, SIXTEEN_SQUARES_MAX, 329 5, SIXTEEN_SQUARES_MEDIAN, 330 1, SIXTEEN_SQUARES_DECILE_1, 331 8, SIXTEEN_SQUARES_DECILE_8); 332 assertThat(dataset).asList().isEqualTo(SIXTEEN_SQUARES_LONGS); 333 } 334 testScale_indexes_varargs_compute_intVarargs()335 public void testScale_indexes_varargs_compute_intVarargs() { 336 int[] dataset = Ints.toArray(SIXTEEN_SQUARES_INTEGERS); 337 assertThat(Quantiles.scale(10).indexes(0, 10, 5, 1, 8, 1).compute(dataset)) 338 .comparingValuesUsing(QUANTILE_CORRESPONDENCE) 339 .containsExactly( 340 0, SIXTEEN_SQUARES_MIN, 341 10, SIXTEEN_SQUARES_MAX, 342 5, SIXTEEN_SQUARES_MEDIAN, 343 1, SIXTEEN_SQUARES_DECILE_1, 344 8, SIXTEEN_SQUARES_DECILE_8); 345 assertThat(dataset).asList().isEqualTo(SIXTEEN_SQUARES_INTEGERS); 346 } 347 testScale_indexes_varargs_computeInPlace()348 public void testScale_indexes_varargs_computeInPlace() { 349 double[] dataset = Doubles.toArray(SIXTEEN_SQUARES_DOUBLES); 350 assertThat(Quantiles.scale(10).indexes(0, 10, 5, 1, 8, 1).computeInPlace(dataset)) 351 .comparingValuesUsing(QUANTILE_CORRESPONDENCE) 352 .containsExactly( 353 0, SIXTEEN_SQUARES_MIN, 354 10, SIXTEEN_SQUARES_MAX, 355 5, SIXTEEN_SQUARES_MEDIAN, 356 1, SIXTEEN_SQUARES_DECILE_1, 357 8, SIXTEEN_SQUARES_DECILE_8); 358 assertThat(dataset).usingExactEquality().containsExactlyElementsIn(SIXTEEN_SQUARES_DOUBLES); 359 } 360 testScale_indexes_varargs_computeInPlace_explicitVarargs()361 public void testScale_indexes_varargs_computeInPlace_explicitVarargs() { 362 assertThat(Quantiles.scale(10).indexes(0, 10).computeInPlace(78.9, 12.3, 45.6)) 363 .comparingValuesUsing(QUANTILE_CORRESPONDENCE) 364 .containsExactly( 365 0, 12.3, 366 10, 78.9); 367 } 368 testScale_indexes_collection_compute_doubleCollection()369 public void testScale_indexes_collection_compute_doubleCollection() { 370 // Note that we specify index 1 twice, which by the method contract should be ignored. 371 assertThat( 372 Quantiles.scale(10) 373 .indexes(ImmutableList.of(0, 10, 5, 1, 8, 1)) 374 .compute(SIXTEEN_SQUARES_DOUBLES)) 375 .comparingValuesUsing(QUANTILE_CORRESPONDENCE) 376 .containsExactly( 377 0, SIXTEEN_SQUARES_MIN, 378 10, SIXTEEN_SQUARES_MAX, 379 5, SIXTEEN_SQUARES_MEDIAN, 380 1, SIXTEEN_SQUARES_DECILE_1, 381 8, SIXTEEN_SQUARES_DECILE_8); 382 } 383 testScale_indexes_collection_computeInPlace()384 public void testScale_indexes_collection_computeInPlace() { 385 double[] dataset = Doubles.toArray(SIXTEEN_SQUARES_DOUBLES); 386 assertThat( 387 Quantiles.scale(10) 388 .indexes(ImmutableList.of(0, 10, 5, 1, 8, 1)) 389 .computeInPlace(dataset)) 390 .comparingValuesUsing(QUANTILE_CORRESPONDENCE) 391 .containsExactly( 392 0, SIXTEEN_SQUARES_MIN, 393 10, SIXTEEN_SQUARES_MAX, 394 5, SIXTEEN_SQUARES_MEDIAN, 395 1, SIXTEEN_SQUARES_DECILE_1, 396 8, SIXTEEN_SQUARES_DECILE_8); 397 assertThat(dataset).usingExactEquality().containsExactlyElementsIn(SIXTEEN_SQUARES_DOUBLES); 398 } 399 400 // 2. Tests on hardcoded datasets include non-finite values for chains starting with scale(10): 401 402 private static final ImmutableList<Double> ONE_TO_FIVE_AND_POSITIVE_INFINITY = 403 ImmutableList.of(3.0, 5.0, POSITIVE_INFINITY, 1.0, 4.0, 2.0); 404 private static final ImmutableList<Double> ONE_TO_FIVE_AND_NEGATIVE_INFINITY = 405 ImmutableList.of(3.0, 5.0, NEGATIVE_INFINITY, 1.0, 4.0, 2.0); 406 private static final ImmutableList<Double> NEGATIVE_INFINITY_AND_FIVE_POSITIVE_INFINITIES = 407 ImmutableList.of( 408 POSITIVE_INFINITY, 409 POSITIVE_INFINITY, 410 NEGATIVE_INFINITY, 411 POSITIVE_INFINITY, 412 POSITIVE_INFINITY, 413 POSITIVE_INFINITY); 414 private static final ImmutableList<Double> ONE_TO_FIVE_AND_NAN = 415 ImmutableList.of(3.0, 5.0, NaN, 1.0, 4.0, 2.0); 416 testScale_indexes_varargs_compute_doubleCollection_positiveInfinity()417 public void testScale_indexes_varargs_compute_doubleCollection_positiveInfinity() { 418 assertThat( 419 Quantiles.scale(10) 420 .indexes(0, 1, 2, 8, 9, 10) 421 .compute(ONE_TO_FIVE_AND_POSITIVE_INFINITY)) 422 .comparingValuesUsing(QUANTILE_CORRESPONDENCE) 423 .containsExactly( 424 0, 1.0, 425 1, 1.5, 426 2, 2.0, 427 8, 5.0, 428 9, POSITIVE_INFINITY, // interpolating between 5.0 and POSITIVE_INFINITY 429 10, POSITIVE_INFINITY); 430 } 431 testScale_index_compute_doubleCollection_positiveInfinity()432 public void testScale_index_compute_doubleCollection_positiveInfinity() { 433 // interpolating between 5.0 and POSITIVE_INFINITY 434 assertThat(Quantiles.scale(10).index(9).compute(ONE_TO_FIVE_AND_POSITIVE_INFINITY)) 435 .isPositiveInfinity(); 436 } 437 testScale_indexes_varargs_compute_doubleCollection_negativeInfinity()438 public void testScale_indexes_varargs_compute_doubleCollection_negativeInfinity() { 439 assertThat( 440 Quantiles.scale(10) 441 .indexes(0, 1, 2, 8, 9, 10) 442 .compute(ONE_TO_FIVE_AND_NEGATIVE_INFINITY)) 443 .comparingValuesUsing(QUANTILE_CORRESPONDENCE) 444 .containsExactly( 445 0, NEGATIVE_INFINITY, 446 1, NEGATIVE_INFINITY, // interpolating between NEGATIVE_INFINITY and 1.0 447 2, 1.0, 448 8, 4.0, 449 9, 4.5, 450 10, 5.0); 451 } 452 testScale_index_compute_doubleCollection_negativeInfinity()453 public void testScale_index_compute_doubleCollection_negativeInfinity() { 454 // interpolating between NEGATIVE_INFINITY and 1.0 455 assertThat(Quantiles.scale(10).index(1).compute(ONE_TO_FIVE_AND_NEGATIVE_INFINITY)) 456 .isNegativeInfinity(); 457 } 458 testScale_indexes_varargs_compute_doubleCollection_bothInfinities()459 public void testScale_indexes_varargs_compute_doubleCollection_bothInfinities() { 460 assertThat( 461 Quantiles.scale(10) 462 .indexes(0, 1, 2, 8, 9, 10) 463 .compute(NEGATIVE_INFINITY_AND_FIVE_POSITIVE_INFINITIES)) 464 .comparingValuesUsing(QUANTILE_CORRESPONDENCE) 465 .containsExactly( 466 0, NEGATIVE_INFINITY, 467 1, NaN, // interpolating between NEGATIVE_ and POSITIVE_INFINITY values 468 2, POSITIVE_INFINITY, 469 8, POSITIVE_INFINITY, 470 9, POSITIVE_INFINITY, // interpolating between two POSITIVE_INFINITY values 471 10, POSITIVE_INFINITY); 472 } 473 testScale_indexes_varargs_compute_doubleCollection_nan()474 public void testScale_indexes_varargs_compute_doubleCollection_nan() { 475 assertThat(Quantiles.scale(10).indexes(0, 1, 2, 8, 9, 10).compute(ONE_TO_FIVE_AND_NAN)) 476 .comparingValuesUsing(QUANTILE_CORRESPONDENCE) 477 .containsExactly( 478 0, NaN, 479 1, NaN, 480 2, NaN, 481 8, NaN, 482 9, NaN, 483 10, NaN); 484 } 485 testScale_index_compute_doubleCollection_nan()486 public void testScale_index_compute_doubleCollection_nan() { 487 assertThat(Quantiles.scale(10).index(5).compute(ONE_TO_FIVE_AND_NAN)).isNaN(); 488 } 489 490 // 3. Tests on a mechanically generated dataset for chains starting with percentiles(): 491 492 private static final int PSEUDORANDOM_DATASET_SIZE = 9951; 493 private static final ImmutableList<Double> PSEUDORANDOM_DATASET = generatePseudorandomDataset(); 494 private static final ImmutableList<Double> PSEUDORANDOM_DATASET_SORTED = 495 Ordering.natural().immutableSortedCopy(PSEUDORANDOM_DATASET); 496 generatePseudorandomDataset()497 private static ImmutableList<Double> generatePseudorandomDataset() { 498 Random random = new Random(2211275185798966364L); 499 ImmutableList.Builder<Double> largeDatasetBuilder = ImmutableList.builder(); 500 for (int i = 0; i < PSEUDORANDOM_DATASET_SIZE; i++) { 501 largeDatasetBuilder.add(random.nextGaussian()); 502 } 503 return largeDatasetBuilder.build(); 504 } 505 expectedLargeDatasetPercentile(int index)506 private static double expectedLargeDatasetPercentile(int index) { 507 // We have q=100, k=index, and N=9951. Therefore k*(N-1)/q is 99.5*index. If index is even, that 508 // is an integer 199*index/2. If index is odd, that is halfway between floor(199*index/2) and 509 // ceil(199*index/2). 510 if (index % 2 == 0) { 511 int position = IntMath.divide(199 * index, 2, UNNECESSARY); 512 return PSEUDORANDOM_DATASET_SORTED.get(position); 513 } else { 514 int positionFloor = IntMath.divide(199 * index, 2, FLOOR); 515 int positionCeil = IntMath.divide(199 * index, 2, CEILING); 516 double lowerValue = PSEUDORANDOM_DATASET_SORTED.get(positionFloor); 517 double upperValue = PSEUDORANDOM_DATASET_SORTED.get(positionCeil); 518 return (lowerValue + upperValue) / 2.0; 519 } 520 } 521 testPercentiles_index_compute_doubleCollection()522 public void testPercentiles_index_compute_doubleCollection() { 523 for (int index = 0; index <= 100; index++) { 524 assertWithMessage("quantile at index " + index) 525 .that(percentiles().index(index).compute(PSEUDORANDOM_DATASET)) 526 .isWithin(ALLOWED_ERROR) 527 .of(expectedLargeDatasetPercentile(index)); 528 } 529 } 530 531 @AndroidIncompatible // slow testPercentiles_index_computeInPlace()532 public void testPercentiles_index_computeInPlace() { 533 // Assert that the computation gives the correct result for all possible percentiles. 534 for (int index = 0; index <= 100; index++) { 535 double[] dataset = Doubles.toArray(PSEUDORANDOM_DATASET); 536 assertWithMessage("quantile at index " + index) 537 .that(percentiles().index(index).computeInPlace(dataset)) 538 .isWithin(ALLOWED_ERROR) 539 .of(expectedLargeDatasetPercentile(index)); 540 } 541 542 // Assert that the dataset contains the same elements after the in-place computation (although 543 // they may be reordered). We only do this for one index rather than for all indexes, as it is 544 // quite expensive (quadratic in the size of PSEUDORANDOM_DATASET). 545 double[] dataset = Doubles.toArray(PSEUDORANDOM_DATASET); 546 @SuppressWarnings("unused") 547 double actual = percentiles().index(33).computeInPlace(dataset); 548 assertThat(dataset).usingExactEquality().containsExactlyElementsIn(PSEUDORANDOM_DATASET); 549 } 550 testPercentiles_indexes_varargsPairs_compute_doubleCollection()551 public void testPercentiles_indexes_varargsPairs_compute_doubleCollection() { 552 for (int index1 = 0; index1 <= 100; index1++) { 553 for (int index2 = 0; index2 <= 100; index2++) { 554 ImmutableMap.Builder<Integer, Double> expectedBuilder = ImmutableMap.builder(); 555 expectedBuilder.put(index1, expectedLargeDatasetPercentile(index1)); 556 if (index2 != index1) { 557 expectedBuilder.put(index2, expectedLargeDatasetPercentile(index2)); 558 } 559 assertThat(percentiles().indexes(index1, index2).compute(PSEUDORANDOM_DATASET)) 560 .comparingValuesUsing(QUANTILE_CORRESPONDENCE) 561 .containsExactlyEntriesIn(expectedBuilder.buildOrThrow()); 562 } 563 } 564 } 565 testPercentiles_indexes_varargsAll_compute_doubleCollection()566 public void testPercentiles_indexes_varargsAll_compute_doubleCollection() { 567 ArrayList<Integer> indexes = new ArrayList<>(); 568 ImmutableMap.Builder<Integer, Double> expectedBuilder = ImmutableMap.builder(); 569 for (int index = 0; index <= 100; index++) { 570 indexes.add(index); 571 expectedBuilder.put(index, expectedLargeDatasetPercentile(index)); 572 } 573 Random random = new Random(770683168895677741L); 574 Collections.shuffle(indexes, random); 575 assertThat(percentiles().indexes(Ints.toArray(indexes)).compute(PSEUDORANDOM_DATASET)) 576 .comparingValuesUsing(QUANTILE_CORRESPONDENCE) 577 .containsExactlyEntriesIn(expectedBuilder.buildOrThrow()); 578 } 579 580 @AndroidIncompatible // slow testPercentiles_indexes_varargsAll_computeInPlace()581 public void testPercentiles_indexes_varargsAll_computeInPlace() { 582 double[] dataset = Doubles.toArray(PSEUDORANDOM_DATASET); 583 List<Integer> indexes = new ArrayList<>(); 584 ImmutableMap.Builder<Integer, Double> expectedBuilder = ImmutableMap.builder(); 585 for (int index = 0; index <= 100; index++) { 586 indexes.add(index); 587 expectedBuilder.put(index, expectedLargeDatasetPercentile(index)); 588 } 589 Random random = new Random(770683168895677741L); 590 Collections.shuffle(indexes, random); 591 assertThat(percentiles().indexes(Ints.toArray(indexes)).computeInPlace(dataset)) 592 .comparingValuesUsing(QUANTILE_CORRESPONDENCE) 593 .containsExactlyEntriesIn(expectedBuilder.buildOrThrow()); 594 assertThat(dataset).usingExactEquality().containsExactlyElementsIn(PSEUDORANDOM_DATASET); 595 } 596 597 // 4. Tests of illegal usages of the API: 598 599 private static final ImmutableList<Double> EMPTY_DATASET = ImmutableList.of(); 600 testScale_zero()601 public void testScale_zero() { 602 assertThrows(IllegalArgumentException.class, () -> Quantiles.scale(0)); 603 } 604 testScale_negative()605 public void testScale_negative() { 606 assertThrows(IllegalArgumentException.class, () -> Quantiles.scale(-4)); 607 } 608 testScale_index_negative()609 public void testScale_index_negative() { 610 Quantiles.Scale intermediate = Quantiles.scale(10); 611 assertThrows(IllegalArgumentException.class, () -> intermediate.index(-1)); 612 } 613 testScale_index_tooHigh()614 public void testScale_index_tooHigh() { 615 Quantiles.Scale intermediate = Quantiles.scale(10); 616 assertThrows(IllegalArgumentException.class, () -> intermediate.index(11)); 617 } 618 testScale_indexes_varargs_negative()619 public void testScale_indexes_varargs_negative() { 620 Quantiles.Scale intermediate = Quantiles.scale(10); 621 assertThrows(IllegalArgumentException.class, () -> intermediate.indexes(1, -1, 3)); 622 } 623 testScale_indexes_varargs_tooHigh()624 public void testScale_indexes_varargs_tooHigh() { 625 Quantiles.Scale intermediate = Quantiles.scale(10); 626 assertThrows(IllegalArgumentException.class, () -> intermediate.indexes(1, 11, 3)); 627 } 628 testScale_indexes_collection_negative()629 public void testScale_indexes_collection_negative() { 630 Quantiles.Scale intermediate = Quantiles.scale(10); 631 assertThrows( 632 IllegalArgumentException.class, () -> intermediate.indexes(ImmutableList.of(1, -1, 3))); 633 } 634 testScale_indexes_collection_tooHigh()635 public void testScale_indexes_collection_tooHigh() { 636 Quantiles.Scale intermediate = Quantiles.scale(10); 637 assertThrows( 638 IllegalArgumentException.class, () -> intermediate.indexes(ImmutableList.of(1, 11, 3))); 639 } 640 testScale_index_compute_doubleCollection_empty()641 public void testScale_index_compute_doubleCollection_empty() { 642 Quantiles.ScaleAndIndex intermediate = Quantiles.scale(10).index(3); 643 assertThrows(IllegalArgumentException.class, () -> intermediate.compute(EMPTY_DATASET)); 644 } 645 testScale_index_compute_doubleVarargs_empty()646 public void testScale_index_compute_doubleVarargs_empty() { 647 Quantiles.ScaleAndIndex intermediate = Quantiles.scale(10).index(3); 648 assertThrows(IllegalArgumentException.class, () -> intermediate.compute(new double[] {})); 649 } 650 testScale_index_compute_longVarargs_empty()651 public void testScale_index_compute_longVarargs_empty() { 652 Quantiles.ScaleAndIndex intermediate = Quantiles.scale(10).index(3); 653 assertThrows(IllegalArgumentException.class, () -> intermediate.compute(new long[] {})); 654 } 655 testScale_index_compute_intVarargs_empty()656 public void testScale_index_compute_intVarargs_empty() { 657 Quantiles.ScaleAndIndex intermediate = Quantiles.scale(10).index(3); 658 assertThrows(IllegalArgumentException.class, () -> intermediate.compute(new int[] {})); 659 } 660 testScale_index_computeInPlace_empty()661 public void testScale_index_computeInPlace_empty() { 662 Quantiles.ScaleAndIndex intermediate = Quantiles.scale(10).index(3); 663 assertThrows( 664 IllegalArgumentException.class, () -> intermediate.computeInPlace(new double[] {})); 665 } 666 testScale_indexes_varargs_compute_doubleCollection_empty()667 public void testScale_indexes_varargs_compute_doubleCollection_empty() { 668 Quantiles.ScaleAndIndexes intermediate = Quantiles.scale(10).indexes(1, 3, 5); 669 assertThrows(IllegalArgumentException.class, () -> intermediate.compute(EMPTY_DATASET)); 670 } 671 testScale_indexes_varargs_compute_doubleVarargs_empty()672 public void testScale_indexes_varargs_compute_doubleVarargs_empty() { 673 Quantiles.ScaleAndIndexes intermediate = Quantiles.scale(10).indexes(1, 3, 5); 674 assertThrows(IllegalArgumentException.class, () -> intermediate.compute(new double[] {})); 675 } 676 testScale_indexes_varargs_compute_longVarargs_empty()677 public void testScale_indexes_varargs_compute_longVarargs_empty() { 678 Quantiles.ScaleAndIndexes intermediate = Quantiles.scale(10).indexes(1, 3, 5); 679 assertThrows(IllegalArgumentException.class, () -> intermediate.compute(new long[] {})); 680 } 681 testScale_indexes_varargs_compute_intVarargs_empty()682 public void testScale_indexes_varargs_compute_intVarargs_empty() { 683 Quantiles.ScaleAndIndexes intermediate = Quantiles.scale(10).indexes(1, 3, 5); 684 assertThrows(IllegalArgumentException.class, () -> intermediate.compute(new int[] {})); 685 } 686 testScale_indexes_varargs_computeInPlace_empty()687 public void testScale_indexes_varargs_computeInPlace_empty() { 688 Quantiles.ScaleAndIndexes intermediate = Quantiles.scale(10).indexes(1, 3, 5); 689 assertThrows( 690 IllegalArgumentException.class, () -> intermediate.computeInPlace(new double[] {})); 691 } 692 testScale_indexes_indexes_computeInPlace_empty()693 public void testScale_indexes_indexes_computeInPlace_empty() { 694 int[] emptyIndexes = {}; 695 assertThrows( 696 IllegalArgumentException.class, 697 () -> { 698 Quantiles.ScaleAndIndexes unused = Quantiles.scale(10).indexes(emptyIndexes); 699 }); 700 } 701 } 702