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.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 }