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  }