-
-
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]; | |
} |
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
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.