gonum.org/v1/gonum@v0.14.0/stat/distuv/gamma.go (about) 1 // Copyright ©2016 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 "gonum.org/v1/gonum/mathext" 13 ) 14 15 // Gamma implements the Gamma distribution, a two-parameter continuous distribution 16 // with support over the positive real numbers. 17 // 18 // The gamma distribution has density function 19 // 20 // β^α / Γ(α) x^(α-1)e^(-βx) 21 // 22 // For more information, see https://en.wikipedia.org/wiki/Gamma_distribution 23 type Gamma struct { 24 // Alpha is the shape parameter of the distribution. Alpha must be greater 25 // than 0. If Alpha == 1, this is equivalent to an exponential distribution. 26 Alpha float64 27 // Beta is the rate parameter of the distribution. Beta must be greater than 0. 28 // If Beta == 2, this is equivalent to a Chi-Squared distribution. 29 Beta float64 30 31 Src rand.Source 32 } 33 34 // CDF computes the value of the cumulative distribution function at x. 35 func (g Gamma) CDF(x float64) float64 { 36 if x < 0 { 37 return 0 38 } 39 return mathext.GammaIncReg(g.Alpha, g.Beta*x) 40 } 41 42 // ExKurtosis returns the excess kurtosis of the distribution. 43 func (g Gamma) ExKurtosis() float64 { 44 return 6 / g.Alpha 45 } 46 47 // LogProb computes the natural logarithm of the value of the probability 48 // density function at x. 49 func (g Gamma) LogProb(x float64) float64 { 50 if x <= 0 { 51 return math.Inf(-1) 52 } 53 a := g.Alpha 54 b := g.Beta 55 lg, _ := math.Lgamma(a) 56 return a*math.Log(b) - lg + (a-1)*math.Log(x) - b*x 57 } 58 59 // Mean returns the mean of the probability distribution. 60 func (g Gamma) Mean() float64 { 61 return g.Alpha / g.Beta 62 } 63 64 // Mode returns the mode of the normal distribution. 65 // 66 // The mode is NaN in the special case where the Alpha (shape) parameter 67 // is less than 1. 68 func (g Gamma) Mode() float64 { 69 if g.Alpha < 1 { 70 return math.NaN() 71 } 72 return (g.Alpha - 1) / g.Beta 73 } 74 75 // NumParameters returns the number of parameters in the distribution. 76 func (Gamma) NumParameters() int { 77 return 2 78 } 79 80 // Prob computes the value of the probability density function at x. 81 func (g Gamma) Prob(x float64) float64 { 82 return math.Exp(g.LogProb(x)) 83 } 84 85 // Quantile returns the inverse of the cumulative distribution function. 86 func (g Gamma) Quantile(p float64) float64 { 87 if p < 0 || p > 1 { 88 panic(badPercentile) 89 } 90 return mathext.GammaIncRegInv(g.Alpha, p) / g.Beta 91 } 92 93 // Rand returns a random sample drawn from the distribution. 94 // 95 // Rand panics if either alpha or beta is <= 0. 96 func (g Gamma) Rand() float64 { 97 const ( 98 // The 0.2 threshold is from https://www4.stat.ncsu.edu/~rmartin/Codes/rgamss.R 99 // described in detail in https://arxiv.org/abs/1302.1884. 100 smallAlphaThresh = 0.2 101 ) 102 if g.Beta <= 0 { 103 panic("gamma: beta <= 0") 104 } 105 106 unifrnd := rand.Float64 107 exprnd := rand.ExpFloat64 108 normrnd := rand.NormFloat64 109 if g.Src != nil { 110 rnd := rand.New(g.Src) 111 unifrnd = rnd.Float64 112 exprnd = rnd.ExpFloat64 113 normrnd = rnd.NormFloat64 114 } 115 116 a := g.Alpha 117 b := g.Beta 118 switch { 119 case a <= 0: 120 panic("gamma: alpha <= 0") 121 case a == 1: 122 // Generate from exponential 123 return exprnd() / b 124 case a < smallAlphaThresh: 125 // Generate using 126 // Liu, Chuanhai, Martin, Ryan and Syring, Nick. "Simulating from a 127 // gamma distribution with small shape parameter" 128 // https://arxiv.org/abs/1302.1884 129 // use this reference: http://link.springer.com/article/10.1007/s00180-016-0692-0 130 131 // Algorithm adjusted to work in log space as much as possible. 132 lambda := 1/a - 1 133 lr := -math.Log1p(1 / lambda / math.E) 134 for { 135 e := exprnd() 136 var z float64 137 if e >= -lr { 138 z = e + lr 139 } else { 140 z = -exprnd() / lambda 141 } 142 eza := math.Exp(-z / a) 143 lh := -z - eza 144 var lEta float64 145 if z >= 0 { 146 lEta = -z 147 } else { 148 lEta = -1 + lambda*z 149 } 150 if lh-lEta > -exprnd() { 151 return eza / b 152 } 153 } 154 case a >= smallAlphaThresh: 155 // Generate using: 156 // Marsaglia, George, and Wai Wan Tsang. "A simple method for generating 157 // gamma variables." ACM Transactions on Mathematical Software (TOMS) 158 // 26.3 (2000): 363-372. 159 d := a - 1.0/3 160 m := 1.0 161 if a < 1 { 162 d += 1.0 163 m = math.Pow(unifrnd(), 1/a) 164 } 165 c := 1 / (3 * math.Sqrt(d)) 166 for { 167 x := normrnd() 168 v := 1 + x*c 169 if v <= 0.0 { 170 continue 171 } 172 v = v * v * v 173 u := unifrnd() 174 if u < 1.0-0.0331*(x*x)*(x*x) { 175 return m * d * v / b 176 } 177 if math.Log(u) < 0.5*x*x+d*(1-v+math.Log(v)) { 178 return m * d * v / b 179 } 180 } 181 } 182 panic("unreachable") 183 } 184 185 // Survival returns the survival function (complementary CDF) at x. 186 func (g Gamma) Survival(x float64) float64 { 187 if x < 0 { 188 return 1 189 } 190 return mathext.GammaIncRegComp(g.Alpha, g.Beta*x) 191 } 192 193 // StdDev returns the standard deviation of the probability distribution. 194 func (g Gamma) StdDev() float64 { 195 return math.Sqrt(g.Alpha) / g.Beta 196 } 197 198 // Variance returns the variance of the probability distribution. 199 func (g Gamma) Variance() float64 { 200 return g.Alpha / g.Beta / g.Beta 201 }