PascalDistribution.java

  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. import org.apache.commons.numbers.combinatorics.BinomialCoefficientDouble;
  19. import org.apache.commons.numbers.combinatorics.LogBinomialCoefficient;
  20. import org.apache.commons.numbers.gamma.RegularizedBeta;

  21. /**
  22.  * Implementation of the Pascal distribution.
  23.  *
  24.  * <p>The Pascal distribution is a special case of the negative binomial distribution
  25.  * where the number of successes parameter is an integer.
  26.  *
  27.  * <p>There are various ways to express the probability mass and distribution
  28.  * functions for the Pascal distribution. The present implementation represents
  29.  * the distribution of the number of failures before \( r \) successes occur.
  30.  * This is the convention adopted in e.g.
  31.  * <a href="https://mathworld.wolfram.com/NegativeBinomialDistribution.html">MathWorld</a>,
  32.  * but <em>not</em> in
  33.  * <a href="https://en.wikipedia.org/wiki/Negative_binomial_distribution">Wikipedia</a>.
  34.  *
  35.  * <p>The probability mass function of \( X \) is:
  36.  *
  37.  * <p>\[ f(k; r, p) = \binom{k+r-1}{r-1} p^r \, (1-p)^k \]
  38.  *
  39.  * <p>for \( r \in \{1, 2, \dots\} \) the number of successes,
  40.  * \( p \in (0, 1] \) the probability of success,
  41.  * \( k \in \{0, 1, 2, \dots\} \) the total number of failures, and
  42.  *
  43.  * <p>\[ \binom{k+r-1}{r-1} = \frac{(k+r-1)!}{(r-1)! \, k!} \]
  44.  *
  45.  * <p>is the binomial coefficient.
  46.  *
  47.  * <p>The cumulative distribution function of \( X \) is:
  48.  *
  49.  * <p>\[ P(X \leq k) = I(p, r, k + 1) \]
  50.  *
  51.  * <p>where \( I \) is the regularized incomplete beta function.
  52.  *
  53.  * @see <a href="https://en.wikipedia.org/wiki/Negative_binomial_distribution">Negative binomial distribution (Wikipedia)</a>
  54.  * @see <a href="https://mathworld.wolfram.com/NegativeBinomialDistribution.html">Negative binomial distribution (MathWorld)</a>
  55.  */
  56. public final class PascalDistribution extends AbstractDiscreteDistribution {
  57.     /** The number of successes. */
  58.     private final int numberOfSuccesses;
  59.     /** The probability of success. */
  60.     private final double probabilityOfSuccess;
  61.     /** The value of {@code log(p) * n}, where {@code p} is the probability of success
  62.      * and {@code n} is the number of successes, stored for faster computation. */
  63.     private final double logProbabilityOfSuccessByNumOfSuccesses;
  64.     /** The value of {@code log(1-p)}, where {@code p} is the probability of success,
  65.      * stored for faster computation. */
  66.     private final double log1mProbabilityOfSuccess;
  67.     /** The value of {@code p^n}, where {@code p} is the probability of success
  68.      * and {@code n} is the number of successes, stored for faster computation. */
  69.     private final double probabilityOfSuccessPowNumOfSuccesses;

  70.     /**
  71.      * @param r Number of successes.
  72.      * @param p Probability of success.
  73.      */
  74.     private PascalDistribution(int r,
  75.                                double p) {
  76.         numberOfSuccesses = r;
  77.         probabilityOfSuccess = p;
  78.         logProbabilityOfSuccessByNumOfSuccesses = Math.log(p) * numberOfSuccesses;
  79.         log1mProbabilityOfSuccess = Math.log1p(-p);
  80.         probabilityOfSuccessPowNumOfSuccesses = Math.pow(probabilityOfSuccess, numberOfSuccesses);
  81.     }

  82.     /**
  83.      * Create a Pascal distribution.
  84.      *
  85.      * @param r Number of successes.
  86.      * @param p Probability of success.
  87.      * @return the distribution
  88.      * @throws IllegalArgumentException if {@code r <= 0} or {@code p <= 0} or
  89.      * {@code p > 1}.
  90.      */
  91.     public static PascalDistribution of(int r,
  92.                                         double p) {
  93.         if (r <= 0) {
  94.             throw new DistributionException(DistributionException.NOT_STRICTLY_POSITIVE, r);
  95.         }
  96.         if (p <= 0 ||
  97.             p > 1) {
  98.             throw new DistributionException(DistributionException.INVALID_NON_ZERO_PROBABILITY, p);
  99.         }
  100.         return new PascalDistribution(r, p);
  101.     }

  102.     /**
  103.      * Gets the number of successes parameter of this distribution.
  104.      *
  105.      * @return the number of successes.
  106.      */
  107.     public int getNumberOfSuccesses() {
  108.         return numberOfSuccesses;
  109.     }

  110.     /**
  111.      * Gets the probability of success parameter of this distribution.
  112.      *
  113.      * @return the probability of success.
  114.      */
  115.     public double getProbabilityOfSuccess() {
  116.         return probabilityOfSuccess;
  117.     }

  118.     /** {@inheritDoc} */
  119.     @Override
  120.     public double probability(int x) {
  121.         if (x <= 0) {
  122.             // Special case of x=0 exploiting cancellation.
  123.             return x == 0 ? probabilityOfSuccessPowNumOfSuccesses : 0.0;
  124.         }
  125.         final int n = x + numberOfSuccesses - 1;
  126.         if (n < 0) {
  127.             // overflow
  128.             return 0.0;
  129.         }
  130.         return BinomialCoefficientDouble.value(n, numberOfSuccesses - 1) *
  131.               probabilityOfSuccessPowNumOfSuccesses *
  132.               Math.pow(1.0 - probabilityOfSuccess, x);
  133.     }

  134.     /** {@inheritDoc} */
  135.     @Override
  136.     public double logProbability(int x) {
  137.         if (x <= 0) {
  138.             // Special case of x=0 exploiting cancellation.
  139.             return x == 0 ? logProbabilityOfSuccessByNumOfSuccesses : Double.NEGATIVE_INFINITY;
  140.         }
  141.         final int n = x + numberOfSuccesses - 1;
  142.         if (n < 0) {
  143.             // overflow
  144.             return Double.NEGATIVE_INFINITY;
  145.         }
  146.         return LogBinomialCoefficient.value(n, numberOfSuccesses - 1) +
  147.               logProbabilityOfSuccessByNumOfSuccesses +
  148.               log1mProbabilityOfSuccess * x;
  149.     }

  150.     /** {@inheritDoc} */
  151.     @Override
  152.     public double cumulativeProbability(int x) {
  153.         if (x < 0) {
  154.             return 0.0;
  155.         }
  156.         return RegularizedBeta.value(probabilityOfSuccess,
  157.                                      numberOfSuccesses, x + 1.0);
  158.     }

  159.     /** {@inheritDoc} */
  160.     @Override
  161.     public double survivalProbability(int x) {
  162.         if (x < 0) {
  163.             return 1.0;
  164.         }
  165.         return RegularizedBeta.complement(probabilityOfSuccess,
  166.                                           numberOfSuccesses, x + 1.0);
  167.     }

  168.     /**
  169.      * {@inheritDoc}
  170.      *
  171.      * <p>For number of successes \( r \) and probability of success \( p \),
  172.      * the mean is:
  173.      *
  174.      * <p>\[ \frac{r (1 - p)}{p} \]
  175.      */
  176.     @Override
  177.     public double getMean() {
  178.         final double p = getProbabilityOfSuccess();
  179.         final double r = getNumberOfSuccesses();
  180.         return (r * (1 - p)) / p;
  181.     }

  182.     /**
  183.      * {@inheritDoc}
  184.      *
  185.      * <p>For number of successes \( r \) and probability of success \( p \),
  186.      * the variance is:
  187.      *
  188.      * <p>\[ \frac{r (1 - p)}{p^2} \]
  189.      */
  190.     @Override
  191.     public double getVariance() {
  192.         final double p = getProbabilityOfSuccess();
  193.         final double r = getNumberOfSuccesses();
  194.         return r * (1 - p) / (p * p);
  195.     }

  196.     /**
  197.      * {@inheritDoc}
  198.      *
  199.      * <p>The lower bound of the support is always 0.
  200.      *
  201.      * @return 0.
  202.      */
  203.     @Override
  204.     public int getSupportLowerBound() {
  205.         return 0;
  206.     }

  207.     /**
  208.      * {@inheritDoc}
  209.      *
  210.      * <p>The upper bound of the support is positive infinity except for the
  211.      * probability parameter {@code p = 1.0}.
  212.      *
  213.      * @return {@link Integer#MAX_VALUE} or 0.
  214.      */
  215.     @Override
  216.     public int getSupportUpperBound() {
  217.         return probabilityOfSuccess < 1 ? Integer.MAX_VALUE : 0;
  218.     }
  219. }