gonum.org/v1/gonum@v0.14.0/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  	"gonum.org/v1/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  //
   177  //	(∂/∂θ) log(p(x;θ))
   178  //
   179  // If deriv is non-nil, len(deriv) must equal the number of parameters otherwise
   180  // Score will panic, and the derivative is stored in-place into deriv. If deriv
   181  // is nil a new slice will be allocated and returned.
   182  //
   183  // The order is [∂LogProb / ∂Mu, ∂LogProb / ∂Scale].
   184  //
   185  // For more information, see https://en.wikipedia.org/wiki/Score_%28statistics%29.
   186  //
   187  // Special cases:
   188  //
   189  //	Score(l.Mu) = [NaN, -1/l.Scale]
   190  func (l Laplace) Score(deriv []float64, x float64) []float64 {
   191  	if deriv == nil {
   192  		deriv = make([]float64, l.NumParameters())
   193  	}
   194  	if len(deriv) != l.NumParameters() {
   195  		panic(badLength)
   196  	}
   197  	diff := x - l.Mu
   198  	if diff > 0 {
   199  		deriv[0] = 1 / l.Scale
   200  	} else if diff < 0 {
   201  		deriv[0] = -1 / l.Scale
   202  	} else {
   203  		// must be NaN
   204  		deriv[0] = math.NaN()
   205  	}
   206  
   207  	deriv[1] = math.Abs(diff)/(l.Scale*l.Scale) - 1/l.Scale
   208  	return deriv
   209  }
   210  
   211  // ScoreInput returns the score function with respect to the input of the
   212  // distribution at the input location specified by x. The score function is the
   213  // derivative of the log-likelihood
   214  //
   215  //	(d/dx) log(p(x)) .
   216  //
   217  // Special cases:
   218  //
   219  //	ScoreInput(l.Mu) = NaN
   220  func (l Laplace) ScoreInput(x float64) float64 {
   221  	diff := x - l.Mu
   222  	if diff == 0 {
   223  		return math.NaN()
   224  	}
   225  	if diff > 0 {
   226  		return -1 / l.Scale
   227  	}
   228  	return 1 / l.Scale
   229  }
   230  
   231  // Skewness returns the skewness of the distribution.
   232  func (Laplace) Skewness() float64 {
   233  	return 0
   234  }
   235  
   236  // StdDev returns the standard deviation of the distribution.
   237  func (l Laplace) StdDev() float64 {
   238  	return math.Sqrt2 * l.Scale
   239  }
   240  
   241  // Survival returns the survival function (complementary CDF) at x.
   242  func (l Laplace) Survival(x float64) float64 {
   243  	if x < l.Mu {
   244  		return 1 - 0.5*math.Exp((x-l.Mu)/l.Scale)
   245  	}
   246  	return 0.5 * math.Exp(-(x-l.Mu)/l.Scale)
   247  }
   248  
   249  // setParameters modifies the parameters of the distribution.
   250  func (l *Laplace) setParameters(p []Parameter) {
   251  	if len(p) != l.NumParameters() {
   252  		panic(badLength)
   253  	}
   254  	if p[0].Name != "Mu" {
   255  		panic("laplace: " + panicNameMismatch)
   256  	}
   257  	if p[1].Name != "Scale" {
   258  		panic("laplace: " + panicNameMismatch)
   259  	}
   260  	l.Mu = p[0].Value
   261  	l.Scale = p[1].Value
   262  }
   263  
   264  // Variance returns the variance of the probability distribution.
   265  func (l Laplace) Variance() float64 {
   266  	return 2 * l.Scale * l.Scale
   267  }