This is an automated email from the ASF dual-hosted git repository. aherbert pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/commons-statistics.git
The following commit(s) were added to refs/heads/master by this push: new 02b3084 STATISTICS-67: Add inference module to the user guide 02b3084 is described below commit 02b30848497ad9d6cecafc8843b9eb9eefdc1c8e Author: aherbert <aherb...@apache.org> AuthorDate: Mon Feb 20 13:52:16 2023 +0000 STATISTICS-67: Add inference module to the user guide --- .../src/site/xdoc/index.xml | 2 +- .../statistics/inference/UserGuideTest.java | 100 +++++++++++++++ src/site/site.xml | 1 + src/site/xdoc/index.xml | 15 +++ src/site/xdoc/userguide/index.xml | 138 +++++++++++++++++++++ 5 files changed, 255 insertions(+), 1 deletion(-) diff --git a/commons-statistics-inference/src/site/xdoc/index.xml b/commons-statistics-inference/src/site/xdoc/index.xml index 78caeef..ab86ff4 100644 --- a/commons-statistics-inference/src/site/xdoc/index.xml +++ b/commons-statistics-inference/src/site/xdoc/index.xml @@ -43,7 +43,7 @@ double alpha = 1e-3; // Fail if we can *reject* the null hypothesis with confidence (1 - alpha) // that the observed match the expected if (ChiSquareTest.withDefaults().test(expected, observed).reject(alpha)) { - // Not significant ... + // Significant deviation from the expected ... } </source> diff --git a/commons-statistics-inference/src/test/java/org/apache/commons/statistics/inference/UserGuideTest.java b/commons-statistics-inference/src/test/java/org/apache/commons/statistics/inference/UserGuideTest.java new file mode 100644 index 0000000..e2e62b4 --- /dev/null +++ b/commons-statistics-inference/src/test/java/org/apache/commons/statistics/inference/UserGuideTest.java @@ -0,0 +1,100 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.statistics.inference; + +import java.util.Arrays; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * Test code used in the inference section of the user guide. + */ +class UserGuideTest { + @Test + void testChiSquaredTest() { + double[] expected = {0.25, 0.5, 0.25}; + long[] observed = {57, 123, 38}; + SignificanceResult result = ChiSquareTest.withDefaults() + .test(expected, observed); + Assertions.assertEquals(0.0316148, result.getPValue(), 1e-4); + Assertions.assertTrue(result.reject(0.05)); + Assertions.assertFalse(result.reject(0.01)); + } + + @Test + void testTTest() { + // Generated with: + // from scipy.stats import norm, ttest_rel + // x = norm(73, 10).rvs(10).astype(int) + // y = norm(66, 12).rvs(10).astype(int) + // ttest_rel(x, y, alternative='greater') + // (repeated until the test was just above the 0.05 significance level) + double[] math = {53, 69, 65, 65, 67, 79, 86, 65, 62, 69}; + double[] science = {75, 65, 68, 63, 55, 65, 73, 45, 51, 52}; + Assertions.assertEquals(68.0, Arrays.stream(math).average().getAsDouble()); + Assertions.assertEquals(61.2, Arrays.stream(science).average().getAsDouble()); + SignificanceResult result = TTest.withDefaults() + .with(AlternativeHypothesis.GREATER_THAN) + .pairedTest(math, science); + Assertions.assertEquals(0.05764, result.getPValue(), 1e-5); + Assertions.assertFalse(result.reject(0.05)); + } + + @Test + void testGTestIntrinsic() { + // See: http://www.biostathandbook.com/gtestgof.html + + // Allele frequencies: Mpi 90/90, Mpi 90/100, Mpi 100/100 + long[] observed = {1203, 2919, 1678}; + // Mpi 90 proportion + double p = (2.0 * observed[0] + observed[1]) / (2 * Arrays.stream(observed).sum()); + Assertions.assertEquals(0.459, p, 1e-2); + + // Hardy-Weinberg proportions + double[] expected = {p * p, 2 * p * (1 - p), (1 - p) * (1 - p)}; + Assertions.assertArrayEquals(new double[] {0.211, 0.497, 0.293}, expected, 5e-3); + + SignificanceResult result = GTest.withDefaults() + .withDegreesOfFreedomAdjustment(1) + .test(expected, observed); + Assertions.assertEquals(1.03, result.getStatistic(), 5e-2); + Assertions.assertEquals(0.309, result.getPValue(), 5e-3); + Assertions.assertFalse(result.reject(0.05)); + } + + @Test + void testAOV() { + // See: http://www.biostathandbook.com/onewayanova.html + + double[] tillamook = {0.0571, 0.0813, 0.0831, 0.0976, 0.0817, 0.0859, 0.0735, 0.0659, 0.0923, 0.0836}; + double[] newport = {0.0873, 0.0662, 0.0672, 0.0819, 0.0749, 0.0649, 0.0835, 0.0725}; + double[] petersburg = {0.0974, 0.1352, 0.0817, 0.1016, 0.0968, 0.1064, 0.105}; + double[] magadan = {0.1033, 0.0915, 0.0781, 0.0685, 0.0677, 0.0697, 0.0764, 0.0689}; + double[] tvarminne = {0.0703, 0.1026, 0.0956, 0.0973, 0.1039, 0.1045}; + + OneWayAnova.Result result = OneWayAnova.withDefaults() + .test(Arrays.asList(tillamook, newport, petersburg, magadan, tvarminne)); + Assertions.assertEquals(4, result.getDFBG()); + Assertions.assertEquals(34, result.getDFWG()); + Assertions.assertEquals(0.001113, result.getMSBG(), 2e-5); + Assertions.assertEquals(0.000159, result.getMSWG(), 1e-5); + Assertions.assertEquals(7.12, result.getStatistic(), 1e-2); + Assertions.assertEquals(2.8e-4, result.getPValue(), 1e-5); + Assertions.assertTrue(result.reject(0.001)); + } +} diff --git a/src/site/site.xml b/src/site/site.xml index a528563..65e41c0 100644 --- a/src/site/site.xml +++ b/src/site/site.xml @@ -49,6 +49,7 @@ <item name="Overview" href="/userguide/index.html#overview"/> <item name="Example Modules" href="/userguide/index.html#example-modules"/> <item name="Probability Distributions" href="/userguide/index.html#distributions"/> + <item name="Inference" href="/userguide/index.html#inference"/> <item name="Ranking" href="/userguide/index.html#ranking"/> </menu> </body> diff --git a/src/site/xdoc/index.xml b/src/site/xdoc/index.xml index 3850790..463e60f 100644 --- a/src/site/xdoc/index.xml +++ b/src/site/xdoc/index.xml @@ -42,6 +42,21 @@ double upperTail = t.survivalProbability(2.75); // P(T(29) > 2.75) PoissonDistribution p = PoissonDistribution.of(4.56); int x = p.inverseCumulativeProbability(0.99); +</source> + + <p> + Hypothesis testing can be performed for various statistical tests, for example: + </p> + +<source class="prettyprint"> +double[] math = {53, 69, 65, 65, 67, 79, 86, 65, 62, 69}; // mean = 68.0 +double[] science = {75, 65, 68, 63, 55, 65, 73, 45, 51, 52}; // mean = 61.2 + +SignificanceResult result = TTest.withDefaults() + .with(AlternativeHypothesis.GREATER_THAN) + .pairedTest(math, science); +result.getPValue(); // 0.05764 +result.reject(0.05); // false </source> <p> diff --git a/src/site/xdoc/userguide/index.xml b/src/site/xdoc/userguide/index.xml index 4ddcee5..c187a15 100644 --- a/src/site/xdoc/userguide/index.xml +++ b/src/site/xdoc/userguide/index.xml @@ -55,6 +55,17 @@ </li> </ul> </li> + <li> + <a href="#inference">Inference</a> + <ul> + <li> + <a href="#inference_overview">Overview</a> + </li> + <li> + <a href="#inference_examples">Examples</a> + </li> + </ul> + </li> <li> <a href="#ranking">Ranking</a> </li> @@ -78,6 +89,14 @@ commons-statistics-distribution</a></code> - Provides interfaces and classes for probability distributions. </li> + <li> + <code><a href="../commons-statistics-inference/index.html"> + commons-statistics-inference</a></code> - Provides hypothesis testing. + </li> + <li> + <code><a href="../commons-statistics-ranking/index.html"> + commons-statistics-ranking</a></code> - Provides rank transformations. + </li> </ul> </section> @@ -335,6 +354,125 @@ double x2 = chi2.inverseSurvivalProbability(q); </p> </subsection> </section> + <section name="Inference" id="inference"> + <p> + The <code>commons-statistics-inference</code> module provides hypothesis testing. + </p> + <subsection name="Overview" id="inference_overview"> + <p> + The module provides test classes that implement a single, or family, of statistical + tests. Each test class provides methods to compute a test statistic and a p-value for the + significance of the statistic. These can be computed together using a <code>test</code> + method and returned as a + <a href="../commons-statistics-inference/apidocs/org/apache/commons/statistics/inference/DiscreteDistribution.html">SignificanceResult</a>. + The <code>SignificanceResult</code> has a method that can be used to <code>reject</code> + the null hypothesis at the provided significance level. Test classes may extend the + <code>SignificanceResult</code> to return more information about the test result, + for example the computed degrees of freedom. + </p> + <p> + Alternatively a <code>statistic</code> method is provided to compute <i>only</i> the + statistic as a <code>double</code> value. This statistic can be compared to a pre-computed + critical value, for example from a table of critical values. + </p> + <p> + A test is obtained using the <code>withDefaults()</code> method to return the test with + all options set to their default value. Any test options can be configured using + property change methods to return a new instance of the test. Tests that support an + <a href="../commons-statistics-inference/apidocs/org/apache/commons/statistics/inference/AlternativeHypothesis.html"> + alternate hypothesis</a> will use a two-sided test by default. Test that support multiple + <a href="../commons-statistics-inference/apidocs/org/apache/commons/statistics/inference/PValueMethod.html"> + p-value methods</a> will default to an appropriate computation for the size of the input + data. Unless otherwise noted test instances are immutable. + </p> + </subsection> + <subsection name="Examples" id="inference_examples"> + <p> + A chi-square test that the observed counts conform to the expected frequencies. + </p> +<source class="prettyprint"> +double[] expected = {0.25, 0.5, 0.25}; +long[] observed = {57, 123, 38}; + +SignificanceResult result = ChiSquareTest.withDefaults() + .test(expected, observed); +result.getPValue(); // 0.0316148 +result.reject(0.05); // true +result.reject(0.01); // false +</source> + <p> + A paired t-test that the student's marks in the math exam were greater than the science + exam. This fails to reject the null hypothesis (that there was no difference) with + 95% confidence. + </p> +<source class="prettyprint"> +double[] math = {53, 69, 65, 65, 67, 79, 86, 65, 62, 69}; // mean = 68.0 +double[] science = {75, 65, 68, 63, 55, 65, 73, 45, 51, 52}; // mean = 61.2 + +SignificanceResult result = TTest.withDefaults() + .with(AlternativeHypothesis.GREATER_THAN) + .pairedTest(math, science); +result.getPValue(); // 0.05764 +result.reject(0.05); // false +</source> + <p> + A G-test that the allele frequencies conform to the expected Hardy-Weinberg proportions. + This is an example of an intrinsic hypothesis where the expected frequencies are computed + using the observations and the degrees of freedom must be adjusted. + The data is from McDonald (1989) Selection component analysis + of the Mpi locus in the amphipod Platorchestia platensis. + <i>Heredity</i> <b>62</b>: 243-249. + </p> +<source class="prettyprint"> +// Allele frequencies: Mpi 90/90, Mpi 90/100, Mpi 100/100 +long[] observed = {1203, 2919, 1678}; +// Mpi 90 proportion +double p = (2.0 * observed[0] + observed[1]) / + (2 * Arrays.stream(observed).sum()); // 5325 / 11600 = 0.459 + +// Hardy-Weinberg proportions +double[] expected = {p * p, 2 * p * (1 - p), (1 - p) * (1 - p)}; +// 0.211, 0.497, 0.293 + +SignificanceResult result = GTest.withDefaults() + .withDegreesOfFreedomAdjustment(1) + .test(expected, observed); +result.getStatistic(); // 1.03 +result.getPValue(); // 0.309 +result.reject(0.05); // false +</source> + <p> + A one-way analysis of variance test. This is an example where the result has more + information than the test statistic and the p-value. + The data is from McDonald <i>et al</i> (1991) Allozymes and morphometric characters of + three species of Mytilus in the Northern and Southern Hemispheres. + <i>Marine Biology</i> <b>111</b>: 323-333. + </p> +<source class="prettyprint"> +double[] tillamook = {0.0571, 0.0813, 0.0831, 0.0976, 0.0817, 0.0859, 0.0735, 0.0659, 0.0923, 0.0836}; +double[] newport = {0.0873, 0.0662, 0.0672, 0.0819, 0.0749, 0.0649, 0.0835, 0.0725}; +double[] petersburg = {0.0974, 0.1352, 0.0817, 0.1016, 0.0968, 0.1064, 0.105}; +double[] magadan = {0.1033, 0.0915, 0.0781, 0.0685, 0.0677, 0.0697, 0.0764, 0.0689}; +double[] tvarminne = {0.0703, 0.1026, 0.0956, 0.0973, 0.1039, 0.1045}; + +Collection<double[]> data = Arrays.asList(tillamook, newport, petersburg, magadan, tvarminne); +OneWayAnova.Result result = OneWayAnova.withDefaults() + .test(data); +result.getStatistic(); // 7.12 +result.getPValue(); // 2.8e-4 +result.reject(0.001); // true +</source> + <p> + The result also provides the between and within group degrees of freedom and the mean + squares allowing reporting of the results in a table: + </p> + <table> + <tr><th></th><th>degrees of freedom</th><th>mean square</th><th>F</th><th>p</th></tr> + <tr><td>between groups</td><td>4</td><td>0.001113</td><td>7.12</td><td>2.8e-4</td></tr> + <tr><td>within groups</td><td>34</td><td>0.000159</td><td></td><td></td></tr> + </table> + </subsection> + </section> <section name="Ranking" id="ranking"> <p> The <code>commons-statistics-ranking</code> module provides rank transformations.