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 }