forked from wcharczuk/go-chart
-
Notifications
You must be signed in to change notification settings - Fork 2
/
linear_regression_series.go
147 lines (122 loc) · 3.73 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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
package chart
import (
"fmt"
"github.com/wcharczuk/go-chart/seq"
util "github.com/wcharczuk/go-chart/util"
)
// LinearRegressionSeries is a series that plots the n-nearest neighbors
// linear regression for the values.
type LinearRegressionSeries struct {
Name string
Style Style
YAxis YAxisType
Limit int
Offset int
InnerSeries ValuesProvider
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 util.Math.MinInt(lrs.GetLimit(), lrs.InnerSeries.Len()-lrs.GetOffset())
}
// GetLimit returns the window size.
func (lrs LinearRegressionSeries) GetLimit() int {
if lrs.Limit == 0 {
return lrs.InnerSeries.Len()
}
return lrs.Limit
}
// GetEndIndex returns the effective limit end.
func (lrs LinearRegressionSeries) GetEndIndex() int {
windowEnd := lrs.GetOffset() + lrs.GetLimit()
innerSeriesLastIndex := lrs.InnerSeries.Len() - 1
return util.Math.MinInt(windowEnd, innerSeriesLastIndex)
}
// GetOffset returns the data offset.
func (lrs LinearRegressionSeries) GetOffset() int {
if lrs.Offset == 0 {
return 0
}
return lrs.Offset
}
// GetValues gets a value at a given index.
func (lrs *LinearRegressionSeries) GetValues(index int) (x, y float64) {
if lrs.InnerSeries == nil || lrs.InnerSeries.Len() == 0 {
return
}
if lrs.m == 0 && lrs.b == 0 {
lrs.computeCoefficients()
}
offset := lrs.GetOffset()
effectiveIndex := util.Math.MinInt(index+offset, lrs.InnerSeries.Len())
x, y = lrs.InnerSeries.GetValues(effectiveIndex)
y = (lrs.m * lrs.normalize(x)) + lrs.b
return
}
// GetLastValues computes the last linear regression value.
func (lrs *LinearRegressionSeries) GetLastValues() (x, y float64) {
if lrs.InnerSeries == nil || lrs.InnerSeries.Len() == 0 {
return
}
if lrs.m == 0 && lrs.b == 0 {
lrs.computeCoefficients()
}
endIndex := lrs.GetEndIndex()
x, y = lrs.InnerSeries.GetValues(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 := seq.NewBufferWithCapacity(lrs.Len())
for index := startIndex; index < endIndex; index++ {
x, _ := lrs.InnerSeries.GetValues(index)
xvalues.Enqueue(x)
}
lrs.avgx = seq.Seq{Provider: xvalues}.Average()
lrs.stddevx = seq.Seq{Provider: xvalues}.StdDev()
var sumx, sumy, sumxx, sumxy float64
for index := startIndex; index < endIndex; index++ {
x, y := lrs.InnerSeries.GetValues(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)
}
// Validate validates the series.
func (lrs *LinearRegressionSeries) Validate() error {
if lrs.InnerSeries == nil {
return fmt.Errorf("linear regression series requires InnerSeries to be set")
}
return nil
}