diff --git a/quartile.go b/quartile.go index 40bbf6e..7778a78 100644 --- a/quartile.go +++ b/quartile.go @@ -32,10 +32,22 @@ func Quartile(input Float64Data) (Quartiles, error) { c2 = c1 + 1 } - // Find the Medians with the cutoff points - Q1, _ := Median(copy[:c1]) - Q2, _ := Median(copy) - Q3, _ := Median(copy[c2:]) + // Find the Medians with the cutoff points. + // For il < 3 the lower and upper halves are empty, so Median + // returns ErrEmptyInput; propagate it instead of silently + // returning NaN quartiles. + Q1, err := Median(copy[:c1]) + if err != nil { + return Quartiles{}, err + } + Q2, err := Median(copy) + if err != nil { + return Quartiles{}, err + } + Q3, err := Median(copy[c2:]) + if err != nil { + return Quartiles{}, err + } return Quartiles{Q1, Q2, Q3}, nil @@ -46,7 +58,10 @@ func InterQuartileRange(input Float64Data) (float64, error) { if input.Len() == 0 { return math.NaN(), EmptyInputErr } - qs, _ := Quartile(input) + qs, err := Quartile(input) + if err != nil { + return math.NaN(), err + } iqr := qs.Q3 - qs.Q1 return iqr, nil } @@ -56,7 +71,10 @@ func Midhinge(input Float64Data) (float64, error) { if input.Len() == 0 { return math.NaN(), EmptyInputErr } - qs, _ := Quartile(input) + qs, err := Quartile(input) + if err != nil { + return math.NaN(), err + } mh := (qs.Q1 + qs.Q3) / 2 return mh, nil } @@ -68,7 +86,10 @@ func Trimean(input Float64Data) (float64, error) { } c := sortedCopy(input) - q, _ := Quartile(c) + q, err := Quartile(c) + if err != nil { + return math.NaN(), err + } return (q.Q1 + (q.Q2 * 2) + q.Q3) / 4, nil } diff --git a/quartile_test.go b/quartile_test.go index 93d524e..54f4913 100644 --- a/quartile_test.go +++ b/quartile_test.go @@ -41,6 +41,19 @@ func TestQuartile(t *testing.T) { } } +func TestQuartileSingleElement(t *testing.T) { + // A single element has no lower or upper half, so it cannot have + // quartiles. Quartile must return an error instead of silently + // producing NaN values with a nil error. + quartiles, err := stats.Quartile([]float64{1}) + if err == nil { + t.Errorf("Single element slice should have returned an error") + } + if quartiles != (stats.Quartiles{}) { + t.Errorf("Errored result should be the zero Quartiles value, got %+v", quartiles) + } +} + func TestInterQuartileRange(t *testing.T) { s1 := []float64{102, 104, 105, 107, 108, 109, 110, 112, 115, 116, 118} iqr, _ := stats.InterQuartileRange(s1) @@ -53,6 +66,11 @@ func TestInterQuartileRange(t *testing.T) { if err == nil { t.Errorf("Empty slice should have returned an error") } + + _, err = stats.InterQuartileRange([]float64{1}) + if err == nil { + t.Errorf("Single element slice should have returned an error") + } } func TestMidhinge(t *testing.T) { @@ -67,6 +85,11 @@ func TestMidhinge(t *testing.T) { if err == nil { t.Errorf("Empty slice should have returned an error") } + + _, err = stats.Midhinge([]float64{1}) + if err == nil { + t.Errorf("Single element slice should have returned an error") + } } func TestTrimean(t *testing.T) { @@ -81,4 +104,9 @@ func TestTrimean(t *testing.T) { if err == nil { t.Errorf("Empty slice should have returned an error") } + + _, err = stats.Trimean([]float64{1}) + if err == nil { + t.Errorf("Single element slice should have returned an error") + } }