001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.statistics.distribution;
018
019import org.apache.commons.numbers.combinatorics.BinomialCoefficientDouble;
020import org.apache.commons.numbers.combinatorics.LogBinomialCoefficient;
021import org.apache.commons.numbers.gamma.RegularizedBeta;
022
023/**
024 * Implementation of the Pascal distribution.
025 *
026 * <p>The Pascal distribution is a special case of the negative binomial distribution
027 * where the number of successes parameter is an integer.
028 *
029 * <p>There are various ways to express the probability mass and distribution
030 * functions for the Pascal distribution. The present implementation represents
031 * the distribution of the number of failures before \( r \) successes occur.
032 * This is the convention adopted in e.g.
033 * <a href="https://mathworld.wolfram.com/NegativeBinomialDistribution.html">MathWorld</a>,
034 * but <em>not</em> in
035 * <a href="https://en.wikipedia.org/wiki/Negative_binomial_distribution">Wikipedia</a>.
036 *
037 * <p>The probability mass function of \( X \) is:
038 *
039 * <p>\[ f(k; r, p) = \binom{k+r-1}{r-1} p^r \, (1-p)^k \]
040 *
041 * <p>for \( r \in \{1, 2, \dots\} \) the number of successes,
042 * \( p \in (0, 1] \) the probability of success,
043 * \( k \in \{0, 1, 2, \dots\} \) the total number of failures, and
044 *
045 * <p>\[ \binom{k+r-1}{r-1} = \frac{(k+r-1)!}{(r-1)! \, k!} \]
046 *
047 * <p>is the binomial coefficient.
048 *
049 * <p>The cumulative distribution function of \( X \) is:
050 *
051 * <p>\[ P(X \leq k) = I(p, r, k + 1) \]
052 *
053 * <p>where \( I \) is the regularized incomplete beta function.
054 *
055 * @see <a href="https://en.wikipedia.org/wiki/Negative_binomial_distribution">Negative binomial distribution (Wikipedia)</a>
056 * @see <a href="https://mathworld.wolfram.com/NegativeBinomialDistribution.html">Negative binomial distribution (MathWorld)</a>
057 */
058public final class PascalDistribution extends AbstractDiscreteDistribution {
059    /** The number of successes. */
060    private final int numberOfSuccesses;
061    /** The probability of success. */
062    private final double probabilityOfSuccess;
063    /** The value of {@code log(p) * n}, where {@code p} is the probability of success
064     * and {@code n} is the number of successes, stored for faster computation. */
065    private final double logProbabilityOfSuccessByNumOfSuccesses;
066    /** The value of {@code log(1-p)}, where {@code p} is the probability of success,
067     * stored for faster computation. */
068    private final double log1mProbabilityOfSuccess;
069    /** The value of {@code p^n}, where {@code p} is the probability of success
070     * and {@code n} is the number of successes, stored for faster computation. */
071    private final double probabilityOfSuccessPowNumOfSuccesses;
072
073    /**
074     * @param r Number of successes.
075     * @param p Probability of success.
076     */
077    private PascalDistribution(int r,
078                               double p) {
079        numberOfSuccesses = r;
080        probabilityOfSuccess = p;
081        logProbabilityOfSuccessByNumOfSuccesses = Math.log(p) * numberOfSuccesses;
082        log1mProbabilityOfSuccess = Math.log1p(-p);
083        probabilityOfSuccessPowNumOfSuccesses = Math.pow(probabilityOfSuccess, numberOfSuccesses);
084    }
085
086    /**
087     * Create a Pascal distribution.
088     *
089     * @param r Number of successes.
090     * @param p Probability of success.
091     * @return the distribution
092     * @throws IllegalArgumentException if {@code r <= 0} or {@code p <= 0} or
093     * {@code p > 1}.
094     */
095    public static PascalDistribution of(int r,
096                                        double p) {
097        if (r <= 0) {
098            throw new DistributionException(DistributionException.NOT_STRICTLY_POSITIVE, r);
099        }
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}