-
-
Save gengkev/2254304 to your computer and use it in GitHub Desktop.
| <!doctype html> | |
| <meta charset="UTF-8" /> | |
| <script src="linreg.js"></script> | |
| <title>Linear Regressions in JS</title> | |
| <script> | |
| function $(d){return document.getElementById(d)}; | |
| var canvas, textarea, func = "linreg"; | |
| window.onload = function(){ | |
| canvas = document.getElementById("view"); | |
| textarea = document.getElementsByTagName("textarea")[0]; | |
| } | |
| function draw() { | |
| var ctx = canvas.getContext("2d"); | |
| ctx.clearRect(0,0,canvas.width,canvas.height); | |
| var points = textarea.value.match(/([0-9\.]+,\s*[0-9\.]+)/gm); | |
| var x = [], y = []; | |
| for (var i=0;i<points.length;i++) { | |
| points[i] = points[i].match(/[0-9\.]+/g); | |
| x.push(points[i][0] = parseInt(points[i][0])); | |
| y.push(points[i][1] = parseInt(points[i][1])); | |
| //ctx.strokeRect(50 * points[i][0] - 5, 50 * points[i][1] - 5, 10, 10); | |
| ctx.strokeRect(points[i][0] - 5, 500-(points[i][1]) - 5, 10, 10); | |
| } | |
| var output = window[func](x,y); | |
| $("slope").textContent = output[0]; | |
| $("y-intercept").textContent = output[1]; | |
| ctx.beginPath(); | |
| //ctx.moveTo(0,output[1] * 50); | |
| //ctx.lineTo(500,output[0] * 10 * 50 + output[1] * 50); | |
| ctx.moveTo(0,500-output[1]); | |
| ctx.lineTo(500,500-(output[0] * 500 + output[1])); | |
| ctx.stroke(); | |
| } | |
| function addPoint(e) { | |
| var x; | |
| var y; | |
| if (e.pageX && e.pageY) { | |
| x = e.pageX; | |
| y = e.pageY; | |
| } | |
| else { | |
| x = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft; | |
| y = e.clientY + document.body.scrollTop + document.documentElement.scrollTop; | |
| } | |
| x -= canvas.offsetLeft; | |
| y -= canvas.offsetTop; | |
| //x /= 50, y /= 50; | |
| y = 500 - y; | |
| textarea.value += " (" + x + "," + y + ")"; | |
| draw(); | |
| } | |
| </script> | |
| <body> | |
| Enter some coordinates from 0 to 500, or click on the canvas.<br /> | |
| <textarea></textarea> | |
| <button onclick="draw()">Draw!</button> <button onclick="textarea.value='';draw()">Reset</button> | |
| <select onchange="func=this.value;draw()"> | |
| <option value="linreg">linreg</option> | |
| <option value="linreg_eval">linreg_eval</option> | |
| <option value="linreg2">linreg2</option> | |
| </select> | |
| <br /><br /> | |
| <canvas id="view" width="500" height="500" style="border:1px solid black" onclick="addPoint(evt)"></canvas> | |
| <br /> | |
| slope = <span id="slope"></span> y-intercept = <span id="y-intercept"></span> | |
| </body> |
| //an implementation of the AIClass linear regression algorithm | |
| //http://www.youtube.com/watch?v=CE-R7a5xodI#t=4m18s | |
| //originally by antimatter15 but I don't know if you can still even recognize it. | |
| //pass a function fn and it'll call that function for every element | |
| // in the subsequent variables that are passed and sum them up. | |
| function sum(fn,a){ | |
| var total = 0, | |
| M = a.length, | |
| args = [].slice.call(arguments,1); | |
| for(var i = 0; i < M; i++){ | |
| total += fn.apply(null,args.map(function(x){return x[i]})); | |
| } | |
| return total; | |
| } | |
| //uses sum() - slow | |
| function linreg(x,y) { | |
| var M = x.length; | |
| var sumx = sum(function(a){return a},x), sumy = sum(function(a){return a},y); | |
| var w1 = ( | |
| M * sum(function(a,b){return a*b},x,y) - sumx * sumy | |
| )/( | |
| M * sum(function(a){return a*a},x,x) - sumx * sumx | |
| ); | |
| var w0 = sumy / M - w1/M * sumx; | |
| return [w1, w0]; | |
| } | |
| function sum_str(fnsrc){ | |
| var args = [].slice.call(arguments,1); | |
| return sum.apply(null,[new Function('a,b,c', 'return '+fnsrc)].concat(args)); | |
| } | |
| //calculates a linear regression, somehow? using num() | |
| function linreg_eval(x,y) { | |
| var M = x.length; | |
| var sumx = sum_str('a',x), sumy = sum_str('a',y); | |
| var w1 = ( | |
| M * sum_str('a * b',x,y) - sumx * sumy | |
| )/( | |
| M * sum_str('a * a',x) - sumx * sumx | |
| ); | |
| var w0 = sumy / M - w1/M * sumx; | |
| return [w1, w0]; | |
| } | |
| function linreg2(x,y) { | |
| if (x.length != y.length) { | |
| throw new TypeError("Invalid input"); | |
| } | |
| var M = x.length; | |
| var sumx = 0, sumy = 0, sumxy = 0, sumxx = 0, cx, cy; | |
| for (var i=0;i<M;i++) { | |
| cx = x[i], cy = y[i]; | |
| sumx += cx; | |
| sumy += cy; | |
| sumxy += cx * cy; | |
| sumxx += cx * cx; | |
| } | |
| var w1 = (M * sumxy - sumx * sumy) / (M * sumxx - sumx * sumx); | |
| var w0 = sumy / M - sumx * w1 / M; | |
| return [w1, w0]; | |
| } |
this one is way slower than antimatter15's and way way more than http://dracoblue.net/dev/linear-least-squares-in-javascript/159/ (which is specifically optimized for the task) thanks to the sumxy function being api-ified b taking unlimited variables.
linreg2 is basically the same thing as the one by dracoblue.net...because whoever wrote that is a lot better at optimizing performance than me. But his serves a slightly different purpose.
now there's a horribly coded test page and it's pretty late now. (still too lazy to set up proper event handlers)
linreg2 is basically the same as http://dracoblue.net/dev/linear-least-squares-in-javascript/159/ but that one returns points instead of a line. I copied a lot of the optimizations since I suck at optimizing but I don't feel too comfortable modifying other people's code. Though I did for http://jsperf.com/linear-least-squares-fitting/2 though I can't really take credit for a lot of the ideas. sum() got a lot slower thanks to my api-ification; I was messing with the sum function at http://jsperf.com/summations
what the heck was this two years ago what
API-ified everything. At the cost of efficiency, though, of course. Now the sum and sumxy functions are more useful...to calculate linear regressions: linreg() and linregeval() return m and b in y=mx+b.