stat/distuv: add Score method to Triangle and Uniform
Also tests and fixes Laplace Score.
MODIFY
stat/distuv/general_test.go
stat/distuv/general_test.go
@@ -227,7 +227,7 @@ func testDerivParam(t *testing.T, d derivParamTester) {
}
fd.Gradient(fdDeriv, f, init, nil)
if !floats.EqualApprox(deriv, fdDeriv, 1e-6) {
- t.Fatal("Derivative mismatch. Want", fdDeriv, ", got", deriv, ".")
+ t.Errorf("Derivative mismatch at x = %g. Want %v, got %v", x, fdDeriv, deriv)
}
d.setParameters(initParams)
d2 := d.Score(nil, x)

MODIFY
stat/distuv/laplace.go
stat/distuv/laplace.go
@@ -104,15 +104,19 @@ func (l Laplace) LogProb(x float64) float64 {
return -math.Ln2 - math.Log(l.Scale) - math.Abs(x-l.Mu)/l.Scale
}
-// MarshalParameters implements the ParameterMarshaler interface
-func (l Laplace) MarshalParameters(p []Parameter) {
- if len(p) != l.NumParameters() {
+// parameters returns the parameters of the distribution.
+func (l Laplace) parameters(p []Parameter) []Parameter {
+ nParam := l.NumParameters()
+ if p == nil {
+ p = make([]Parameter, nParam)
+ } else if len(p) != nParam {
panic(badLength)
}
p[0].Name = "Mu"
p[0].Value = l.Mu
p[1].Name = "Scale"
p[1].Value = l.Scale
+ return p
}
// Mean returns the mean of the probability distribution.
@@ -179,7 +183,7 @@ func (l Laplace) Rand() float64 {
// For more information, see https://en.wikipedia.org/wiki/Score_%28statistics%29.
//
// Special cases:
-// Score(0) = [0, -0.5/l.Scale]
+// Score(l.Mu) = [NaN, -1/l.Scale]
func (l Laplace) Score(deriv []float64, x float64) []float64 {
if deriv == nil {
deriv = make([]float64, l.NumParameters())
@@ -192,14 +196,12 @@ func (l Laplace) Score(deriv []float64, x float64) []float64 {
deriv[0] = 1 / l.Scale
} else if diff < 0 {
deriv[0] = -1 / l.Scale
- } else if diff == 0 {
- deriv[0] = 0
} else {
// must be NaN
deriv[0] = math.NaN()
}
- deriv[1] = math.Abs(diff)/(l.Scale*l.Scale) - 0.5/(l.Scale)
+ deriv[1] = math.Abs(diff)/(l.Scale*l.Scale) - 1/l.Scale
return deriv
}
@@ -238,8 +240,8 @@ func (l Laplace) Survival(x float64) float64 {
return 0.5 * math.Exp(-(x-l.Mu)/l.Scale)
}
-// UnmarshalParameters implements the ParameterMarshaler interface
-func (l *Laplace) UnmarshalParameters(p []Parameter) {
+// setParameters modifies the parameters of the distribution.
+func (l *Laplace) setParameters(p []Parameter) {
if len(p) != l.NumParameters() {
panic(badLength)
}

MODIFY
stat/distuv/laplace_test.go
stat/distuv/laplace_test.go
@@ -93,10 +93,18 @@ func testLaplace(t *testing.T, dist Laplace, i int) {
checkQuantileCDFSurvival(t, i, x, dist, tol)
checkProbContinuous(t, i, x, math.Inf(-1), math.Inf(1), dist, 1e-10)
checkProbQuantContinuous(t, i, x, dist, tol)
+ testDerivParam(t, &dist)
if dist.Mu != dist.Mode() {
t.Errorf("Mismatch in mode value, got: %v, want: %g", dist.Mode(), dist.Mu)
}
+ score := dist.Score(nil, dist.Mu)
+ if !math.IsNaN(score[0]) {
+ t.Errorf("Expected NaN score over Mu for x == Mu, got %v", score[0])
+ }
+ if score[1] != -1/dist.Scale {
+ t.Errorf("Mismatch in score over Scale value for x == Mu, got: %v, want: %g", score[1], -1/dist.Scale)
+ }
}
func TestLaplaceFit(t *testing.T) {

MODIFY
stat/distuv/triangle_test.go
stat/distuv/triangle_test.go
@@ -52,6 +52,11 @@ func TestTriangle(t *testing.T) {
b: 1.0,
c: 0.0,
},
+ {
+ a: 0.0,
+ b: 1.2,
+ c: 1.2,
+ },
} {
f := NewTriangle(test.a, test.b, test.c, src)
const (
@@ -108,3 +113,55 @@ func TestTriangleProb(t *testing.T) {
}
testDistributionProbs(t, NewTriangle(1, 3, 2, nil), "Standard 1,2,3 Triangle", pts)
}
+
+func TestTriangleScore(t *testing.T) {
+ const (
+ h = 1e-6
+ tol = 1e-6
+ )
+ t.Parallel()
+
+ f := Triangle{a: -0.5, b: 0.7, c: 0.1}
+ testDerivParam(t, &f)
+
+ f = Triangle{a: 0, b: 1, c: 0}
+ x := 0.5
+ score := f.Score(nil, x)
+ if !math.IsNaN(score[0]) {
+ t.Errorf("Expected score over A to be NaN for A == C, got %v", score[0])
+ }
+ if !math.IsNaN(score[2]) {
+ t.Errorf("Expected score over C to be NaN for A == C, got %v", score[2])
+ }
+ expectedScore := logProbDerivative(f, x, 1, h)
+ if math.Abs(expectedScore-score[1]) > tol {
+ t.Errorf("Mismatch in score over B for A == C: want %g, got %v", expectedScore, score[1])
+ }
+
+ f = Triangle{a: 0, b: 1, c: 1}
+ score = f.Score(nil, x)
+ if !math.IsNaN(score[1]) {
+ t.Errorf("Expected score over B to be NaN for B == C, got %v", score[0])
+ }
+ if !math.IsNaN(score[2]) {
+ t.Errorf("Expected score over C to be NaN for B == C, got %v", score[2])
+ }
+ expectedScore = logProbDerivative(f, x, 0, h)
+ if math.Abs(expectedScore-score[0]) > tol {
+ t.Errorf("Mismatch in score over A for B == C: want %g, got %v", expectedScore, score[0])
+ }
+}
+
+func logProbDerivative(t Triangle, x float64, i int, h float64) float64 {
+ origParams := t.parameters(nil)
+ params := make([]Parameter, len(origParams))
+ copy(params, origParams)
+ params[i].Value = origParams[i].Value + h
+ t.setParameters(params)
+ lpUp := t.LogProb(x)
+ params[i].Value = origParams[i].Value - h
+ t.setParameters(params)
+ lpDown := t.LogProb(x)
+ t.setParameters(origParams)
+ return (lpUp - lpDown) / (2 * h)
+}

MODIFY
stat/distuv/uniform_test.go
stat/distuv/uniform_test.go
@@ -76,4 +76,5 @@ func testUniform(t *testing.T, u Uniform, i int) {
checkExKurtosis(t, i, x, u, 7e-2)
checkProbContinuous(t, i, x, u.Min, u.Max, u, 1e-10)
checkQuantileCDFSurvival(t, i, x, u, 1e-2)
+ testDerivParam(t, &u)
}

stat/distuv: add Score method to Triangle and Uniform

Also tests and fixes Laplace Score.