最小二乘回归逐步线性代数计算


22

作为有关R中线性混合模型的问题的前传,并作为初学者/中级统计爱好者的参考,我决定以独立的“问答式”形式发布“手动”计算简单线性回归的系数和预测值。

该示例使用R内置数据集,mtcars并将其设置为充当自变量的车辆所消耗的每加仑英里数,并根据汽车的重量(连续变量)进行回归,并将汽缸数作为没有相互作用的三个水平(4、6或8)的因子。

编辑:如果您对此问题感兴趣,您肯定会在CV之外的Matthew Drury的这篇帖子中找到详细而令人满意的答案。


当您说“手动计算”时,您要寻找的是什么?展示一系列相对简单的步骤来获得参数估计值是相对简单的,等等(例如,通过Gram-Schmidt正交化,或通过SWEEP运算符),但这不是R在内部进行计算的方式;它(以及大多数其他统计数据包)使用QR分解(在网站上的多个帖子中讨论-搜索QR分解会发现很多帖子,您可能会从中获得直接价值)
Glen_b-恢复莫妮卡·

是。我相信这是MD答案中的一个很好的解决方法,我应该编辑我的帖子,也许强调我的答案背后的几何方法-列空间,投影矩阵...
Antoni Parellada 2015年

是的 @Matthew Drury您要我在OP中删除该行,还是更新链接?
安东尼帕雷拉达

1
不知道您是否具有此链接,但这是密切相关的,我真的很喜欢JM的回答。stats.stackexchange.com/questions/1829/...
杜海涛

Answers:


51

注意:我已经在我的网站上发布了该答案的扩展版本。

您是否愿意考虑发布一个与实际R引擎相似的答案?

当然!沿着兔子洞我们去了。

第一层是lm,暴露给R程序员的接口。您可以通过lm在R控制台中键入内容来查看其来源。它的大部分(与大多数生产级别代码中的大多数一样)忙于检查输入,设置对象属性和引发错误;但是这条线伸出

lm.fit(x, y, offset = offset, singular.ok = singular.ok, 
                ...)

lm.fit是另一个R函数,您可以自己调用它。虽然lm方便地使用公式和数据框,但需要lm.fit矩阵,因此这是删除的抽象级别之一。检查源代码lm.fit,进行更繁琐的工作,以及以下非常有趣的行

z <- .Call(C_Cdqrls, x, y, tol, FALSE)

现在我们到了某个地方。 .Call是R调用C代码的方式。R源中的某个地方有一个C函数C_Cdqrls,我们需要找到它。 在这里

再次看一下C函数,我们发现大部分是边界检查,错误清除和繁忙的工作。但是这条线是不同的

F77_CALL(dqrls)(REAL(qr), &n, &p, REAL(y), &ny, &rtol,
        REAL(coefficients), REAL(residuals), REAL(effects),
        &rank, INTEGER(pivot), REAL(qraux), work);

因此,现在我们使用第三种语言,R调用了C,后者正在调用fortran。 这是fortran代码

第一条评论说明了一切

c     dqrfit is a subroutine to compute least squares solutions
c     to the system
c
c     (1)               x * b = y

(有趣的是,看起来该例程的名称在某个时候已更改,但是有人忘记了更新注释)。因此,我们终于可以进行一些线性代数运算,并实际求解方程组。这是fortran真正擅长的事情,这解释了为什么我们经过这么多层次才能到达这里。

注释还解释了代码将要做什么

c     on return
c
c        x      contains the output array from dqrdc2.
c               namely the qr decomposition of x stored in
c               compact form.

因此,fortran将通过查找分解来解决系统问题。[R

发生的第一件事,也是最重要的是

call dqrdc2(x,n,n,p,tol,k,qraux,jpvt,work)

这将dqrdc2在我们的输入矩阵上调用fortran函数x。这是什么?

 c     dqrfit uses the linpack routines dqrdc and dqrsl.

所以我们终于把它放到linpack了。Linpack是一个Fortran线性代数库,自70年代以来一直存在。最后,最严重的线性代数找到了行进的方式。在我们的例子中,我们使用函数dqrdc2

c     dqrdc2 uses householder transformations to compute the qr
c     factorization of an n by p matrix x.

这是完成实际工作的地方。我需要花一整天的时间来弄清楚这段代码在做什么,因为代码级别很低。但总的来说,我们有一个矩阵,我们想将其分解为乘积,其中是一个正交矩阵,是一个上三角矩阵。这是一件很明智的事情,因为一旦有了和,就可以求解线性方程式进行回归XX=[R[R[R

XŤXβ=XŤÿ

非常简单地。确实

XŤX=[RŤŤ[R=[RŤ[R

所以整个系统变成

[RŤ[Rβ=[RŤŤÿ

但是是上三角并且与具有相同的秩,因此只要我们的问题很好地被提出,它就是完整的秩,我们也可以只解决简化的系统[RXŤX

[Rβ=Ťÿ

但这是很棒的事情。 是上三角,所以这里的最后一个线性方程是,因此求解很简单。然后,您可以逐行上移行,并替换为您已经知道的,每次得到一个简单的线性变量方程式即可求解。因此,一旦有了和,整个过程就会崩溃,即所谓的向后替换,这很容易。您可以在此处更详细地了解此内容,在这里可以找到一个明确的小例子。[Rconstant * beta_n = constantβñβ[R


4
这是人们可以想象的最有趣的数学/编码短文。我几乎对编码一无所知,但是您看似无害的R函数的“步伐”确实让人大开眼界。出色的写作!因为“好心”的伎俩......你能好心考虑这个作为一个相关的挑战?:-)
安东尼·帕雷拉达

6
+1我以前没看过,不错的总结。只是添加一些信息,以防万一@Antoni不熟悉Householderer转换。它本质上是一个线性变换,可让您将要实现的R矩阵的一部分归零,而不会处理已经处理过的部分(只要按正确的顺序进行处理即可),因此非常理想用于将矩阵转换为上三角形式(Givens旋转完成了类似的工作,也许更易于可视化,但速度稍慢)。建立R时,您必须同时建立Q
Glen_b -Reinstate Monica

2
马修(+1),我建议您以指向您更详尽的论文madrury.github.io/jekyll/update/2016/07/20/lm-in-R.html的链接开头或结尾。
变形虫说莫妮卡

3
-1用于降低速度,而不涉及机器代码。
S. Kolassa-恢复莫妮卡

3
(对不起,只是在开玩笑;-)
S. Kolassa-恢复莫妮卡

8

Matthew Drury在同一线程中的答案很好地描述了R中的实际分步计算。在这个答案中,我想逐步证明自己,可以通过简单的示例证明R的结果,这些结果可以通过对列空间的投影的线性代数以及垂直(点积)误差的概念来实现,这在不同的文章中都有说明。 ,由Strang博士在《线性代数及其应用》中作了很好的解释,可在此处轻松访问。

为了估算回归系数β

pG=一世ñŤË[RCËpŤCÿ=4+β1个wË一世GHŤ+d1个一世ñŤË[RCËpŤCÿ=6+d2一世ñŤË[RCËpŤCÿ=8[]

d1个d2X

attach(mtcars)    
x1 <- wt

    x2 <- cyl; x2[x2==4] <- 1; x2[!x2==1] <-0

    x3 <- cyl; x3[x3==6] <- 1; x3[!x3==1] <-0

    x4 <- cyl; x4[x4==8] <- 1; x4[!x4==1] <-0

    X <- cbind(x1, x2, x3, x4)
    colnames(X) <-c('wt','4cyl', '6cyl', '8cyl')

head(X)
        wt 4cyl 6cyl 8cyl
[1,] 2.620    0    1    0
[2,] 2.875    0    1    0
[3,] 2.320    1    0    0
[4,] 3.215    0    1    0
[5,] 3.440    0    0    1
[6,] 3.460    0    1    0

[]lm

βP[RØĴ中号一种Ť[R一世X=XŤX-1个XŤ[P[RØĴ中号一种Ť[R一世X][ÿ]=[[RËG[RCØËFs]XŤX-1个XŤÿ=β

X_tr_X_inv <- solve(t(X) %*% X)    
Proj_M <- X_tr_X_inv %*% t(X)
Proj_M %*% mpg

          [,1]
wt   -3.205613
4cyl 33.990794
6cyl 29.735212
8cyl 27.919934

与:相同coef(lm(mpg ~ wt + as.factor(cyl)-1))

H一种Ť中号一种Ť[R一世X=XXŤX-1个XŤ

HAT <- X %*% X_tr_X_inv %*% t(X)

ÿ^XXŤX-1个XŤÿy_hat <- HAT %*% mpg

cyl <- as.factor(cyl); OLS <- lm(mpg ~ wt + cyl); predict(OLS)

y_hat <- as.numeric(y_hat)
predicted <- as.numeric(predict(OLS))
all.equal(y_hat,predicted)
[1] TRUE

1
通常,在数值计算中,我认为最好是求解线性方程,而不是计算逆矩阵。因此,我认为beta = solve(t(X) %*% X, t(X) %*% y)实际上比更加准确solve(t(X) %*% X) %*% t(X) %*% y
马修·德鲁里

R不会那样做-它使用QR分解。如果要描述所使用的算法,我怀疑在计算机上有人会使用您显示的算法。
恢复莫妮卡-G.辛普森

不用算法,只需尝试理解线性代数基础即可。
安东尼·帕雷拉达

@AntoniParellada即使在那种情况下,我仍然发现在许多情况下,根据线性方程式进行的思考更具启发性。
马修·德鲁里

1
考虑到该线程与我们网站目标的外围关系,虽然在说明R用于重要计算的用法时看到了价值,但我建议您考虑将其转化为对我们博客
ub
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.