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}