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