From 59ccf693bf7d7d26c5bdbd71f3227f1c33860be8 Mon Sep 17 00:00:00 2001 From: Will Charczuk Date: Tue, 18 Apr 2017 20:16:51 -0700 Subject: [PATCH] poly regression works. --- _examples/poly_regression/main.go | 41 ++++++++++++++++++ matrix/matrix_test.go | 37 +++++++++++++++++ polynomial_regression_series.go | 69 +++++++++++++++++++++++++------ 3 files changed, 135 insertions(+), 12 deletions(-) create mode 100644 _examples/poly_regression/main.go diff --git a/_examples/poly_regression/main.go b/_examples/poly_regression/main.go new file mode 100644 index 0000000..4ed25bf --- /dev/null +++ b/_examples/poly_regression/main.go @@ -0,0 +1,41 @@ +package main + +import ( + "net/http" + + "github.com/wcharczuk/go-chart" +) + +func drawChart(res http.ResponseWriter, req *http.Request) { + + /* + In this example we add a new type of series, a `PolynomialRegressionSeries` that takes another series as a required argument. + InnerSeries only needs to implement `ValueProvider`, so really you could chain `PolynomialRegressionSeries` together if you wanted. + */ + + mainSeries := chart.ContinuousSeries{ + Name: "A test series", + XValues: chart.Sequence.Float64(1.0, 100.0), //generates a []float64 from 1.0 to 100.0 in 1.0 step increments, or 100 elements. + YValues: chart.Sequence.Random(100, 100), //generates a []float64 randomly from 0 to 100 with 100 elements. + } + + polyRegSeries := &chart.PolynomialRegressionSeries{ + Degree: 3, + InnerSeries: mainSeries, + } + + graph := chart.Chart{ + Series: []chart.Series{ + mainSeries, + polyRegSeries, + }, + } + + res.Header().Set("Content-Type", "image/png") + graph.Render(chart.PNG, res) +} + +func main() { + http.HandleFunc("/", drawChart) + http.ListenAndServe(":8080", nil) +} diff --git a/matrix/matrix_test.go b/matrix/matrix_test.go index 52475f2..4a88bc1 100644 --- a/matrix/matrix_test.go +++ b/matrix/matrix_test.go @@ -204,6 +204,22 @@ func TestMatrixRow(t *testing.T) { assert.Equal([]float64{7, 8, 9}, m.Row(2)) } +func TestMatrixSwapRows(t *testing.T) { + assert := assert.New(t) + + m := NewFromArrays([][]float64{ + {1, 2, 3}, + {4, 5, 6}, + {7, 8, 9}, + }) + + m.SwapRows(0,1) + + assert.Equal([]float64{4,5,6}, m.Row(0)) + assert.Equal([]float64{1,2,3}, m.Row(1)) + assert.Equal([]float64{7,8,9}, m.Row(2)) +} + func TestMatrixCopy(t *testing.T) { assert := assert.New(t) @@ -357,3 +373,24 @@ func TestMatrixQR(t *testing.T) { assert.NotNil(q) assert.NotNil(r) } + +func TestMatrixTranspose(t *testing.T) { + assert := assert.New(t) + + m := NewFromArrays([][]float64{ + {1, 2, 3}, + {4, 5, 6}, + {7, 8, 9}, + {10, 11, 12}, + }) + + m2 := m.Transpose() + + rows, cols := m2.Size() + assert.Equal(3, rows) + assert.Equal(4, cols) + + assert.Equal(1, m2.Get(0,0)) + assert.Equal(10, m2.Get(0,3)) + assert.Equal(3, m2.Get(2,0)) +) diff --git a/polynomial_regression_series.go b/polynomial_regression_series.go index d01c4cc..70498c4 100644 --- a/polynomial_regression_series.go +++ b/polynomial_regression_series.go @@ -1,6 +1,11 @@ package chart -import "fmt" +import ( + "fmt" + "math" + + "github.com/wcharczuk/go-chart/matrix" +) // PolynomialRegressionSeries implements a polynomial regression over a given // inner series. @@ -11,7 +16,7 @@ type PolynomialRegressionSeries struct { Limit int Offset int - Order int + Degree int InnerSeries ValueProvider coeffs []float64 @@ -65,6 +70,12 @@ func (prs *PolynomialRegressionSeries) Validate() error { if prs.InnerSeries == nil { return fmt.Errorf("linear regression series requires InnerSeries to be set") } + + endIndex := prs.GetEndIndex() + if endIndex >= prs.InnerSeries.Len() { + return fmt.Errorf("invalid window; inner series has length %d but end index is %d", prs.InnerSeries.Len(), endIndex) + } + return nil } @@ -73,18 +84,52 @@ func (prs *PolynomialRegressionSeries) GetValue(index int) (x, y float64) { if prs.InnerSeries == nil || prs.InnerSeries.Len() == 0 { return } + + if prs.coeffs == nil { + coeffs, err := prs.computeCoefficients() + if err != nil { + panic(err) + } + prs.coeffs = coeffs + } + + offset := prs.GetOffset() + effectiveIndex := Math.MinInt(index+offset, prs.InnerSeries.Len()) + x, y = prs.InnerSeries.GetValue(effectiveIndex) + y = prs.apply(x) return } -func (prs *PolynomialRegressionSeries) computeCoefficients() { - vandMatrix := make([][]float64, prs.Len(), prs.Order+1) - var xvalue float64 - for i := 0; i < prs.Len(); i++ { - _, xvalue = prs.InnerSeries.GetValue(i) - var mult float64 = 1.0 - for j := 0; j < prs.Order+1; j++ { - vandMatrix[i][j] = mult - mult = mult * xvalue - } +func (prs *PolynomialRegressionSeries) apply(v float64) (out float64) { + for index, coeff := range prs.coeffs { + out = out + (coeff * math.Pow(v, float64(index))) } + return +} + +func (prs *PolynomialRegressionSeries) computeCoefficients() ([]float64, error) { + xvalues, yvalues := prs.values() + return matrix.Poly(xvalues, yvalues, prs.Degree) +} + +func (prs *PolynomialRegressionSeries) values() (xvalues, yvalues []float64) { + startIndex := prs.GetOffset() + endIndex := prs.GetEndIndex() + + xvalues = make([]float64, endIndex-startIndex) + yvalues = make([]float64, endIndex-startIndex) + + for index := startIndex; index < endIndex; index++ { + x, y := prs.InnerSeries.GetValue(index) + xvalues[index] = x + yvalues[index] = y + } + + return +} + +// Render renders the series. +func (prs *PolynomialRegressionSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) { + style := prs.Style.InheritFrom(defaults) + Draw.LineSeries(r, canvasBox, xrange, yrange, style, prs) }