github.com/gopherd/gonum@v0.0.4/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  	"math/rand"
    12  	"github.com/gopherd/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  }