最小二乗回帰の段階的な線形代数計算


22

Rの線形混合モデルに関する質問の前編として、および初心者/中級の統計愛好家の参考として共有するために、独立した「Q&Aスタイル」として、単純な線形回帰の係数と予測値。

この例は、Rの組み込みデータセットをmtcars使用しており、独立変数として機能する車両が消費するガロンあたりのマイル数として設定され、車の重量(連続変数)に対して回帰し、シリンダー数を相互作用のない3つのレベル(4、6、または8)の係数。

編集:あなたがこの質問に興味があるなら、あなたは間違いなくCVの外のマシュー・ドゥルリーによるこの投稿で詳細で満足のいく答えを見つけるでしょう。


「手動計算」と言うとき、何を求めますか?パラメーターの推定値などを取得するための一連の比較的単純な手順を示すのは比較的簡単です(たとえば、Gram-Schmidtの直交化を介して、またはSWEEP演算子を使用して)。それは、Rが内部で計算を行う方法ではありません。it(および他のほとんどの統計パッケージ)は、QR分解を使用します(サイト上の多数の投稿で
説明-QR

はい。私は、これはMD Iによって答えのアドレスは、おそらく、おそらく私の答えの後ろに幾何学的なアプローチを強調し、自分の投稿を編集する必要があり、非常にきれいだったと信じている-列空間、投影行列...
アントニParellada

うん!@Matthew Drury OPのその行を消去するか、リンクを更新しますか?
アントニ・パレラダ

1
このリンクがあるかどうかはわかりませんが、これは密接に関連しており、JMの答えが大好きです。stats.stackexchange.com/questions/1829/...
ハイタオ・ドゥ

回答:


51

:この回答の拡張バージョンをWebサイトに投稿しました。

実際のRエンジンが公開された状態で、同様の回答を投稿してください。

もちろん!ウサギの穴を下って行きます。

最初の層はlm、Rプログラマーに公開されるインターフェースです。lmRコンソールで入力するだけで、このソースを確認できます。その大部分(ほとんどの製品レベルのコードの大部分と同様)は、入力のチェック、オブジェクト属性の設定、およびエラーのスローでビジーです。しかし、この行は突き出ています

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

lm.fit別のR関数です。自分で呼び出すことができます。一方でlm便利な数式やデータフレームで動作する、lm.fitそれが取り除か抽象化の1つのレベルがありますので、行列を望んでいます。lm.fit、より忙しい仕事、および以下の本当に興味深い行のソースを確認します

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

今、私たちはどこかに到達しています。 .CallRが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は分解を見つけることでシステムを解決します。QR

最初に発生すること、そして最も重要なことは、

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は、70年代から使用されているFortran線形代数ライブラリです。最も深刻な線形代数は最終的にlinpackへの道を見つけます。このケースでは、関数dqrdc2を使用しています

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

ここで実際の作業が行われます。このコードが何をしているのかを理解するには、丸1日かかります。しかし一般的には、行列あり、それを直交行列、が上三角行列である積に分解したいです。とが得られると、回帰の線形方程式を解くことができるため、これは賢明なことです。バツバツ=QRQRQR

バツtバツβ=バツtY

とても簡単に。確かに

バツtバツ=RtQtQR=RtR

したがって、システム全体が

RtRβ=RtQty

しかし、は上三角で、と同じランクを持っているので、問題が適切に提起されている限り、それはフルランクであり、縮小システムを解くこともできます。Rバツtバツ

Rβ=Qty

しかし、ここに素晴らしいものがあります。 は上三角であるため、ここでの最後の線形方程式はjust であるため、は簡単です。次に、行を1つずつ上に移動し、既知の代入して、毎回簡単な1変数線形方程式を取得します。したがって、とをと、全体が簡単に後方置換と呼ばれるものに崩壊します。これについてここで詳細に読むことができます。ここでは、明示的な小さな例を完全に解決しています。Rconstant * beta_n = constantβnβQR


4
これは、想像できる最も楽しい数学/コーディングの短いエッセイでした。私はコーディングについてほとんど何も知っていませんが、一見無害なR関数の内部を通る「ツアー」は本当に目を見張るものでした。素晴らしい文章!「親切に」トリックを行ったので... これを関連する課題として親切に考えてもらえますか?:
アントニ・パレラダ

6
+1これは前に見たことがなかった、いい要約。@AntoniがHouseholder変換に精通していない場合に備えて、少し情報を追加するだけです。それは基本的に線形変換であり、すでに扱った部分を壊すことなく達成しようとしているR行列の一部をゼロにすることができます(正しい順序でそれを通過する限り)マトリックスを上三角形式に変換します(Givensの回転は同様の作業を行い、おそらく視覚化は簡単ですが、少し遅くなります)。あなたはRを構築するとして、あなたは同じ時間コンストラクトQでなければならない
Glen_b -Reinstateモニカ

2
マシュー(+1)、より詳細な記事madrury.github.io/jekyll/update/2016/07/20/lm-in-R.htmlへのリンクで投稿を開始または終了することをお勧めします。
アメーバは、モニカを復活させる

3
-1チキンアウトし、マシンコードにダウンしないため。
S. Kolassa -復活モニカ

3
(申し訳ありませんが、冗談です;-)
S.コラッサ-モニカの復活

8

Rでの実際の段階的な計算は、この同じスレッドのMatthew Druryによる回答で美しく説明されています。この答えでは、列空間への投影の線形代数と異なる投稿に示されている垂直(ドット積)エラーの概念に従って、簡単な例でRの結果に到達できることを自分自身に証明するプロセスを歩きたい、Strang博士が線形代数とその応用でうまく説明し、ここから簡単にアクセスできます

β

mpg=nterceptcyl=4+β1weght+D1nterceptcyl=6+D2nterceptcyl=8[]

D1D2バツ

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

βProjMatrバツ=バツTバツ1バツT[ProjMatrバツ][y]=[RegrCoefs]バツTバツ1バツTy=β

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))

HatMatrバツ=バツバツTバツ1バツT

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

y^バツバツTバツ1バツTyy_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重要な計算に使用することを説明することの価値を見ながら、私たちのブログ
whuber
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.