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