Quartile called Median on empty half-slices when the input held one value
and discarded both errors, so it returned {NaN, v, NaN} with a nil error.
The NaN spread silently through InterQuartileRange, Midhinge and Trimean.
A single observation is its own lower hinge, median and upper hinge, which
is what Percentile already returns for the same data. Return that instead,
and note why the remaining discarded Median errors cannot fire.
126 lines
2.9 KiB
Go
126 lines
2.9 KiB
Go
package stats_test
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/montanaflynn/stats"
|
|
)
|
|
|
|
func TestQuartile(t *testing.T) {
|
|
s1 := []float64{6, 7, 15, 36, 39, 40, 41, 42, 43, 47, 49}
|
|
s2 := []float64{7, 15, 36, 39, 40, 41}
|
|
|
|
for _, c := range []struct {
|
|
in []float64
|
|
Q1 float64
|
|
Q2 float64
|
|
Q3 float64
|
|
}{
|
|
{s1, 15, 40, 43},
|
|
{s2, 15, 37.5, 40},
|
|
} {
|
|
quartiles, err := stats.Quartile(c.in)
|
|
if err != nil {
|
|
t.Errorf("Should not have returned an error")
|
|
}
|
|
|
|
if quartiles.Q1 != c.Q1 {
|
|
t.Errorf("Q1 %v != %v", quartiles.Q1, c.Q1)
|
|
}
|
|
if quartiles.Q2 != c.Q2 {
|
|
t.Errorf("Q2 %v != %v", quartiles.Q2, c.Q2)
|
|
}
|
|
if quartiles.Q3 != c.Q3 {
|
|
t.Errorf("Q3 %v != %v", quartiles.Q3, c.Q3)
|
|
}
|
|
}
|
|
|
|
_, err := stats.Quartile([]float64{})
|
|
if err == nil {
|
|
t.Errorf("Empty slice should have returned an error")
|
|
}
|
|
}
|
|
|
|
func TestQuartile_SingleValue(t *testing.T) {
|
|
quartiles, err := stats.Quartile([]float64{43})
|
|
if err != nil {
|
|
t.Errorf("Should not have returned an error; got %v", err)
|
|
}
|
|
if quartiles.Q1 != 43 {
|
|
t.Errorf("Q1 %v != 43", quartiles.Q1)
|
|
}
|
|
if quartiles.Q2 != 43 {
|
|
t.Errorf("Q2 %v != 43", quartiles.Q2)
|
|
}
|
|
if quartiles.Q3 != 43 {
|
|
t.Errorf("Q3 %v != 43", quartiles.Q3)
|
|
}
|
|
|
|
// The quartiles of one value agree with Percentile on the same value.
|
|
for _, percent := range []float64{25, 50, 75} {
|
|
value, err := stats.Percentile([]float64{43}, percent)
|
|
if err != nil || value != 43 {
|
|
t.Errorf("Percentile %v on one value: got %v (err %v)", percent, value, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestQuartile_SingleValueDependents(t *testing.T) {
|
|
iqr, err := stats.InterQuartileRange([]float64{43})
|
|
if err != nil || iqr != 0 {
|
|
t.Errorf("IQR of one value: got %v (err %v), want 0", iqr, err)
|
|
}
|
|
|
|
mh, err := stats.Midhinge([]float64{43})
|
|
if err != nil || mh != 43 {
|
|
t.Errorf("Midhinge of one value: got %v (err %v), want 43", mh, err)
|
|
}
|
|
|
|
tr, err := stats.Trimean([]float64{43})
|
|
if err != nil || tr != 43 {
|
|
t.Errorf("Trimean of one value: got %v (err %v), want 43", tr, err)
|
|
}
|
|
}
|
|
|
|
func TestInterQuartileRange(t *testing.T) {
|
|
s1 := []float64{102, 104, 105, 107, 108, 109, 110, 112, 115, 116, 118}
|
|
iqr, _ := stats.InterQuartileRange(s1)
|
|
|
|
if iqr != 10 {
|
|
t.Errorf("IQR %v != 10", iqr)
|
|
}
|
|
|
|
_, err := stats.InterQuartileRange([]float64{})
|
|
if err == nil {
|
|
t.Errorf("Empty slice should have returned an error")
|
|
}
|
|
}
|
|
|
|
func TestMidhinge(t *testing.T) {
|
|
s1 := []float64{1, 3, 4, 4, 6, 6, 6, 6, 7, 7, 7, 8, 8, 9, 9, 10, 11, 12, 13}
|
|
mh, _ := stats.Midhinge(s1)
|
|
|
|
if mh != 7.5 {
|
|
t.Errorf("Midhinge %v != 7.5", mh)
|
|
}
|
|
|
|
_, err := stats.Midhinge([]float64{})
|
|
if err == nil {
|
|
t.Errorf("Empty slice should have returned an error")
|
|
}
|
|
}
|
|
|
|
func TestTrimean(t *testing.T) {
|
|
s1 := []float64{1, 3, 4, 4, 6, 6, 6, 6, 7, 7, 7, 8, 8, 9, 9, 10, 11, 12, 13}
|
|
tr, _ := stats.Trimean(s1)
|
|
|
|
if tr != 7.25 {
|
|
t.Errorf("Trimean %v != 7.25", tr)
|
|
}
|
|
|
|
_, err := stats.Trimean([]float64{})
|
|
if err == nil {
|
|
t.Errorf("Empty slice should have returned an error")
|
|
}
|
|
}
|