github.com/jingcheng-WU/gonum@v0.9.1-0.20210323123734-f1a2a11a8f7b/stat/distuv/laplace.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 "sort" 10 11 "golang.org/x/exp/rand" 12 "github.com/jingcheng-WU/gonum/stat" 13 ) 14 15 // Laplace represents the Laplace distribution (https://en.wikipedia.org/wiki/Laplace_distribution). 16 type Laplace struct { 17 Mu float64 // Mean of the Laplace distribution 18 Scale float64 // Scale of the Laplace distribution 19 Src rand.Source 20 } 21 22 // CDF computes the value of the cumulative density function at x. 23 func (l Laplace) CDF(x float64) float64 { 24 if x < l.Mu { 25 return 0.5 * math.Exp((x-l.Mu)/l.Scale) 26 } 27 return 1 - 0.5*math.Exp(-(x-l.Mu)/l.Scale) 28 } 29 30 // Entropy returns the entropy of the distribution. 31 func (l Laplace) Entropy() float64 { 32 return 1 + math.Log(2*l.Scale) 33 } 34 35 // ExKurtosis returns the excess kurtosis of the distribution. 36 func (l Laplace) ExKurtosis() float64 { 37 return 3 38 } 39 40 // Fit sets the parameters of the probability distribution from the 41 // data samples x with relative weights w. 42 // If weights is nil, then all the weights are 1. 43 // If weights is not nil, then the len(weights) must equal len(samples). 44 // 45 // Note: Laplace distribution has no FitPrior because it has no sufficient 46 // statistics. 47 func (l *Laplace) Fit(samples, weights []float64) { 48 if weights != nil && len(samples) != len(weights) { 49 panic(badLength) 50 } 51 52 if len(samples) == 0 { 53 panic(errNoSamples) 54 } 55 if len(samples) == 1 { 56 l.Mu = samples[0] 57 l.Scale = 0 58 return 59 } 60 61 var ( 62 sortedSamples []float64 63 sortedWeights []float64 64 ) 65 if sort.Float64sAreSorted(samples) { 66 sortedSamples = samples 67 sortedWeights = weights 68 } else { 69 // Need to copy variables so the input variables aren't effected by the sorting 70 sortedSamples = make([]float64, len(samples)) 71 copy(sortedSamples, samples) 72 sortedWeights := make([]float64, len(samples)) 73 copy(sortedWeights, weights) 74 75 stat.SortWeighted(sortedSamples, sortedWeights) 76 } 77 78 // The (weighted) median of the samples is the maximum likelihood estimate 79 // of the mean parameter 80 // TODO: Rethink quantile type when stat has more options 81 l.Mu = stat.Quantile(0.5, stat.Empirical, sortedSamples, sortedWeights) 82 83 // The scale parameter is the average absolute distance 84 // between the sample and the mean 85 var absError float64 86 var sumWeights float64 87 if weights != nil { 88 for i, v := range samples { 89 absError += weights[i] * math.Abs(l.Mu-v) 90 sumWeights += weights[i] 91 } 92 l.Scale = absError / sumWeights 93 } else { 94 for _, v := range samples { 95 absError += math.Abs(l.Mu - v) 96 } 97 l.Scale = absError / float64(len(samples)) 98 } 99 } 100 101 // LogProb computes the natural logarithm of the value of the probability density 102 // function at x. 103 func (l Laplace) LogProb(x float64) float64 { 104 return -math.Ln2 - math.Log(l.Scale) - math.Abs(x-l.Mu)/l.Scale 105 } 106 107 // parameters returns the parameters of the distribution. 108 func (l Laplace) parameters(p []Parameter) []Parameter { 109 nParam := l.NumParameters() 110 if p == nil { 111 p = make([]Parameter, nParam) 112 } else if len(p) != nParam { 113 panic(badLength) 114 } 115 p[0].Name = "Mu" 116 p[0].Value = l.Mu 117 p[1].Name = "Scale" 118 p[1].Value = l.Scale 119 return p 120 } 121 122 // Mean returns the mean of the probability distribution. 123 func (l Laplace) Mean() float64 { 124 return l.Mu 125 } 126 127 // Median returns the median of the LaPlace distribution. 128 func (l Laplace) Median() float64 { 129 return l.Mu 130 } 131 132 // Mode returns the mode of the LaPlace distribution. 133 func (l Laplace) Mode() float64 { 134 return l.Mu 135 } 136 137 // NumParameters returns the number of parameters in the distribution. 138 func (l Laplace) NumParameters() int { 139 return 2 140 } 141 142 // Quantile returns the inverse of the cumulative probability distribution. 143 func (l Laplace) Quantile(p float64) float64 { 144 if p < 0 || p > 1 { 145 panic(badPercentile) 146 } 147 if p < 0.5 { 148 return l.Mu + l.Scale*math.Log(1+2*(p-0.5)) 149 } 150 return l.Mu - l.Scale*math.Log(1-2*(p-0.5)) 151 } 152 153 // Prob computes the value of the probability density function at x. 154 func (l Laplace) Prob(x float64) float64 { 155 return math.Exp(l.LogProb(x)) 156 } 157 158 // Rand returns a random sample drawn from the distribution. 159 func (l Laplace) Rand() float64 { 160 var rnd float64 161 if l.Src == nil { 162 rnd = rand.Float64() 163 } else { 164 rnd = rand.New(l.Src).Float64() 165 } 166 u := rnd - 0.5 167 if u < 0 { 168 return l.Mu + l.Scale*math.Log(1+2*u) 169 } 170 return l.Mu - l.Scale*math.Log(1-2*u) 171 } 172 173 // Score returns the score function with respect to the parameters of the 174 // distribution at the input location x. The score function is the derivative 175 // of the log-likelihood at x with respect to the parameters 176 // (∂/∂θ) log(p(x;θ)) 177 // If deriv is non-nil, len(deriv) must equal the number of parameters otherwise 178 // Score will panic, and the derivative is stored in-place into deriv. If deriv 179 // is nil a new slice will be allocated and returned. 180 // 181 // The order is [∂LogProb / ∂Mu, ∂LogProb / ∂Scale]. 182 // 183 // For more information, see https://en.wikipedia.org/wiki/Score_%28statistics%29. 184 // 185 // Special cases: 186 // Score(l.Mu) = [NaN, -1/l.Scale] 187 func (l Laplace) Score(deriv []float64, x float64) []float64 { 188 if deriv == nil { 189 deriv = make([]float64, l.NumParameters()) 190 } 191 if len(deriv) != l.NumParameters() { 192 panic(badLength) 193 } 194 diff := x - l.Mu 195 if diff > 0 { 196 deriv[0] = 1 / l.Scale 197 } else if diff < 0 { 198 deriv[0] = -1 / l.Scale 199 } else { 200 // must be NaN 201 deriv[0] = math.NaN() 202 } 203 204 deriv[1] = math.Abs(diff)/(l.Scale*l.Scale) - 1/l.Scale 205 return deriv 206 } 207 208 // ScoreInput returns the score function with respect to the input of the 209 // distribution at the input location specified by x. The score function is the 210 // derivative of the log-likelihood 211 // (d/dx) log(p(x)) . 212 // Special cases: 213 // ScoreInput(l.Mu) = NaN 214 func (l Laplace) ScoreInput(x float64) float64 { 215 diff := x - l.Mu 216 if diff == 0 { 217 return math.NaN() 218 } 219 if diff > 0 { 220 return -1 / l.Scale 221 } 222 return 1 / l.Scale 223 } 224 225 // Skewness returns the skewness of the distribution. 226 func (Laplace) Skewness() float64 { 227 return 0 228 } 229 230 // StdDev returns the standard deviation of the distribution. 231 func (l Laplace) StdDev() float64 { 232 return math.Sqrt2 * l.Scale 233 } 234 235 // Survival returns the survival function (complementary CDF) at x. 236 func (l Laplace) Survival(x float64) float64 { 237 if x < l.Mu { 238 return 1 - 0.5*math.Exp((x-l.Mu)/l.Scale) 239 } 240 return 0.5 * math.Exp(-(x-l.Mu)/l.Scale) 241 } 242 243 // setParameters modifies the parameters of the distribution. 244 func (l *Laplace) setParameters(p []Parameter) { 245 if len(p) != l.NumParameters() { 246 panic(badLength) 247 } 248 if p[0].Name != "Mu" { 249 panic("laplace: " + panicNameMismatch) 250 } 251 if p[1].Name != "Scale" { 252 panic("laplace: " + panicNameMismatch) 253 } 254 l.Mu = p[0].Value 255 l.Scale = p[1].Value 256 } 257 258 // Variance returns the variance of the probability distribution. 259 func (l Laplace) Variance() float64 { 260 return 2 * l.Scale * l.Scale 261 }