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 }