Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add multi percentils #49

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,11 @@ func MinkowskiDistance(dataPointX, dataPointY Float64Data, lambda float64) (dist
func Mode(input Float64Data) (mode []float64, err error) {}
func Pearson(data1, data2 Float64Data) (float64, error) {}
func Percentile(input Float64Data, percent float64) (percentile float64, err error) {}
func MultiPercentile(p ...float64) ([]float64, error) {}
func PercentileNearestRank(input Float64Data, percent float64) (percentile float64, err error) {}
func PopulationVariance(input Float64Data) (pvar float64, err error) {}
func Sample(input Float64Data, takenum int, replacement bool) ([]float64, error) {}
func StableSample(takenum int) ([]float64, error) {}
func SampleVariance(input Float64Data) (svar float64, err error) {}
func Sigmoid(input Float64Data) ([]float64, error) {}
func SoftMax(input Float64Data) ([]float64, error) {}
Expand Down
10 changes: 10 additions & 0 deletions data.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ func (f Float64Data) Percentile(p float64) (float64, error) {
return Percentile(f, p)
}

// MultiPercentile returns multi percentiles by only one sorting
func (f Float64Data) MultiPercentile(p ...float64) ([]float64, error) {
return MultiPercentile(f, p...)
}

// PercentileNearestRank finds the relative standing using the Nearest Rank method
func (f Float64Data) PercentileNearestRank(p float64) (float64, error) {
return PercentileNearestRank(f, p)
Expand Down Expand Up @@ -122,6 +127,11 @@ func (f Float64Data) Sample(n int, r bool) ([]float64, error) {
return Sample(f, n, r)
}

// StableSample like stable sort, it returns takenum samples from input while keeps the order of original data.
func (f Float64Data) StableSample(takenum int) ([]float64, error) {
return StableSample(f, takenum)
}

// Variance the amount of variation in the dataset
func (f Float64Data) Variance() (float64, error) {
return Variance(f)
Expand Down
27 changes: 27 additions & 0 deletions data_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,20 @@ func TestPercentileMethods(t *testing.T) {

}

func TestMultiPercentileMethod(t *testing.T) {
data := Float64Data{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
ret, err := data.MultiPercentile(10, 20)
if err != nil {
t.Errorf("%s returned an error", getFunctionName(data.MultiPercentile))
}
if len(ret) != 2 {
t.Errorf("%s doesn't return 2 percentiles", getFunctionName(data.MultiPercentile))
}
if ret[0] != 1 || ret[1] != 2 {
t.Errorf("%s return wrong percentile", getFunctionName(data.MultiPercentile))
}
}

func assertOtherDataMethods(fn func(d Float64Data) (float64, error), d Float64Data, f float64, t *testing.T) {
res, err := fn(d)
checkResult(res, err, getFunctionName(fn), f, t)
Expand Down Expand Up @@ -176,6 +190,19 @@ func TestSampleMethod(t *testing.T) {
}
}

func TestStableSampleMethod(t *testing.T) {
data := Float64Data{1, 2, 3, 4, 5}
ret, err := data.StableSample(3)
if err != nil {
t.Errorf("%s shouldn't return error", getFunctionName(data.StableSample))
}
for i := 1; i < 3; i++ {
if ret[i] < ret[i-1] {
t.Errorf("%s doesn't keep order", getFunctionName(data.StableSample))
}
}
}

func TestQuartileMethods(t *testing.T) {
// Test QuartileOutliers method
_, err := data1.QuartileOutliers()
Expand Down
41 changes: 41 additions & 0 deletions percentile.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,47 @@ func Percentile(input Float64Data, percent float64) (percentile float64, err err

}

// MultiPercentile can return multiple percents by only one sorting
func MultiPercentile(input Float64Data, percents ...float64) ([]float64, error) {
length := input.Len()
if length == 0 {
return nil, EmptyInputErr
}
for _, percent := range percents {
if percent <= 0 || percent > 100 {
return nil, BoundsErr
}
}
ret := make([]float64, len(percents))
c := sortedCopy(input)
for l, percent := range percents {
// Multiply percent by length of input
index := (percent / 100) * float64(len(c))

// Check if the index is a whole number
if index == float64(int64(index)) {

// Convert float to int
i := int(index)

// Find the value at the index
ret[l] = c[i-1]

} else if index > 1 {

// Convert float to int via truncation
i := int(index)

// Find the average of the index and following values
ret[l], _ = Mean(Float64Data{c[i-1], c[i]})

} else {
return nil, BoundsErr
}
}
return ret, nil
}

// PercentileNearestRank finds the relative standing in a slice of floats using the Nearest Rank method
func PercentileNearestRank(input Float64Data, percent float64) (percentile float64, err error) {

Expand Down
35 changes: 35 additions & 0 deletions percentile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,41 @@ func TestPercentileSortSideEffects(t *testing.T) {
}
}

func TestMultiPercentile(t *testing.T) {
ret, err := MultiPercentile([]float64{43, 54, 56, 61, 62, 66}, 20, 90)
if err != nil {
t.Errorf("error should be nil")
}
if len(ret) != 2 {
t.Errorf("ret len should be 2")
}
if ret[0] != 48.5 && ret[1] != 64.0 {
t.Errorf("ret wrong percentile")
}
_, err = MultiPercentile([]float64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 20, 30, 110)
if err != BoundsErr {
t.Errorf("err should be BoundsErr")
}
_, err = MultiPercentile([]float64{}, 50)
if err != EmptyInputErr {
t.Errorf("err should be EmptyInputErr")
}
_, err = MultiPercentile([]float64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 20, 30, 5)
if err != BoundsErr {
t.Errorf("err should be BoundsErr")
}
ret, err = MultiPercentile([]float64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 30, 50, 90)
if err != nil {
t.Errorf("err should be nil")
}
if len(ret) != 3 {
t.Errorf("should return 3 percentiles")
}
if ret[0] != 3 || ret[1] != 5 || ret[2] != 9 {
t.Errorf("return wrong percentile")
}
}

func BenchmarkPercentileSmallFloatSlice(b *testing.B) {
for i := 0; i < b.N; i++ {
Percentile(makeFloatSlice(5), 50)
Expand Down