forked from vsoch/regression-wasm
-
Notifications
You must be signed in to change notification settings - Fork 0
/
runner.go
185 lines (145 loc) · 5.08 KB
/
runner.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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
// Copyright 2019 Vanessa Sochat. All rights reserved.
// Use of this source code is governed by the Polyform Strict license
// that can be found in the LICENSE file and available at
// https://polyformproject.org/licenses/noncommercial/1.0.0
package main
import (
"encoding/csv"
"fmt"
"strings"
"strconv"
"syscall/js"
"github.com/sajari/regression"
)
// RegressionRunner stores input data for the file
type RegressionRunner struct {
records [][]string // array of records
y []float64 // array of parsed data (y only)
x [][]float64 // array of parsed data (x only)
header []string // first row of headers
predictCol int // prediction column
residuals []float64 // final array of residuals
predictions []float64 // final array of predictions
model *regression.Regression // the regression model
}
// readCsv file and set the records on the runner
func (runner *RegressionRunner) readCsv(csvString string, hasHeader bool, delim string) error {
reader := csv.NewReader(strings.NewReader(csvString))
records, err := reader.ReadAll()
var header []string
if err != nil {
return err
}
// Remove header row, if we have it
if hasHeader {
header, records = records[0], records[1:]
runner.header = header
} else {
// Add dummy names
for i := range records[0] {
header = append(header, fmt.Sprintf("Element %d", i))
}
}
runner.records = records
return nil
}
// runRegression and generate model to save to runner
func (runner *RegressionRunner) runRegression() {
r := new(regression.Regression)
// Iterate through headers to generate variables
count := 0
for index, element := range runner.header {
// Add as regression or observed variable
if (index != runner.predictCol) {
r.SetVar(count, element)
count++
} else {
r.SetObserved(element)
}
}
// We will unwrap an array of data points
var dataPoints regression.DataPoints
var predictor float64
var regressors []float64
// Iterate through records to generate dataPoints
for _, row := range runner.records {
regressors = nil
for index, record := range row {
if (index != runner.predictCol) {
if n, err := strconv.ParseFloat(record, 64); err == nil {
regressors = append(regressors, n)
}
} else {
if n, err := strconv.ParseFloat(record, 64); err == nil {
predictor = n
}
}
}
dataPoints = append(dataPoints, regression.DataPoint(predictor, regressors))
runner.x = append(runner.x, regressors)
runner.y = append(runner.y, predictor)
}
fmt.Printf("X variables:\n%v\n", runner.x)
fmt.Printf("Y variables:\n%v\n", runner.y)
// Unwrap data points into function
r.Train(dataPoints...)
r.Run()
// Show and save the regression model
fmt.Printf("Regression formula:\n%v\n", r.Formula)
//fmt.Printf("Regression:\n%s\n", r)
runner.model = r
}
// Calculate residuals using the model. If we have more than two covariates,
// then we will plot residuals in a histogram.
func (runner *RegressionRunner) calculateResiduals() {
// Calculate residuals and predictions
var predictions []float64
var residuals []float64
for i, row := range runner.x {
if prediction, err := runner.model.Predict(row); err == nil {
predictions = append(predictions, prediction)
residuals = append(residuals, runner.y[i] - prediction)
}
}
fmt.Println("Residuals:", residuals)
fmt.Println("Predictions:", predictions)
// Save to the runner!
runner.residuals = residuals
runner.predictions = predictions
}
// plotResult will generate a histogram for multivariate regression (of
// residuals) or a line plot given only two variables.
func (runner *RegressionRunner) plotRegression() {
// Greater than two variables -> regression line
if len(runner.header) != 2 {
runner.plotResiduals()
} else {
runner.plotLinear()
}
}
// plotLinear is called given 2 variables, and we create a line plot
func (runner *RegressionRunner) plotLinear() {
plotFunc := js.Global().Get("plotLinear")
describeFunc := js.Global().Get("describePlot")
// Convert data to string to send back to browser
X := floatArrayToString(runner.x) // only uses first entry
Y := floatToString(runner.y)
predictions := floatToString(runner.predictions)
headers := strings.Join(runner.header, ",")
result := fmt.Sprintf("Dinosaur Regression Wasm\n%s\n%s", runner.model.Formula, runner.model)
// provide the title, X, Y, headers, and result
plotFunc.Invoke(runner.header[runner.predictCol], X, Y, predictions, headers, result)
describeFunc.Invoke(runner.model.Formula)
}
// plotResiduals calls plotResiduals on the front end and passes residuals
func (runner *RegressionRunner) plotResiduals() {
// Get the functions to do and describe the plot
plotFunc := js.Global().Get("plotResiduals")
describeFunc := js.Global().Get("describePlot")
// Comma separated list of residuals
resultString := floatToString(runner.residuals)
// provide the title (predictor) and string data
result := fmt.Sprintf("Dinosaur Regression Wasm\n%s\n%s", runner.model.Formula, runner.model)
plotFunc.Invoke(runner.header[runner.predictCol], resultString, result)
describeFunc.Invoke(runner.model.Formula)
}