mirror of
https://github.com/gonum/gonum.git
synced 2025-10-19 05:24:52 +08:00

The former behavior of Histogram did not agree with the documentation. The documentation matched the spirit of floats.Within, so keep the documentation and fix the behavior. This change updates the function behavior, as well as corrects the test and the example.
1345 lines
34 KiB
Go
1345 lines
34 KiB
Go
// Copyright ©2014 The gonum Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package stat
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"testing"
|
|
|
|
"github.com/gonum/floats"
|
|
)
|
|
|
|
func ExampleCorrelation() {
|
|
x := []float64{8, -3, 7, 8, -4}
|
|
y := []float64{10, 5, 6, 3, -1}
|
|
w := []float64{2, 1.5, 3, 3, 2}
|
|
|
|
fmt.Println("Correlation computes the degree to which two datasets move together")
|
|
fmt.Println("about their mean. For example, x and y above move similarly.")
|
|
|
|
c := Correlation(x, y, w)
|
|
fmt.Printf("Correlation is %.5f\n", c)
|
|
|
|
// Output:
|
|
// Correlation computes the degree to which two datasets move together
|
|
// about their mean. For example, x and y above move similarly.
|
|
// Correlation is 0.59915
|
|
}
|
|
|
|
func TestCorrelation(t *testing.T) {
|
|
for i, test := range []struct {
|
|
x []float64
|
|
y []float64
|
|
w []float64
|
|
ans float64
|
|
}{
|
|
{
|
|
x: []float64{8, -3, 7, 8, -4},
|
|
y: []float64{8, -3, 7, 8, -4},
|
|
w: nil,
|
|
ans: 1,
|
|
},
|
|
{
|
|
x: []float64{8, -3, 7, 8, -4},
|
|
y: []float64{8, -3, 7, 8, -4},
|
|
w: []float64{1, 1, 1, 1, 1},
|
|
ans: 1,
|
|
},
|
|
{
|
|
x: []float64{8, -3, 7, 8, -4},
|
|
y: []float64{8, -3, 7, 8, -4},
|
|
w: []float64{1, 6, 7, 0.8, 2.1},
|
|
ans: 1,
|
|
},
|
|
{
|
|
x: []float64{8, -3, 7, 8, -4},
|
|
y: []float64{10, 15, 4, 5, -1},
|
|
w: nil,
|
|
ans: 0.0093334660769059,
|
|
},
|
|
{
|
|
x: []float64{8, -3, 7, 8, -4},
|
|
y: []float64{10, 15, 4, 5, -1},
|
|
w: nil,
|
|
ans: 0.0093334660769059,
|
|
},
|
|
{
|
|
x: []float64{8, -3, 7, 8, -4},
|
|
y: []float64{10, 15, 4, 5, -1},
|
|
w: []float64{1, 3, 1, 2, 2},
|
|
ans: -0.13966633352689,
|
|
},
|
|
} {
|
|
c := Correlation(test.x, test.y, test.w)
|
|
if math.Abs(test.ans-c) > 1e-14 {
|
|
t.Errorf("Correlation mismatch case %d. Expected %v, Found %v", i, test.ans, c)
|
|
}
|
|
}
|
|
}
|
|
|
|
func ExampleCovariance() {
|
|
fmt.Println("Covariance computes the degree to which datasets move together")
|
|
fmt.Println("about their mean.")
|
|
x := []float64{8, -3, 7, 8, -4}
|
|
y := []float64{10, 2, 2, 4, 1}
|
|
cov := Covariance(x, y, nil)
|
|
fmt.Printf("Cov = %.4f\n", cov)
|
|
fmt.Println("If datasets move perfectly together, the variance equals the covariance")
|
|
y2 := []float64{12, 1, 11, 12, 0}
|
|
cov2 := Covariance(x, y2, nil)
|
|
varX := Variance(x, nil)
|
|
fmt.Printf("Cov2 is %.4f, VarX is %.4f", cov2, varX)
|
|
// Output:
|
|
// Covariance computes the degree to which datasets move together
|
|
// about their mean.
|
|
// Cov = 13.8000
|
|
// If datasets move perfectly together, the variance equals the covariance
|
|
// Cov2 is 37.7000, VarX is 37.7000
|
|
}
|
|
|
|
func TestCovariance(t *testing.T) {
|
|
for i, test := range []struct {
|
|
p []float64
|
|
q []float64
|
|
weights []float64
|
|
ans float64
|
|
}{
|
|
{
|
|
p: []float64{0.75, 0.1, 0.05},
|
|
q: []float64{0.5, 0.25, 0.25},
|
|
ans: 0.05625,
|
|
},
|
|
{
|
|
p: []float64{1, 2, 3},
|
|
q: []float64{2, 4, 6},
|
|
ans: 2,
|
|
},
|
|
{
|
|
p: []float64{1, 2, 3},
|
|
q: []float64{1, 4, 9},
|
|
ans: 4,
|
|
},
|
|
{
|
|
p: []float64{1, 2, 3},
|
|
q: []float64{1, 4, 9},
|
|
weights: []float64{1, 1.5, 1},
|
|
ans: 3.2,
|
|
},
|
|
{
|
|
p: []float64{1, 4, 9},
|
|
q: []float64{1, 4, 9},
|
|
weights: []float64{1, 1.5, 1},
|
|
ans: 13.142857142857146,
|
|
},
|
|
} {
|
|
c := Covariance(test.p, test.q, test.weights)
|
|
if math.Abs(c-test.ans) > 1e-14 {
|
|
t.Errorf("Covariance mismatch case %d: Expected %v, Found %v", i, test.ans, c)
|
|
}
|
|
}
|
|
|
|
// test the panic states
|
|
if !Panics(func() { Covariance(make([]float64, 2), make([]float64, 3), nil) }) {
|
|
t.Errorf("Covariance did not panic with x, y length mismatch")
|
|
}
|
|
if !Panics(func() { Covariance(make([]float64, 3), make([]float64, 3), make([]float64, 2)) }) {
|
|
t.Errorf("Covariance did not panic with x, weights length mismatch")
|
|
}
|
|
|
|
}
|
|
|
|
func TestCrossEntropy(t *testing.T) {
|
|
for i, test := range []struct {
|
|
p []float64
|
|
q []float64
|
|
ans float64
|
|
}{
|
|
{
|
|
p: []float64{0.75, 0.1, 0.05},
|
|
q: []float64{0.5, 0.25, 0.25},
|
|
ans: 0.7278045395879426,
|
|
},
|
|
{
|
|
p: []float64{0.75, 0.1, 0.05, 0, 0, 0},
|
|
q: []float64{0.5, 0.25, 0.25, 0, 0, 0},
|
|
ans: 0.7278045395879426,
|
|
},
|
|
{
|
|
p: []float64{0.75, 0.1, 0.05, 0, 0, 0.1},
|
|
q: []float64{0.5, 0.25, 0.25, 0, 0, 0},
|
|
ans: math.Inf(1),
|
|
},
|
|
{
|
|
p: nil,
|
|
q: nil,
|
|
ans: 0,
|
|
},
|
|
} {
|
|
c := CrossEntropy(test.p, test.q)
|
|
if math.Abs(c-test.ans) > 1e-14 {
|
|
t.Errorf("Cross entropy mismatch case %d: Expected %v, Found %v", i, test.ans, c)
|
|
}
|
|
}
|
|
if !Panics(func() { CrossEntropy(make([]float64, 3), make([]float64, 2)) }) {
|
|
t.Errorf("CrossEntropy did not panic with p, q length mismatch")
|
|
}
|
|
}
|
|
|
|
func ExampleEntropy() {
|
|
|
|
p := []float64{0.05, 0.1, 0.9, 0.05}
|
|
entP := Entropy(p)
|
|
|
|
q := []float64{0.2, 0.4, 0.25, 0.15}
|
|
entQ := Entropy(q)
|
|
|
|
r := []float64{0.2, 0, 0, 0.5, 0, 0.2, 0.1, 0, 0, 0}
|
|
entR := Entropy(r)
|
|
|
|
s := []float64{0, 0, 1, 0}
|
|
entS := Entropy(s)
|
|
|
|
fmt.Println("Entropy is a measure of the amount of uncertainty in a distribution")
|
|
fmt.Printf("The second bin of p is very likely to occur. It's entropy is %.4f\n", entP)
|
|
fmt.Printf("The distribution of q is more spread out. It's entropy is %.4f\n", entQ)
|
|
fmt.Println("Adding buckets with zero probability does not change the entropy.")
|
|
fmt.Printf("The entropy of r is: %.4f\n", entR)
|
|
fmt.Printf("A distribution with no uncertainty has entropy %.4f\n", entS)
|
|
// Output:
|
|
// Entropy is a measure of the amount of uncertainty in a distribution
|
|
// The second bin of p is very likely to occur. It's entropy is 0.6247
|
|
// The distribution of q is more spread out. It's entropy is 1.3195
|
|
// Adding buckets with zero probability does not change the entropy.
|
|
// The entropy of r is: 1.2206
|
|
// A distribution with no uncertainty has entropy 0.0000
|
|
}
|
|
|
|
func ExampleExKurtosis() {
|
|
fmt.Println(`Kurtosis is a measure of the 'peakedness' of a distribution, and the
|
|
excess kurtosis is the kurtosis above or below that of the standard normal
|
|
distribution`)
|
|
x := []float64{5, 4, -3, -2}
|
|
kurt := ExKurtosis(x, nil)
|
|
fmt.Printf("ExKurtosis = %.5f\n", kurt)
|
|
weights := []float64{1, 2, 3, 5}
|
|
wKurt := ExKurtosis(x, weights)
|
|
fmt.Printf("Weighted ExKurtosis is %.4f", wKurt)
|
|
// Output:
|
|
// Kurtosis is a measure of the 'peakedness' of a distribution, and the
|
|
// excess kurtosis is the kurtosis above or below that of the standard normal
|
|
// distribution
|
|
// ExKurtosis = -5.41200
|
|
// Weighted ExKurtosis is -0.6779
|
|
}
|
|
|
|
func TestExKurtosis(t *testing.T) {
|
|
// the example does a good job, this just has to cover the panic
|
|
if !Panics(func() { ExKurtosis(make([]float64, 3), make([]float64, 2)) }) {
|
|
t.Errorf("ExKurtosis did not panic with x, weights length mismatch")
|
|
}
|
|
}
|
|
|
|
func ExampleGeometricMean() {
|
|
x := []float64{8, 2, 9, 15, 4}
|
|
weights := []float64{2, 2, 6, 7, 1}
|
|
mean := Mean(x, weights)
|
|
gmean := GeometricMean(x, weights)
|
|
|
|
logx := make([]float64, len(x))
|
|
for i, v := range x {
|
|
logx[i] = math.Log(v)
|
|
}
|
|
expMeanLog := math.Exp(Mean(logx, weights))
|
|
fmt.Printf("The arithmetic mean is %.4f, but the geometric mean is %.4f.\n", mean, gmean)
|
|
fmt.Printf("The exponential of the mean of the logs is %.4f\n", expMeanLog)
|
|
// Output:
|
|
// The arithmetic mean is 10.1667, but the geometric mean is 8.7637.
|
|
// The exponential of the mean of the logs is 8.7637
|
|
}
|
|
|
|
func TestGeometricMean(t *testing.T) {
|
|
for i, test := range []struct {
|
|
x []float64
|
|
wts []float64
|
|
ans float64
|
|
}{
|
|
{
|
|
x: []float64{2, 8},
|
|
ans: 4,
|
|
},
|
|
{
|
|
x: []float64{3, 81},
|
|
wts: []float64{2, 1},
|
|
ans: 9,
|
|
},
|
|
} {
|
|
c := GeometricMean(test.x, test.wts)
|
|
if math.Abs(c-test.ans) > 1e-14 {
|
|
t.Errorf("Geometric mean mismatch case %d: Expected %v, Found %v", i, test.ans, c)
|
|
}
|
|
}
|
|
if !Panics(func() { GeometricMean(make([]float64, 3), make([]float64, 2)) }) {
|
|
t.Errorf("GeometricMean did not panic with x, wts length mismatch")
|
|
}
|
|
}
|
|
|
|
func ExampleHarmonicMean() {
|
|
x := []float64{8, 2, 9, 15, 4}
|
|
weights := []float64{2, 2, 6, 7, 1}
|
|
mean := Mean(x, weights)
|
|
hmean := HarmonicMean(x, weights)
|
|
|
|
fmt.Printf("The arithmetic mean is %.5f, but the harmonic mean is %.4f.\n", mean, hmean)
|
|
// Output:
|
|
// The arithmetic mean is 10.16667, but the harmonic mean is 6.8354.
|
|
}
|
|
|
|
func TestHarmonicMean(t *testing.T) {
|
|
for i, test := range []struct {
|
|
x []float64
|
|
wts []float64
|
|
ans float64
|
|
}{
|
|
{
|
|
x: []float64{.5, .125},
|
|
ans: .2,
|
|
},
|
|
{
|
|
x: []float64{.5, .125},
|
|
wts: []float64{2, 1},
|
|
ans: .25,
|
|
},
|
|
} {
|
|
c := HarmonicMean(test.x, test.wts)
|
|
if math.Abs(c-test.ans) > 1e-14 {
|
|
t.Errorf("Harmonic mean mismatch case %d: Expected %v, Found %v", i, test.ans, c)
|
|
}
|
|
}
|
|
if !Panics(func() { HarmonicMean(make([]float64, 3), make([]float64, 2)) }) {
|
|
t.Errorf("HarmonicMean did not panic with x, wts length mismatch")
|
|
}
|
|
}
|
|
|
|
func TestHistogram(t *testing.T) {
|
|
for i, test := range []struct {
|
|
x []float64
|
|
weights []float64
|
|
dividers []float64
|
|
ans []float64
|
|
}{
|
|
{
|
|
x: []float64{1, 3, 5, 6, 7, 8},
|
|
dividers: []float64{0, 2, 4, 6, 7, 9},
|
|
ans: []float64{1, 1, 1, 1, 2},
|
|
},
|
|
{
|
|
x: []float64{1, 3, 5, 6, 7, 8},
|
|
dividers: []float64{1, 2, 4, 6, 7, 9},
|
|
weights: []float64{1, 2, 1, 1, 1, 2},
|
|
ans: []float64{1, 2, 1, 1, 3},
|
|
},
|
|
{
|
|
x: []float64{1, 8},
|
|
dividers: []float64{0, 2, 4, 6, 7, 9},
|
|
weights: []float64{1, 2},
|
|
ans: []float64{1, 0, 0, 0, 2},
|
|
},
|
|
{
|
|
x: []float64{1, 8},
|
|
dividers: []float64{0, 2, 4, 6, 7, 9},
|
|
ans: []float64{1, 0, 0, 0, 1},
|
|
},
|
|
{
|
|
x: []float64{},
|
|
dividers: []float64{1, 3},
|
|
ans: []float64{0},
|
|
},
|
|
} {
|
|
hist := Histogram(nil, test.dividers, test.x, test.weights)
|
|
if !floats.Equal(hist, test.ans) {
|
|
t.Errorf("Hist mismatch case %d. Expected %v, Found %v", i, test.ans, hist)
|
|
}
|
|
}
|
|
// panic cases
|
|
for _, test := range []struct {
|
|
name string
|
|
x []float64
|
|
weights []float64
|
|
dividers []float64
|
|
count []float64
|
|
}{
|
|
{
|
|
name: "len(x) != len(weights)",
|
|
x: []float64{1, 3, 5, 6, 7, 8},
|
|
weights: []float64{1, 1, 1, 1},
|
|
},
|
|
{
|
|
name: "len(count) != len(dividers) - 1",
|
|
x: []float64{1, 3, 5, 6, 7, 8},
|
|
dividers: []float64{1, 4, 9},
|
|
count: make([]float64, 6),
|
|
},
|
|
{
|
|
name: "dividers not sorted",
|
|
x: []float64{1, 3, 5, 6, 7, 8},
|
|
dividers: []float64{0, -1, 0},
|
|
},
|
|
{
|
|
name: "x not sorted",
|
|
x: []float64{1, 5, 2, 9, 7, 8},
|
|
dividers: []float64{1, 4, 9},
|
|
},
|
|
{
|
|
name: "fewer than 2 dividers",
|
|
x: []float64{1, 2, 3},
|
|
dividers: []float64{5},
|
|
},
|
|
{
|
|
name: "x too large",
|
|
x: []float64{1, 2, 3},
|
|
dividers: []float64{1, 3},
|
|
},
|
|
{
|
|
name: "x too small",
|
|
x: []float64{1, 2, 3},
|
|
dividers: []float64{2, 3},
|
|
},
|
|
} {
|
|
if !Panics(func() { Histogram(test.count, test.dividers, test.x, test.weights) }) {
|
|
t.Errorf("Histogram did not panic when %s", test.name)
|
|
}
|
|
}
|
|
}
|
|
|
|
func ExampleHistogram() {
|
|
x := make([]float64, 101)
|
|
for i := range x {
|
|
x[i] = 1.1 * float64(i) // x data ranges from 0 to 110
|
|
}
|
|
dividers := []float64{0, 7, 20, 100, 1000}
|
|
fmt.Println(`Histogram counts the amount of data in the bins specified by
|
|
the dividers. In this data set, there are 7 data points less than 7 (between dividers[0]
|
|
and dividers[1]), 12 data points between 7 and 20 (dividers[1] and dividers[2]),
|
|
and 0 data points above 1000. Since dividers has length 5, there will be 4 bins.`)
|
|
hist := Histogram(nil, dividers, x, nil)
|
|
fmt.Printf("Hist = %v\n", hist)
|
|
|
|
fmt.Println()
|
|
fmt.Println("For ease, the floats Span function can be used to set the dividers")
|
|
nBins := 10
|
|
dividers = make([]float64, nBins+1)
|
|
min, _ := floats.Min(x)
|
|
max, _ := floats.Max(x)
|
|
// Increase the maximum divider so that the maximum value of x is contained
|
|
// within the last bucket.
|
|
max += 1
|
|
floats.Span(dividers, min, max)
|
|
// Span includes the min and the max. Trim the dividers to create 10 buckets
|
|
hist = Histogram(nil, dividers, x, nil)
|
|
fmt.Printf("Hist = %v\n", hist)
|
|
fmt.Println()
|
|
fmt.Println(`Histogram also works with weighted data, and allows reusing of
|
|
the count field in order to avoid extra garbage`)
|
|
weights := make([]float64, len(x))
|
|
for i := range weights {
|
|
weights[i] = float64(i + 1)
|
|
}
|
|
Histogram(hist, dividers, x, weights)
|
|
fmt.Printf("Weighted Hist = %v\n", hist)
|
|
|
|
// Output:
|
|
// Histogram counts the amount of data in the bins specified by
|
|
// the dividers. In this data set, there are 7 data points less than 7 (between dividers[0]
|
|
// and dividers[1]), 12 data points between 7 and 20 (dividers[1] and dividers[2]),
|
|
// and 0 data points above 1000. Since dividers has length 5, there will be 4 bins.
|
|
// Hist = [7 12 72 10]
|
|
//
|
|
// For ease, the floats Span function can be used to set the dividers
|
|
// Hist = [11 10 10 10 10 10 10 10 10 10]
|
|
//
|
|
// Histogram also works with weighted data, and allows reusing of
|
|
// the count field in order to avoid extra garbage
|
|
// Weighted Hist = [77 175 275 375 475 575 675 775 875 975]
|
|
}
|
|
|
|
func TestJensenShannon(t *testing.T) {
|
|
for i, test := range []struct {
|
|
p []float64
|
|
q []float64
|
|
}{
|
|
{
|
|
p: []float64{0.5, 0.1, 0.3, 0.1},
|
|
q: []float64{0.1, 0.4, 0.25, 0.25},
|
|
},
|
|
{
|
|
p: []float64{0.4, 0.6, 0.0},
|
|
q: []float64{0.2, 0.2, 0.6},
|
|
},
|
|
{
|
|
p: []float64{0.1, 0.1, 0.0, 0.8},
|
|
q: []float64{0.6, 0.3, 0.0, 0.1},
|
|
},
|
|
{
|
|
p: []float64{0.5, 0.1, 0.3, 0.1},
|
|
q: []float64{0.5, 0, 0.25, 0.25},
|
|
},
|
|
{
|
|
p: []float64{0.5, 0.1, 0, 0.4},
|
|
q: []float64{0.1, 0.4, 0.25, 0.25},
|
|
},
|
|
} {
|
|
|
|
m := make([]float64, len(test.p))
|
|
p := test.p
|
|
q := test.q
|
|
floats.Add(m, p)
|
|
floats.Add(m, q)
|
|
floats.Scale(0.5, m)
|
|
|
|
js1 := 0.5*KullbackLeibler(p, m) + 0.5*KullbackLeibler(q, m)
|
|
js2 := JensenShannon(p, q)
|
|
|
|
if math.IsNaN(js2) {
|
|
t.Errorf("In case %v, JS distance is NaN", i)
|
|
}
|
|
|
|
if math.Abs(js1-js2) > 1e-14 {
|
|
t.Errorf("JS mismatch case %v. Expected %v, found %v.", i, js1, js2)
|
|
}
|
|
}
|
|
if !Panics(func() { JensenShannon(make([]float64, 3), make([]float64, 2)) }) {
|
|
t.Errorf("JensenShannon did not panic with p, q length mismatch")
|
|
}
|
|
}
|
|
|
|
func TestKolmogorovSmirnov(t *testing.T) {
|
|
for i, test := range []struct {
|
|
x []float64
|
|
xWeights []float64
|
|
y []float64
|
|
yWeights []float64
|
|
dist float64
|
|
}{
|
|
|
|
{
|
|
dist: 0,
|
|
},
|
|
{
|
|
x: []float64{1},
|
|
dist: 1,
|
|
},
|
|
{
|
|
y: []float64{1},
|
|
dist: 1,
|
|
},
|
|
{
|
|
x: []float64{1},
|
|
xWeights: []float64{8},
|
|
dist: 1,
|
|
},
|
|
{
|
|
y: []float64{1},
|
|
yWeights: []float64{8},
|
|
dist: 1,
|
|
},
|
|
{
|
|
x: []float64{1},
|
|
xWeights: []float64{8},
|
|
y: []float64{1},
|
|
yWeights: []float64{8},
|
|
dist: 0,
|
|
},
|
|
{
|
|
x: []float64{1, 1, 1},
|
|
xWeights: []float64{2, 3, 7},
|
|
y: []float64{1},
|
|
yWeights: []float64{8},
|
|
dist: 0,
|
|
},
|
|
{
|
|
x: []float64{1, 1, 1, 1, 1},
|
|
y: []float64{1, 1, 1},
|
|
yWeights: []float64{2, 5, 2},
|
|
dist: 0,
|
|
},
|
|
|
|
{
|
|
x: []float64{1, 2, 3},
|
|
y: []float64{1, 2, 3},
|
|
dist: 0,
|
|
},
|
|
{
|
|
x: []float64{1, 2, 3},
|
|
y: []float64{1, 2, 3},
|
|
yWeights: []float64{1, 1, 1},
|
|
dist: 0,
|
|
},
|
|
|
|
{
|
|
x: []float64{1, 2, 3},
|
|
xWeights: []float64{1, 1, 1},
|
|
y: []float64{1, 2, 3},
|
|
yWeights: []float64{1, 1, 1},
|
|
dist: 0,
|
|
},
|
|
{
|
|
x: []float64{1, 2},
|
|
xWeights: []float64{2, 5},
|
|
y: []float64{1, 1, 2, 2, 2, 2, 2},
|
|
dist: 0,
|
|
},
|
|
{
|
|
x: []float64{1, 1, 2, 2, 2, 2, 2},
|
|
y: []float64{1, 2},
|
|
yWeights: []float64{2, 5},
|
|
dist: 0,
|
|
},
|
|
{
|
|
x: []float64{1, 1, 2, 2, 2},
|
|
xWeights: []float64{0.5, 1.5, 1, 2, 2},
|
|
y: []float64{1, 2},
|
|
yWeights: []float64{2, 5},
|
|
dist: 0,
|
|
},
|
|
{
|
|
x: []float64{1, 2, 3, 4},
|
|
y: []float64{5, 6},
|
|
dist: 1,
|
|
},
|
|
{
|
|
x: []float64{5, 6},
|
|
y: []float64{1, 2, 3, 4},
|
|
dist: 1,
|
|
},
|
|
{
|
|
x: []float64{5, 6},
|
|
xWeights: []float64{8, 7},
|
|
y: []float64{1, 2, 3, 4},
|
|
dist: 1,
|
|
},
|
|
{
|
|
x: []float64{5, 6},
|
|
xWeights: []float64{8, 7},
|
|
y: []float64{1, 2, 3, 4},
|
|
yWeights: []float64{9, 2, 1, 6},
|
|
dist: 1,
|
|
},
|
|
{
|
|
x: []float64{-4, 5, 6},
|
|
xWeights: []float64{0, 8, 7},
|
|
y: []float64{1, 2, 3, 4},
|
|
yWeights: []float64{9, 2, 1, 6},
|
|
dist: 1,
|
|
},
|
|
{
|
|
x: []float64{-4, -2, -2, 5, 6},
|
|
xWeights: []float64{0, 0, 0, 8, 7},
|
|
y: []float64{1, 2, 3, 4},
|
|
yWeights: []float64{9, 2, 1, 6},
|
|
dist: 1,
|
|
},
|
|
{
|
|
x: []float64{1, 2, 3},
|
|
y: []float64{1, 1, 3},
|
|
dist: 1.0 / 3.0,
|
|
},
|
|
{
|
|
x: []float64{1, 2, 3},
|
|
y: []float64{1, 3},
|
|
yWeights: []float64{2, 1},
|
|
dist: 1.0 / 3.0,
|
|
},
|
|
{
|
|
x: []float64{1, 2, 3},
|
|
xWeights: []float64{2, 2, 2},
|
|
y: []float64{1, 3},
|
|
yWeights: []float64{2, 1},
|
|
dist: 1.0 / 3.0,
|
|
},
|
|
{
|
|
x: []float64{2, 3, 4},
|
|
y: []float64{1, 5},
|
|
dist: 1.0 / 2.0,
|
|
},
|
|
{
|
|
x: []float64{1, 2, math.NaN()},
|
|
y: []float64{1, 1, 3},
|
|
dist: math.NaN(),
|
|
},
|
|
{
|
|
x: []float64{1, 2, 3},
|
|
y: []float64{1, 1, math.NaN()},
|
|
dist: math.NaN(),
|
|
},
|
|
} {
|
|
dist := KolmogorovSmirnov(test.x, test.xWeights, test.y, test.yWeights)
|
|
if math.Abs(dist-test.dist) > 1e-14 && !(math.IsNaN(test.dist) && math.IsNaN(dist)) {
|
|
t.Errorf("Distance mismatch case %v: Expected: %v, Found: %v", i, test.dist, dist)
|
|
}
|
|
}
|
|
// panic cases
|
|
for _, test := range []struct {
|
|
name string
|
|
x []float64
|
|
xWeights []float64
|
|
y []float64
|
|
yWeights []float64
|
|
}{
|
|
{
|
|
name: "len(x) != len(xWeights)",
|
|
x: []float64{1, 3, 5, 6, 7, 8},
|
|
xWeights: []float64{1, 1, 1, 1},
|
|
},
|
|
{
|
|
name: "len(y) != len(yWeights)",
|
|
x: []float64{1, 3, 5, 6, 7, 8},
|
|
y: []float64{1, 3, 5, 6, 7, 8},
|
|
yWeights: []float64{1, 1, 1, 1},
|
|
},
|
|
{
|
|
name: "x not sorted",
|
|
x: []float64{10, 3, 5, 6, 7, 8},
|
|
y: []float64{1, 3, 5, 6, 7, 8},
|
|
},
|
|
{
|
|
name: "y not sorted",
|
|
x: []float64{1, 3, 5, 6, 7, 8},
|
|
y: []float64{10, 3, 5, 6, 7, 8},
|
|
},
|
|
} {
|
|
if !Panics(func() { KolmogorovSmirnov(test.x, test.xWeights, test.y, test.yWeights) }) {
|
|
t.Errorf("KolmogorovSmirnov did not panic when %s", test.name)
|
|
}
|
|
}
|
|
}
|
|
|
|
func ExampleKullbackLeibler() {
|
|
|
|
p := []float64{0.05, 0.1, 0.9, 0.05}
|
|
q := []float64{0.2, 0.4, 0.25, 0.15}
|
|
s := []float64{0, 0, 1, 0}
|
|
|
|
klPQ := KullbackLeibler(p, q)
|
|
klPS := KullbackLeibler(p, s)
|
|
klPP := KullbackLeibler(p, p)
|
|
|
|
fmt.Println("Kullback-Leibler is one measure of the difference between two distributions")
|
|
fmt.Printf("The K-L distance between p and q is %.4f\n", klPQ)
|
|
fmt.Println("It is impossible for s and p to be the same distribution, because")
|
|
fmt.Println("the first bucket has zero probability in s and non-zero in p. Thus,")
|
|
fmt.Printf("the K-L distance between them is %.4f\n", klPS)
|
|
fmt.Printf("The K-L distance between identical distributions is %.4f\n", klPP)
|
|
|
|
// Kullback-Leibler is one measure of the difference between two distributions
|
|
// The K-L distance between p and q is 0.8900
|
|
// It is impossible for s and p to be the same distribution, because
|
|
// the first bucket has zero probability in s and non-zero in p. Thus,
|
|
// the K-L distance between them is +Inf
|
|
// The K-L distance between identical distributions is 0.0000
|
|
}
|
|
|
|
func TestKullbackLeibler(t *testing.T) {
|
|
if !Panics(func() { KullbackLeibler(make([]float64, 3), make([]float64, 2)) }) {
|
|
t.Errorf("KullbackLeibler did not panic with p, q length mismatch")
|
|
}
|
|
}
|
|
|
|
func TestChiSquare(t *testing.T) {
|
|
for i, test := range []struct {
|
|
p []float64
|
|
q []float64
|
|
res float64
|
|
}{
|
|
{
|
|
p: []float64{16, 18, 16, 14, 12, 12},
|
|
q: []float64{16, 16, 16, 16, 16, 8},
|
|
res: 3.5,
|
|
},
|
|
{
|
|
p: []float64{16, 18, 16, 14, 12, 12},
|
|
q: []float64{8, 20, 20, 16, 12, 12},
|
|
res: 9.25,
|
|
},
|
|
{
|
|
p: []float64{40, 60, 30, 45},
|
|
q: []float64{50, 50, 50, 50},
|
|
res: 12.5,
|
|
},
|
|
{
|
|
p: []float64{40, 60, 30, 45, 0, 0},
|
|
q: []float64{50, 50, 50, 50, 0, 0},
|
|
res: 12.5,
|
|
},
|
|
} {
|
|
resultpq := ChiSquare(test.p, test.q)
|
|
|
|
if math.Abs(resultpq-test.res) > 1e-10 {
|
|
t.Errorf("ChiSquare distance mismatch in case %d. Expected %v, Found %v", i, test.res, resultpq)
|
|
}
|
|
}
|
|
if !Panics(func() { ChiSquare(make([]float64, 2), make([]float64, 3)) }) {
|
|
t.Errorf("ChiSquare did not panic with length mismatch")
|
|
}
|
|
}
|
|
|
|
// Panics returns true if the called function panics during evaluation.
|
|
func Panics(fun func()) (b bool) {
|
|
defer func() {
|
|
err := recover()
|
|
if err != nil {
|
|
b = true
|
|
}
|
|
}()
|
|
fun()
|
|
return
|
|
}
|
|
|
|
func TestBhattacharyya(t *testing.T) {
|
|
for i, test := range []struct {
|
|
p []float64
|
|
q []float64
|
|
res float64
|
|
}{
|
|
{
|
|
p: []float64{0.5, 0.1, 0.3, 0.1},
|
|
q: []float64{0.1, 0.4, 0.25, 0.25},
|
|
res: 0.15597338718671386,
|
|
},
|
|
{
|
|
p: []float64{0.4, 0.6, 0.0},
|
|
q: []float64{0.2, 0.2, 0.6},
|
|
res: 0.46322207765351153,
|
|
},
|
|
{
|
|
p: []float64{0.1, 0.1, 0.0, 0.8},
|
|
q: []float64{0.6, 0.3, 0.0, 0.1},
|
|
res: 0.3552520032137785,
|
|
},
|
|
} {
|
|
resultpq := Bhattacharyya(test.p, test.q)
|
|
resultqp := Bhattacharyya(test.q, test.p)
|
|
|
|
if math.Abs(resultpq-test.res) > 1e-10 {
|
|
t.Errorf("Bhattacharyya distance mismatch in case %d. Expected %v, Found %v", i, test.res, resultpq)
|
|
}
|
|
if math.Abs(resultpq-resultqp) > 1e-10 {
|
|
t.Errorf("Bhattacharyya distance is assymmetric in case %d.", i)
|
|
}
|
|
}
|
|
// Bhattacharyya should panic if the inputs have different length
|
|
if !Panics(func() { Bhattacharyya(make([]float64, 2), make([]float64, 3)) }) {
|
|
t.Errorf("Bhattacharyya did not panic with length mismatch")
|
|
}
|
|
}
|
|
|
|
func TestHellinger(t *testing.T) {
|
|
for i, test := range []struct {
|
|
p []float64
|
|
q []float64
|
|
res float64
|
|
}{
|
|
{
|
|
p: []float64{0.5, 0.1, 0.3, 0.1},
|
|
q: []float64{0.1, 0.4, 0.25, 0.25},
|
|
res: 0.3800237367441919,
|
|
},
|
|
{
|
|
p: []float64{0.4, 0.6, 0.0},
|
|
q: []float64{0.2, 0.2, 0.6},
|
|
res: 0.6088900771170487,
|
|
},
|
|
{
|
|
p: []float64{0.1, 0.1, 0.0, 0.8},
|
|
q: []float64{0.6, 0.3, 0.0, 0.1},
|
|
res: 0.5468118803484205,
|
|
},
|
|
} {
|
|
resultpq := Hellinger(test.p, test.q)
|
|
resultqp := Hellinger(test.q, test.p)
|
|
|
|
if math.Abs(resultpq-test.res) > 1e-10 {
|
|
t.Errorf("Hellinger distance mismatch in case %d. Expected %v, Found %v", i, test.res, resultpq)
|
|
}
|
|
if math.Abs(resultpq-resultqp) > 1e-10 {
|
|
t.Errorf("Hellinger distance is assymmetric in case %d.", i)
|
|
}
|
|
}
|
|
if !Panics(func() { Hellinger(make([]float64, 2), make([]float64, 3)) }) {
|
|
t.Errorf("Hellinger did not panic with length mismatch")
|
|
}
|
|
}
|
|
|
|
func ExampleMean() {
|
|
x := []float64{8.2, -6, 5, 7}
|
|
mean := Mean(x, nil)
|
|
fmt.Printf("The mean of the samples is %.4f\n", mean)
|
|
w := []float64{2, 6, 3, 5}
|
|
weightedMean := Mean(x, w)
|
|
fmt.Printf("The weighted mean of the samples is %.4f\n", weightedMean)
|
|
x2 := []float64{8.2, 8.2, -6, -6, -6, -6, -6, -6, 5, 5, 5, 7, 7, 7, 7, 7}
|
|
mean2 := Mean(x2, nil)
|
|
fmt.Printf("The mean of x2 is %.4f\n", mean2)
|
|
fmt.Println("The weights act as if there were more samples of that number")
|
|
// Output:
|
|
// The mean of the samples is 3.5500
|
|
// The weighted mean of the samples is 1.9000
|
|
// The mean of x2 is 1.9000
|
|
// The weights act as if there were more samples of that number
|
|
}
|
|
func TestMean(t *testing.T) {
|
|
if !Panics(func() { Mean(make([]float64, 3), make([]float64, 2)) }) {
|
|
t.Errorf("Mean did not panic with x, weights length mismatch")
|
|
}
|
|
}
|
|
|
|
func TestMode(t *testing.T) {
|
|
for i, test := range []struct {
|
|
x []float64
|
|
weights []float64
|
|
ans float64
|
|
count float64
|
|
}{
|
|
{},
|
|
{
|
|
x: []float64{1, 6, 1, 9, -2},
|
|
ans: 1,
|
|
count: 2,
|
|
},
|
|
{
|
|
x: []float64{1, 6, 1, 9, -2},
|
|
weights: []float64{1, 7, 3, 5, 0},
|
|
ans: 6,
|
|
count: 7,
|
|
},
|
|
} {
|
|
m, count := Mode(test.x, test.weights)
|
|
if test.ans != m {
|
|
t.Errorf("Mode mismatch case %d. Expected %v, found %v", i, test.ans, m)
|
|
}
|
|
if test.count != count {
|
|
t.Errorf("Mode count mismatch case %d. Expected %v, found %v", i, test.count, count)
|
|
}
|
|
}
|
|
if !Panics(func() { Mode(make([]float64, 3), make([]float64, 2)) }) {
|
|
t.Errorf("Mode did not panic with x, weights length mismatch")
|
|
}
|
|
}
|
|
|
|
func TestMoment(t *testing.T) {
|
|
for i, test := range []struct {
|
|
x []float64
|
|
weights []float64
|
|
moment float64
|
|
ans float64
|
|
}{
|
|
{
|
|
x: []float64{6, 2, 4, 8, 10},
|
|
moment: 5,
|
|
ans: 0,
|
|
},
|
|
{
|
|
x: []float64{6, 2, 4, 8, 10},
|
|
weights: []float64{1, 2, 2, 2, 1},
|
|
moment: 5,
|
|
ans: 121.875,
|
|
},
|
|
} {
|
|
m := Moment(test.moment, test.x, test.weights)
|
|
if math.Abs(test.ans-m) > 1e-14 {
|
|
t.Errorf("Moment mismatch case %d. Expected %v, found %v", i, test.ans, m)
|
|
}
|
|
}
|
|
if !Panics(func() { Moment(1, make([]float64, 3), make([]float64, 2)) }) {
|
|
t.Errorf("Moment did not panic with x, weights length mismatch")
|
|
}
|
|
}
|
|
|
|
func TestMomentAbout(t *testing.T) {
|
|
for i, test := range []struct {
|
|
x []float64
|
|
weights []float64
|
|
moment float64
|
|
mean float64
|
|
ans float64
|
|
}{
|
|
{
|
|
x: []float64{6, 2, 4, 8, 9},
|
|
mean: 3,
|
|
moment: 5,
|
|
ans: 2.2288e3,
|
|
},
|
|
{
|
|
x: []float64{6, 2, 4, 8, 9},
|
|
weights: []float64{1, 2, 2, 2, 1},
|
|
mean: 3,
|
|
moment: 5,
|
|
ans: 1.783625e3,
|
|
},
|
|
} {
|
|
m := MomentAbout(test.moment, test.x, test.mean, test.weights)
|
|
if math.Abs(test.ans-m) > 1e-14 {
|
|
t.Errorf("MomentAbout mismatch case %d. Expected %v, found %v", i, test.ans, m)
|
|
}
|
|
}
|
|
if !Panics(func() { MomentAbout(1, make([]float64, 3), 0, make([]float64, 2)) }) {
|
|
t.Errorf("MomentAbout did not panic with x, weights length mismatch")
|
|
}
|
|
}
|
|
|
|
func TestCDF(t *testing.T) {
|
|
cumulantKinds := []CumulantKind{Empirical}
|
|
for i, test := range []struct {
|
|
q []float64
|
|
x []float64
|
|
weights []float64
|
|
ans [][]float64
|
|
}{
|
|
{},
|
|
{
|
|
q: []float64{0, 0.9, 1, 1.1, 2.9, 3, 3.1, 4.9, 5, 5.1},
|
|
x: []float64{1, 2, 3, 4, 5},
|
|
ans: [][]float64{{0, 0, 0.2, 0.2, 0.4, 0.6, 0.6, 0.8, 1, 1}},
|
|
},
|
|
{
|
|
q: []float64{0, 0.9, 1, 1.1, 2.9, 3, 3.1, 4.9, 5, 5.1},
|
|
x: []float64{1, 2, 3, 4, 5},
|
|
weights: []float64{1, 1, 1, 1, 1},
|
|
ans: [][]float64{{0, 0, 0.2, 0.2, 0.4, 0.6, 0.6, 0.8, 1, 1}},
|
|
},
|
|
{
|
|
q: []float64{0, 0.9, 1},
|
|
x: []float64{math.NaN()},
|
|
ans: [][]float64{{math.NaN(), math.NaN(), math.NaN()}},
|
|
},
|
|
} {
|
|
copyX := make([]float64, len(test.x))
|
|
copy(copyX, test.x)
|
|
var copyW []float64
|
|
if test.weights != nil {
|
|
copyW = make([]float64, len(test.weights))
|
|
copy(copyW, test.weights)
|
|
}
|
|
for j, q := range test.q {
|
|
for k, kind := range cumulantKinds {
|
|
v := CDF(q, kind, test.x, test.weights)
|
|
if !floats.Equal(copyX, test.x) && !math.IsNaN(v) {
|
|
t.Errorf("x changed for case %d kind %d percentile %v", i, k, q)
|
|
}
|
|
if !floats.Equal(copyW, test.weights) {
|
|
t.Errorf("x changed for case %d kind %d percentile %v", i, k, q)
|
|
}
|
|
if v != test.ans[k][j] && !(math.IsNaN(v) && math.IsNaN(test.ans[k][j])) {
|
|
t.Errorf("mismatch case %d kind %d percentile %v. Expected: %v, found: %v", i, k, q, test.ans[k][j], v)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// these test cases should all result in a panic
|
|
for i, test := range []struct {
|
|
name string
|
|
q float64
|
|
kind CumulantKind
|
|
x []float64
|
|
weights []float64
|
|
}{
|
|
{
|
|
name: "len(x) != len(weights)",
|
|
q: 1.5,
|
|
kind: Empirical,
|
|
x: []float64{1, 2, 3, 4, 5},
|
|
weights: []float64{1, 2, 3},
|
|
},
|
|
{
|
|
name: "unsorted x",
|
|
q: 1.5,
|
|
kind: Empirical,
|
|
x: []float64{3, 2, 1},
|
|
},
|
|
{
|
|
name: "unknown CumulantKind",
|
|
q: 1.5,
|
|
kind: CumulantKind(1000), // bogus
|
|
x: []float64{1, 2, 3},
|
|
},
|
|
} {
|
|
if !Panics(func() { CDF(test.q, test.kind, test.x, test.weights) }) {
|
|
t.Errorf("did not panic as expected with %s for case %d kind %d percentile %v x %v weights %v", test.name, i, test.kind, test.q, test.x, test.weights)
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
func TestQuantile(t *testing.T) {
|
|
cumulantKinds := []CumulantKind{Empirical}
|
|
for i, test := range []struct {
|
|
p []float64
|
|
x []float64
|
|
w []float64
|
|
ans [][]float64
|
|
}{
|
|
{
|
|
p: []float64{0, 0.05, 0.1, 0.15, 0.45, 0.5, 0.55, 0.85, 0.9, 0.95, 1},
|
|
x: []float64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
|
|
w: nil,
|
|
ans: [][]float64{{1, 1, 1, 2, 5, 5, 6, 9, 9, 10, 10}},
|
|
},
|
|
{
|
|
p: []float64{0, 0.05, 0.1, 0.15, 0.45, 0.5, 0.55, 0.85, 0.9, 0.95, 1},
|
|
x: []float64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
|
|
w: []float64{3, 3, 3, 3, 3, 3, 3, 3, 3, 3},
|
|
ans: [][]float64{{1, 1, 1, 2, 5, 5, 6, 9, 9, 10, 10}},
|
|
},
|
|
{
|
|
p: []float64{0.5},
|
|
x: []float64{1, 2, 3, 4, 5, 6, 7, 8, math.NaN(), 10},
|
|
ans: [][]float64{{math.NaN()}},
|
|
},
|
|
} {
|
|
copyX := make([]float64, len(test.x))
|
|
copy(copyX, test.x)
|
|
var copyW []float64
|
|
if test.w != nil {
|
|
copyW = make([]float64, len(test.w))
|
|
copy(copyW, test.w)
|
|
}
|
|
for j, p := range test.p {
|
|
for k, kind := range cumulantKinds {
|
|
v := Quantile(p, kind, test.x, test.w)
|
|
if !floats.Same(copyX, test.x) {
|
|
t.Errorf("x changed for case %d kind %d percentile %v", i, k, p)
|
|
}
|
|
if !floats.Same(copyW, test.w) {
|
|
t.Errorf("x changed for case %d kind %d percentile %v", i, k, p)
|
|
}
|
|
if v != test.ans[k][j] && !(math.IsNaN(v) && math.IsNaN(test.ans[k][j])) {
|
|
t.Errorf("mismatch case %d kind %d percentile %v. Expected: %v, found: %v", i, k, p, test.ans[k][j], v)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// panic cases
|
|
for _, test := range []struct {
|
|
name string
|
|
p float64
|
|
c CumulantKind
|
|
x []float64
|
|
w []float64
|
|
}{
|
|
{
|
|
name: "p < 0",
|
|
c: Empirical,
|
|
p: -1,
|
|
},
|
|
{
|
|
name: "p > 1",
|
|
c: Empirical,
|
|
p: 2,
|
|
},
|
|
{
|
|
name: "p is NaN",
|
|
c: Empirical,
|
|
p: math.NaN(),
|
|
},
|
|
{
|
|
name: "len(x) != len(weights)",
|
|
c: Empirical,
|
|
p: .5,
|
|
x: make([]float64, 4),
|
|
w: make([]float64, 2),
|
|
},
|
|
{
|
|
name: "x not sorted",
|
|
c: Empirical,
|
|
p: .5,
|
|
x: []float64{3, 2, 1},
|
|
},
|
|
{
|
|
name: "CumulantKind is unknown",
|
|
c: CumulantKind(1000),
|
|
p: .5,
|
|
x: []float64{1, 2, 3},
|
|
},
|
|
} {
|
|
if !Panics(func() { Quantile(test.p, test.c, test.x, test.w) }) {
|
|
t.Errorf("Quantile did not panic when %s", test.name)
|
|
}
|
|
}
|
|
}
|
|
|
|
func ExampleStdDev() {
|
|
x := []float64{8, 2, -9, 15, 4}
|
|
stdev := StdDev(x, nil)
|
|
fmt.Printf("The standard deviation of the samples is %.4f\n", stdev)
|
|
|
|
weights := []float64{2, 2, 6, 7, 1}
|
|
weightedStdev := StdDev(x, weights)
|
|
fmt.Printf("The weighted standard deviation of the samples is %.4f\n", weightedStdev)
|
|
// Output:
|
|
// The standard deviation of the samples is 8.8034
|
|
// The weighted standard deviation of the samples is 10.5733
|
|
}
|
|
|
|
func ExampleStdErr() {
|
|
x := []float64{8, 2, -9, 15, 4}
|
|
weights := []float64{2, 2, 6, 7, 1}
|
|
mean := Mean(x, weights)
|
|
stdev := StdDev(x, weights)
|
|
nSamples := floats.Sum(weights)
|
|
stdErr := StdErr(stdev, nSamples)
|
|
fmt.Printf("The standard deviation is %.4f and there are %g samples, so the mean\nis likely %.4f ± %.4f.", stdev, nSamples, mean, stdErr)
|
|
// Output:
|
|
// The standard deviation is 10.5733 and there are 18 samples, so the mean
|
|
// is likely 4.1667 ± 2.4921.
|
|
}
|
|
|
|
func TestSkew(t *testing.T) {
|
|
for i, test := range []struct {
|
|
x []float64
|
|
weights []float64
|
|
ans float64
|
|
}{
|
|
{
|
|
x: []float64{8, 3, 7, 8, 4},
|
|
weights: nil,
|
|
ans: -0.581456499151665,
|
|
},
|
|
{
|
|
x: []float64{8, 3, 7, 8, 4},
|
|
weights: []float64{1, 1, 1, 1, 1},
|
|
ans: -0.581456499151665,
|
|
},
|
|
{
|
|
x: []float64{8, 3, 7, 8, 4},
|
|
weights: []float64{2, 1, 2, 1, 1},
|
|
ans: -1.12066646837198,
|
|
},
|
|
} {
|
|
skew := Skew(test.x, test.weights)
|
|
if math.Abs(skew-test.ans) > 1e-14 {
|
|
t.Errorf("Skew mismatch case %d. Expected %v, Found %v", i, test.ans, skew)
|
|
}
|
|
}
|
|
if !Panics(func() { Skew(make([]float64, 3), make([]float64, 2)) }) {
|
|
t.Errorf("Skew did not panic with x, weights length mismatch")
|
|
}
|
|
}
|
|
|
|
func TestSortWeighted(t *testing.T) {
|
|
for i, test := range []struct {
|
|
x []float64
|
|
w []float64
|
|
ansx []float64
|
|
answ []float64
|
|
}{
|
|
{
|
|
x: []float64{8, 3, 7, 8, 4},
|
|
ansx: []float64{3, 4, 7, 8, 8},
|
|
},
|
|
{
|
|
x: []float64{8, 3, 7, 8, 4},
|
|
w: []float64{.5, 1, 1, .5, 1},
|
|
ansx: []float64{3, 4, 7, 8, 8},
|
|
answ: []float64{1, 1, 1, .5, .5},
|
|
},
|
|
} {
|
|
SortWeighted(test.x, test.w)
|
|
if !floats.Same(test.x, test.ansx) {
|
|
t.Errorf("SortWeighted mismatch case %d. Expected x %v, Found x %v", i, test.ansx, test.x)
|
|
}
|
|
if !(test.w == nil) && !floats.Same(test.w, test.answ) {
|
|
t.Errorf("SortWeighted mismatch case %d. Expected w %v, Found w %v", i, test.answ, test.w)
|
|
}
|
|
}
|
|
if !Panics(func() { SortWeighted(make([]float64, 3), make([]float64, 2)) }) {
|
|
t.Errorf("SortWeighted did not panic with x, weights length mismatch")
|
|
}
|
|
}
|
|
|
|
func TestVariance(t *testing.T) {
|
|
for i, test := range []struct {
|
|
x []float64
|
|
weights []float64
|
|
ans float64
|
|
}{
|
|
{
|
|
x: []float64{8, -3, 7, 8, -4},
|
|
weights: nil,
|
|
ans: 37.7,
|
|
},
|
|
{
|
|
x: []float64{8, -3, 7, 8, -4},
|
|
weights: []float64{1, 1, 1, 1, 1},
|
|
ans: 37.7,
|
|
},
|
|
{
|
|
x: []float64{8, 3, 7, 8, 4},
|
|
weights: []float64{2, 1, 2, 1, 1},
|
|
ans: 4.2857142857142865,
|
|
},
|
|
{
|
|
x: []float64{1, 4, 9},
|
|
weights: []float64{1, 1.5, 1},
|
|
ans: 13.142857142857146,
|
|
},
|
|
{
|
|
x: []float64{1, 2, 3},
|
|
weights: []float64{1, 1.5, 1},
|
|
ans: .8,
|
|
},
|
|
} {
|
|
variance := Variance(test.x, test.weights)
|
|
if math.Abs(variance-test.ans) > 1e-14 {
|
|
t.Errorf("Variance mismatch case %d. Expected %v, Found %v", i, test.ans, variance)
|
|
}
|
|
}
|
|
if !Panics(func() { Variance(make([]float64, 3), make([]float64, 2)) }) {
|
|
t.Errorf("Variance did not panic with x, weights length mismatch")
|
|
}
|
|
|
|
}
|
|
|
|
func ExampleVariance() {
|
|
x := []float64{8, 2, -9, 15, 4}
|
|
variance := Variance(x, nil)
|
|
fmt.Printf("The variance of the samples is %.4f\n", variance)
|
|
|
|
weights := []float64{2, 2, 6, 7, 1}
|
|
weightedVariance := Variance(x, weights)
|
|
fmt.Printf("The weighted variance of the samples is %.4f\n", weightedVariance)
|
|
// Output:
|
|
// The variance of the samples is 77.5000
|
|
// The weighted variance of the samples is 111.7941
|
|
}
|
|
|
|
func TestStdScore(t *testing.T) {
|
|
for i, test := range []struct {
|
|
x float64
|
|
u float64
|
|
s float64
|
|
z float64
|
|
}{
|
|
{
|
|
x: 4,
|
|
u: -6,
|
|
s: 5,
|
|
z: 2,
|
|
},
|
|
{
|
|
x: 1,
|
|
u: 0,
|
|
s: 1,
|
|
z: 1,
|
|
},
|
|
} {
|
|
z := StdScore(test.x, test.u, test.s)
|
|
if math.Abs(z-test.z) > 1e-14 {
|
|
t.Errorf("StdScore mismatch case %d. Expected %v, Found %v", i, test.z, z)
|
|
}
|
|
}
|
|
|
|
}
|