github.com/noriah/catnip@v1.8.5/util/window.go (about)

     1  package util
     2  
     3  import "math"
     4  
     5  // MovingWindow is a moving window
     6  type MovingWindow struct {
     7  	index    int
     8  	length   int
     9  	capacity int
    10  	variance float64
    11  	stddev   float64
    12  	average  float64
    13  
    14  	data []float64
    15  }
    16  
    17  func NewMovingWindow(size int) *MovingWindow {
    18  	return &MovingWindow{
    19  		data:     make([]float64, size),
    20  		length:   0,
    21  		capacity: size,
    22  	}
    23  }
    24  
    25  func (mw *MovingWindow) calcFinal() (mean float64, stddev float64) {
    26  	if mw.length <= 0 {
    27  		mw.average = 0
    28  
    29  	} else if mw.length > 1 {
    30  		// mw.stddev = math.Sqrt(mw.variance / (mw.length - 1))
    31  		// this came from dpayne/cli-visualizer
    32  		stddev = math.Abs((mw.variance / float64(mw.length-1)))
    33  		stddev = math.Sqrt(stddev)
    34  	}
    35  
    36  	mw.stddev = stddev
    37  
    38  	return mw.Stats()
    39  }
    40  
    41  // Update adds the new value to the moving window, returns average and stddev.
    42  // If the window is full, the oldest value will be removed and the new value
    43  // is added. Returns calculated Average and Standard Deviation.
    44  func (mw *MovingWindow) Update(value float64) (mean float64, stddev float64) {
    45  	if mw.length < mw.capacity {
    46  		mw.length++
    47  		mw.average += (value - mw.average) / float64(mw.length)
    48  		mw.variance += math.Pow(value-mw.average, 2.0)
    49  
    50  	} else {
    51  		old := mw.data[mw.index]
    52  		newAverage := mw.average + (value-old)/float64(mw.length)
    53  		mw.variance += math.Pow(value-newAverage, 2.0) - math.Pow(old-mw.average, 2.0)
    54  		mw.average = newAverage
    55  	}
    56  
    57  	mw.data[mw.index] = value
    58  
    59  	if mw.index++; mw.index >= mw.capacity {
    60  		mw.index = 0
    61  	}
    62  
    63  	return mw.calcFinal()
    64  }
    65  
    66  // Drop removes count values from the window.
    67  func (mw *MovingWindow) Drop(count int) (mean float64, stddev float64) {
    68  	if mw.length <= 0 {
    69  		return mw.calcFinal()
    70  	}
    71  
    72  	for count > 0 && mw.length > 0 {
    73  		idx := (mw.index - mw.length)
    74  
    75  		if idx < 0 {
    76  			idx = mw.capacity + idx
    77  		}
    78  
    79  		old := mw.data[idx]
    80  
    81  		mw.variance -= math.Pow(old-mw.average, 2.0)
    82  		mw.average -= old / float64(mw.length)
    83  
    84  		mw.length--
    85  		count--
    86  	}
    87  
    88  	// If we dont have enough length for standard dev, clear variance
    89  	if mw.length < 2 {
    90  		mw.variance = 0.0
    91  
    92  		if mw.length < 1 {
    93  			mw.length = 0
    94  			// same idea with sum. just clear it so we dont have a rouding issue
    95  			mw.average = 0.0
    96  		}
    97  	}
    98  
    99  	return mw.calcFinal()
   100  }
   101  
   102  func (mw *MovingWindow) Recalculate() (mean float64, stddev float64) {
   103  	if mw.length <= 0 {
   104  		return mw.Stats()
   105  	}
   106  
   107  	sum := 0.0
   108  
   109  	for count := mw.length; count > 0; count-- {
   110  		idx := (mw.index - count)
   111  
   112  		if idx < 0 {
   113  			idx = mw.capacity + idx
   114  
   115  		}
   116  
   117  		sum += mw.data[idx]
   118  	}
   119  
   120  	mw.average = sum / float64(mw.length)
   121  
   122  	dev := 0.0
   123  
   124  	for count := mw.length; count > 0; count-- {
   125  		idx := (mw.index - count)
   126  
   127  		if idx < 0 {
   128  			idx = mw.capacity + idx
   129  		}
   130  
   131  		dev += math.Pow(mw.data[idx]-mw.average, 2.0)
   132  	}
   133  
   134  	mw.stddev = math.Sqrt(dev / float64(mw.length))
   135  
   136  	return mw.Stats()
   137  }
   138  
   139  // Len returns how many items in the window
   140  func (mw *MovingWindow) Len() int {
   141  	// logical length
   142  	return mw.length
   143  }
   144  
   145  // Cap returns max size of window
   146  func (mw *MovingWindow) Cap() int {
   147  	return mw.capacity
   148  }
   149  
   150  // Mean is the moving window average
   151  func (mw *MovingWindow) Mean() float64 {
   152  	return mw.average
   153  }
   154  
   155  // StdDev is the moving average std
   156  func (mw *MovingWindow) StdDev() float64 {
   157  	return mw.stddev
   158  }
   159  
   160  // Stats returns the statistics of this window
   161  func (mw *MovingWindow) Stats() (mean float64, stddev float64) {
   162  	return mw.average, mw.stddev
   163  }