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