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.inference; 23 24 import org.hipparchus.distribution.discrete.BinomialDistribution; 25 import org.hipparchus.exception.LocalizedCoreFormats; 26 import org.hipparchus.exception.MathIllegalArgumentException; 27 import org.hipparchus.exception.MathRuntimeException; 28 import org.hipparchus.util.MathUtils; 29 30 /** 31 * Implements binomial test statistics. 32 * <p> 33 * Exact test for the statistical significance of deviations from a 34 * theoretically expected distribution of observations into two categories. 35 * 36 * @see <a href="http://en.wikipedia.org/wiki/Binomial_test">Binomial test (Wikipedia)</a> 37 */ 38 public class BinomialTest { // NOPMD - this is not a Junit test class, PMD false positive here 39 40 /** Empty constructor. 41 * <p> 42 * This constructor is not strictly necessary, but it prevents spurious 43 * javadoc warnings with JDK 18 and later. 44 * </p> 45 * @since 3.0 46 */ 47 public BinomialTest() { // NOPMD - unnecessary constructor added intentionally to make javadoc happy 48 // nothing to do 49 } 50 51 /** 52 * Returns whether the null hypothesis can be rejected with the given confidence level. 53 * <p> 54 * <strong>Preconditions</strong>: 55 * <ul> 56 * <li>Number of trials must be ≥ 0.</li> 57 * <li>Number of successes must be ≥ 0.</li> 58 * <li>Number of successes must be ≤ number of trials.</li> 59 * <li>Probability must be ≥ 0 and ≤ 1.</li> 60 * </ul> 61 * 62 * @param numberOfTrials number of trials performed 63 * @param numberOfSuccesses number of successes observed 64 * @param probability assumed probability of a single trial under the null hypothesis 65 * @param alternativeHypothesis type of hypothesis being evaluated (one- or two-sided) 66 * @param alpha significance level of the test 67 * @return true if the null hypothesis can be rejected with confidence {@code 1 - alpha} 68 * @throws MathIllegalArgumentException if {@code numberOfTrials} or {@code numberOfSuccesses} is negative 69 * @throws MathIllegalArgumentException if {@code probability} is not between 0 and 1 70 * @throws MathIllegalArgumentException if {@code numberOfTrials} < {@code numberOfSuccesses} or 71 * if {@code alternateHypothesis} is null. 72 * @see AlternativeHypothesis 73 */ 74 public boolean binomialTest(int numberOfTrials, int numberOfSuccesses, double probability, 75 AlternativeHypothesis alternativeHypothesis, double alpha) { 76 double pValue = binomialTest(numberOfTrials, numberOfSuccesses, probability, alternativeHypothesis); 77 return pValue < alpha; 78 } 79 80 /** 81 * Returns the <i>observed significance level</i>, or 82 * <a href="http://www.cas.lancs.ac.uk/glossary_v1.1/hyptest.html#pvalue">p-value</a>, 83 * associated with a <a href="http://en.wikipedia.org/wiki/Binomial_test"> Binomial test</a>. 84 * <p> 85 * The number returned is the smallest significance level at which one can reject the null hypothesis. 86 * The form of the hypothesis depends on {@code alternativeHypothesis}.</p> 87 * <p> 88 * The p-Value represents the likelihood of getting a result at least as extreme as the sample, 89 * given the provided {@code probability} of success on a single trial. For single-sided tests, 90 * this value can be directly derived from the Binomial distribution. For the two-sided test, 91 * the implementation works as follows: we start by looking at the most extreme cases 92 * (0 success and n success where n is the number of trials from the sample) and determine their likelihood. 93 * The lower value is added to the p-Value (if both values are equal, both are added). Then we continue with 94 * the next extreme value, until we added the value for the actual observed sample.</p> 95 * <p>* <strong>Preconditions</strong>:</p> 96 * <ul> 97 * <li>Number of trials must be ≥ 0.</li> 98 * <li>Number of successes must be ≥ 0.</li> 99 * <li>Number of successes must be ≤ number of trials.</li> 100 * <li>Probability must be ≥ 0 and ≤ 1.</li> 101 * </ul> 102 * 103 * @param numberOfTrials number of trials performed 104 * @param numberOfSuccesses number of successes observed 105 * @param probability assumed probability of a single trial under the null hypothesis 106 * @param alternativeHypothesis type of hypothesis being evaluated (one- or two-sided) 107 * @return p-value 108 * @throws MathIllegalArgumentException if {@code numberOfTrials} or {@code numberOfSuccesses} is negative 109 * @throws MathIllegalArgumentException if {@code probability} is not between 0 and 1 110 * @throws MathIllegalArgumentException if {@code numberOfTrials} < {@code numberOfSuccesses} or 111 * if {@code alternateHypothesis} is null. 112 * @see AlternativeHypothesis 113 */ 114 public double binomialTest(int numberOfTrials, int numberOfSuccesses, double probability, 115 AlternativeHypothesis alternativeHypothesis) { 116 if (numberOfTrials < 0) { 117 throw new MathIllegalArgumentException(LocalizedCoreFormats.NUMBER_TOO_SMALL, numberOfTrials, 0); 118 } 119 if (numberOfSuccesses < 0) { 120 throw new MathIllegalArgumentException(LocalizedCoreFormats.NUMBER_TOO_SMALL, numberOfSuccesses, 0); 121 } 122 MathUtils.checkRangeInclusive(probability, 0, 1); 123 if (numberOfTrials < numberOfSuccesses) { 124 throw new MathIllegalArgumentException( 125 LocalizedCoreFormats.BINOMIAL_INVALID_PARAMETERS_ORDER, 126 numberOfTrials, numberOfSuccesses); 127 } 128 MathUtils.checkNotNull(alternativeHypothesis); 129 130 final BinomialDistribution distribution = new BinomialDistribution(numberOfTrials, probability); 131 switch (alternativeHypothesis) { 132 case GREATER_THAN: 133 return 1 - distribution.cumulativeProbability(numberOfSuccesses - 1); 134 case LESS_THAN: 135 return distribution.cumulativeProbability(numberOfSuccesses); 136 case TWO_SIDED: 137 int criticalValueLow = 0; 138 int criticalValueHigh = numberOfTrials; 139 double pTotal = 0; 140 141 while (true) { 142 final double pLow = distribution.probability(criticalValueLow); 143 final double pHigh = distribution.probability(criticalValueHigh); 144 145 if (pLow == pHigh) { 146 if (criticalValueLow == criticalValueHigh) { // One side can't move 147 pTotal += pLow; 148 } else { 149 pTotal += 2 * pLow; 150 } 151 criticalValueLow++; 152 criticalValueHigh--; 153 } else if (pLow < pHigh) { 154 pTotal += pLow; 155 criticalValueLow++; 156 } else { 157 pTotal += pHigh; 158 criticalValueHigh--; 159 } 160 161 if (criticalValueLow > numberOfSuccesses || criticalValueHigh < numberOfSuccesses) { 162 break; 163 } 164 } 165 return pTotal; 166 default: 167 // this should never happen 168 throw MathRuntimeException.createInternalError(); 169 } 170 } 171 }