Marco Plebani
Marco Plebani

Reputation: 486

Home-made implementation of a least squares method in R showing unexpected behavior

I am building an example to show, graphically, how the least square method works. I am applying a numerical approach where I feed R a number of combinations of possible values of intercept (a) and slope (b), then compute the sum of squares (SSE) for all possible combinations. The combinations of a and b with associated the lowest SSE should be the best one, but somehow my estimates of a are always off mark compared to the real value computed by lm(). On top of that, My estimate of a is sensitive to the range of possible values of a given to R - the broader the range, the more the estimate of a is off.

Here is my example. I'm using the dataset "longley", built in R:

    data(longley)
    plot(GNP ~ Employed, data = longley,
        xlab="% employed adults",
        ylab="Gross National Product (million $?)",
        main="Money money money"
        )

scatterplot of the data from the longley dataset

    # ranges of a and be where we think their true value lies:
    possible.a.vals <- seq(-1431,-1430, by=0.01)
    possible.b.vals <- seq(27,28.5, by=0.01)
    # all possible combinations of a and b:
    possible.ab <- expand.grid(possible.a.vals = possible.a.vals,
                            possible.b.vals = possible.b.vals
                            )

    possible.ab.SSE <- as.data.frame(possible.ab)
    head(possible.ab.SSE); tail(possible.ab.SSE)
    possible.ab.SSE$SSE <- rep(NA, length.out = length(possible.ab.SSE[,1]))
    for (i in 1:length(possible.ab.SSE[,1])){
        predicted.GNP <- possible.ab.SSE$possible.a.vals[i] + possible.ab.SSE$possible.b.vals[i] * longley$Employed
        possible.ab.SSE$SSE[i] <- sum((longley$GNP - predicted.GNP)^2)
    }
    possible.ab.SSE$possible.a.vals[which(possible.ab.SSE$SSE == min(possible.ab.SSE$SSE))]
    possible.ab.SSE$possible.b.vals[which(possible.ab.SSE$SSE == min(possible.ab.SSE$SSE))]

# Estimate of a = -1430.73
# estimate of b = 27.84

    # True values of a and b:
    # a = -1430.48
    # b = 27.84

My estimate of b is spot on but a is slightly off. Moreover, extending the range of possible values of a and b produces estimates of a that are even further from the real value, giving me an estimate of a around -1428 - besides making my loop work forever, which I could solve by using apply() if I weren't a lazy ass.

# plot in 3d:
require(akima) # this helps interpolating the values of a,b, and SSE to create a surface
x= possible.ab.SSE$possible.a.vals
y= possible.ab.SSE$possible.b.vals
z=possible.ab.SSE$SSE
s=interp(x,y,z)

persp(x = s$x,
        y = s$y,
        z = s$z,
        theta =50, phi = 10, 
        xlab="a", ylab="b", zlab="SSE",
        box=T
        )

Changes in SSE for each combination of a and b

This suggests that the correlation between sum of squares and possible a values is roughly flat, which explains why estimates of a tend to be off mark. This still puzzles me: if the analytical approach to the Least Squares method nails the estimates of the parameter values, so should a numerical approach.

Should it not?

Thank you in advance for your feedback.

EDIT

It's been pointed out that the issue is a resolution one. I overlooked that the value of SSE associated to each value of a is not independent from b; on top of that, changes in SSE are more strongly affected by changes in b than they are by changes in a (or at least that's my understanding of it). The result is that approximations in the estimated value of the slope b can thow off the estimate of the intercept a.

Here are three graphs showing the correlation between a, b, and SSE for broader and sparser ranges of values:

possible.a.vals <- seq(-3000,1000, by=10)
possible.b.vals <- seq(-30,60, by=2)

Correlations between a, b, and SSE

Upvotes: 3

Views: 206

Answers (1)

Weihuang Wong
Weihuang Wong

Reputation: 13128

@ben-bolker is right. It is not entirely correct to say that your "estimate of b is spot on." The difference between the value that minimizes SSE in your example, 27.84, and the OLS estimate, 27.83626, turns out to significantly affect the intercept estimate.

data(longley)
# ranges of a and be where we think their true value lies:
possible.a.vals <- seq(-1431,-1430, by = 0.005)
possible.b.vals <- seq(27.5,28, by = 0.00001)
# all possible combinations of a and b:
possible.ab.SSE <- expand.grid(possible.a.vals = possible.a.vals,
                               possible.b.vals = possible.b.vals)
possible.ab.SSE <- as.matrix(possible.ab.SSE)
out <- tcrossprod(cbind(1, longley$Employed), possible.ab.SSE)
possible.ab.SSE <- as.data.frame(possible.ab.SSE)
possible.ab.SSE$SSE <- colSums((out - longley$GNP)^2)

possible.ab.SSE[order(possible.ab.SSE$SSE), ][1, ]
#         possible.a.vals possible.b.vals      SSE
# 6758127        -1430.48        27.83622 4834.891
coef(lm(GNP ~ Employed, data = longley))
# (Intercept)    Employed 
# -1430.48231    27.83626 

Upvotes: 1

Related Questions