BinomialTest.java

  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.  * This is not the original file distributed by the Apache Software Foundation
  19.  * It has been modified by the Hipparchus project
  20.  */
  21. package org.hipparchus.stat.inference;

  22. import org.hipparchus.distribution.discrete.BinomialDistribution;
  23. import org.hipparchus.exception.LocalizedCoreFormats;
  24. import org.hipparchus.exception.MathIllegalArgumentException;
  25. import org.hipparchus.exception.MathRuntimeException;
  26. import org.hipparchus.util.MathUtils;

  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.  */
  35. public class BinomialTest { // NOPMD - this is not a Junit test class, PMD false positive here

  36.     /** Empty constructor.
  37.      * <p>
  38.      * This constructor is not strictly necessary, but it prevents spurious
  39.      * javadoc warnings with JDK 18 and later.
  40.      * </p>
  41.      * @since 3.0
  42.      */
  43.     public BinomialTest() { // NOPMD - unnecessary constructor added intentionally to make javadoc happy
  44.         // nothing to do
  45.     }

  46.     /**
  47.      * Returns whether the null hypothesis can be rejected with the given confidence level.
  48.      * <p>
  49.      * <strong>Preconditions</strong>:
  50.      * <ul>
  51.      * <li>Number of trials must be &ge; 0.</li>
  52.      * <li>Number of successes must be &ge; 0.</li>
  53.      * <li>Number of successes must be &le; number of trials.</li>
  54.      * <li>Probability must be &ge; 0 and &le; 1.</li>
  55.      * </ul>
  56.      *
  57.      * @param numberOfTrials number of trials performed
  58.      * @param numberOfSuccesses number of successes observed
  59.      * @param probability assumed probability of a single trial under the null hypothesis
  60.      * @param alternativeHypothesis type of hypothesis being evaluated (one- or two-sided)
  61.      * @param alpha significance level of the test
  62.      * @return true if the null hypothesis can be rejected with confidence {@code 1 - alpha}
  63.      * @throws MathIllegalArgumentException if {@code numberOfTrials} or {@code numberOfSuccesses} is negative
  64.      * @throws MathIllegalArgumentException if {@code probability} is not between 0 and 1
  65.      * @throws MathIllegalArgumentException if {@code numberOfTrials} &lt; {@code numberOfSuccesses} or
  66.      * if {@code alternateHypothesis} is null.
  67.      * @see AlternativeHypothesis
  68.      */
  69.     public boolean binomialTest(int numberOfTrials, int numberOfSuccesses, double probability,
  70.                                 AlternativeHypothesis alternativeHypothesis, double alpha) {
  71.         double pValue = binomialTest(numberOfTrials, numberOfSuccesses, probability, alternativeHypothesis);
  72.         return pValue < alpha;
  73.     }

  74.     /**
  75.      * Returns the <i>observed significance level</i>, or
  76.      * <a href="http://www.cas.lancs.ac.uk/glossary_v1.1/hyptest.html#pvalue">p-value</a>,
  77.      * associated with a <a href="http://en.wikipedia.org/wiki/Binomial_test"> Binomial test</a>.
  78.      * <p>
  79.      * The number returned is the smallest significance level at which one can reject the null hypothesis.
  80.      * The form of the hypothesis depends on {@code alternativeHypothesis}.</p>
  81.      * <p>
  82.      * The p-Value represents the likelihood of getting a result at least as extreme as the sample,
  83.      * given the provided {@code probability} of success on a single trial. For single-sided tests,
  84.      * this value can be directly derived from the Binomial distribution. For the two-sided test,
  85.      * the implementation works as follows: we start by looking at the most extreme cases
  86.      * (0 success and n success where n is the number of trials from the sample) and determine their likelihood.
  87.      * The lower value is added to the p-Value (if both values are equal, both are added). Then we continue with
  88.      * the next extreme value, until we added the value for the actual observed sample.</p>
  89.      * <p>* <strong>Preconditions</strong>:</p>
  90.      * <ul>
  91.      * <li>Number of trials must be &ge; 0.</li>
  92.      * <li>Number of successes must be &ge; 0.</li>
  93.      * <li>Number of successes must be &le; number of trials.</li>
  94.      * <li>Probability must be &ge; 0 and &le; 1.</li>
  95.      * </ul>
  96.      *
  97.      * @param numberOfTrials number of trials performed
  98.      * @param numberOfSuccesses number of successes observed
  99.      * @param probability assumed probability of a single trial under the null hypothesis
  100.      * @param alternativeHypothesis type of hypothesis being evaluated (one- or two-sided)
  101.      * @return p-value
  102.      * @throws MathIllegalArgumentException if {@code numberOfTrials} or {@code numberOfSuccesses} is negative
  103.      * @throws MathIllegalArgumentException if {@code probability} is not between 0 and 1
  104.      * @throws MathIllegalArgumentException if {@code numberOfTrials} &lt; {@code numberOfSuccesses} or
  105.      * if {@code alternateHypothesis} is null.
  106.      * @see AlternativeHypothesis
  107.      */
  108.     public double binomialTest(int numberOfTrials, int numberOfSuccesses, double probability,
  109.                                AlternativeHypothesis alternativeHypothesis) {
  110.         if (numberOfTrials < 0) {
  111.             throw new MathIllegalArgumentException(LocalizedCoreFormats.NUMBER_TOO_SMALL, numberOfTrials, 0);
  112.         }
  113.         if (numberOfSuccesses < 0) {
  114.             throw new MathIllegalArgumentException(LocalizedCoreFormats.NUMBER_TOO_SMALL, numberOfSuccesses, 0);
  115.         }
  116.         MathUtils.checkRangeInclusive(probability, 0, 1);
  117.         if (numberOfTrials < numberOfSuccesses) {
  118.             throw new MathIllegalArgumentException(
  119.                 LocalizedCoreFormats.BINOMIAL_INVALID_PARAMETERS_ORDER,
  120.                 numberOfTrials, numberOfSuccesses);
  121.         }
  122.         MathUtils.checkNotNull(alternativeHypothesis);

  123.         final BinomialDistribution distribution = new BinomialDistribution(numberOfTrials, probability);
  124.         switch (alternativeHypothesis) {
  125.         case GREATER_THAN:
  126.             return 1 - distribution.cumulativeProbability(numberOfSuccesses - 1);
  127.         case LESS_THAN:
  128.             return distribution.cumulativeProbability(numberOfSuccesses);
  129.         case TWO_SIDED:
  130.             int criticalValueLow = 0;
  131.             int criticalValueHigh = numberOfTrials;
  132.             double pTotal = 0;

  133.             while (true) {
  134.                 final double pLow = distribution.probability(criticalValueLow);
  135.                 final double pHigh = distribution.probability(criticalValueHigh);

  136.                 if (pLow == pHigh) {
  137.                     if (criticalValueLow == criticalValueHigh) { // One side can't move
  138.                         pTotal += pLow;
  139.                     } else {
  140.                         pTotal += 2 * pLow;
  141.                     }
  142.                     criticalValueLow++;
  143.                     criticalValueHigh--;
  144.                 } else if (pLow < pHigh) {
  145.                     pTotal += pLow;
  146.                     criticalValueLow++;
  147.                 } else {
  148.                     pTotal += pHigh;
  149.                     criticalValueHigh--;
  150.                 }

  151.                 if (criticalValueLow > numberOfSuccesses || criticalValueHigh < numberOfSuccesses) {
  152.                     break;
  153.                 }
  154.             }
  155.             return pTotal;
  156.         default:
  157.             // this should never happen
  158.             throw MathRuntimeException.createInternalError();
  159.         }
  160.     }
  161. }