JavaScript: fitting a line with least squares
Compute the slope, intercept, and R² of the best-fit line in closed form.
const x = [1, 2, 3, 4, 5];
const y = [2.2, 4.1, 6.2, 7.9, 10.1];
const mean = (values) => values.reduce((a, b) => a + b, 0) / values.length;
const mx = mean(x);
const my = mean(y);
let sxy = 0;
let sxx = 0;
for (let i = 0; i < x.length; i++) {
sxy += (x[i] - mx) * (y[i] - my);
sxx += (x[i] - mx) ** 2;
}
const slope = sxy / sxx;
const intercept = my - slope * mx;
let residual = 0;
let total = 0;
for (let i = 0; i < x.length; i++) {
residual += (y[i] - (slope * x[i] + intercept)) ** 2;
total += (y[i] - my) ** 2;
}
console.log("slope " + slope.toFixed(3) + ", intercept " + intercept.toFixed(3));
console.log("R squared " + (1 - residual / total).toFixed(4));
console.log("prediction for x=6: " + (slope * 6 + intercept).toFixed(2));slope 1.960, intercept 0.220 R squared 0.9989 prediction for x=6: 11.98
Explanation
Simple linear regression finds the line y = m·x + b that minimizes the sum of squared vertical errors. For one input variable there is a closed-form solution: the slope is the covariance of x and y divided by the variance of x, and the intercept makes the line pass through the point of means.
R² measures how much of the variation in y the line explains. It is 1 minus the ratio of the residual sum of squares to the total sum of squares, and it is 1 for a perfect fit and near 0 for a useless one. The data here are roughly y = 2x with a little noise, so the slope should be close to 2.
Explore the dry run
const x = [1, 2, 3, 4, 5];const y = [2.2, 4.1, 6.2, 7.9, 10.1]; const mean = (values) => values.reduce((a, b) => a + b, 0) / values.length;const mx = mean(x);const my = mean(y); let sxy = 0;let sxx = 0;for (let i = 0; i < x.length; i++) { sxy += (x[i] - mx) * (y[i] - my); sxx += (x[i] - mx) ** 2;}const slope = sxy / sxx;const intercept = my - slope * mx; let residual = 0;let total = 0;for (let i = 0; i < x.length; i++) { residual += (y[i] - (slope * x[i] + intercept)) ** 2; total += (y[i] - my) ** 2;} console.log("slope " + slope.toFixed(3) + ", intercept " + intercept.toFixed(3));console.log("R squared " + (1 - residual / total).toFixed(4));console.log("prediction for x=6: " + (slope * 6 + intercept).toFixed(2));Follow the example
Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.
View the complete dry-run table
| Step / line | What happens | State after step | Output so far |
|---|---|---|---|
| Covariance divided by variance: 19.6 / 10 = 1.96. | slope = 1.96 | — | |
| The line passes through the means (3, 6.1): 6.1 − 1.96·3 = 0.22. | intercept = 0.22 | — | |
| The fitted line is close to y = 2x. | intercept = 0.22 | slope 1.960, intercept 0.220 | |
| Almost all of the variation is explained by the line. | intercept = 0.22 | slope 1.960, intercept 0.220 R squared 0.9989 | |
| 1.96·6 + 0.22. | intercept = 0.22 | slope 1.960, intercept 0.220 R squared 0.9989 prediction for x=6: 11.98 |
Cost and common mistakes
O(n) time and O(1) extra space.
- A high R² does not prove that the relationship is causal, or that the model will predict well outside the observed range.
- Least squares is sensitive to outliers, because errors are squared.
- toFixed returns a string and rounds for display only. Keep full precision for calculation.
Try it yourself
What does the model predict for x = 0?
Reveal answer and reasoning
The intercept, which is 0.22.