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.combinatorics.BinomialCoefficientDouble;
20 import org.apache.commons.numbers.combinatorics.LogBinomialCoefficient;
21 import org.apache.commons.numbers.gamma.RegularizedBeta;
22
23 /**
24 * Implementation of the Pascal distribution.
25 *
26 * <p>The Pascal distribution is a special case of the negative binomial distribution
27 * where the number of successes parameter is an integer.
28 *
29 * <p>There are various ways to express the probability mass and distribution
30 * functions for the Pascal distribution. The present implementation represents
31 * the distribution of the number of failures before \( r \) successes occur.
32 * This is the convention adopted in e.g.
33 * <a href="https://mathworld.wolfram.com/NegativeBinomialDistribution.html">MathWorld</a>,
34 * but <em>not</em> in
35 * <a href="https://en.wikipedia.org/wiki/Negative_binomial_distribution">Wikipedia</a>.
36 *
37 * <p>The probability mass function of \( X \) is:
38 *
39 * <p>\[ f(k; r, p) = \binom{k+r-1}{r-1} p^r \, (1-p)^k \]
40 *
41 * <p>for \( r \in \{1, 2, \dots\} \) the number of successes,
42 * \( p \in (0, 1] \) the probability of success,
43 * \( k \in \{0, 1, 2, \dots\} \) the total number of failures, and
44 *
45 * <p>\[ \binom{k+r-1}{r-1} = \frac{(k+r-1)!}{(r-1)! \, k!} \]
46 *
47 * <p>is the binomial coefficient.
48 *
49 * <p>The cumulative distribution function of \( X \) is:
50 *
51 * <p>\[ P(X \leq k) = I(p, r, k + 1) \]
52 *
53 * <p>where \( I \) is the regularized incomplete beta function.
54 *
55 * @see <a href="https://en.wikipedia.org/wiki/Negative_binomial_distribution">Negative binomial distribution (Wikipedia)</a>
56 * @see <a href="https://mathworld.wolfram.com/NegativeBinomialDistribution.html">Negative binomial distribution (MathWorld)</a>
57 */
58 public final class PascalDistribution extends AbstractDiscreteDistribution {
59 /** The number of successes. */
60 private final int numberOfSuccesses;
61 /** The probability of success. */
62 private final double probabilityOfSuccess;
63 /** The value of {@code log(p) * n}, where {@code p} is the probability of success
64 * and {@code n} is the number of successes, stored for faster computation. */
65 private final double logProbabilityOfSuccessByNumOfSuccesses;
66 /** The value of {@code log(1-p)}, where {@code p} is the probability of success,
67 * stored for faster computation. */
68 private final double log1mProbabilityOfSuccess;
69 /** The value of {@code p^n}, where {@code p} is the probability of success
70 * and {@code n} is the number of successes, stored for faster computation. */
71 private final double probabilityOfSuccessPowNumOfSuccesses;
72
73 /**
74 * @param r Number of successes.
75 * @param p Probability of success.
76 */
77 private PascalDistribution(int r,
78 double p) {
79 numberOfSuccesses = r;
80 probabilityOfSuccess = p;
81 logProbabilityOfSuccessByNumOfSuccesses = Math.log(p) * numberOfSuccesses;
82 log1mProbabilityOfSuccess = Math.log1p(-p);
83 probabilityOfSuccessPowNumOfSuccesses = Math.pow(probabilityOfSuccess, numberOfSuccesses);
84 }
85
86 /**
87 * Create a Pascal distribution.
88 *
89 * @param r Number of successes.
90 * @param p Probability of success.
91 * @return the distribution
92 * @throws IllegalArgumentException if {@code r <= 0} or {@code p <= 0} or
93 * {@code p > 1}.
94 */
95 public static PascalDistribution of(int r,
96 double p) {
97 if (r <= 0) {
98 throw new DistributionException(DistributionException.NOT_STRICTLY_POSITIVE, r);
99 }
100 if (p <= 0 ||
101 p > 1) {
102 throw new DistributionException(DistributionException.INVALID_NON_ZERO_PROBABILITY, p);
103 }
104 return new PascalDistribution(r, p);
105 }
106
107 /**
108 * Gets the number of successes parameter of this distribution.
109 *
110 * @return the number of successes.
111 */
112 public int getNumberOfSuccesses() {
113 return numberOfSuccesses;
114 }
115
116 /**
117 * Gets the probability of success parameter of this distribution.
118 *
119 * @return the probability of success.
120 */
121 public double getProbabilityOfSuccess() {
122 return probabilityOfSuccess;
123 }
124
125 /** {@inheritDoc} */
126 @Override
127 public double probability(int x) {
128 if (x <= 0) {
129 // Special case of x=0 exploiting cancellation.
130 return x == 0 ? probabilityOfSuccessPowNumOfSuccesses : 0.0;
131 }
132 final int n = x + numberOfSuccesses - 1;
133 if (n < 0) {
134 // overflow
135 return 0.0;
136 }
137 return BinomialCoefficientDouble.value(n, numberOfSuccesses - 1) *
138 probabilityOfSuccessPowNumOfSuccesses *
139 Math.pow(1.0 - probabilityOfSuccess, x);
140 }
141
142 /** {@inheritDoc} */
143 @Override
144 public double logProbability(int x) {
145 if (x <= 0) {
146 // Special case of x=0 exploiting cancellation.
147 return x == 0 ? logProbabilityOfSuccessByNumOfSuccesses : Double.NEGATIVE_INFINITY;
148 }
149 final int n = x + numberOfSuccesses - 1;
150 if (n < 0) {
151 // overflow
152 return Double.NEGATIVE_INFINITY;
153 }
154 return LogBinomialCoefficient.value(n, numberOfSuccesses - 1) +
155 logProbabilityOfSuccessByNumOfSuccesses +
156 log1mProbabilityOfSuccess * x;
157 }
158
159 /** {@inheritDoc} */
160 @Override
161 public double cumulativeProbability(int x) {
162 if (x < 0) {
163 return 0.0;
164 }
165 return RegularizedBeta.value(probabilityOfSuccess,
166 numberOfSuccesses, x + 1.0);
167 }
168
169 /** {@inheritDoc} */
170 @Override
171 public double survivalProbability(int x) {
172 if (x < 0) {
173 return 1.0;
174 }
175 return RegularizedBeta.complement(probabilityOfSuccess,
176 numberOfSuccesses, x + 1.0);
177 }
178
179 /**
180 * {@inheritDoc}
181 *
182 * <p>For number of successes \( r \) and probability of success \( p \),
183 * the mean is:
184 *
185 * <p>\[ \frac{r (1 - p)}{p} \]
186 */
187 @Override
188 public double getMean() {
189 final double p = getProbabilityOfSuccess();
190 final double r = getNumberOfSuccesses();
191 return (r * (1 - p)) / p;
192 }
193
194 /**
195 * {@inheritDoc}
196 *
197 * <p>For number of successes \( r \) and probability of success \( p \),
198 * the variance is:
199 *
200 * <p>\[ \frac{r (1 - p)}{p^2} \]
201 */
202 @Override
203 public double getVariance() {
204 final double p = getProbabilityOfSuccess();
205 final double r = getNumberOfSuccesses();
206 return r * (1 - p) / (p * p);
207 }
208
209 /**
210 * {@inheritDoc}
211 *
212 * <p>The lower bound of the support is always 0.
213 *
214 * @return 0.
215 */
216 @Override
217 public int getSupportLowerBound() {
218 return 0;
219 }
220
221 /**
222 * {@inheritDoc}
223 *
224 * <p>The upper bound of the support is positive infinity except for the
225 * probability parameter {@code p = 1.0}.
226 *
227 * @return {@link Integer#MAX_VALUE} or 0.
228 */
229 @Override
230 public int getSupportUpperBound() {
231 return probabilityOfSuccess < 1 ? Integer.MAX_VALUE : 0;
232 }
233 }