View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  /*
19   * This is not the original file distributed by the Apache Software Foundation
20   * It has been modified by the Hipparchus project
21   */
22  package org.hipparchus.stat.descriptive.rank;
23  
24  import org.hipparchus.distribution.continuous.NormalDistribution;
25  import org.hipparchus.exception.MathIllegalArgumentException;
26  import org.hipparchus.exception.NullArgumentException;
27  import org.hipparchus.random.RandomDataGenerator;
28  import org.hipparchus.stat.LocalizedStatFormats;
29  import org.hipparchus.stat.descriptive.UnivariateStatistic;
30  import org.hipparchus.stat.descriptive.UnivariateStatisticAbstractTest;
31  import org.hipparchus.stat.descriptive.rank.Percentile.EstimationType;
32  import org.hipparchus.stat.ranking.NaNStrategy;
33  import org.hipparchus.util.KthSelector;
34  import org.hipparchus.util.PivotingStrategy;
35  import org.junit.jupiter.api.Assertions;
36  import org.junit.jupiter.api.BeforeEach;
37  import org.junit.jupiter.api.Test;
38  
39  import java.util.Arrays;
40  import java.util.Locale;
41  
42  import static org.junit.jupiter.api.Assertions.assertEquals;
43  import static org.junit.jupiter.api.Assertions.assertFalse;
44  import static org.junit.jupiter.api.Assertions.assertNotEquals;
45  import static org.junit.jupiter.api.Assertions.assertThrows;
46  import static org.junit.jupiter.api.Assertions.assertTrue;
47  import static org.junit.jupiter.api.Assertions.fail;
48  
49  /**
50   * Test cases for the {@link Percentile} class.
51   */
52  public class PercentileTest extends UnivariateStatisticAbstractTest{
53  
54      private double quantile;
55  
56      /**
57       * {@link Percentile.EstimationType type}
58       * of estimation to be used while calling {@link #getUnivariateStatistic()}
59       */
60      private Percentile.EstimationType type;
61  
62      /**
63       * {@link NaNStrategy}
64       * of estimation to be used while calling {@link #getUnivariateStatistic()}
65       */
66      private NaNStrategy nanStrategy;
67  
68      /**
69       * kth selector
70       */
71      private KthSelector kthSelector;
72  
73      /**
74       * A default percentile to be used for {@link #getUnivariateStatistic()}
75       */
76      protected final double DEFAULT_PERCENTILE = 95d;
77  
78      /**
79       * Before method to ensure defaults retained
80       */
81      @BeforeEach
82      void setup() {
83          quantile         = 95.0;
84          type             = Percentile.EstimationType.LEGACY;
85          nanStrategy      = NaNStrategy.REMOVED;
86          kthSelector      = new KthSelector(PivotingStrategy.MEDIAN_OF_3);
87      }
88  
89      private void reset(final double p, final Percentile.EstimationType type) {
90          this.quantile = p;
91          this.type     = type;
92          nanStrategy   = (type == Percentile.EstimationType.LEGACY) ? NaNStrategy.FIXED : NaNStrategy.REMOVED;
93      }
94  
95      @Override
96      public Percentile getUnivariateStatistic() {
97          return new Percentile(quantile)
98                  .withEstimationType(type)
99                  .withNaNStrategy(nanStrategy)
100                 .withKthSelector(kthSelector);
101     }
102 
103     @Override
104     public double expectedValue() {
105         return this.percentile95;
106     }
107 
108     @Test
109     void testHighPercentile(){
110         final double[] d = new double[]{1, 2, 3};
111         final Percentile p = new Percentile(75);
112         assertEquals(3.0, p.evaluate(d), 1.0e-5);
113     }
114 
115     @Test
116     void testLowPercentile() {
117         final double[] d = new double[] {0, 1};
118         final Percentile p = new Percentile(25);
119         assertEquals(0d, p.evaluate(d), Double.MIN_VALUE);
120     }
121 
122     @Test
123     void testPercentile() {
124         final double[] d = new double[] {1, 3, 2, 4};
125         final Percentile p = new Percentile(30);
126         assertEquals(1.5, p.evaluate(d), 1.0e-5);
127         p.setQuantile(25);
128         assertEquals(1.25, p.evaluate(d), 1.0e-5);
129         p.setQuantile(75);
130         assertEquals(3.75, p.evaluate(d), 1.0e-5);
131         p.setQuantile(50);
132         assertEquals(2.5, p.evaluate(d), 1.0e-5);
133 
134         // invalid percentiles
135         try {
136             p.evaluate(d, 0, d.length, -1.0);
137             fail();
138         } catch (final MathIllegalArgumentException ex) {
139             Assertions.assertEquals(LocalizedStatFormats.OUT_OF_BOUNDS_QUANTILE_VALUE, ex.getSpecifier());
140             Assertions.assertEquals(-1, (Double) ex.getParts()[0], 1.0e-15);
141             Assertions.assertEquals("out of bounds quantile value: -1, must be in (0, 100]",
142                                     ex.getMessage(Locale.TRADITIONAL_CHINESE));
143         }
144         try {
145             p.evaluate(d, 0, d.length, 101.0);
146             fail();
147         } catch (final MathIllegalArgumentException ex) {
148             // success
149         }
150     }
151 
152     @Test
153     void testNISTExample() {
154         final double[] d = new double[] {95.1772, 95.1567, 95.1937, 95.1959,
155                 95.1442, 95.0610,  95.1591, 95.1195, 95.1772, 95.0925, 95.1990, 95.1682
156         };
157         final Percentile p = new Percentile(90);
158         assertEquals(95.1981, p.evaluate(d), 1.0e-4);
159         assertEquals(95.1990, p.evaluate(d,0,d.length, 100d), 0);
160     }
161 
162     @Test
163     void test5() {
164         final Percentile percentile = new Percentile(5);
165         assertEquals(this.percentile5, percentile.evaluate(testArray), getTolerance());
166     }
167 
168     @Test
169     void testNullEmpty() {
170         final Percentile percentile = new Percentile(50);
171         final double[] nullArray = null;
172         final double[] emptyArray = new double[] {};
173         try {
174             percentile.evaluate(nullArray);
175             fail("Expecting NullArgumentException for null array");
176         } catch (final NullArgumentException ex) {
177             // expected
178         }
179         assertTrue(Double.isNaN(percentile.evaluate(emptyArray)));
180     }
181 
182     @Test
183     void testSingleton() {
184         final Percentile percentile = new Percentile(50);
185         final double[] singletonArray = new double[] {1d};
186         assertEquals(1d, percentile.evaluate(singletonArray), 0);
187         assertEquals(1d, percentile.evaluate(singletonArray, 0, 1), 0);
188         assertEquals(1d, percentile.evaluate(singletonArray, 0, 1, 5), 0);
189         assertEquals(1d, percentile.evaluate(singletonArray, 0, 1, 100), 0);
190         assertTrue(Double.isNaN(percentile.evaluate(singletonArray, 0, 0)));
191     }
192 
193     @Test
194     void testSpecialValues() {
195         final Percentile percentile = new Percentile(50);
196         double[] specialValues = new double[] {0d, 1d, 2d, 3d, 4d,  Double.NaN};
197         assertEquals(/*2.5d*/2d, percentile.evaluate(specialValues), 0);
198         specialValues = new double[] { Double.NEGATIVE_INFINITY, 1d, 2d, 3d, Double.NaN, Double.POSITIVE_INFINITY };
199         assertEquals(/*2.5d*/2d, percentile.evaluate(specialValues), 0);
200         specialValues = new double[] {1d, 1d, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY};
201         assertTrue(Double.isInfinite(percentile.evaluate(specialValues)));
202         specialValues = new double[] {1d, 1d, Double.NaN, Double.NaN};
203         assertFalse(Double.isNaN(percentile.evaluate(specialValues)));
204         assertEquals(1d, percentile.evaluate(specialValues));
205         specialValues = new double[] {1d, 1d, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY};
206         // Interpolation results in NEGATIVE_INFINITY + POSITIVE_INFINITY
207         assertTrue(Double.isNaN(percentile.evaluate(specialValues)));
208     }
209 
210     @Test
211     void testSetQuantile() {
212         final Percentile percentile = new Percentile(10);
213         percentile.setQuantile(100); // OK
214         assertEquals(100, percentile.getQuantile(), 0);
215         try {
216             percentile.setQuantile(0);
217             fail("Expecting MathIllegalArgumentException");
218         } catch (final MathIllegalArgumentException ex) {
219             // expected
220         }
221         try {
222             new Percentile(0);
223             fail("Expecting MathIllegalArgumentException");
224         } catch (final MathIllegalArgumentException ex) {
225             // expected
226         }
227     }
228 
229     //Below tests are basically to run for all estimation types.
230     /**
231      * While {@link #testHighPercentile()} checks only for the existing
232      * implementation; this method verifies for all the types including Percentile.Type.CM Percentile.Type.
233      */
234     @Test
235     void testAllTechniquesHighPercentile() {
236         final double[] d = new double[] { 1, 2, 3 };
237         testAssertMappedValues(d, new Object[][] { { Percentile.EstimationType.LEGACY, 3d }, { Percentile.EstimationType.R_1, 3d },
238                 { Percentile.EstimationType.R_2, 3d }, { Percentile.EstimationType.R_3, 2d }, { Percentile.EstimationType.R_4, 2.25 }, { Percentile.EstimationType.R_5, 2.75 },
239                 { Percentile.EstimationType.R_6, 3d }, { Percentile.EstimationType.R_7, 2.5 },{ Percentile.EstimationType.R_8, 2.83333 }, {Percentile.EstimationType.R_9,2.81250} },
240                 75d, 1.0e-5);
241     }
242 
243     @Test
244     void testAllTechniquesLowPercentile() {
245         final double[] d = new double[] { 0, 1 };
246         testAssertMappedValues(d, new Object[][] { { Percentile.EstimationType.LEGACY, 0d }, { Percentile.EstimationType.R_1, 0d },
247                 { Percentile.EstimationType.R_2, 0d }, { Percentile.EstimationType.R_3, 0d }, { Percentile.EstimationType.R_4, 0d }, {Percentile.EstimationType.R_5, 0d}, {Percentile.EstimationType.R_6, 0d},
248                 { Percentile.EstimationType.R_7, 0.25 }, { Percentile.EstimationType.R_8, 0d }, {Percentile.EstimationType.R_9, 0d} },
249                 25d, Double.MIN_VALUE);
250     }
251 
252     public void checkAllTechniquesPercentile() {
253         final double[] d = new double[] { 1, 3, 2, 4 };
254 
255         testAssertMappedValues(d, new Object[][] { { Percentile.EstimationType.LEGACY, 1.5d },
256                 { Percentile.EstimationType.R_1, 2d }, { Percentile.EstimationType.R_2, 2d }, { Percentile.EstimationType.R_3, 1d }, { Percentile.EstimationType.R_4, 1.2 }, {Percentile.EstimationType.R_5, 1.7},
257                 { Percentile.EstimationType.R_6, 1.5 },{ Percentile.EstimationType.R_7, 1.9 }, { Percentile.EstimationType.R_8, 1.63333 },{ Percentile.EstimationType.R_9, 1.65 } },
258                 30d, 1.0e-05);
259 
260         testAssertMappedValues(d, new Object[][] { { Percentile.EstimationType.LEGACY, 1.25d },
261                 { Percentile.EstimationType.R_1, 1d }, { Percentile.EstimationType.R_2, 1.5d }, { Percentile.EstimationType.R_3, 1d }, { Percentile.EstimationType.R_4, 1d }, {Percentile.EstimationType.R_5, 1.5},
262                 { Percentile.EstimationType.R_6, 1.25 },{ Percentile.EstimationType.R_7, 1.75 },
263                 { Percentile.EstimationType.R_8, 1.41667 }, { Percentile.EstimationType.R_9, 1.43750 } }, 25d, 1.0e-05);
264 
265         testAssertMappedValues(d, new Object[][] { { Percentile.EstimationType.LEGACY, 3.75d },
266                 { Percentile.EstimationType.R_1, 3d }, { Percentile.EstimationType.R_2, 3.5d }, { Percentile.EstimationType.R_3, 3d }, { Percentile.EstimationType.R_4, 3d },
267                 { Percentile.EstimationType.R_5, 3.5d },{ Percentile.EstimationType.R_6, 3.75d }, { Percentile.EstimationType.R_7, 3.25 },
268                 { Percentile.EstimationType.R_8, 3.58333 },{ Percentile.EstimationType.R_9, 3.56250} }, 75d, 1.0e-05);
269 
270         testAssertMappedValues(d, new Object[][] { { Percentile.EstimationType.LEGACY, 2.5d },
271                 { Percentile.EstimationType.R_1, 2d }, { Percentile.EstimationType.R_2, 2.5d }, { Percentile.EstimationType.R_3, 2d }, { Percentile.EstimationType.R_4, 2d },
272                 { Percentile.EstimationType.R_5, 2.5 },{ Percentile.EstimationType.R_6, 2.5 },{ Percentile.EstimationType.R_7, 2.5 },
273                 { Percentile.EstimationType.R_8, 2.5 },{ Percentile.EstimationType.R_9, 2.5 } }, 50d, 1.0e-05);
274 
275         // invalid percentiles
276         for (final Percentile.EstimationType e : Percentile.EstimationType.values()) {
277             try {
278                 reset(-1.0, e);
279                 getUnivariateStatistic().evaluate(d, 0, d.length);
280                 fail();
281             } catch (final MathIllegalArgumentException ex) {
282                 // success
283             }
284         }
285 
286         for (final Percentile.EstimationType e : Percentile.EstimationType.values()) {
287             try {
288                 reset(101.0, e);
289                 getUnivariateStatistic().evaluate(d, 0, d.length);
290                 fail();
291             } catch (final MathIllegalArgumentException ex) {
292                 // success
293             }
294         }
295     }
296 
297     @Test
298     void testAllTechniquesPercentileUsingMedianOf3Pivoting() {
299         kthSelector = new KthSelector(PivotingStrategy.MEDIAN_OF_3);
300         checkAllTechniquesPercentile();
301     }
302 
303     @Test
304     void testAllTechniquesPercentileUsingCentralPivoting() {
305         kthSelector = new KthSelector(PivotingStrategy.CENTRAL);
306         checkAllTechniquesPercentile();
307     }
308 
309     @Test
310     void testAllTechniquesNISTExample() {
311         final double[] d =
312                 new double[] { 95.1772, 95.1567, 95.1937, 95.1959, 95.1442, 95.0610,
313                                95.1591, 95.1195, 95.1772, 95.0925, 95.1990, 95.1682 };
314 
315         testAssertMappedValues(d, new Object[][] { { Percentile.EstimationType.LEGACY, 95.1981 },
316                 { Percentile.EstimationType.R_1, 95.19590 }, { Percentile.EstimationType.R_2, 95.19590 }, { Percentile.EstimationType.R_3, 95.19590 },
317                 { Percentile.EstimationType.R_4, 95.19546 }, { Percentile.EstimationType.R_5, 95.19683 }, { Percentile.EstimationType.R_6, 95.19807 },
318                 { Percentile.EstimationType.R_7, 95.19568 }, { Percentile.EstimationType.R_8, 95.19724 }, { Percentile.EstimationType.R_9, 95.19714 } }, 90d,
319                 1.0e-04);
320 
321         for (final Percentile.EstimationType e : Percentile.EstimationType.values()) {
322             reset(100.0, e);
323             assertEquals(95.1990, getUnivariateStatistic().evaluate(d), 1.0e-4);
324         }
325     }
326 
327     @Test
328     void testAllTechniques5() {
329         reset(5, Percentile.EstimationType.LEGACY);
330         final UnivariateStatistic percentile = getUnivariateStatistic();
331         assertEquals(this.percentile5, percentile.evaluate(testArray), getTolerance());
332         testAssertMappedValues(testArray,
333                 new Object[][] { { Percentile.EstimationType.LEGACY, percentile5 }, { Percentile.EstimationType.R_1, 8.8000 },
334                         { Percentile.EstimationType.R_2, 8.8000 }, { Percentile.EstimationType.R_3, 8.2000 }, { Percentile.EstimationType.R_4, 8.2600 },
335                         { Percentile.EstimationType.R_5, 8.5600 }, { Percentile.EstimationType.R_6, 8.2900 },
336                         { Percentile.EstimationType.R_7, 8.8100 }, { Percentile.EstimationType.R_8, 8.4700 },
337                         { Percentile.EstimationType.R_9, 8.4925 }}, 5d, getTolerance());
338     }
339 
340     @Test
341     void testAllTechniquesNullEmpty() {
342 
343         final double[] nullArray = null;
344         final double[] emptyArray = new double[] {};
345         for (final Percentile.EstimationType e : Percentile.EstimationType.values()) {
346             reset (50, e);
347             final UnivariateStatistic percentile = getUnivariateStatistic();
348             try {
349                 percentile.evaluate(nullArray);
350                 fail("Expecting NullArgumentException "
351                         + "for null array");
352             } catch (final NullArgumentException ex) {
353                 // expected
354             }
355             assertTrue(Double.isNaN(percentile.evaluate(emptyArray)));
356         }
357 
358     }
359 
360     @Test
361     void testAllTechniquesSingleton() {
362         final double[] singletonArray = new double[] { 1d };
363         for (final Percentile.EstimationType e : Percentile.EstimationType.values()) {
364             reset (50, e);
365             final UnivariateStatistic percentile = getUnivariateStatistic();
366             assertEquals(1d, percentile.evaluate(singletonArray), 0);
367             assertEquals(1d, percentile.evaluate(singletonArray, 0, 1), 0);
368             assertEquals(1d, new Percentile().evaluate(singletonArray, 0, 1, 5), 0);
369             assertEquals(1d, new Percentile().evaluate(singletonArray, 0, 1, 100), 0);
370             assertTrue(Double.isNaN(percentile.evaluate(singletonArray, 0, 0)));
371         }
372     }
373 
374     @Test
375     void testAllTechniquesEmpty() {
376         final double[] singletonArray = new double[] { };
377         for (final Percentile.EstimationType e : Percentile.EstimationType.values()) {
378             reset (50, e);
379             final UnivariateStatistic percentile = getUnivariateStatistic();
380             assertEquals(Double.NaN, percentile.evaluate(singletonArray), 0);
381             assertEquals(Double.NaN, percentile.evaluate(singletonArray, 0, 0), 0);
382             assertEquals(Double.NaN, new Percentile().evaluate(singletonArray, 0, 0, 5), 0);
383             assertEquals(Double.NaN, new Percentile().evaluate(singletonArray, 0, 0, 100), 0);
384             assertTrue(Double.isNaN(percentile.evaluate(singletonArray, 0, 0)));
385         }
386     }
387 
388     @Test
389     void testReplaceNanInRange() {
390         final double[] specialValues =
391             new double[] { 0d, 1d, 2d, 3d, 4d, Double.NaN, Double.NaN, 5d, 7d, Double.NaN, 8d};
392         assertEquals(/*Double.NaN*/3.5, new Percentile(50d).evaluate(specialValues), 0d);
393         reset (50, Percentile.EstimationType.R_1);
394         assertEquals(3d, getUnivariateStatistic().evaluate(specialValues), 0d);
395         reset (50, Percentile.EstimationType.R_2);
396         assertEquals(3.5d, getUnivariateStatistic().evaluate(specialValues), 0d);
397         assertEquals(Double.POSITIVE_INFINITY,
398                      new Percentile(70).withNaNStrategy(NaNStrategy.MAXIMAL).evaluate(specialValues),
399                      0d);
400     }
401 
402     @Test
403     void testRemoveNan() {
404         final double[] specialValues = new double[] { 0d, 1d, 2d, 3d, 4d, Double.NaN };
405         final double[] expectedValues = new double[] { 0d, 1d, 2d, 3d, 4d };
406         reset (50, Percentile.EstimationType.R_1);
407         assertEquals(2.0, getUnivariateStatistic().evaluate(specialValues), 0d);
408         assertEquals(2.0, getUnivariateStatistic().evaluate(expectedValues),0d);
409         assertTrue(Double.isNaN(getUnivariateStatistic().evaluate(specialValues,5,1)));
410         assertEquals(4d, getUnivariateStatistic().evaluate(specialValues, 4, 2), 0d);
411         assertEquals(3d, getUnivariateStatistic().evaluate(specialValues,3,3),0d);
412         reset(50, Percentile.EstimationType.R_2);
413         assertEquals(3.5d, getUnivariateStatistic().evaluate(specialValues,3,3),0d);
414     }
415 
416     @Test
417     void testPercentileCopy() {
418        reset(50d, Percentile.EstimationType.LEGACY);
419        final Percentile original = getUnivariateStatistic();
420        final Percentile copy = new Percentile(original);
421        assertEquals(original.getNaNStrategy(),copy.getNaNStrategy());
422        assertEquals(original.getQuantile(), copy.getQuantile(),0d);
423        assertEquals(original.getEstimationType(),copy.getEstimationType());
424        assertEquals(NaNStrategy.FIXED, original.getNaNStrategy());
425     }
426 
427     @Test
428     void testAllTechniquesSpecialValues() {
429         reset(50d, Percentile.EstimationType.LEGACY);
430         final UnivariateStatistic percentile = getUnivariateStatistic();
431         double[] specialValues = new double[] { 0d, 1d, 2d, 3d, 4d, Double.NaN };
432         assertEquals(2.5d, percentile.evaluate(specialValues), 0);
433 
434         testAssertMappedValues(specialValues, new Object[][] {
435                 { Percentile.EstimationType.LEGACY, 2.5d }, { Percentile.EstimationType.R_1, 2.0 }, { Percentile.EstimationType.R_2, 2.0 }, { Percentile.EstimationType.R_3, 1.0 },
436                 { Percentile.EstimationType.R_4, 1.5 }, { Percentile.EstimationType.R_5, 2.0 }, { Percentile.EstimationType.R_6, 2.0 },
437                 { Percentile.EstimationType.R_7, 2.0 }, { Percentile.EstimationType.R_8, 2.0 }, { Percentile.EstimationType.R_9, 2.0 }}, 50d, 0d);
438 
439         specialValues =
440                 new double[] { Double.NEGATIVE_INFINITY, 1d, 2d, 3d, Double.NaN, Double.POSITIVE_INFINITY };
441         assertEquals(2.5d, percentile.evaluate(specialValues), 0);
442 
443         testAssertMappedValues(specialValues, new Object[][] {
444                 { Percentile.EstimationType.LEGACY, 2.5d }, { Percentile.EstimationType.R_1, 2.0 }, { Percentile.EstimationType.R_2, 2.0 }, { Percentile.EstimationType.R_3, 1.0 },
445                 { Percentile.EstimationType.R_4, 1.5 }, { Percentile.EstimationType.R_5, 2.0 }, { Percentile.EstimationType.R_7, 2.0 }, { Percentile.EstimationType.R_7, 2.0 },
446                 { Percentile.EstimationType.R_8, 2.0 }, { Percentile.EstimationType.R_9, 2.0 } }, 50d, 0d);
447 
448         specialValues =
449                 new double[] { 1d, 1d, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY };
450         assertTrue(Double.isInfinite(percentile.evaluate(specialValues)));
451 
452         testAssertMappedValues(specialValues, new Object[][] {
453                 // This is one test not matching with R results.
454                 { Percentile.EstimationType.LEGACY, Double.POSITIVE_INFINITY },
455                 { Percentile.EstimationType.R_1,/* 1.0 */Double.NaN },
456                 { Percentile.EstimationType.R_2, /* Double.POSITIVE_INFINITY */Double.NaN },
457                 { Percentile.EstimationType.R_3, /* 1.0 */Double.NaN }, { Percentile.EstimationType.R_4, /* 1.0 */Double.NaN },
458                 { Percentile.EstimationType.R_5, Double.POSITIVE_INFINITY },
459                 { Percentile.EstimationType.R_6, Double.POSITIVE_INFINITY },
460                 { Percentile.EstimationType.R_7, Double.POSITIVE_INFINITY },
461                 { Percentile.EstimationType.R_8, Double.POSITIVE_INFINITY },
462                 { Percentile.EstimationType.R_9, Double.POSITIVE_INFINITY }, }, 50d, 0d);
463 
464         specialValues = new double[] { 1d, 1d, Double.NaN, Double.NaN };
465         assertTrue(Double.isNaN(percentile.evaluate(specialValues)));
466         testAssertMappedValues(specialValues, new Object[][] {
467                 { Percentile.EstimationType.LEGACY, Double.NaN }, { Percentile.EstimationType.R_1, 1.0 }, { Percentile.EstimationType.R_2, 1.0 }, { Percentile.EstimationType.R_3, 1.0 },
468                 { Percentile.EstimationType.R_4, 1.0 }, { Percentile.EstimationType.R_5, 1.0 },{ Percentile.EstimationType.R_6, 1.0 },{ Percentile.EstimationType.R_7, 1.0 },
469                 { Percentile.EstimationType.R_8, 1.0 }, { Percentile.EstimationType.R_9, 1.0 },}, 50d, 0d);
470 
471         specialValues =
472                 new double[] { 1d, 1d, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY };
473 
474         testAssertMappedValues(specialValues, new Object[][] {
475                 { Percentile.EstimationType.LEGACY, Double.NaN }, { Percentile.EstimationType.R_1, Double.NaN },
476                 { Percentile.EstimationType.R_2, Double.NaN }, { Percentile.EstimationType.R_3, Double.NaN }, { Percentile.EstimationType.R_4, Double.NaN },
477                 { Percentile.EstimationType.R_5, Double.NaN }, { Percentile.EstimationType.R_6, Double.NaN },
478                 { Percentile.EstimationType.R_7, Double.NaN }, { Percentile.EstimationType.R_8, Double.NaN }, { Percentile.EstimationType.R_9, Double.NaN }
479                 }, 50d, 0d);
480     }
481 
482     @Test
483     void testAllTechniquesSetQuantile() {
484         for (final Percentile.EstimationType e : Percentile.EstimationType.values()) {
485             reset(10, e);
486             final Percentile percentile = getUnivariateStatistic();
487             percentile.setQuantile(100); // OK
488             assertEquals(100, percentile.getQuantile(), 0);
489             try {
490                 percentile.setQuantile(0);
491                 fail("Expecting MathIllegalArgumentException");
492             } catch (final MathIllegalArgumentException ex) {
493                 // expected
494             }
495             try {
496                 new Percentile(0);
497                 fail("Expecting MathIllegalArgumentException");
498             } catch (final MathIllegalArgumentException ex) {
499                 // expected
500             }
501         }
502     }
503 
504     @Test
505     void testAllTechniquesEvaluateArraySegmentWeighted() {
506         for (final Percentile.EstimationType e : Percentile.EstimationType.values()) {
507             reset(quantile, e);
508             testEvaluateArraySegmentWeighted();
509         }
510     }
511 
512     @Test
513     void testAllTechniquesEvaluateArraySegment() {
514         for (final Percentile.EstimationType e : Percentile.EstimationType.values()) {
515             reset(quantile, e);
516             testEvaluateArraySegment();
517         }
518     }
519 
520     @Test
521     void testAllTechniquesWeightedConsistency() {
522         for (final Percentile.EstimationType e : Percentile.EstimationType.values()) {
523             reset(quantile, e);
524             testWeightedConsistency();
525         }
526     }
527 
528     @Test
529     void testAllTechniquesEvaluation() {
530 
531         testAssertMappedValues(testArray, new Object[][] { { Percentile.EstimationType.LEGACY, 20.820 },
532                 { Percentile.EstimationType.R_1, 19.800 }, { Percentile.EstimationType.R_2, 19.800 }, { Percentile.EstimationType.R_3, 19.800 },
533                 { Percentile.EstimationType.R_4, 19.310 }, { Percentile.EstimationType.R_5, 20.280 }, { Percentile.EstimationType.R_6, 20.820 },
534                 { Percentile.EstimationType.R_7, 19.555 }, { Percentile.EstimationType.R_8, 20.460 },{ Percentile.EstimationType.R_9, 20.415} },
535                 DEFAULT_PERCENTILE, tolerance);
536     }
537 
538     @Test
539     void testPercentileWithTechnique() {
540         reset (50, Percentile.EstimationType.LEGACY);
541         final Percentile p = getUnivariateStatistic();
542         assertEquals(Percentile.EstimationType.LEGACY, p.getEstimationType());
543         assertNotEquals(Percentile.EstimationType.R_1, p.getEstimationType());
544     }
545 
546     static final int TINY = 10, SMALL = 50, NOMINAL = 100, MEDIUM = 500,
547                      STANDARD = 1000, BIG = 10000, VERY_BIG = 50000, LARGE = 1000000,
548                      VERY_LARGE = 10000000;
549     static final int[] sampleSizes= { TINY, SMALL, NOMINAL, MEDIUM, STANDARD, BIG };
550 
551     @Test
552     void testStoredVsDirect() {
553         final RandomDataGenerator randomDataGenerator = new RandomDataGenerator(100);
554         final NormalDistribution normalDistribution = new NormalDistribution(4000, 50);
555         for (final int sampleSize:sampleSizes) {
556             final double[] data = randomDataGenerator.nextDeviates(normalDistribution, sampleSize);
557             for (final double p:new double[] {50d,95d}) {
558                 for (final Percentile.EstimationType e : Percentile.EstimationType.values()) {
559                     reset(p, e);
560                     final Percentile pStoredData = getUnivariateStatistic();
561                     pStoredData.setData(data);
562                     final double storedDataResult=pStoredData.evaluate();
563                     pStoredData.setData(null);
564                     final Percentile pDirect = getUnivariateStatistic();
565                     assertEquals(storedDataResult,
566                                  pDirect.evaluate(data), 0d, "Sample="+sampleSize+",P="+p+" e="+e);
567                 }
568             }
569         }
570     }
571 
572     @Test
573     void testPercentileWithDataRef() {
574         reset(50.0, Percentile.EstimationType.R_7);
575         final Percentile p = getUnivariateStatistic();
576         p.setData(testArray);
577         assertEquals(Percentile.EstimationType.R_7, p.getEstimationType());
578         assertNotEquals(Percentile.EstimationType.R_1, p.getEstimationType());
579         assertEquals(12d, p.evaluate(), 0d);
580         assertEquals(12.16d, p.evaluate(60d), 0d);
581     }
582 
583     @Test
584     void testNullEstimation() {
585         assertThrows(NullArgumentException.class, () -> {
586             type = null;
587             getUnivariateStatistic();
588         });
589     }
590 
591     @Test
592     void testAllEstimationTechniquesOnlyLimits() {
593         final int N=testArray.length;
594 
595         final double[] input = testArray.clone();
596         Arrays.sort(input);
597         final double min = input[0];
598         final double max=input[input.length-1];
599         //limits may be ducked by 0.01 to induce the condition of p<pMin
600         final Object[][] map =
601                 new Object[][] { { Percentile.EstimationType.LEGACY, 0d, 1d }, { Percentile.EstimationType.R_1, 0d, 1d },
602                         { Percentile.EstimationType.R_2, 0d,1d }, { Percentile.EstimationType.R_3, 0.5/N,1d },
603                         { Percentile.EstimationType.R_4, 1d/N-0.001,1d },
604                         { Percentile.EstimationType.R_5, 0.5/N-0.001,(N-0.5)/N}, { Percentile.EstimationType.R_6, 0.99d/(N+1),
605                             1.01d*N/(N+1)},
606                         { Percentile.EstimationType.R_7, 0d,1d}, { Percentile.EstimationType.R_8, 1.99d/3/(N+1d/3),
607                             (N-1d/3)/(N+1d/3)},
608                         { Percentile.EstimationType.R_9, 4.99d/8/(N+0.25), (N-3d/8)/(N+0.25)} };
609 
610         for(final Object[] arr:map) {
611             final Percentile.EstimationType t= (Percentile.EstimationType) arr[0];
612             double pMin=(Double)arr[1];
613             final double pMax=(Double)arr[2];
614             assertEquals(0d, t.index(pMin, N),0d,"Type:"+t);
615             assertEquals(N, t.index(pMax, N),0.5d,"Type:"+t);
616             pMin=pMin==0d?pMin+0.01:pMin;
617             testAssertMappedValues(testArray, new Object[][] { { t, min }}, pMin, 0.01);
618             testAssertMappedValues(testArray, new Object[][] { { t, max }}, pMax * 100, tolerance);
619         }
620     }
621 
622     @Test
623     void testAllEstimationTechniquesOnly() {
624         assertEquals("Legacy Hipparchus",Percentile.EstimationType.LEGACY.getName());
625         final Object[][] map =
626                 new Object[][] { { Percentile.EstimationType.LEGACY, 20.82 }, { Percentile.EstimationType.R_1, 19.8 },
627                         { Percentile.EstimationType.R_2, 19.8 }, { Percentile.EstimationType.R_3, 19.8 }, { Percentile.EstimationType.R_4, 19.310 },
628                         { Percentile.EstimationType.R_5, 20.280}, { Percentile.EstimationType.R_6, 20.820},
629                         { Percentile.EstimationType.R_7, 19.555 }, { Percentile.EstimationType.R_8, 20.460 },{Percentile.EstimationType.R_9,20.415} };
630         try {
631             Percentile.EstimationType.LEGACY.evaluate(testArray, -1d, new KthSelector(PivotingStrategy.MEDIAN_OF_3));
632         } catch (final MathIllegalArgumentException oore) {
633         }
634         try {
635             Percentile.EstimationType.LEGACY.evaluate(testArray, 101d, new KthSelector());
636         } catch (final MathIllegalArgumentException oore) {
637         }
638         try {
639             Percentile.EstimationType.LEGACY.evaluate(testArray, 50d, new KthSelector());
640         } catch(final MathIllegalArgumentException oore) {
641         }
642         for (final Object[] o : map) {
643             final Percentile.EstimationType e = (Percentile.EstimationType) o[0];
644             final double expected = (Double) o[1];
645             final double result = e.evaluate(testArray, DEFAULT_PERCENTILE, new KthSelector());
646             assertEquals(expected, result, tolerance, "expected[" + e + "] = " + expected +
647                     " but was = " + result);
648         }
649     }
650 
651     @Test
652     void testAllEstimationTechniquesOnlyForAllPivotingStrategies() {
653 
654         assertEquals("Legacy Hipparchus",Percentile.EstimationType.LEGACY.getName());
655 
656         for (final PivotingStrategy strategy : PivotingStrategy.values()) {
657             kthSelector = new KthSelector(strategy);
658             testAllEstimationTechniquesOnly();
659         }
660     }
661 
662     @Test
663     void testAllEstimationTechniquesOnlyForExtremeIndexes() {
664         final double MAX=100;
665         final Object[][] map =
666                 new Object[][] { { Percentile.EstimationType.LEGACY, 0d, MAX}, { Percentile.EstimationType.R_1, 0d,MAX+0.5 },
667                 { Percentile.EstimationType.R_2, 0d,MAX}, { Percentile.EstimationType.R_3, 0d,MAX }, { Percentile.EstimationType.R_4, 0d,MAX },
668                 { Percentile.EstimationType.R_5, 0d,MAX }, { Percentile.EstimationType.R_6, 0d,MAX },
669                 { Percentile.EstimationType.R_7, 0d,MAX }, { Percentile.EstimationType.R_8, 0d,MAX }, { Percentile.EstimationType.R_9, 0d,MAX }  };
670         for (final Object[] o : map) {
671             final Percentile.EstimationType e = (Percentile.EstimationType) o[0];
672                 assertEquals(((Double)o[1]).doubleValue(), e.index(0d, (int)MAX),0d);
673                 assertEquals(((Double)o[2]).doubleValue(), e.index(1.0, (int)MAX),0d,"Enum:"+e);
674             }
675     }
676 
677     @Test
678     void testAllEstimationTechniquesOnlyForNullsAndOOR() {
679 
680         final Object[][] map =
681                 new Object[][] { { Percentile.EstimationType.LEGACY, 20.82 }, { Percentile.EstimationType.R_1, 19.8 },
682                         { Percentile.EstimationType.R_2, 19.8 }, { Percentile.EstimationType.R_3, 19.8 }, { Percentile.EstimationType.R_4, 19.310 },
683                         { Percentile.EstimationType.R_5, 20.280}, { Percentile.EstimationType.R_6, 20.820},
684                         { Percentile.EstimationType.R_7, 19.555 }, { Percentile.EstimationType.R_8, 20.460 },{ Percentile.EstimationType.R_9, 20.415 } };
685         for (final Object[] o : map) {
686             final Percentile.EstimationType e = (Percentile.EstimationType) o[0];
687             try {
688                 e.evaluate(null, DEFAULT_PERCENTILE, new KthSelector());
689                 fail("Expecting NullArgumentException");
690             } catch (final NullArgumentException nae) {
691                 // expected
692             }
693             try {
694                 e.evaluate(testArray, 120, new KthSelector());
695                 fail("Expecting MathIllegalArgumentException");
696             } catch (final MathIllegalArgumentException oore) {
697                 // expected
698             }
699         }
700     }
701 
702     /**
703      * Simple test assertion utility method assuming {@link NaNStrategy default}
704      * nan handling strategy specific to each {@link EstimationType type}
705      *
706      * @param data input data
707      * @param map of expected result against a {@link EstimationType}
708      * @param p the quantile to compute for
709      * @param tolerance the tolerance of difference allowed
710      */
711     protected void testAssertMappedValues(final double[] data, final Object[][] map,
712             final Double p, final Double tolerance) {
713         for (final Object[] o : map) {
714             final Percentile.EstimationType e = (Percentile.EstimationType) o[0];
715             final double expected = (Double) o[1];
716             try {
717                 reset(p, e);
718                 final double result = getUnivariateStatistic().evaluate(data);
719                 assertEquals(expected, result, tolerance, "expected[" + e + "] = " + expected +
720                              " but was = " + result);
721             } catch(final Exception ex) {
722                 fail("Exception occured for estimation type "+e+":"+
723                      ex.getLocalizedMessage());
724             }
725         }
726     }
727 
728     // Some NaNStrategy specific testing
729     @Test
730     void testNanStrategySpecific() {
731         double[] specialValues = new double[] { 0d, 1d, 2d, 3d, 4d, Double.NaN };
732         assertTrue(Double.isNaN(new Percentile(50d).withEstimationType(Percentile.EstimationType.LEGACY).withNaNStrategy(NaNStrategy.MAXIMAL).evaluate(specialValues, 3, 3)));
733         assertEquals(2d,new Percentile(50d).withEstimationType(Percentile.EstimationType.R_1).withNaNStrategy(NaNStrategy.REMOVED).evaluate(specialValues),0d);
734         assertEquals(Double.NaN,new Percentile(50d).withEstimationType(Percentile.EstimationType.R_5).withNaNStrategy(NaNStrategy.REMOVED).evaluate(new double[] {Double.NaN,Double.NaN,Double.NaN}),0d);
735         assertEquals(50d,new Percentile(50d).withEstimationType(Percentile.EstimationType.R_7).withNaNStrategy(NaNStrategy.MINIMAL).evaluate(new double[] {50d,50d,50d},1,2),0d);
736 
737         specialValues = new double[] { 0d, 1d, 2d, 3d, 4d, Double.NaN, Double.NaN };
738         assertEquals(3.5,new Percentile().evaluate(specialValues, 3, 4),0d);
739         assertEquals(4d,new Percentile().evaluate(specialValues, 4, 3),0d);
740         assertTrue(Double.isNaN(new Percentile().evaluate(specialValues, 5, 2)));
741 
742         specialValues = new double[] { 0d, 1d, 2d, 3d, 4d, Double.NaN, Double.NaN, 5d, 6d };
743         assertEquals(4.5,new Percentile().evaluate(specialValues, 3, 6),0d);
744         assertEquals(5d,new Percentile().evaluate(specialValues, 4, 5),0d);
745         assertTrue(Double.isNaN(new Percentile().evaluate(specialValues, 5, 2)));
746         assertTrue(Double.isNaN(new Percentile().evaluate(specialValues, 5, 1)));
747         assertEquals(5.5,new Percentile().evaluate(specialValues, 5, 4),0d);
748     }
749 
750     // Some NaNStrategy specific testing
751     @Test
752     void testNanStrategyFailed() {
753         assertThrows(MathIllegalArgumentException.class, () -> {
754             double[] specialValues =
755                 new double[]{0d, 1d, 2d, 3d, 4d, Double.NaN};
756             new Percentile(50d).
757                 withEstimationType(Percentile.EstimationType.R_9).
758                 withNaNStrategy(NaNStrategy.FAILED).
759                 evaluate(specialValues);
760         });
761     }
762 
763     @Test
764     void testAllTechniquesSpecialValuesWithNaNStrategy() {
765         double[] specialValues =
766                 new double[] { 0d, 1d, 2d, 3d, 4d, Double.NaN };
767         try {
768             new Percentile(50d).withEstimationType(Percentile.EstimationType.LEGACY).withNaNStrategy(null);
769             fail("Expecting NullArgumentArgumentException "
770                     + "for null Nan Strategy");
771         } catch (NullArgumentException ex) {
772             // expected
773         }
774         //This is as per each type's default NaNStrategy
775         testAssertMappedValues(specialValues, new Object[][] {
776                 { Percentile.EstimationType.LEGACY, 2.5d }, { Percentile.EstimationType.R_1, 2.0 }, { Percentile.EstimationType.R_2, 2.0 }, { Percentile.EstimationType.R_3, 1.0 },
777                 { Percentile.EstimationType.R_4, 1.5 }, { Percentile.EstimationType.R_5, 2.0 }, { Percentile.EstimationType.R_6, 2.0 },
778                 { Percentile.EstimationType.R_7, 2.0 }, { Percentile.EstimationType.R_8, 2.0 }, { Percentile.EstimationType.R_9, 2.0 }}, 50d, 0d);
779 
780         //This is as per MAXIMAL and hence the values tend a +0.5 upward
781         testAssertMappedValues(specialValues, new Object[][] {
782                 { Percentile.EstimationType.LEGACY, 2.5d }, { Percentile.EstimationType.R_1, 2.0 }, { Percentile.EstimationType.R_2, 2.5 }, { Percentile.EstimationType.R_3, 2.0 },
783                 { Percentile.EstimationType.R_4, 2.0 }, { Percentile.EstimationType.R_5, 2.5 }, { Percentile.EstimationType.R_6, 2.5 },
784                 { Percentile.EstimationType.R_7, 2.5 }, { Percentile.EstimationType.R_8, 2.5 }, { Percentile.EstimationType.R_9, 2.5 }}, 50d, 0d,
785                 NaNStrategy.MAXIMAL);
786 
787         //This is as per MINIMAL and hence the values tend a -0.5 downward
788         testAssertMappedValues(specialValues, new Object[][] {
789                 { Percentile.EstimationType.LEGACY, 1.5d }, { Percentile.EstimationType.R_1, 1.0 }, { Percentile.EstimationType.R_2, 1.5 }, { Percentile.EstimationType.R_3, 1.0 },
790                 { Percentile.EstimationType.R_4, 1.0 }, { Percentile.EstimationType.R_5, 1.5 }, { Percentile.EstimationType.R_6, 1.5 },
791                 { Percentile.EstimationType.R_7, 1.5 }, { Percentile.EstimationType.R_8, 1.5 }, { Percentile.EstimationType.R_9, 1.5 }}, 50d, 0d,
792                 NaNStrategy.MINIMAL);
793 
794         //This is as per REMOVED as here Percentile.Type.CM changed its value from default
795         //while rest of Estimation types were anyways defaulted to REMOVED
796         testAssertMappedValues(specialValues, new Object[][] {
797                 { Percentile.EstimationType.LEGACY, 2.0 }, { Percentile.EstimationType.R_1, 2.0 }, { Percentile.EstimationType.R_2, 2.0 }, { Percentile.EstimationType.R_3, 1.0 },
798                 { Percentile.EstimationType.R_4, 1.5 }, { Percentile.EstimationType.R_5, 2.0 }, { Percentile.EstimationType.R_6, 2.0 },
799                 { Percentile.EstimationType.R_7, 2.0 }, { Percentile.EstimationType.R_8, 2.0 }, { Percentile.EstimationType.R_9, 2.0 }}, 50d, 0d,
800                 NaNStrategy.REMOVED);
801     }
802 
803     /**
804      * Simple test assertion utility method
805      *
806      * @param data input data
807      * @param map of expected result against a {@link EstimationType}
808      * @param p the quantile to compute for
809      * @param tolerance the tolerance of difference allowed
810      * @param nanStrategy NaNStrategy to be passed
811      */
812     protected void testAssertMappedValues(double[] data, Object[][] map,
813                                           Double p, Double tolerance, NaNStrategy nanStrategy) {
814         for (Object[] o : map) {
815             Percentile.EstimationType e = (Percentile.EstimationType) o[0];
816             double expected = (Double) o[1];
817             try {
818                 double result = new Percentile(p).withEstimationType(e).withNaNStrategy(nanStrategy).evaluate(data);
819                 assertEquals(expected, result, tolerance, "expected[" + e + "] = " + expected + " but was = " + result);
820             } catch(Exception ex) {
821                 fail("Exception occured for estimation type " + e + ":" + ex.getLocalizedMessage());
822             }
823         }
824     }
825 
826 }