diff --git a/README.md b/README.md index a3cd8b6..c79302d 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ To capture seasonality, the bootstrap ks test should consider an active window l ### Fence -The fence test can be configured to use custom `UpperBound` and `LowerBound` values for the fences. If no lower bound is desired, set the value of `LowerBound` to `anomalyzer.NA`. +The fence test can be configured to use custom `UpperBound` and `LowerBound` values for the fences. If no lower bound is desired, set the value of `LowerBound` to the const variable: `anomalyzer.NA`. ### Diff & Rank @@ -77,6 +77,13 @@ func main() { // by a call to the Eval method. prob := anom.Push(8.0) fmt.Println("Anomalous Probability:", prob) + + // PushFixed method will keep the size of the Data vector constant. + // Oldest data points will be evicted as points are added. + anom2, _ := anomalyzer.NewAnomalyzer(conf, data) + prob2, _ := anom2.PushFixed(8.0) + // returns an error as second value if the array size changed unexpectantly + fmt.Println("Anomalous Probability:", prob2) } ``` diff --git a/anomalyze.go b/anomalyze.go index bb3d225..3e98c62 100644 --- a/anomalyze.go +++ b/anomalyze.go @@ -139,6 +139,17 @@ func (a *Anomalyzer) Push(x float64) float64 { return a.Eval() } +func (a *Anomalyzer) PushFixed(x float64) (float64, error) { + // Add data to fixed size array which will not grow + err := a.Data.PushFixed(x) + if err != nil { + return NA, err + } + + // evaluate the anomalous probability + return a.Eval(), nil +} + // Return the weighted average of all statistical tests // for anomaly detection, which yields the probability that // the currently observed behavior is anomalous. diff --git a/anomalyze_test.go b/anomalyze_test.go index 6d53215..e87ab4a 100644 --- a/anomalyze_test.go +++ b/anomalyze_test.go @@ -45,6 +45,58 @@ func TestAnomalyzer(t *testing.T) { assert.Tf(t, prob > 0.5, "Anomalyzer returned a probability that was too small") } +func TestAnomalyzerPushFixed(t *testing.T) { + conf := &AnomalyzerConf{ + Sensitivity: 0.1, + UpperBound: 5, + LowerBound: 0, + ActiveSize: 1, + NSeasons: 4, + Methods: []string{"cdf", "fence", "highrank", "lowrank", "magnitude"}, + } + + // initialize with empty data or an actual slice of floats + data := []float64{0.1, 2.05, 1.5, 2.5, 2.6, 2.55} + + anomalyzer, err := NewAnomalyzer(conf, data) + assert.Equal(t, nil, err, "Error initializing new anomalyzer") + + prob, err := anomalyzer.PushFixed(8.0) + prob, err = anomalyzer.PushFixed(10.0) + prob, err = anomalyzer.PushFixed(8.0) + prob, err = anomalyzer.PushFixed(9.0) + assert.Equal(t, err, nil, "There was an error with array size") + assert.Tf(t, prob > 0.5, "Anomalyzer returned a probability that was too small") + assert.Equal(t, len(anomalyzer.Data), 6, "Array size did not stay at original size") +} + +func TestAnomalyzerPushMixed(t *testing.T) { + conf := &AnomalyzerConf{ + Sensitivity: 0.1, + UpperBound: 5, + LowerBound: 0, + ActiveSize: 1, + NSeasons: 4, + Methods: []string{"cdf", "fence", "highrank", "lowrank", "magnitude"}, + } + + // initialize with empty data or an actual slice of floats + data := []float64{0.1, 2.05, 1.5, 2.5, 2.6, 2.55} + + anomalyzer, err := NewAnomalyzer(conf, data) + assert.Equal(t, nil, err, "Error initializing new anomalyzer") + + prob, err := anomalyzer.PushFixed(8.0) + prob = anomalyzer.Push(10.0) + prob, err = anomalyzer.PushFixed(8.0) + prob = anomalyzer.Push(9.0) + assert.Equal(t, err, nil, "There was an error with mixing array extension") + assert.Tf(t, prob > 0.5, "Anomalyzer returned a probability that was too small") + assert.Equal(t, len(anomalyzer.Data), 8, "Array size Push* functions failed to grow Data to expected size") + assert.Equal(t, anomalyzer.Data[7], 9.0) + assert.Equal(t, anomalyzer.Data[0], 1.5, "Two values were appended, two values were popped from the array. 3rd original element should be tail.") +} + func Example() { conf := &AnomalyzerConf{ Sensitivity: 0.1,