github.com/go-graphite/carbonapi@v0.17.0/expr/functions/pearsonClosest/function.go (about)

     1  package pearsonClosest
     2  
     3  import (
     4  	"container/heap"
     5  	"context"
     6  	"errors"
     7  	"math"
     8  
     9  	"github.com/dgryski/go-onlinestats"
    10  	"github.com/go-graphite/carbonapi/expr/helper"
    11  	"github.com/go-graphite/carbonapi/expr/interfaces"
    12  	"github.com/go-graphite/carbonapi/expr/types"
    13  	"github.com/go-graphite/carbonapi/pkg/parser"
    14  )
    15  
    16  type pearsonClosest struct{}
    17  
    18  func GetOrder() interfaces.Order {
    19  	return interfaces.Any
    20  }
    21  
    22  func New(configFile string) []interfaces.FunctionMetadata {
    23  	res := make([]interfaces.FunctionMetadata, 0)
    24  	f := &pearsonClosest{}
    25  	functions := []string{"pearsonClosest"}
    26  	for _, n := range functions {
    27  		res = append(res, interfaces.FunctionMetadata{Name: n, F: f})
    28  	}
    29  	return res
    30  }
    31  
    32  // pearsonClosest(series, seriesList, n, direction=abs)
    33  func (f *pearsonClosest) Do(ctx context.Context, eval interfaces.Evaluator, e parser.Expr, from, until int64, values map[parser.MetricRequest][]*types.MetricData) ([]*types.MetricData, error) {
    34  	if e.ArgsLen() > 3 {
    35  		return nil, types.ErrTooManyArguments
    36  	}
    37  
    38  	ref, err := helper.GetSeriesArg(ctx, eval, e.Arg(0), from, until, values)
    39  	if err != nil {
    40  		return nil, err
    41  	}
    42  	if len(ref) != 1 {
    43  		// TODO(nnuss) error("First argument must be single reference series")
    44  		return nil, types.ErrWildcardNotAllowed
    45  	}
    46  
    47  	compare, err := helper.GetSeriesArg(ctx, eval, e.Arg(1), from, until, values)
    48  	if err != nil {
    49  		return nil, err
    50  	}
    51  
    52  	n, err := e.GetIntArg(2)
    53  	if err != nil {
    54  		return nil, err
    55  	}
    56  
    57  	direction, err := e.GetStringNamedOrPosArgDefault("direction", 3, "abs")
    58  	if err != nil {
    59  		return nil, err
    60  	}
    61  	if direction != "pos" && direction != "neg" && direction != "abs" {
    62  		return nil, errors.New("direction must be one of: pos, neg, abs")
    63  	}
    64  
    65  	// NOTE: if direction == "abs" && len(compare) <= n : we'll still do the work to rank them
    66  
    67  	refValues := make([]float64, len(ref[0].Values))
    68  	copy(refValues, ref[0].Values)
    69  
    70  	mh := make(types.MetricHeap, 0, len(compare))
    71  
    72  	for index, a := range compare {
    73  		compareValues := make([]float64, len(a.Values))
    74  		copy(compareValues, a.Values)
    75  		if len(refValues) != len(compareValues) {
    76  			// Pearson will panic if arrays are not equal length; skip
    77  			continue
    78  		}
    79  
    80  		value := onlinestats.Pearson(refValues, compareValues)
    81  		// Standardize the value so sort ASC will have strongest correlation first
    82  		switch {
    83  		case math.IsNaN(value):
    84  			// special case of at least one series containing all zeros which leads to div-by-zero in Pearson
    85  			continue
    86  		case direction == "abs":
    87  			value = math.Abs(value) * -1
    88  		case direction == "pos" && value >= 0:
    89  			value = value * -1
    90  		case direction == "neg" && value <= 0:
    91  		default:
    92  			continue
    93  		}
    94  		heap.Push(&mh, types.MetricHeapElement{Idx: index, Val: value})
    95  	}
    96  
    97  	if n > len(mh) {
    98  		n = len(mh)
    99  	}
   100  	results := make([]*types.MetricData, n)
   101  	for i := range results {
   102  		v := heap.Pop(&mh).(types.MetricHeapElement)
   103  		results[i] = compare[v.Idx]
   104  	}
   105  
   106  	return results, nil
   107  }
   108  
   109  func (f *pearsonClosest) Description() map[string]types.FunctionDescription {
   110  	return map[string]types.FunctionDescription{
   111  		"pearsonClosest": {
   112  			Description: `
   113  Implementation of Pearson product-moment correlation coefficient (PMCC) function(s)
   114  
   115  .. code-block:: none
   116  
   117  	pearsonClosest( series, seriesList, n, direction="abs" )
   118  
   119  
   120  Return the n series in seriesList with closest Pearson score to the first series argument.
   121  An optional direction parameter may also be given:
   122  	"abs"   - (default) Series with any Pearson score + or - [-1 .. 1].
   123      "pos"   - Only series with positive correlation score [0 .. 1]
   124      "neg"   - Only series with negative correlation score [1 .. 0]
   125  
   126  
   127  The default is "abs" which is most correlated (in either direction)
   128  
   129  Examples:
   130  
   131  .. code-block:: none
   132  
   133  	#  metrics from 'metric.forest.*'' that "look like" 'metric.abnormal'' (have closest correllation coeeficient)
   134  	pearsonClosest( metric.abnormal , metric.forest.* , 2, direction="pos" )
   135  
   136  
   137  .. code-block:: none
   138  
   139  	# 2 metrics from "metric.forest.*"" that are most negatively correlated to "metric.increasing" (ie. "metric.forest.decreasing" )
   140  	pearsonClosest( metric.increasing , metric.forest.* , 2, direction="neg" )
   141  
   142  
   143  .. code-block:: none
   144  
   145      # you'd get "metric.increasing", "metric.decreasing"
   146      pearsonClosest( metric.increasing, group (metric.increasing, metric.decreasing, metric.flat, metric.sine), 2 )
   147  
   148  Note:
   149  Pearson will discard epochs where either series has a missing value.
   150  
   151  Additionally there is a special case where a series (or window) containing only zeros leads to a division-by-zero
   152  and will manifest as if the entire window/series had missing values.`,
   153  			Function: "pearsonClosest(seriesList, seriesList, n, direction)",
   154  			Group:    "Transform",
   155  			Module:   "graphite.render.functions.custom",
   156  			Name:     "pearsonClosest",
   157  			Params: []types.FunctionParam{
   158  				{
   159  					Name:     "seriesList",
   160  					Required: true,
   161  					Type:     types.SeriesList,
   162  				},
   163  				{
   164  					Name:     "seriesList",
   165  					Required: true,
   166  					Type:     types.SeriesList,
   167  				},
   168  				{
   169  					Name:     "n",
   170  					Required: true,
   171  					Type:     types.Integer,
   172  				},
   173  				{
   174  					Name:     "direction",
   175  					Required: true,
   176  					Options: types.StringsToSuggestionList([]string{
   177  						"abs",
   178  						"pos",
   179  						"neg",
   180  					}),
   181  					Type: types.String,
   182  				},
   183  			},
   184  			SeriesChange: true, // function aggregate metrics or change series items count
   185  			NameChange:   true, // name changed
   186  			TagsChange:   true, // name tag changed
   187  			ValuesChange: true, // values changed
   188  		},
   189  	}
   190  }