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    *      http://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  package org.apache.commons.statistics.inference;
18  
19  import java.util.Arrays;
20  import java.util.function.BiConsumer;
21  import java.util.stream.IntStream;
22  import java.util.stream.Stream;
23  import org.junit.jupiter.api.Assertions;
24  import org.junit.jupiter.api.MethodOrderer;
25  import org.junit.jupiter.api.Order;
26  import org.junit.jupiter.api.Test;
27  import org.junit.jupiter.api.TestMethodOrder;
28  import org.junit.jupiter.params.ParameterizedTest;
29  import org.junit.jupiter.params.provider.Arguments;
30  import org.junit.jupiter.params.provider.MethodSource;
31  
32  /**
33   * Test cases for {@link MannWhitneyUTest}.
34   */
35  @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
36  class MannWhitneyUTestTest {
37  
38      @Test
39      void testInvalidOptionsThrows() {
40          final MannWhitneyUTest test = MannWhitneyUTest.withDefaults();
41          Assertions.assertThrows(NullPointerException.class, () ->
42              test.with((AlternativeHypothesis) null));
43          Assertions.assertThrows(IllegalArgumentException.class, () ->
44              test.with((PValueMethod) null));
45          Assertions.assertThrows(IllegalArgumentException.class, () ->
46              test.with(PValueMethod.ESTIMATE));
47          Assertions.assertThrows(NullPointerException.class, () ->
48              test.with((ContinuityCorrection) null));
49          for (final double v : new double[] {Double.POSITIVE_INFINITY, Double.NaN, Double.NEGATIVE_INFINITY}) {
50              Assertions.assertThrows(IllegalArgumentException.class, () -> test.withMu(v));
51          }
52      }
53  
54      @Test
55      void testMannWhitneyUTestThrows() {
56          assertMannWhitneyUTestThrows(MannWhitneyUTest.withDefaults()::statistic);
57          assertMannWhitneyUTestThrows(MannWhitneyUTest.withDefaults()::test);
58      }
59  
60      private static void assertMannWhitneyUTestThrows(BiConsumer<double[], double[]> action) {
61          // Samples must be present, i.e. length > 0
62          TestUtils.assertThrowsWithMessage(IllegalArgumentException.class,
63              () -> action.accept(new double[] {}, new double[] {1.0}), "values", "size");
64          TestUtils.assertThrowsWithMessage(IllegalArgumentException.class,
65              () -> action.accept(new double[] {1.0}, new double[] {}), "values", "size");
66  
67          // x and y is null
68          TestUtils.assertThrowsWithMessage(NullPointerException.class,
69              () -> action.accept(null, null));
70  
71          // x or y is null
72          TestUtils.assertThrowsWithMessage(NullPointerException.class,
73              () -> action.accept(null, new double[] {1.0}));
74          TestUtils.assertThrowsWithMessage(NullPointerException.class,
75              () -> action.accept(new double[] {1.0}, null));
76      }
77  
78      @ParameterizedTest
79      @MethodSource
80      void testMannWhitneyU(double[] x, double[] y, double statistic, double[] p,
81                            double mu, PValueMethod method, boolean correct, double eps) {
82          MannWhitneyUTest test = MannWhitneyUTest.withDefaults().withMu(mu).with(method)
83              .with(correct ? ContinuityCorrection.ENABLED : ContinuityCorrection.DISABLED);
84          final double s = mu == 0 ?
85              MannWhitneyUTest.withDefaults().statistic(x, y) :
86              test.statistic(x, y);
87          Assertions.assertEquals(statistic, s, "statistic");
88          final boolean hasTies = hasTies(x, y);
89          // Note: Assertions are within a try-catch block to allow printing the R command
90          int i = 0;
91          try {
92              for (final AlternativeHypothesis h : AlternativeHypothesis.values()) {
93                  test = test.with(h);
94                  // Test the default if possible
95                  final MannWhitneyUTest.Result r =
96                      h == AlternativeHypothesis.TWO_SIDED &&
97                      mu == 0 &&
98                      method == PValueMethod.AUTO &&
99                      correct ?
100                     MannWhitneyUTest.withDefaults().test(x, y) :
101                     test.test(x, y);
102                 Assertions.assertEquals(statistic, r.getStatistic(), () -> h + " statistic");
103                 TestUtils.assertProbability(p[i++], r.getPValue(), eps, () -> h + " p-value");
104                 Assertions.assertEquals(hasTies, r.hasTiedValues(), () -> " tied values");
105                 // Check symmetry: must reverse sign of mu
106                 final double other = (long) x.length * y.length - statistic;
107                 Assertions.assertEquals(other, test.withMu(-mu).statistic(y, x), () -> h + " statistic (y, x)");
108             }
109         } catch (final AssertionError e) {
110             // print failing cases
111             TestUtils.printf("x=c%s;%ny=c%s;%nwilcox.test(x, y, exact=%s, correct=%s, alternative='%c', mu=%s)%n",
112                 Arrays.toString(x).replace('[', '(').replace(']', ')'),
113                 Arrays.toString(y).replace('[', '(').replace(']', ')'),
114                 PValueMethod.EXACT == method ? "TRUE" : "FALSE",
115                 correct ? "TRUE" : "FALSE",
116                 Character.toLowerCase(AlternativeHypothesis.values()[i - 1].toString().charAt(0)),
117                 mu);
118             throw e;
119         }
120     }
121 
122     static Stream<Arguments> testMannWhitneyU() {
123         // For relative error of p-value
124         final double eps = Math.ulp(1.0);
125 
126         // p-values are in the AlternativeHypothesis enum order: two-sided, greater, less.
127         // continuity correction is only required for the asymptotic p-value.
128         final boolean ignoredContinuityCorrection = false;
129         final Stream.Builder<Arguments> builder = Stream.builder();
130         // Target values computed using R version 3.4.0
131         // R command to compute the values can be printed to stdout in the test.
132 
133         // Exact cases: Requires no ties
134         // x <- c(19, 22, 16, 29, 24)
135         // y <- c(20, 11, 17, 12)
136         // wilcox.test(x, y, alternative = "two.sided", mu = 0, paired = FALSE, exact = TRUE)
137         builder.add(Arguments.of(
138             new double[] {19, 22, 16, 29, 24},
139             new double[] {20, 11, 17, 12},
140             17, new double[] {0.1111111111111111, 0.055555555555555552, 0.96825396825396826},
141             0, PValueMethod.EXACT, ignoredContinuityCorrection, 0));
142         builder.add(Arguments.of(
143             new double[] {2, 4, 6, 8, 10, 12, 14, 15, 16, 17, 18},
144             new double[] {1, 3, 5, 7, 9, 11, 13, 19, 20},
145             56, new double[] {0.65563229340319129, 0.32781614670159565, 0.69866039533222191},
146             0, PValueMethod.EXACT, ignoredContinuityCorrection, 0));
147         builder.add(Arguments.of(
148             new double[] {2, 4, 6, 8, 10, 12, 14, 16, 18},
149             new double[] {-3, -1, 1, 3, 5, 7, 9, 11},
150             57, new double[] {0.046400658165364046, 0.023200329082682023, 0.98202385849444673},
151             0, PValueMethod.EXACT, ignoredContinuityCorrection, 0));
152         // Auto selects the exact
153         builder.add(Arguments.of(
154             new double[] {2, 4, 6, 8, 10, 12, 14, 16, 18},
155             new double[] {-3, -1, 1, 3, 5, 7, 9, 11},
156             57, new double[] {0.046400658165364046, 0.023200329082682023, 0.98202385849444673},
157             0, PValueMethod.AUTO, ignoredContinuityCorrection, 0));
158         // Extreme u value
159         builder.add(Arguments.of(
160             new double[] {1, 2, 3, 4},
161             new double[] {5, 6, 7, 8},
162             0, new double[] {0.028571428571428571, 1, 0.014285714285714285},
163             0, PValueMethod.EXACT, ignoredContinuityCorrection, 0));
164         // u = n*m/2
165         builder.add(Arguments.of(
166             new double[] {1, 3, 5, 7},
167             new double[] {2, 4, 6},
168             6, new double[] {1, 0.5714285714285714, 0.5714285714285714},
169             0, PValueMethod.EXACT, ignoredContinuityCorrection, 0));
170 
171         // Location shift
172         builder.add(Arguments.of(
173             new double[] {2, 4, 6, 8, 10, 12, 14, 16, 18},
174             new double[] {-3, -1, 1, 3, 5, 7, 9, 11},
175             66, new double[] {0.0024681201151789383, 0.0012340600575894691, 0.99921842863019328},
176             -4, PValueMethod.EXACT, ignoredContinuityCorrection, eps));
177         builder.add(Arguments.of(
178             new double[] {2, 4, 6, 8, 10, 12, 14, 15, 16, 17, 18},
179             new double[] {1, 3, 5, 7, 9, 11, 13, 19, 20},
180             48, new double[] {0.94084305787092171, 0.55900809716599187, 0.47042152893546085},
181             2.5, PValueMethod.EXACT, ignoredContinuityCorrection, eps));
182 
183         // Random data without ties
184         // UniformRandomProvider rng = RandomSource.XO_RO_SHI_RO_128_PP.create();
185         // ZigguratSampler.NormalizedGaussian s = ZigguratSampler.NormalizedGaussian.of(rng);
186         // for (int n = 50; n <= 150; n += 50) {
187         //     double[] x = s.samples(n).map(i -> Precision.round(i, 3)).distinct().toArray();
188         //     double[] y = s.samples().map(i -> Precision.round(i, 3))
189         //                             .filter(i -> Arrays.stream(x).noneMatch(j -> i == j))
190         //                             .limit(n+10).distinct().toArray();
191         // CHECKSTYLE: stop regex
192         //     System.out.println(Arrays.stream(x).mapToObj(Double::toString).collect(Collectors.joining(", ", "new double[] {", "},")));
193         //     System.out.println(Arrays.stream(y).mapToObj(Double::toString).collect(Collectors.joining(", ", "new double[] {", "},")));
194         // CHECKSTYLE: resume regex
195         // }
196         builder.add(Arguments.of(
197             new double[] {1.208, -1.411, -0.507, -0.521, 0.325, 0.887, -0.543, -0.012, -2.185, 0.718, 0.659, -1.095, -0.41, 0.921, -0.442, 0.883, 2.817, 0.963, 0.452, -1.171, 1.32, -0.224, 1.88, -1.459, -0.955, 2.512, 1.147, -0.471, -1.124, 0.577, 0.362, 1.737, 0.407, 0.701, -0.302, -0.859, 0.648, 0.65, 1.869, -0.685, 0.317, -0.049, 0.155, 0.943, -1.516, -0.615, 0.663, 0.048, 1.386, -0.444},
198             new double[] {1.042, -0.735, 3.151, 0.628, -1.442, 1.142, 0.834, 1.686, 0.37, 1.474, 0.975, 0.697, 1.552, 0.388, -0.408, 0.62, 0.032, 2.458, 1.723, 0.549, 1.055, 0.822, -0.549, -0.517, 2.322, 1.172, -1.63, -1.151, -1.065, -0.464, -1.188, 0.472, -2.228, -0.626, 0.521, -0.334, -0.687, -1.894, 1.217, 1.061, -0.393, 1.366, 2.217, -0.045, -0.552, 1.047, 0.138, 1.27, -0.838, 0.107, 0.555, 0.1, 0.276, -0.156, 1.11, -1.498, 0.26, -1.071},
199             1380, new double[] {0.66972967418927165, 0.66736881602680143, 0.33486483709463583},
200             0, PValueMethod.EXACT, ignoredContinuityCorrection, 32 * eps));
201         builder.add(Arguments.of(
202             new double[] {1.012, -1.187, 0.737, -0.465, -0.426, 0.373, -2.206, 0.102, 0.032, 1.171, 1.615, -0.167, 0.138, -0.043, -0.391, -0.318, -0.257, 0.053, 0.129, -1.385, 0.246, 0.189, 0.286, 0.26, 0.781, -1.124, -0.404, 1.364, -0.175, -0.567, 0.224, 0.075, 1.194, -0.549, 1.277, -0.337, 0.221, -0.29, -0.26, -0.904, 0.402, -0.645, 0.88, 0.497, 1.125, -0.803, -0.66, -0.082, -1.763, -0.631, -0.85, -1.661, -1.24, 2.018, -0.013, -0.272, 2.12, -0.913, 1.151, -0.759, 1.724, -0.021, -1.84, -0.417, 0.656, -0.814, -0.179, 1.282, 0.204, 1.122, 1.434, 1.293, 0.761, -0.668, -0.527, -0.712, -0.616, -2.102, -1.03, 1.138, 0.019, -1.038, -1.085, -0.579, 1.427, -1.184, 0.196, -0.145, -0.545, 0.876, -1.262, -1.833, -0.482, 0.209, -0.159, -0.163, -1.457, -0.339, -0.08, -0.459},
203             new double[] {0.207, -0.381, 0.564, 1.116, 1.365, 0.417, -0.694, -1.301, 0.803, 0.238, 0.97, -1.597, 1.123, -1.296, -0.119, -0.176, -1.188, -1.303, 1.472, 0.212, 0.895, -1.919, -1.047, -0.419, 1.499, 0.033, 1.513, 0.762, 0.531, 1.071, -0.306, 0.896, 0.709, -0.363, -0.766, -0.61, 0.888, -0.435, 0.839, 1.192, -0.375, -0.861, -2.061, -0.834, -1.093, 2.483, -0.115, -2.47, 0.058, -0.187, -1.067, 0.2, 1.643, -2.492, -1.577, 0.086, 0.622, -0.658, -1.024, 2.55, 1.989, -0.767, 1.17, -0.866, 0.89, 0.979, 0.486, 1.089, 0.574, -1.013, 0.348, 0.851, 0.304, -0.711, 0.018, -0.5, 0.797, 0.017, 0.287, -0.935, 0.644, -0.857, -0.671, -0.797, -0.411, 1.048, -0.705, -1.383, -1.791, 0.142, -0.95, -0.263, -1.591, -1.36, 0.848, -0.716, -1.281, 0.659, 2.224, -0.737, -2.193, 0.79, -0.371, 0.46, -1.135, 0.733, -0.596, 0.303, 0.904},
204             5348, new double[] {0.81661195397099406, 0.59258116744016509, 0.40830597698549703},
205             0, PValueMethod.EXACT, ignoredContinuityCorrection, 16 * eps));
206         // Larger samples are too slow in R
207 
208         // Exact with ties -> asymptotic
209         builder.add(Arguments.of(
210             new double[] {2, 4, 6, 8, 11, 11, 14, 16, 18},
211             new double[] {-3, -1, 1, 3, 5, 7, 9, 11},
212             57, new double[] {0.047988968229705727, 0.023994484114852863, 0.98095699468343656},
213             0, PValueMethod.EXACT, true, 2 * eps));
214         builder.add(Arguments.of(
215             new double[] {2, 4, 6, 8, 11, 11, 14, 16, 18},
216             new double[] {-3, -1, 1, 3, 5, 7, 9, 11},
217             57, new double[] {0.042795845837304496, 0.021397922918652248, 0.97860207708134772},
218             0, PValueMethod.EXACT, false, 2 * eps));
219         builder.add(Arguments.of(
220             new double[] {1, 2, 3, 4, 10},
221             new double[] {2, 4, 6, 4, 5},
222             8.5, new double[] {0.45780739419094196, 0.83010851282209053, 0.22890369709547098},
223             0, PValueMethod.EXACT, true, 2 * eps));
224         builder.add(Arguments.of(
225             new double[] {1, 2, 3, 4, 10},
226             new double[] {2, 4, 6, 4, 5},
227             8.5, new double[] {0.39614390915207404, 0.80192804542396301, 0.19807195457603702},
228             0, PValueMethod.EXACT, false, 2 * eps));
229 
230         // Inexact cases using continuity correction
231         // wilcox.test(x, y, alternative = "two.sided", mu = 0, paired = FALSE, exact = FALSE, correct = TRUE)
232         builder.add(Arguments.of(
233             new double[] {19, 22, 16, 29, 24},
234             new double[] {20, 11, 17, 12},
235             17, new double[] {0.11134688653314045, 0.055673443266570227, 0.96690371013890331},
236             0, PValueMethod.ASYMPTOTIC, true, 2 * eps));
237         builder.add(Arguments.of(
238             new double[] {2, 4, 6, 8, 10, 12, 14, 15, 16, 17, 18},
239             new double[] {1, 3, 5, 7, 9, 11, 13, 19, 20},
240             56, new double[] {0.648503379652976, 0.324251689826488, 0.7025732881058373},
241             0, PValueMethod.ASYMPTOTIC, true, eps));
242         builder.add(Arguments.of(
243             new double[] {2, 4, 6, 8, 10, 12, 14, 16, 18},
244             new double[] {-3, -1, 1, 3, 5, 7, 9, 11},
245             57, new double[] {0.048539622897320618, 0.024269811448660309, 0.98071937620128768},
246             0, PValueMethod.ASYMPTOTIC, true, eps));
247         builder.add(Arguments.of(
248             new double[] {1.208, -1.411, -0.507, -0.521, 0.325, 0.887, -0.543, -0.012, -2.185, 0.718, 0.659, -1.095, -0.41, 0.921, -0.442, 0.883, 2.817, 0.963, 0.452, -1.171, 1.32, -0.224, 1.88, -1.459, -0.955, 2.512, 1.147, -0.471, -1.124, 0.577, 0.362, 1.737, 0.407, 0.701, -0.302, -0.859, 0.648, 0.65, 1.869, -0.685, 0.317, -0.049, 0.155, 0.943, -1.516, -0.615, 0.663, 0.048, 1.386, -0.444},
249             new double[] {1.042, -0.735, 3.151, 0.628, -1.442, 1.142, 0.834, 1.686, 0.37, 1.474, 0.975, 0.697, 1.552, 0.388, -0.408, 0.62, 0.032, 2.458, 1.723, 0.549, 1.055, 0.822, -0.549, -0.517, 2.322, 1.172, -1.63, -1.151, -1.065, -0.464, -1.188, 0.472, -2.228, -0.626, 0.521, -0.334, -0.687, -1.894, 1.217, 1.061, -0.393, 1.366, 2.217, -0.045, -0.552, 1.047, 0.138, 1.27, -0.838, 0.107, 0.555, 0.1, 0.276, -0.156, 1.11, -1.498, 0.26, -1.071},
250             1380, new double[] {0.66849366084453488, 0.66799289503005865, 0.33424683042226744},
251             0, PValueMethod.ASYMPTOTIC, true, eps));
252         builder.add(Arguments.of(
253             new double[] {1.012, -1.187, 0.737, -0.465, -0.426, 0.373, -2.206, 0.102, 0.032, 1.171, 1.615, -0.167, 0.138, -0.043, -0.391, -0.318, -0.257, 0.053, 0.129, -1.385, 0.246, 0.189, 0.286, 0.26, 0.781, -1.124, -0.404, 1.364, -0.175, -0.567, 0.224, 0.075, 1.194, -0.549, 1.277, -0.337, 0.221, -0.29, -0.26, -0.904, 0.402, -0.645, 0.88, 0.497, 1.125, -0.803, -0.66, -0.082, -1.763, -0.631, -0.85, -1.661, -1.24, 2.018, -0.013, -0.272, 2.12, -0.913, 1.151, -0.759, 1.724, -0.021, -1.84, -0.417, 0.656, -0.814, -0.179, 1.282, 0.204, 1.122, 1.434, 1.293, 0.761, -0.668, -0.527, -0.712, -0.616, -2.102, -1.03, 1.138, 0.019, -1.038, -1.085, -0.579, 1.427, -1.184, 0.196, -0.145, -0.545, 0.876, -1.262, -1.833, -0.482, 0.209, -0.159, -0.163, -1.457, -0.339, -0.08, -0.459},
254             new double[] {0.207, -0.381, 0.564, 1.116, 1.365, 0.417, -0.694, -1.301, 0.803, 0.238, 0.97, -1.597, 1.123, -1.296, -0.119, -0.176, -1.188, -1.303, 1.472, 0.212, 0.895, -1.919, -1.047, -0.419, 1.499, 0.033, 1.513, 0.762, 0.531, 1.071, -0.306, 0.896, 0.709, -0.363, -0.766, -0.61, 0.888, -0.435, 0.839, 1.192, -0.375, -0.861, -2.061, -0.834, -1.093, 2.483, -0.115, -2.47, 0.058, -0.187, -1.067, 0.2, 1.643, -2.492, -1.577, 0.086, 0.622, -0.658, -1.024, 2.55, 1.989, -0.767, 1.17, -0.866, 0.89, 0.979, 0.486, 1.089, 0.574, -1.013, 0.348, 0.851, 0.304, -0.711, 0.018, -0.5, 0.797, 0.017, 0.287, -0.935, 0.644, -0.857, -0.671, -0.797, -0.411, 1.048, -0.705, -1.383, -1.791, 0.142, -0.95, -0.263, -1.591, -1.36, 0.848, -0.716, -1.281, 0.659, 2.224, -0.737, -2.193, 0.79, -0.371, 0.46, -1.135, 0.733, -0.596, 0.303, 0.904},
255             5348, new double[] {0.8162283275427048, 0.59277469738715016, 0.4081141637713524},
256             0, PValueMethod.ASYMPTOTIC, true, eps));
257         builder.add(Arguments.of(
258             new double[] {1.606, 0.442, -0.039, 0.837, -0.28, 0.826, 0.521, -1.523, 0.452, -0.252, 0.934, -0.607, -0.416, 0.211, -0.479, 0.298, -0.25, 0.249, 0.863, -0.964, 0.18, 1.183, -1.903, 0.536, 0.901, -0.759, -0.275, 0.432, -0.745, 1.235, -2.398, -0.946, 0.469, 0.235, -1.278, 0.024, -0.263, 0.382, -0.739, -0.369, 0.179, 0.595, -0.884, 0.499, 0.677, 1.014, -1.216, -0.49, 0.247, -0.192, -1.272, 0.824, -0.72, -0.876, -0.381, 1.232, -0.037, 1.96, -0.737, 1.485, 1.286, 0.256, -0.341, 0.419, -1.028, -0.34, 1.72, -0.802, 1.299, 0.087, 2.023, 0.584, 1.456, -0.873, 2.247, -0.496, 1.15, 1.569, -0.305, 0.941, 0.882, -0.505, 2.011, -2.787, -0.04, 0.652, 0.04, -0.935, -1.706, -0.772, -0.877, -0.64, 1.464, 0.054, 0.761, -1.241, 1.677, -0.024, 1.397, 0.322, 0.148, 0.698, -1.82, -1.785, 0.586, -0.021, -0.636, -0.257, -0.388, 1.163, 0.66, 1.552, -0.857, -0.987, -0.116, 0.244, -0.372, -0.256, -0.206, 1.504, 0.146, 1.347, -0.034, -0.044, -1.19, 0.21, -0.657, 2.021, 0.875, -1.304, -0.154, -0.574, 0.706, 0.724, 1.295, -0.307, -0.797, -0.627, 1.089, 0.38, -2.377, -1.209, -1.426, 0.263, 0.515, 0.013, 0.887},
259             new double[] {-1.335, 0.377, -0.167, 0.137, 0.763, -0.98, -0.073, 2.204, -0.173, -0.886, 1.217, 0.855, 1.189, 1.121, 0.864, 0.528, -1.2, 0.716, -1.033, 0.142, 1.543, 1.101, -1.388, 0.326, -0.032, 0.083, 1.429, 0.274, 0.209, -1.115, 0.714, -0.516, -0.085, 1.004, -0.985, -0.897, -0.179, 0.556, -0.622, 0.843, -0.48, 2.202, -1.519, 1.478, -0.054, 0.954, -0.484, -0.778, 0.898, 0.389, -0.129, 0.358, -0.611, 0.487, 0.175, -0.816, -0.15, -0.397, 0.64, 0.375, 1.305, 2.19, -0.974, 0.131, 0.725, -1.746, -0.202, -0.194, 0.29, 0.756, -0.068, 1.261, -0.333, -0.486, 1.421, -0.653, -0.195, -0.88, 0.098, 0.701, 0.656, 1.288, -0.696, 0.694, -0.284, 2.131, 0.136, -0.115, 0.857, -0.243, -1.344, -1.555, -1.622, -0.148, 1.283, 1.075, -0.3, 1.568, -0.648, -1.755, 1.11, 1.178, 0.072, -0.342, 0.154, -0.466, 0.111, 0.189, 0.509, -0.438, 0.12, 1.687, 0.443, -1.87, -0.236, -1.223, 1.74, 0.866, 2.509, 1.773, 0.27, 0.893, 2.269, 0.15, 0.103, -0.841, -1.164, 1.061, 0.292, -0.337, 1.581, 2.529, 1.186, -0.017, -0.03, -0.087, -2.128, -0.635, 2.048, -0.049, -3.219, 0.58, 0.315, 0.047, -0.483, -0.639, 0.35, -2.385, 0.679, 0.665, 0.197, -0.851, -1.874, -0.523, -1.154},
260             11005, new double[] {0.60989875841900276, 0.69551223629252534, 0.30494937920950138},
261             0, PValueMethod.ASYMPTOTIC, true, eps));
262         // Auto selects the asymptotic
263         builder.add(Arguments.of(
264             new double[] {1.606, 0.442, -0.039, 0.837, -0.28, 0.826, 0.521, -1.523, 0.452, -0.252, 0.934, -0.607, -0.416, 0.211, -0.479, 0.298, -0.25, 0.249, 0.863, -0.964, 0.18, 1.183, -1.903, 0.536, 0.901, -0.759, -0.275, 0.432, -0.745, 1.235, -2.398, -0.946, 0.469, 0.235, -1.278, 0.024, -0.263, 0.382, -0.739, -0.369, 0.179, 0.595, -0.884, 0.499, 0.677, 1.014, -1.216, -0.49, 0.247, -0.192, -1.272, 0.824, -0.72, -0.876, -0.381, 1.232, -0.037, 1.96, -0.737, 1.485, 1.286, 0.256, -0.341, 0.419, -1.028, -0.34, 1.72, -0.802, 1.299, 0.087, 2.023, 0.584, 1.456, -0.873, 2.247, -0.496, 1.15, 1.569, -0.305, 0.941, 0.882, -0.505, 2.011, -2.787, -0.04, 0.652, 0.04, -0.935, -1.706, -0.772, -0.877, -0.64, 1.464, 0.054, 0.761, -1.241, 1.677, -0.024, 1.397, 0.322, 0.148, 0.698, -1.82, -1.785, 0.586, -0.021, -0.636, -0.257, -0.388, 1.163, 0.66, 1.552, -0.857, -0.987, -0.116, 0.244, -0.372, -0.256, -0.206, 1.504, 0.146, 1.347, -0.034, -0.044, -1.19, 0.21, -0.657, 2.021, 0.875, -1.304, -0.154, -0.574, 0.706, 0.724, 1.295, -0.307, -0.797, -0.627, 1.089, 0.38, -2.377, -1.209, -1.426, 0.263, 0.515, 0.013, 0.887},
265             new double[] {-1.335, 0.377, -0.167, 0.137, 0.763, -0.98, -0.073, 2.204, -0.173, -0.886, 1.217, 0.855, 1.189, 1.121, 0.864, 0.528, -1.2, 0.716, -1.033, 0.142, 1.543, 1.101, -1.388, 0.326, -0.032, 0.083, 1.429, 0.274, 0.209, -1.115, 0.714, -0.516, -0.085, 1.004, -0.985, -0.897, -0.179, 0.556, -0.622, 0.843, -0.48, 2.202, -1.519, 1.478, -0.054, 0.954, -0.484, -0.778, 0.898, 0.389, -0.129, 0.358, -0.611, 0.487, 0.175, -0.816, -0.15, -0.397, 0.64, 0.375, 1.305, 2.19, -0.974, 0.131, 0.725, -1.746, -0.202, -0.194, 0.29, 0.756, -0.068, 1.261, -0.333, -0.486, 1.421, -0.653, -0.195, -0.88, 0.098, 0.701, 0.656, 1.288, -0.696, 0.694, -0.284, 2.131, 0.136, -0.115, 0.857, -0.243, -1.344, -1.555, -1.622, -0.148, 1.283, 1.075, -0.3, 1.568, -0.648, -1.755, 1.11, 1.178, 0.072, -0.342, 0.154, -0.466, 0.111, 0.189, 0.509, -0.438, 0.12, 1.687, 0.443, -1.87, -0.236, -1.223, 1.74, 0.866, 2.509, 1.773, 0.27, 0.893, 2.269, 0.15, 0.103, -0.841, -1.164, 1.061, 0.292, -0.337, 1.581, 2.529, 1.186, -0.017, -0.03, -0.087, -2.128, -0.635, 2.048, -0.049, -3.219, 0.58, 0.315, 0.047, -0.483, -0.639, 0.35, -2.385, 0.679, 0.665, 0.197, -0.851, -1.874, -0.523, -1.154},
266             11005, new double[] {0.60989875841900276, 0.69551223629252534, 0.30494937920950138},
267             0, PValueMethod.AUTO, true, eps));
268 
269         // No continuity correction
270         builder.add(Arguments.of(
271             new double[] {19, 22, 16, 29, 24},
272             new double[] {20, 11, 17, 12},
273             17, new double[] {0.086410732973700027, 0.043205366486850014, 0.95679463351314997},
274             0, PValueMethod.ASYMPTOTIC, false, 2 * eps));
275         builder.add(Arguments.of(
276             new double[] {2, 4, 6, 8, 10, 12, 14, 16, 18},
277             new double[] {-3, -1, 1, 3, 5, 7, 9, 11},
278             57, new double[] {0.043308142810791969, 0.021654071405395985, 0.97834592859460401},
279             0, PValueMethod.ASYMPTOTIC, false, eps));
280         // u = n*m/2 - no correction required for two-sided
281         builder.add(Arguments.of(
282             new double[] {1, 3, 5, 7},
283             new double[] {2, 4, 6},
284             6, new double[] {1, 0.57015810240066689, 0.57015810240066689},
285             0, PValueMethod.ASYMPTOTIC, true, eps));
286         builder.add(Arguments.of(
287             new double[] {1, 3, 5, 7},
288             new double[] {2, 4, 6},
289             6, new double[] {1, 0.5, 0.5},
290             0, PValueMethod.ASYMPTOTIC, false, eps));
291 
292         // Location shift
293         builder.add(Arguments.of(
294             new double[] {2, 4, 6, 8, 10, 12, 14, 16, 18},
295             new double[] {-3, -1, 1, 3, 5, 7, 9, 11},
296             66, new double[] {0.0045306406763359836, 0.0022653203381679918, 0.99833152974630091},
297             -4, PValueMethod.ASYMPTOTIC, true, 5 * eps));
298 
299         // Exact computation requested but it is not currently supported. Compare to R inexact.
300         // x = 0:515
301         // y = 516:1 + 0.5
302         // wilcox.test(x, y, alternative = "two.sided", exact = FALSE, correct = TRUE)
303         // Currently the code does not throw an exception
304         builder.accept(Arguments.of(
305             IntStream.rangeClosed(0, 515).asDoubleStream().toArray(),
306             IntStream.rangeClosed(0, 515).mapToDouble(i -> 516 - i + 0.5).toArray(),
307             132355, new double[] {0.87181181107702632, 0.56417634519959059, 0.43590590553851316},
308             0, PValueMethod.EXACT, true, eps));
309         return builder.build();
310     }
311 
312     @Test
313     void testBigDataSet() {
314         final double[] x = new double[1500];
315         final double[] y = new double[1500];
316         for (int i = 0; i < 1500; i++) {
317             x[i] = 2 * i;
318             y[i] = 2 * i + 1;
319         }
320         final MannWhitneyUTest test = MannWhitneyUTest.withDefaults().with(PValueMethod.ASYMPTOTIC);
321         final double p = test.test(x, y).getPValue();
322         // R: wilcox.test(0:1499 * 2, 0:1499 * 2 + 1)
323         TestUtils.assertProbability(0.97479389112077031, p, 1e-14, "p-value");
324         // These revert to the asymptotic computation.
325         Assertions.assertEquals(p, test.with(PValueMethod.AUTO).test(x, y).getPValue());
326         Assertions.assertEquals(p, test.with(PValueMethod.EXACT).test(x, y).getPValue());
327     }
328 
329     @Test
330     void testBigDataSetOverflow() {
331         // MATH-1145: n*m > Integer.MAX_VALUE
332         final double[] x = new double[110000];
333         for (int i = 0; i < 110000; i++) {
334             x[i] = i;
335         }
336         final double[] y = x.clone();
337         final double u = MannWhitneyUTest.withDefaults().statistic(x, y);
338         Assertions.assertEquals(6.05e+09, u);
339         final double result = MannWhitneyUTest.withDefaults().test(x, y).getPValue();
340         Assertions.assertEquals(1.0, result);
341     }
342 
343     @Test
344     void testCalculateExactPValueTooLarge() {
345         // Hit the edge case where u is not an integer. This cannot be computed exactly.
346         // Note: binom(2^31 - 1, 8) is finite
347         final int m = 8;
348         final int n = Integer.MAX_VALUE;
349         // u in [0, m*n] can be above an integer
350         Assertions.assertEquals(-1, MannWhitneyUTest.calculateExactPValue(1L << 32, m, n, AlternativeHypothesis.TWO_SIDED));
351     }
352 
353     /**
354      * Test the exact CDF computation.
355      * This hits all edge cases for expanding the cache of f.
356      *
357      * <p>Note: The method must run first of all the tests.
358      */
359     @ParameterizedTest
360     @MethodSource
361     @Order(1)
362     void testCDF(int u, int m, int n, double p) {
363         // Use 'less than' to compute the wilcox distribution CDF(u)
364         TestUtils.assertProbability(p, MannWhitneyUTest.calculateExactPValue(u, m, n, AlternativeHypothesis.LESS_THAN), 1e-14, "p-value");
365     }
366 
367     static Stream<Arguments> testCDF() {
368         final Stream.Builder<Arguments> builder = Stream.builder();
369         // Target values computed using R version 3.4.0
370         // pwilcox(u, m, n)
371         // Here we gradually increase the value of cdf(u, m, n) to
372         // deliberately expand the cache.
373         builder.add(Arguments.of(0, 1, 1, 0.5));
374         builder.add(Arguments.of(1, 1, 1, 1));
375         // Grow M
376         builder.add(Arguments.of(0, 2, 1, 0.33333333333333331483));
377         builder.add(Arguments.of(1, 2, 1, 0.66666666666666662966));
378         builder.add(Arguments.of(2, 2, 1, 1));
379         // Grow N
380         builder.add(Arguments.of(0, 2, 2, 0.16666666666666665741));
381         builder.add(Arguments.of(1, 2, 2, 0.33333333333333331483));
382         builder.add(Arguments.of(2, 2, 2, 0.66666666666666662966));
383         builder.add(Arguments.of(3, 2, 2, 0.83333333333333337034));
384         builder.add(Arguments.of(4, 2, 2, 1));
385         // Grow all and reuse
386         builder.add(Arguments.of(5, 3, 3, 0.6500000000000000222));
387         builder.add(Arguments.of(4, 3, 3, 0.5));
388         builder.add(Arguments.of(3, 3, 3, 0.3499999999999999778));
389         builder.add(Arguments.of(2, 3, 3, 0.2000000000000000111));
390         builder.add(Arguments.of(1, 3, 3, 0.10000000000000000555));
391         builder.add(Arguments.of(0, 3, 3, 0.050000000000000002776));
392         // Large values. R is slow here.
393         // The Java version takes approximately 1.6 seconds total.
394         // library(tictoc)
395         // tic(); pwilcox(4999, 100, 100); toc();
396         // 107.975 sec
397         builder.add(Arguments.of(4999, 100, 100, 0.49951371413271111743));
398         // 27.247 sec
399         builder.add(Arguments.of(1234, 100, 100, 1.0356236722237297086e-23));
400         // 0.883 sec
401         builder.add(Arguments.of(123, 100, 100, 2.669299519135844296e-49));
402         // 0.015 sec
403         builder.add(Arguments.of(12, 100, 100, 3.0039145427513413762e-57));
404         // 83.244 sec
405         builder.add(Arguments.of(6789, 100, 100, 0.99999525470611971834));
406         // 52.777 sec
407         builder.add(Arguments.of(7890, 100, 100, 0.99999999999990418775));
408         // 22.447 sec
409         builder.add(Arguments.of(8901, 100, 100, 1.0));
410         return builder.build();
411     }
412 
413     /**
414      * Checks for ties in the data (i.e. a shared value between the two samples).
415      *
416      * @param x Sample 1.
417      * @param y Sample 2.
418      * @return true if at least 1 value is shared
419      */
420     private static boolean hasTies(double[] x, double[] y) {
421         final double[] sx = x.clone();
422         Arrays.sort(sx);
423         for (final double v : y) {
424             if (Arrays.binarySearch(sx, v) >= 0) {
425                 return true;
426             }
427         }
428         return false;
429     }
430 }