github.com/MontFerret/ferret@v0.18.0/pkg/stdlib/math/percentile.go (about)

     1  package math
     2  
     3  import (
     4  	"context"
     5  	"math"
     6  
     7  	"github.com/pkg/errors"
     8  
     9  	"github.com/MontFerret/ferret/pkg/runtime/core"
    10  	"github.com/MontFerret/ferret/pkg/runtime/values"
    11  	"github.com/MontFerret/ferret/pkg/runtime/values/types"
    12  )
    13  
    14  // PERCENTILE returns the nth percentile of the values in a given array.
    15  // @param {Int[] | Float[]} array - Array of numbers.
    16  // @param {Int} number - A number which must be between 0 (excluded) and 100 (included).
    17  // @param {String} [method="rank"] - "rank" or "interpolation".
    18  // @return {Float} - The nth percentile, or null if the array is empty or only null values are contained in it or the percentile cannot be calculated.
    19  func Percentile(_ context.Context, args ...core.Value) (core.Value, error) {
    20  	err := core.ValidateArgs(args, 2, 3)
    21  
    22  	if err != nil {
    23  		return values.None, err
    24  	}
    25  
    26  	err = core.ValidateType(args[0], types.Array)
    27  
    28  	if err != nil {
    29  		return values.None, err
    30  	}
    31  
    32  	err = core.ValidateType(args[1], types.Int)
    33  
    34  	if err != nil {
    35  		return values.None, err
    36  	}
    37  
    38  	// TODO: Implement different methods
    39  	//method := "rank"
    40  	//
    41  	//if len(args) > 2 {
    42  	//	err = core.ValidateType(args[2], core.StringType)
    43  	//
    44  	//	if err != nil {
    45  	//		return values.None, err
    46  	//	}
    47  	//
    48  	//	if args[2].String() == "interpolation" {
    49  	//		method = "interpolation"
    50  	//	}
    51  	//}
    52  
    53  	arr := args[0].(*values.Array)
    54  	percent := values.Float(args[1].(values.Int))
    55  
    56  	if arr.Length() == 0 {
    57  		return values.NewFloat(math.NaN()), nil
    58  	}
    59  
    60  	if percent <= 0 || percent > 100 {
    61  		return values.NewFloat(math.NaN()), errors.New("input is outside of range")
    62  	}
    63  
    64  	sorted := arr.Sort()
    65  
    66  	// Multiply percent by length of input
    67  	l := values.Float(sorted.Length())
    68  	index := (percent / 100) * l
    69  	even := values.Float(values.Int(index))
    70  
    71  	var percentile core.Value
    72  
    73  	// Check if the index is a whole number
    74  	switch {
    75  	case index == even:
    76  		i := values.Int(index)
    77  		percentile = sorted.Get(i - 1)
    78  	case index > 1:
    79  		// Convert float to int via truncation
    80  		i := values.Int(index)
    81  		// Find the average of the index and following values
    82  		percentile, _ = mean(values.NewArrayWith(sorted.Get(i-1), sorted.Get(i)))
    83  	default:
    84  		return values.NewFloat(math.NaN()), errors.New("input is outside of range")
    85  	}
    86  
    87  	return percentile, nil
    88  }