gonum.org/v1/gonum@v0.14.0/stat/distuv/norm.go (about) 1 // Copyright ©2014 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/floats" 13 "gonum.org/v1/gonum/mathext" 14 "gonum.org/v1/gonum/stat" 15 ) 16 17 // UnitNormal is an instantiation of the normal distribution with Mu = 0 and Sigma = 1. 18 var UnitNormal = Normal{Mu: 0, Sigma: 1} 19 20 // Normal represents a normal (Gaussian) distribution (https://en.wikipedia.org/wiki/Normal_distribution). 21 type Normal struct { 22 Mu float64 // Mean of the normal distribution 23 Sigma float64 // Standard deviation of the normal distribution 24 Src rand.Source 25 26 // Needs to be Mu and Sigma and not Mean and StdDev because Normal has functions 27 // Mean and StdDev 28 } 29 30 // CDF computes the value of the cumulative density function at x. 31 func (n Normal) CDF(x float64) float64 { 32 return 0.5 * math.Erfc(-(x-n.Mu)/(n.Sigma*math.Sqrt2)) 33 } 34 35 // ConjugateUpdate updates the parameters of the distribution from the sufficient 36 // statistics of a set of samples. The sufficient statistics, suffStat, have been 37 // observed with nSamples observations. The prior values of the distribution are those 38 // currently in the distribution, and have been observed with priorStrength samples. 39 // 40 // For the normal distribution, the sufficient statistics are the mean and 41 // uncorrected standard deviation of the samples. 42 // The prior is having seen strength[0] samples with mean Normal.Mu 43 // and strength[1] samples with standard deviation Normal.Sigma. As a result of 44 // this function, Normal.Mu and Normal.Sigma are updated based on the weighted 45 // samples, and strength is modified to include the new number of samples observed. 46 // 47 // This function panics if len(suffStat) != n.NumSuffStat() or 48 // len(priorStrength) != n.NumSuffStat(). 49 func (n *Normal) ConjugateUpdate(suffStat []float64, nSamples float64, priorStrength []float64) { 50 // TODO: Support prior strength with math.Inf(1) to allow updating with 51 // a known mean/standard deviation 52 if len(suffStat) != n.NumSuffStat() { 53 panic("norm: incorrect suffStat length") 54 } 55 if len(priorStrength) != n.NumSuffStat() { 56 panic("norm: incorrect priorStrength length") 57 } 58 59 totalMeanSamples := nSamples + priorStrength[0] 60 totalSum := suffStat[0]*nSamples + n.Mu*priorStrength[0] 61 62 totalVarianceSamples := nSamples + priorStrength[1] 63 // sample variance 64 totalVariance := nSamples * suffStat[1] * suffStat[1] 65 // add prior variance 66 totalVariance += priorStrength[1] * n.Sigma * n.Sigma 67 // add cross variance from the difference of the means 68 meanDiff := (suffStat[0] - n.Mu) 69 totalVariance += priorStrength[0] * nSamples * meanDiff * meanDiff / totalMeanSamples 70 71 n.Mu = totalSum / totalMeanSamples 72 n.Sigma = math.Sqrt(totalVariance / totalVarianceSamples) 73 floats.AddConst(nSamples, priorStrength) 74 } 75 76 // Entropy returns the differential entropy of the distribution. 77 func (n Normal) Entropy() float64 { 78 return 0.5 * (log2Pi + 1 + 2*math.Log(n.Sigma)) 79 } 80 81 // ExKurtosis returns the excess kurtosis of the distribution. 82 func (Normal) ExKurtosis() float64 { 83 return 0 84 } 85 86 // Fit sets the parameters of the probability distribution from the 87 // data samples x with relative weights w. If weights is nil, then all the weights 88 // are 1. If weights is not nil, then the len(weights) must equal len(samples). 89 func (n *Normal) Fit(samples, weights []float64) { 90 suffStat := make([]float64, n.NumSuffStat()) 91 nSamples := n.SuffStat(suffStat, samples, weights) 92 n.ConjugateUpdate(suffStat, nSamples, make([]float64, n.NumSuffStat())) 93 } 94 95 // LogProb computes the natural logarithm of the value of the probability density function at x. 96 func (n Normal) LogProb(x float64) float64 { 97 return negLogRoot2Pi - math.Log(n.Sigma) - (x-n.Mu)*(x-n.Mu)/(2*n.Sigma*n.Sigma) 98 } 99 100 // Mean returns the mean of the probability distribution. 101 func (n Normal) Mean() float64 { 102 return n.Mu 103 } 104 105 // Median returns the median of the normal distribution. 106 func (n Normal) Median() float64 { 107 return n.Mu 108 } 109 110 // Mode returns the mode of the normal distribution. 111 func (n Normal) Mode() float64 { 112 return n.Mu 113 } 114 115 // NumParameters returns the number of parameters in the distribution. 116 func (Normal) NumParameters() int { 117 return 2 118 } 119 120 // NumSuffStat returns the number of sufficient statistics for the distribution. 121 func (Normal) NumSuffStat() int { 122 return 2 123 } 124 125 // Prob computes the value of the probability density function at x. 126 func (n Normal) Prob(x float64) float64 { 127 return math.Exp(n.LogProb(x)) 128 } 129 130 // Quantile returns the inverse of the cumulative probability distribution. 131 func (n Normal) Quantile(p float64) float64 { 132 if p < 0 || p > 1 { 133 panic(badPercentile) 134 } 135 return n.Mu + n.Sigma*mathext.NormalQuantile(p) 136 } 137 138 // Rand returns a random sample drawn from the distribution. 139 func (n Normal) Rand() float64 { 140 var rnd float64 141 if n.Src == nil { 142 rnd = rand.NormFloat64() 143 } else { 144 rnd = rand.New(n.Src).NormFloat64() 145 } 146 return rnd*n.Sigma + n.Mu 147 } 148 149 // Score returns the score function with respect to the parameters of the 150 // distribution at the input location x. The score function is the derivative 151 // of the log-likelihood at x with respect to the parameters 152 // 153 // (∂/∂θ) log(p(x;θ)) 154 // 155 // If deriv is non-nil, len(deriv) must equal the number of parameters otherwise 156 // Score will panic, and the derivative is stored in-place into deriv. If deriv 157 // is nil a new slice will be allocated and returned. 158 // 159 // The order is [∂LogProb / ∂Mu, ∂LogProb / ∂Sigma]. 160 // 161 // For more information, see https://en.wikipedia.org/wiki/Score_%28statistics%29. 162 func (n Normal) Score(deriv []float64, x float64) []float64 { 163 if deriv == nil { 164 deriv = make([]float64, n.NumParameters()) 165 } 166 if len(deriv) != n.NumParameters() { 167 panic(badLength) 168 } 169 deriv[0] = (x - n.Mu) / (n.Sigma * n.Sigma) 170 deriv[1] = 1 / n.Sigma * (-1 + ((x-n.Mu)/n.Sigma)*((x-n.Mu)/n.Sigma)) 171 return deriv 172 } 173 174 // ScoreInput returns the score function with respect to the input of the 175 // distribution at the input location specified by x. The score function is the 176 // derivative of the log-likelihood 177 // 178 // (d/dx) log(p(x)) . 179 func (n Normal) ScoreInput(x float64) float64 { 180 return -(1 / (2 * n.Sigma * n.Sigma)) * 2 * (x - n.Mu) 181 } 182 183 // Skewness returns the skewness of the distribution. 184 func (Normal) Skewness() float64 { 185 return 0 186 } 187 188 // StdDev returns the standard deviation of the probability distribution. 189 func (n Normal) StdDev() float64 { 190 return n.Sigma 191 } 192 193 // SuffStat computes the sufficient statistics of a set of samples to update 194 // the distribution. The sufficient statistics are stored in place, and the 195 // effective number of samples are returned. 196 // 197 // The normal distribution has two sufficient statistics, the mean of the samples 198 // and the standard deviation of the samples. 199 // 200 // If weights is nil, the weights are assumed to be 1, otherwise panics if 201 // len(samples) != len(weights). Panics if len(suffStat) != NumSuffStat(). 202 func (Normal) SuffStat(suffStat, samples, weights []float64) (nSamples float64) { 203 lenSamp := len(samples) 204 if len(weights) != 0 && len(samples) != len(weights) { 205 panic(badLength) 206 } 207 if len(suffStat) != (Normal{}).NumSuffStat() { 208 panic(badSuffStat) 209 } 210 211 if len(weights) == 0 { 212 nSamples = float64(lenSamp) 213 } else { 214 nSamples = floats.Sum(weights) 215 } 216 217 mean := stat.Mean(samples, weights) 218 suffStat[0] = mean 219 220 // Use Moment and not StdDev because we want it to be uncorrected 221 variance := stat.MomentAbout(2, samples, mean, weights) 222 suffStat[1] = math.Sqrt(variance) 223 return nSamples 224 } 225 226 // Survival returns the survival function (complementary CDF) at x. 227 func (n Normal) Survival(x float64) float64 { 228 return 0.5 * (1 - math.Erf((x-n.Mu)/(n.Sigma*math.Sqrt2))) 229 } 230 231 // setParameters modifies the parameters of the distribution. 232 func (n *Normal) setParameters(p []Parameter) { 233 if len(p) != n.NumParameters() { 234 panic("normal: incorrect number of parameters to set") 235 } 236 if p[0].Name != "Mu" { 237 panic("normal: " + panicNameMismatch) 238 } 239 if p[1].Name != "Sigma" { 240 panic("normal: " + panicNameMismatch) 241 } 242 n.Mu = p[0].Value 243 n.Sigma = p[1].Value 244 } 245 246 // Variance returns the variance of the probability distribution. 247 func (n Normal) Variance() float64 { 248 return n.Sigma * n.Sigma 249 } 250 251 // parameters returns the parameters of the distribution. 252 func (n Normal) parameters(p []Parameter) []Parameter { 253 nParam := n.NumParameters() 254 if p == nil { 255 p = make([]Parameter, nParam) 256 } else if len(p) != nParam { 257 panic("normal: improper parameter length") 258 } 259 p[0].Name = "Mu" 260 p[0].Value = n.Mu 261 p[1].Name = "Sigma" 262 p[1].Value = n.Sigma 263 return p 264 }