Skip to content

Instantly share code, notes, and snippets.

@tpogden
Last active August 29, 2015 14:08
Show Gist options
  • Save tpogden/f416a5c27971908845a9 to your computer and use it in GitHub Desktop.
Save tpogden/f416a5c27971908845a9 to your computer and use it in GitHub Desktop.
Least Squares Fit

Fits a straight line to data using the Least Squares method.

"Least squares" means that the overall solution minimizes the sum of the squares of the errors made in the results of every single equation.

Least squares function by Ben van Dyke.

x y
0 0
1 1
2 3.5
3 6
4 7
5 9
6 10.5
7 11
8 11.5
9 11
10 17.5
11 23
12 20
13 27
14 30.5
15 37
16 46
17 53
18 56.5
19 47
20 54
21 51
22 43.5
23 40
24 40
25 44
26 46
27 53
28 46.5
29 40
30 49.5
31 53
32 55.5
33 47
34 57
35 50
36 52
37 58
38 62.5
39 66
40 72
41 66
42 60
43 56
44 63
45 63
46 56
47 59
48 61
49 51
50 58.5
51 60
52 65.5
53 68
54 66.5
55 68
56 75.5
57 87
58 85.5
59 79
60 77.5
61 82
62 75.5
63 69
64 71.5
65 73
66 75.5
67 76
68 88.5
69 79
70 70
71 75
72 83.5
73 91
74 84.5
75 85
76 78
77 92
78 98
79 97
80 99
81 100
82 102.5
83 102
84 112
85 121
86 112.5
87 115
88 111
89 113
90 128
91 135
92 126.5
93 131
94 120
95 119
96 130.5
97 130
98 130
99 131
100 143.5
<!DOCTYPE html>
<meta charset="utf-8">
<html>
<head>
<title>Least Squares Fit</title>
<script src="http://d3js.org/d3.v3.min.js"></script>
<style>
body {
font-family: "Helvetica", sans-serif;
font-size: 10px;
margin: 8px;
}
.axis {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.path {
fill: none;
stroke: rgba(189,54,19,1);
stroke-width: 2px;
}
.trendline {
stroke: rgba(208,199,198,1);
stroke-width: 1px;
stroke-opacity: 0.75;
}
</style>
<body>
<script>
(function() {
var margin = {top: 20, right: 20, bottom: 40, left: 40},
width = 944 - margin.left - margin.right,
height = 480 - margin.top - margin.bottom;
var numSteps = 100;
var yMax = 150;
// X scale
var xScale = d3.scale.linear()
.domain([0, numSteps])
.range([0, width]);
// Y scale
var yScale = d3.scale.linear()
.domain([0, yMax])
.range([height, 0]);
// Draw SVG
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// Draw axes
var xAxis = d3.svg.axis()
.scale(xScale)
.ticks(5)
.orient("bottom");
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
var yAxis = d3.svg.axis()
.scale(yScale)
.ticks(3)
.orient("left");
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
// equ text in top left
var eqText = svg.append("text")
.attr("id", "eq-text")
.style("text-anchor", "start")
.attr("x", 8)
.attr("y", 8);
// Get data from CSV and draw line
d3.csv("data.csv", function(error, data) {
var lineFunction = d3.svg.line()
.x(function(d) { return xScale(d['x']); })
.y(function(d) { return yScale(d['y']); });
svg.append("path")
.datum(data)
.attr("class", "path")
.attr("d", lineFunction);
// get the x and y values for least squares
var xSeries = data.map(function(d) { return parseFloat(d['x']); });
var ySeries = data.map(function(d) { return parseFloat(d['y']); });
console.log('xSeries: ' + xSeries);
console.log('ySeries: ' + ySeries);
var leastSquaresCoeff = leastSquares(xSeries, ySeries);
var slope = leastSquaresCoeff[0];
var intercept = leastSquaresCoeff[1];
var rSquare = leastSquaresCoeff[2];
console.log("slope: " + slope);
console.log("intercept: " + intercept);
var trendline = svg.append("line")
.attr("x1", xScale(0.))
.attr("y1", yScale(intercept))
.attr("x2", xScale(numSteps))
.attr("y2", yScale(numSteps*slope + intercept))
.classed("trendline", true);
d3.select('#eq-text').text("y = " + format2d(slope)
+ "x + " + format2d(intercept));
})
// returns slope, intercept and r-square of the line
// from: http://bl.ocks.org/benvandyke/8459843
function leastSquares(xSeries, ySeries) {
var reduceSumFunc = function(prev, cur) { return prev + cur; };
var xBar = xSeries.reduce(reduceSumFunc) * 1.0 / xSeries.length;
var yBar = ySeries.reduce(reduceSumFunc) * 1.0 / ySeries.length;
var ssXX = xSeries.map(function(d) { return Math.pow(d - xBar, 2); })
.reduce(reduceSumFunc);
var ssYY = ySeries.map(function(d) { return Math.pow(d - yBar, 2); })
.reduce(reduceSumFunc);
var ssXY = xSeries.map(function(d, i) { return (d - xBar) * (ySeries[i] - yBar); })
.reduce(reduceSumFunc);
var slope = ssXY / ssXX;
var intercept = yBar - (xBar * slope);
var rSquare = Math.pow(ssXY, 2) / (ssXX * ssYY);
return [slope, intercept, rSquare];
}
var format2d = d3.format("0.1f");
})()
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment