github.com/jingcheng-WU/gonum@v0.9.1-0.20210323123734-f1a2a11a8f7b/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 "github.com/jingcheng-WU/gonum/floats" 13 "github.com/jingcheng-WU/gonum/mathext" 14 "github.com/jingcheng-WU/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 respresents 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 // (∂/∂θ) log(p(x;θ)) 153 // If deriv is non-nil, len(deriv) must equal the number of parameters otherwise 154 // Score will panic, and the derivative is stored in-place into deriv. If deriv 155 // is nil a new slice will be allocated and returned. 156 // 157 // The order is [∂LogProb / ∂Mu, ∂LogProb / ∂Sigma]. 158 // 159 // For more information, see https://en.wikipedia.org/wiki/Score_%28statistics%29. 160 func (n Normal) Score(deriv []float64, x float64) []float64 { 161 if deriv == nil { 162 deriv = make([]float64, n.NumParameters()) 163 } 164 if len(deriv) != n.NumParameters() { 165 panic(badLength) 166 } 167 deriv[0] = (x - n.Mu) / (n.Sigma * n.Sigma) 168 deriv[1] = 1 / n.Sigma * (-1 + ((x-n.Mu)/n.Sigma)*((x-n.Mu)/n.Sigma)) 169 return deriv 170 } 171 172 // ScoreInput returns the score function with respect to the input of the 173 // distribution at the input location specified by x. The score function is the 174 // derivative of the log-likelihood 175 // (d/dx) log(p(x)) . 176 func (n Normal) ScoreInput(x float64) float64 { 177 return -(1 / (2 * n.Sigma * n.Sigma)) * 2 * (x - n.Mu) 178 } 179 180 // Skewness returns the skewness of the distribution. 181 func (Normal) Skewness() float64 { 182 return 0 183 } 184 185 // StdDev returns the standard deviation of the probability distribution. 186 func (n Normal) StdDev() float64 { 187 return n.Sigma 188 } 189 190 // SuffStat computes the sufficient statistics of a set of samples to update 191 // the distribution. The sufficient statistics are stored in place, and the 192 // effective number of samples are returned. 193 // 194 // The normal distribution has two sufficient statistics, the mean of the samples 195 // and the standard deviation of the samples. 196 // 197 // If weights is nil, the weights are assumed to be 1, otherwise panics if 198 // len(samples) != len(weights). Panics if len(suffStat) != NumSuffStat(). 199 func (Normal) SuffStat(suffStat, samples, weights []float64) (nSamples float64) { 200 lenSamp := len(samples) 201 if len(weights) != 0 && len(samples) != len(weights) { 202 panic(badLength) 203 } 204 if len(suffStat) != (Normal{}).NumSuffStat() { 205 panic(badSuffStat) 206 } 207 208 if len(weights) == 0 { 209 nSamples = float64(lenSamp) 210 } else { 211 nSamples = floats.Sum(weights) 212 } 213 214 mean := stat.Mean(samples, weights) 215 suffStat[0] = mean 216 217 // Use Moment and not StdDev because we want it to be uncorrected 218 variance := stat.MomentAbout(2, samples, mean, weights) 219 suffStat[1] = math.Sqrt(variance) 220 return nSamples 221 } 222 223 // Survival returns the survival function (complementary CDF) at x. 224 func (n Normal) Survival(x float64) float64 { 225 return 0.5 * (1 - math.Erf((x-n.Mu)/(n.Sigma*math.Sqrt2))) 226 } 227 228 // setParameters modifies the parameters of the distribution. 229 func (n *Normal) setParameters(p []Parameter) { 230 if len(p) != n.NumParameters() { 231 panic("normal: incorrect number of parameters to set") 232 } 233 if p[0].Name != "Mu" { 234 panic("normal: " + panicNameMismatch) 235 } 236 if p[1].Name != "Sigma" { 237 panic("normal: " + panicNameMismatch) 238 } 239 n.Mu = p[0].Value 240 n.Sigma = p[1].Value 241 } 242 243 // Variance returns the variance of the probability distribution. 244 func (n Normal) Variance() float64 { 245 return n.Sigma * n.Sigma 246 } 247 248 // parameters returns the parameters of the distribution. 249 func (n Normal) parameters(p []Parameter) []Parameter { 250 nParam := n.NumParameters() 251 if p == nil { 252 p = make([]Parameter, nParam) 253 } else if len(p) != nParam { 254 panic("normal: improper parameter length") 255 } 256 p[0].Name = "Mu" 257 p[0].Value = n.Mu 258 p[1].Name = "Sigma" 259 p[1].Value = n.Sigma 260 return p 261 }