forked from wcharczuk/go-chart
-
Notifications
You must be signed in to change notification settings - Fork 0
/
linear_regression_series.go
132 lines (109 loc) · 3.32 KB
/
linear_regression_series.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
package chart
// LinearRegressionSeries is a series that plots the n-nearest neighbors
// linear regression for the values.
type LinearRegressionSeries struct {
Name string
Style Style
YAxis yAxisType
Window int
Offset int
InnerSeries ValueProvider
m float64
b float64
avgx float64
stddevx float64
}
// GetName returns the name of the time series.
func (lrs LinearRegressionSeries) GetName() string {
return lrs.Name
}
// GetStyle returns the line style.
func (lrs LinearRegressionSeries) GetStyle() Style {
return lrs.Style
}
// GetYAxis returns which YAxis the series draws on.
func (lrs LinearRegressionSeries) GetYAxis() yAxisType {
return lrs.YAxis
}
// Len returns the number of elements in the series.
func (lrs LinearRegressionSeries) Len() int {
return Math.MinInt(lrs.GetWindow(), lrs.InnerSeries.Len()-lrs.GetOffset())
}
// GetWindow returns the window size.
func (lrs LinearRegressionSeries) GetWindow() int {
if lrs.Window == 0 {
return lrs.InnerSeries.Len()
}
return lrs.Window
}
// GetEndIndex returns the effective window end.
func (lrs LinearRegressionSeries) GetEndIndex() int {
return Math.MinInt(lrs.GetOffset()+(lrs.Len()), (lrs.InnerSeries.Len() - 1))
}
// GetOffset returns the data offset.
func (lrs LinearRegressionSeries) GetOffset() int {
if lrs.Offset == 0 {
return 0
}
return lrs.Offset
}
// GetValue gets a value at a given index.
func (lrs *LinearRegressionSeries) GetValue(index int) (x, y float64) {
if lrs.InnerSeries == nil {
return
}
if lrs.m == 0 && lrs.b == 0 {
lrs.computeCoefficients()
}
offset := lrs.GetOffset()
effectiveIndex := Math.MinInt(index+offset, lrs.InnerSeries.Len())
x, y = lrs.InnerSeries.GetValue(effectiveIndex)
y = (lrs.m * lrs.normalize(x)) + lrs.b
return
}
// GetLastValue computes the last moving average value but walking back window size samples,
// and recomputing the last moving average chunk.
func (lrs *LinearRegressionSeries) GetLastValue() (x, y float64) {
if lrs.InnerSeries == nil {
return
}
if lrs.m == 0 && lrs.b == 0 {
lrs.computeCoefficients()
}
endIndex := lrs.GetEndIndex()
x, y = lrs.InnerSeries.GetValue(endIndex)
y = (lrs.m * lrs.normalize(x)) + lrs.b
return
}
func (lrs *LinearRegressionSeries) normalize(xvalue float64) float64 {
return (xvalue - lrs.avgx) / lrs.stddevx
}
// computeCoefficients computes the `m` and `b` terms in the linear formula given by `y = mx+b`.
func (lrs *LinearRegressionSeries) computeCoefficients() {
startIndex := lrs.GetOffset()
endIndex := lrs.GetEndIndex()
p := float64(endIndex - startIndex)
xvalues := NewRingBufferWithCapacity(lrs.Len())
for index := startIndex; index < endIndex; index++ {
x, _ := lrs.InnerSeries.GetValue(index)
xvalues.Enqueue(x)
}
lrs.avgx = xvalues.Average()
lrs.stddevx = xvalues.StdDev()
var sumx, sumy, sumxx, sumxy float64
for index := startIndex; index < endIndex; index++ {
x, y := lrs.InnerSeries.GetValue(index)
x = lrs.normalize(x)
sumx += x
sumy += y
sumxx += x * x
sumxy += x * y
}
lrs.m = (p*sumxy - sumx*sumy) / (p*sumxx - sumx*sumx)
lrs.b = (sumy / p) - (lrs.m * sumx / p)
}
// Render renders the series.
func (lrs *LinearRegressionSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) {
style := lrs.Style.InheritFrom(defaults)
Draw.LineSeries(r, canvasBox, xrange, yrange, style, lrs)
}