Section 10.3 Minimizing MSE
Earlier I said that the least squares fit is the straight line that minimizes the mean squared error (MSE). We wonβt prove that, but we can test it by adding small random values to the intercept and slope, and checking whether the MSE gets worse.
intercept = result.intercept + np.random.normal(0, 1)
slope = result.slope + np.random.normal(0, 1)
To run the test, we need to make an object with
intercept and slope attributes -- weβll use the SimpleNamespace object provided by the types module.
$ from types import SimpleNamespace
fake_result = SimpleNamespace(intercept=intercept, slope=slope)
fake_result
namespace(intercept=np.float64(-2533.193389187175),
slope=np.float64(32.15393529728294))
We can pass this object to
compute_residuals and use the residuals to compute the MSE.
fake_residuals = compute_residuals(fake_result, flipper_length, body_mass)
fake_mse = np.mean(fake_residuals**2)
If we compare the result to the MSE of the least squares line, it is always worse.
$ mse, fake_mse, fake_mse > mse
(np.float64(163098.85902884745), np.float64(179019.20812685465), np.True_)
Minimizing MSE is nice, but itβs not the only definition of "best". One alternative is to minimize the absolute values of the errors. Another is to minimize the shortest distance from each point to the fitted line, which is called the "total error". In some contexts, guessing too high might be better (or worse) than guessing too low. In that case you might want to compute a cost function for each residual, and minimize total cost.
But the least squares fit is much more widely used than these alternatives, primarily because it is efficient to compute. The following function shows how.
def least_squares(xs, ys):
xbar = np.mean(xs)
ybar = np.mean(ys)
xdev = xs - xbar
ydev = ys - ybar
slope = np.sum(xdev * ydev) / np.sum(xdev**2)
intercept = ybar - slope * xbar
return intercept, slope
To test this function, weβll use flipper length and body mass again.
$ intercept, slope = least_squares(flipper_length, body_mass)
intercept, slope
(np.float64(-2535.8368022002524), np.float64(32.831689751150094))
And we can confirm that we get the same results we got from
linregress.
$ np.allclose([intercept, slope], [result.intercept, result.slope])
True
Minimizing MSE made sense when computational efficiency was more important than choosing the method most appropriate to the problem at hand. But thatβs no longer the case, so it is worth considering whether squared residuals are the right thing to minimize.
