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.statistics.distribution;
18  
19  import org.apache.commons.numbers.gamma.RegularizedBeta;
20  
21  /**
22   * Implementation of the binomial distribution.
23   *
24   * <p>The probability mass function of \( X \) is:
25   *
26   * <p>\[ f(k; n, p) = \binom{n}{k} p^k (1-p)^{n-k} \]
27   *
28   * <p>for \( n \in \{0, 1, 2, \dots\} \) the number of trials,
29   * \( p \in [0, 1] \) the probability of success,
30   * \( k \in \{0, 1, \dots, n\} \) the number of successes, and
31   *
32   * <p>\[ \binom{n}{k} = \frac{n!}{k! \, (n-k)!} \]
33   *
34   * <p>is the binomial coefficient.
35   *
36   * @see <a href="https://en.wikipedia.org/wiki/Binomial_distribution">Binomial distribution (Wikipedia)</a>
37   * @see <a href="https://mathworld.wolfram.com/BinomialDistribution.html">Binomial distribution (MathWorld)</a>
38   */
39  public final class BinomialDistribution extends AbstractDiscreteDistribution {
40      /** 1/2. */
41      private static final float HALF = 0.5f;
42  
43      /** The number of trials. */
44      private final int numberOfTrials;
45      /** The probability of success. */
46      private final double probabilityOfSuccess;
47      /** Cached value for pmf(x=0). */
48      private final double pmf0;
49      /** Cached value for pmf(x=n). */
50      private final double pmfn;
51  
52      /**
53       * @param trials Number of trials.
54       * @param p Probability of success.
55       */
56      private BinomialDistribution(int trials,
57                                   double p) {
58          probabilityOfSuccess = p;
59          numberOfTrials = trials;
60          // Special pmf cases where the power function is more accurate:
61          //   (n choose k) == 1 for k=0, k=n
62          //   pmf = p^k (1-p)^(n-k)
63          // Note: This handles the edge case of n=0: pmf(k=0) = 1, else 0
64          if (probabilityOfSuccess >= HALF) {
65              pmf0 = Math.pow(1 - probabilityOfSuccess, numberOfTrials);
66          } else {
67              pmf0 = Math.exp(numberOfTrials * Math.log1p(-probabilityOfSuccess));
68          }
69          pmfn = Math.pow(probabilityOfSuccess, numberOfTrials);
70      }
71  
72      /**
73       * Creates a binomial distribution.
74       *
75       * @param trials Number of trials.
76       * @param p Probability of success.
77       * @return the distribution
78       * @throws IllegalArgumentException if {@code trials < 0}, or if {@code p < 0}
79       * or {@code p > 1}.
80       */
81      public static BinomialDistribution of(int trials,
82                                            double p) {
83          if (trials < 0) {
84              throw new DistributionException(DistributionException.NEGATIVE,
85                                              trials);
86          }
87          ArgumentUtils.checkProbability(p);
88          // Avoid p = -0.0 to avoid returning -0.0 for some probability computations.
89          return new BinomialDistribution(trials, Math.abs(p));
90      }
91  
92      /**
93       * Gets the number of trials parameter of this distribution.
94       *
95       * @return the number of trials.
96       */
97      public int getNumberOfTrials() {
98          return numberOfTrials;
99      }
100 
101     /**
102      * Gets the probability of success parameter of this distribution.
103      *
104      * @return the probability of success.
105      */
106     public double getProbabilityOfSuccess() {
107         return probabilityOfSuccess;
108     }
109 
110     /** {@inheritDoc} */
111     @Override
112     public double probability(int x) {
113         if (x < 0 || x > numberOfTrials) {
114             return 0;
115         } else if (x == 0) {
116             return pmf0;
117         } else if (x == numberOfTrials) {
118             return pmfn;
119         }
120         return Math.exp(SaddlePointExpansionUtils.logBinomialProbability(x,
121                         numberOfTrials, probabilityOfSuccess,
122                         1.0 - probabilityOfSuccess));
123     }
124 
125     /** {@inheritDoc} **/
126     @Override
127     public double logProbability(int x) {
128         if (numberOfTrials == 0) {
129             return (x == 0) ? 0.0 : Double.NEGATIVE_INFINITY;
130         } else if (x < 0 || x > numberOfTrials) {
131             return Double.NEGATIVE_INFINITY;
132         }
133         // Special cases for x=0, x=n
134         // are handled in the saddle point expansion
135         return SaddlePointExpansionUtils.logBinomialProbability(x,
136                 numberOfTrials, probabilityOfSuccess,
137                 1.0 - probabilityOfSuccess);
138     }
139 
140     /** {@inheritDoc} */
141     @Override
142     public double cumulativeProbability(int x) {
143         if (x < 0) {
144             return 0.0;
145         } else if (x >= numberOfTrials) {
146             return 1.0;
147         } else if (x == 0) {
148             return pmf0;
149         }
150         return RegularizedBeta.complement(probabilityOfSuccess,
151                                           x + 1.0, (double) numberOfTrials - x);
152     }
153 
154     /** {@inheritDoc} */
155     @Override
156     public double survivalProbability(int x) {
157         if (x < 0) {
158             return 1.0;
159         } else if (x >= numberOfTrials) {
160             return 0.0;
161         } else if (x == numberOfTrials - 1) {
162             return pmfn;
163         }
164         return RegularizedBeta.value(probabilityOfSuccess,
165                                      x + 1.0, (double) numberOfTrials - x);
166     }
167 
168     /**
169      * {@inheritDoc}
170      *
171      * <p>For number of trials \( n \) and probability of success \( p \), the mean is \( np \).
172      */
173     @Override
174     public double getMean() {
175         return numberOfTrials * probabilityOfSuccess;
176     }
177 
178     /**
179      * {@inheritDoc}
180      *
181      * <p>For number of trials \( n \) and probability of success \( p \), the variance is \( np (1 - p) \).
182      */
183     @Override
184     public double getVariance() {
185         final double p = probabilityOfSuccess;
186         return numberOfTrials * p * (1 - p);
187     }
188 
189     /**
190      * {@inheritDoc}
191      *
192      * <p>The lower bound of the support is always 0 except for the probability
193      * parameter {@code p = 1}.
194      *
195      * @return 0 or the number of trials.
196      */
197     @Override
198     public int getSupportLowerBound() {
199         return probabilityOfSuccess < 1.0 ? 0 : numberOfTrials;
200     }
201 
202     /**
203      * {@inheritDoc}
204      *
205      * <p>The upper bound of the support is the number of trials except for the
206      * probability parameter {@code p = 0}.
207      *
208      * @return number of trials or 0.
209      */
210     @Override
211     public int getSupportUpperBound() {
212         return probabilityOfSuccess > 0.0 ? numberOfTrials : 0;
213     }
214 
215     /** {@inheritDoc} */
216     @Override
217     int getMedian() {
218         // Overridden for the probability(int, int) method.
219         // This is intentionally not a public method.
220         // Can be floor or ceiling of np. For the probability in a range use the floor
221         // as this only used for values >= median+1.
222         return (int) (numberOfTrials * probabilityOfSuccess);
223     }
224 }