Fitting a line: least squares
You have a cloud of noisy data points and you suspect a straight-line trend. Linear regression finds the one line y = m x + b that fits best — and "best" has a precise meaning: the line that makes the sum of squared vertical distances from the points as small as possible.
Remarkably, that best line has a simple closed-form formula (no searching required). Below you can set the underlying trend and how much noise we sprinkle on, then watch the fitted line track the data live in WebAssembly, alongside the true line it's trying to recover.
begin
using PlutoUI, WasmMakie
endtrue slope =
noise level =
number of points =
seed =
let
# build noisy data y = m x + b + noise, accumulating the sums needed for the
# closed-form least-squares fit in the SAME single pass
m_true = Float64(slopei) / 10.0
b_true = 2.0
noise = Float64(noisei) / 10.0
n = npoints
s = (seed * 2654435761 + 12345) % 2147483647
if s == 0
s = 1
end
dx = Vector{Float64}(undef, n)
dy = Vector{Float64}(undef, n)
sx = 0.0
sy = 0.0
sxx = 0.0
sxy = 0.0
for i in 1:n
x = 10.0 * Float64(i - 1) / Float64(n - 1)
s = (s * 16807) % 2147483647
u = Float64(s) / 2147483647.0
y = m_true * x + b_true + noise * (u - 0.5) * 2.0
dx[i] = x
dy[i] = y
sx = sx + x
sy = sy + y
sxx = sxx + x * x
sxy = sxy + x * y
end
nn = Float64(n)
m_fit = (nn * sxy - sx * sy) / (nn * sxx - sx * sx)
b_fit = (sy - m_fit * sx) / nn
fig = Figure(size = (600, 360))
ax = Axis(fig[1, 1])
# the data points, each drawn as a small vertical tick
for i in 1:n
lines!(ax, [dx[i], dx[i]], [dy[i] - 0.25, dy[i] + 0.25])
end
# the true line (what generated the data) and the fitted line (what we recovered)
lines!(ax, [0.0, 10.0], [b_true, m_true * 10.0 + b_true])
lines!(ax, [0.0, 10.0], [b_fit, m_fit * 10.0 + b_fit])
fig
endfit_stats = let
# compute the least-squares fit in a SEPARATE bond-dependent cell so the markdown
# below interpolates it live (values inside a markdown cell's own `let` bake to the
# slider defaults).
m_true = Float64(slopei) / 10.0
noise = Float64(noisei) / 10.0
n = npoints
s = (seed * 2654435761 + 12345) % 2147483647
if s == 0
s = 1
end
sx = 0.0
sy = 0.0
sxx = 0.0
sxy = 0.0
for i in 1:n
x = 10.0 * Float64(i - 1) / Float64(n - 1)
s = (s * 16807) % 2147483647
u = Float64(s) / 2147483647.0
y = m_true * x + 2.0 + noise * (u - 0.5) * 2.0
sx = sx + x
sy = sy + y
sxx = sxx + x * x
sxy = sxy + x * y
end
nn = Float64(n)
m_fit = (nn * sxy - sx * sy) / (nn * sxx - sx * sx)
b_fit = (sy - m_fit * sx) / nn
(floor(m_fit * 1000.0) / 1000.0, floor(b_fit * 1000.0) / 1000.0, floor(m_true * 100.0) / 100.0)
end;Fitted line: y = 1.486 x + 2.125 (true slope 1.5, true intercept 2.0). Add more noise and the fit wobbles; add more points and it locks back onto the truth – more data beats noisier data.
Where the formula comes from
"Best fit" means minimizing the total squared error Σ (yᵢ − m xᵢ − b)². Setting the derivatives with respect to m and b to zero gives two linear equations — the normal equations — whose solution is exactly the slope and intercept we computed above. No iteration, no learning rate: a single formula.
This is the simplest member of a huge family. Replace the line with a plane, a polynomial, or millions of features and the same least-squares idea becomes the backbone of statistics and machine learning. It is also the moment "data science" stops being plotting and starts being modeling: proposing a relationship and measuring how well it explains the data.
Appendix
The MIT lecture fits with GLM.jl over DataFrames. WebAssembly can't run that stack, so we generate the data with an inline Park-Miller generator and apply the closed-form least-squares formula by hand, drawing with WasmMakie. The fit is identical to what GLM would return.