github.com/jingcheng-WU/gonum@v0.9.1-0.20210323123734-f1a2a11a8f7b/stat/distuv/binomial.go (about) 1 // Copyright ©2018 The Gonum Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package distuv 6 7 import ( 8 "math" 9 10 "golang.org/x/exp/rand" 11 12 "github.com/jingcheng-WU/gonum/mathext" 13 "github.com/jingcheng-WU/gonum/stat/combin" 14 ) 15 16 // Binomial implements the binomial distribution, a discrete probability distribution 17 // that expresses the probability of a given number of successful Bernoulli trials 18 // out of a total of n, each with success probability p. 19 // The binomial distribution has the density function: 20 // f(k) = (n choose k) p^k (1-p)^(n-k) 21 // For more information, see https://en.wikipedia.org/wiki/Binomial_distribution. 22 type Binomial struct { 23 // N is the total number of Bernoulli trials. N must be greater than 0. 24 N float64 25 // P is the probability of success in any given trial. P must be in [0, 1]. 26 P float64 27 28 Src rand.Source 29 } 30 31 // CDF computes the value of the cumulative distribution function at x. 32 func (b Binomial) CDF(x float64) float64 { 33 if x < 0 { 34 return 0 35 } 36 if x >= b.N { 37 return 1 38 } 39 x = math.Floor(x) 40 return mathext.RegIncBeta(b.N-x, x+1, 1-b.P) 41 } 42 43 // ExKurtosis returns the excess kurtosis of the distribution. 44 func (b Binomial) ExKurtosis() float64 { 45 v := b.P * (1 - b.P) 46 return (1 - 6*v) / (b.N * v) 47 } 48 49 // LogProb computes the natural logarithm of the value of the probability 50 // density function at x. 51 func (b Binomial) LogProb(x float64) float64 { 52 if x < 0 || x > b.N || math.Floor(x) != x { 53 return math.Inf(-1) 54 } 55 lb := combin.LogGeneralizedBinomial(b.N, x) 56 return lb + x*math.Log(b.P) + (b.N-x)*math.Log(1-b.P) 57 } 58 59 // Mean returns the mean of the probability distribution. 60 func (b Binomial) Mean() float64 { 61 return b.N * b.P 62 } 63 64 // NumParameters returns the number of parameters in the distribution. 65 func (Binomial) NumParameters() int { 66 return 2 67 } 68 69 // Prob computes the value of the probability density function at x. 70 func (b Binomial) Prob(x float64) float64 { 71 return math.Exp(b.LogProb(x)) 72 } 73 74 // Rand returns a random sample drawn from the distribution. 75 func (b Binomial) Rand() float64 { 76 // NUMERICAL RECIPES IN C: THE ART OF SCIENTIFIC COMPUTING (ISBN 0-521-43108-5) 77 // p. 295-6 78 // http://www.aip.de/groups/soe/local/numres/bookcpdf/c7-3.pdf 79 80 runif := rand.Float64 81 rexp := rand.ExpFloat64 82 if b.Src != nil { 83 rnd := rand.New(b.Src) 84 runif = rnd.Float64 85 rexp = rnd.ExpFloat64 86 } 87 88 p := b.P 89 if p > 0.5 { 90 p = 1 - p 91 } 92 am := b.N * p 93 94 if b.N < 25 { 95 // Use direct method. 96 bnl := 0.0 97 for i := 0; i < int(b.N); i++ { 98 if runif() < p { 99 bnl++ 100 } 101 } 102 if p != b.P { 103 return b.N - bnl 104 } 105 return bnl 106 } 107 108 if am < 1 { 109 // Use rejection method with Poisson proposal. 110 const logM = 2.6e-2 // constant for rejection sampling (https://en.wikipedia.org/wiki/Rejection_sampling) 111 var bnl float64 112 z := -p 113 pclog := (1 + 0.5*z) * z / (1 + (1+1.0/6*z)*z) // Padé approximant of log(1 + x) 114 for { 115 bnl = 0.0 116 t := 0.0 117 for i := 0; i < int(b.N); i++ { 118 t += rexp() 119 if t >= am { 120 break 121 } 122 bnl++ 123 } 124 bnlc := b.N - bnl 125 z = -bnl / b.N 126 log1p := (1 + 0.5*z) * z / (1 + (1+1.0/6*z)*z) 127 t = (bnlc+0.5)*log1p + bnl - bnlc*pclog + 1/(12*bnlc) - am + logM // Uses Stirling's expansion of log(n!) 128 if rexp() >= t { 129 break 130 } 131 } 132 if p != b.P { 133 return b.N - bnl 134 } 135 return bnl 136 } 137 // Original algorithm samples from a Poisson distribution with the 138 // appropriate expected value. However, the Poisson approximation is 139 // asymptotic such that the absolute deviation in probability is O(1/n). 140 // Rejection sampling produces exact variates with at worst less than 3% 141 // rejection with miminal additional computation. 142 143 // Use rejection method with Cauchy proposal. 144 g, _ := math.Lgamma(b.N + 1) 145 plog := math.Log(p) 146 pclog := math.Log1p(-p) 147 sq := math.Sqrt(2 * am * (1 - p)) 148 for { 149 var em, y float64 150 for { 151 y = math.Tan(math.Pi * runif()) 152 em = sq*y + am 153 if em >= 0 && em < b.N+1 { 154 break 155 } 156 } 157 em = math.Floor(em) 158 lg1, _ := math.Lgamma(em + 1) 159 lg2, _ := math.Lgamma(b.N - em + 1) 160 t := 1.2 * sq * (1 + y*y) * math.Exp(g-lg1-lg2+em*plog+(b.N-em)*pclog) 161 if runif() <= t { 162 if p != b.P { 163 return b.N - em 164 } 165 return em 166 } 167 } 168 } 169 170 // Skewness returns the skewness of the distribution. 171 func (b Binomial) Skewness() float64 { 172 return (1 - 2*b.P) / b.StdDev() 173 } 174 175 // StdDev returns the standard deviation of the probability distribution. 176 func (b Binomial) StdDev() float64 { 177 return math.Sqrt(b.Variance()) 178 } 179 180 // Survival returns the survival function (complementary CDF) at x. 181 func (b Binomial) Survival(x float64) float64 { 182 return 1 - b.CDF(x) 183 } 184 185 // Variance returns the variance of the probability distribution. 186 func (b Binomial) Variance() float64 { 187 return b.N * b.P * (1 - b.P) 188 }