BinomialDistribution.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.  *      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.distribution;

  18. import org.apache.commons.numbers.gamma.RegularizedBeta;

  19. /**
  20.  * Implementation of the binomial distribution.
  21.  *
  22.  * <p>The probability mass function of \( X \) is:
  23.  *
  24.  * <p>\[ f(k; n, p) = \binom{n}{k} p^k (1-p)^{n-k} \]
  25.  *
  26.  * <p>for \( n \in \{0, 1, 2, \dots\} \) the number of trials,
  27.  * \( p \in [0, 1] \) the probability of success,
  28.  * \( k \in \{0, 1, \dots, n\} \) the number of successes, and
  29.  *
  30.  * <p>\[ \binom{n}{k} = \frac{n!}{k! \, (n-k)!} \]
  31.  *
  32.  * <p>is the binomial coefficient.
  33.  *
  34.  * @see <a href="https://en.wikipedia.org/wiki/Binomial_distribution">Binomial distribution (Wikipedia)</a>
  35.  * @see <a href="https://mathworld.wolfram.com/BinomialDistribution.html">Binomial distribution (MathWorld)</a>
  36.  */
  37. public final class BinomialDistribution extends AbstractDiscreteDistribution {
  38.     /** 1/2. */
  39.     private static final float HALF = 0.5f;

  40.     /** The number of trials. */
  41.     private final int numberOfTrials;
  42.     /** The probability of success. */
  43.     private final double probabilityOfSuccess;
  44.     /** Cached value for pmf(x=0). */
  45.     private final double pmf0;
  46.     /** Cached value for pmf(x=n). */
  47.     private final double pmfn;

  48.     /**
  49.      * @param trials Number of trials.
  50.      * @param p Probability of success.
  51.      */
  52.     private BinomialDistribution(int trials,
  53.                                  double p) {
  54.         probabilityOfSuccess = p;
  55.         numberOfTrials = trials;
  56.         // Special pmf cases where the power function is more accurate:
  57.         //   (n choose k) == 1 for k=0, k=n
  58.         //   pmf = p^k (1-p)^(n-k)
  59.         // Note: This handles the edge case of n=0: pmf(k=0) = 1, else 0
  60.         if (probabilityOfSuccess >= HALF) {
  61.             pmf0 = Math.pow(1 - probabilityOfSuccess, numberOfTrials);
  62.         } else {
  63.             pmf0 = Math.exp(numberOfTrials * Math.log1p(-probabilityOfSuccess));
  64.         }
  65.         pmfn = Math.pow(probabilityOfSuccess, numberOfTrials);
  66.     }

  67.     /**
  68.      * Creates a binomial distribution.
  69.      *
  70.      * @param trials Number of trials.
  71.      * @param p Probability of success.
  72.      * @return the distribution
  73.      * @throws IllegalArgumentException if {@code trials < 0}, or if {@code p < 0}
  74.      * or {@code p > 1}.
  75.      */
  76.     public static BinomialDistribution of(int trials,
  77.                                           double p) {
  78.         if (trials < 0) {
  79.             throw new DistributionException(DistributionException.NEGATIVE,
  80.                                             trials);
  81.         }
  82.         ArgumentUtils.checkProbability(p);
  83.         // Avoid p = -0.0 to avoid returning -0.0 for some probability computations.
  84.         return new BinomialDistribution(trials, Math.abs(p));
  85.     }

  86.     /**
  87.      * Gets the number of trials parameter of this distribution.
  88.      *
  89.      * @return the number of trials.
  90.      */
  91.     public int getNumberOfTrials() {
  92.         return numberOfTrials;
  93.     }

  94.     /**
  95.      * Gets the probability of success parameter of this distribution.
  96.      *
  97.      * @return the probability of success.
  98.      */
  99.     public double getProbabilityOfSuccess() {
  100.         return probabilityOfSuccess;
  101.     }

  102.     /** {@inheritDoc} */
  103.     @Override
  104.     public double probability(int x) {
  105.         if (x < 0 || x > numberOfTrials) {
  106.             return 0;
  107.         } else if (x == 0) {
  108.             return pmf0;
  109.         } else if (x == numberOfTrials) {
  110.             return pmfn;
  111.         }
  112.         return Math.exp(SaddlePointExpansionUtils.logBinomialProbability(x,
  113.                         numberOfTrials, probabilityOfSuccess,
  114.                         1.0 - probabilityOfSuccess));
  115.     }

  116.     /** {@inheritDoc} **/
  117.     @Override
  118.     public double logProbability(int x) {
  119.         if (numberOfTrials == 0) {
  120.             return (x == 0) ? 0.0 : Double.NEGATIVE_INFINITY;
  121.         } else if (x < 0 || x > numberOfTrials) {
  122.             return Double.NEGATIVE_INFINITY;
  123.         }
  124.         // Special cases for x=0, x=n
  125.         // are handled in the saddle point expansion
  126.         return SaddlePointExpansionUtils.logBinomialProbability(x,
  127.                 numberOfTrials, probabilityOfSuccess,
  128.                 1.0 - probabilityOfSuccess);
  129.     }

  130.     /** {@inheritDoc} */
  131.     @Override
  132.     public double cumulativeProbability(int x) {
  133.         if (x < 0) {
  134.             return 0.0;
  135.         } else if (x >= numberOfTrials) {
  136.             return 1.0;
  137.         } else if (x == 0) {
  138.             return pmf0;
  139.         }
  140.         return RegularizedBeta.complement(probabilityOfSuccess,
  141.                                           x + 1.0, (double) numberOfTrials - x);
  142.     }

  143.     /** {@inheritDoc} */
  144.     @Override
  145.     public double survivalProbability(int x) {
  146.         if (x < 0) {
  147.             return 1.0;
  148.         } else if (x >= numberOfTrials) {
  149.             return 0.0;
  150.         } else if (x == numberOfTrials - 1) {
  151.             return pmfn;
  152.         }
  153.         return RegularizedBeta.value(probabilityOfSuccess,
  154.                                      x + 1.0, (double) numberOfTrials - x);
  155.     }

  156.     /**
  157.      * {@inheritDoc}
  158.      *
  159.      * <p>For number of trials \( n \) and probability of success \( p \), the mean is \( np \).
  160.      */
  161.     @Override
  162.     public double getMean() {
  163.         return numberOfTrials * probabilityOfSuccess;
  164.     }

  165.     /**
  166.      * {@inheritDoc}
  167.      *
  168.      * <p>For number of trials \( n \) and probability of success \( p \), the variance is \( np (1 - p) \).
  169.      */
  170.     @Override
  171.     public double getVariance() {
  172.         final double p = probabilityOfSuccess;
  173.         return numberOfTrials * p * (1 - p);
  174.     }

  175.     /**
  176.      * {@inheritDoc}
  177.      *
  178.      * <p>The lower bound of the support is always 0 except for the probability
  179.      * parameter {@code p = 1}.
  180.      *
  181.      * @return 0 or the number of trials.
  182.      */
  183.     @Override
  184.     public int getSupportLowerBound() {
  185.         return probabilityOfSuccess < 1.0 ? 0 : numberOfTrials;
  186.     }

  187.     /**
  188.      * {@inheritDoc}
  189.      *
  190.      * <p>The upper bound of the support is the number of trials except for the
  191.      * probability parameter {@code p = 0}.
  192.      *
  193.      * @return number of trials or 0.
  194.      */
  195.     @Override
  196.     public int getSupportUpperBound() {
  197.         return probabilityOfSuccess > 0.0 ? numberOfTrials : 0;
  198.     }

  199.     /** {@inheritDoc} */
  200.     @Override
  201.     int getMedian() {
  202.         // Overridden for the probability(int, int) method.
  203.         // This is intentionally not a public method.
  204.         // Can be floor or ceiling of np. For the probability in a range use the floor
  205.         // as this only used for values >= median+1.
  206.         return (int) (numberOfTrials * probabilityOfSuccess);
  207.     }
  208. }