1 はじめに

この資料では、線形回帰モデルの回帰係数をPythonRJuliaの3つの言語で推定する方法について紹介する。どの言語でもライブラリを使う方法と、NumPyやそれに相当するような機能のみを用いたやり方を紹介している。

2 参考文献

筆者はJuliaDataFrames.jlや行列計算に関して調べ物が必要だったので、以下のサイトに大変お世話になった。ありがたい。ほかの2つの言語については、別の機会に参考になりそうなものをまとめる。

  1. https://zenn.dev/hyrodium/articles/3fa3882e4bca04#2%E3%81%A4%E3%81%AE%E3%83%99%E3%82%AF%E3%83%88%E3%83%AB%E3%81%AE%E7%A9%8D

また、線形回帰に関する話題は線形回帰モデルに関する説明をしているサイト、の欄をみてほしい。説明をみごとにまるごとブン投げてしまっているのでリッジ回帰あたりからはちゃんと書き直したいよねの気持ち。

3 線形回帰モデルに関する説明をしているサイト

ここでは、線形回帰モデルの説明をするとみせかけて参考となるようなサイトを紹介するにとどめる。線形回帰モデルについての説明は2022年4月時点でも素晴らしいものが星の数ほどある状態で、いい参考文献を紹介する方が筆者の曖昧な理解から繰り出される怪文書よりもよほどいいと思ったからそうする。

  1. https://qiita.com/fujiisoup/items/e7f703fc57e2dfc441ad

4 Rによる回帰係数の推定

Rによる回帰係数の推定は、よくlm関数によって行われている。そこで、本資料では下記の2種類の方法で回帰係数を推定してその結果を比較する。

  • lm関数による推定
  • Rの行列計算による推定

4.1 lm関数による推定

# irisを直接用いる
model <- lm("Sepal.Width ~ Sepal.Length + Petal.Length", data = iris)
model$coef
##  (Intercept) Sepal.Length Petal.Length 
##    1.0380691    0.5611860   -0.3352667

4.2 Rの行列計算による推定

my_coefは、model$coefと誤差などの情報以外は同じ結果であることがわかる。

# データの処理はなるべくtidyverseを用いることにした。
library(tidyverse)
## -- Attaching packages --------------------------------------- tidyverse 1.3.1 --
## v ggplot2 3.3.5     v purrr   0.3.4
## v tibble  3.1.6     v dplyr   1.0.7
## v tidyr   1.1.4     v stringr 1.4.0
## v readr   2.1.1     v forcats 0.5.1
## -- Conflicts ------------------------------------------ tidyverse_conflicts() --
## x dplyr::filter() masks stats::filter()
## x dplyr::lag()    masks stats::lag()
X0 <- iris %>% select(Sepal.Length, Petal.Length)
intercept <- rep(1, dim(X0)[1])
X <- cbind(intercept, X0) %>% as.matrix()
y <- iris %>% select(Sepal.Width) %>% as.matrix()
my_coef <- solve(t(X)%*%X)%*%t(X)%*%y
my_coef
##              Sepal.Width
## intercept      1.0380691
## Sepal.Length   0.5611860
## Petal.Length  -0.3352667

5 Pythonによる推定

Pythonによる回帰係数の推定は、ライブラリのsklearnstatsmodelsによって行われている。この2つのライブラリによる推定結果を比較すると、statsmodelsのほうが推定に関する情報を多く記録してくれるという印象がある。そのため、本資料では下記の2種類の方法で回帰係数を推定してその結果を比較する。

  • ライブラリstatsmodelsによる推定
  • NumPyの行列計算による推定

5.1 ライブラリstatsmodelsによる推定

推定結果model.paramsは、my_coefmodel$coefと誤差を除けば同じになっている。

# インポート
import statsmodels.api as sm
# データirisをインポートする
iris = sm.datasets.get_rdataset("iris", "datasets").data
# 説明変数の行列Xと被説明変数のベクトルyを作る
X = iris[["Sepal.Length", "Petal.Length"]]
y = iris["Sepal.Width"]
# sm.add_constant(X)は、定数項部分の1をXに加えたもの。
model = sm.OLS(y, sm.add_constant(X)).fit()
# 係数をとってきたい場合、paramsメソッドを用いる。
model.params
## const           1.038069
## Sepal.Length    0.561186
## Petal.Length   -0.335267
## dtype: float64

5.2 NumPyの行列計算による推定

推定結果coefは、いままで行ってきた推定結果に一致しているように見える。

# インポート
import numpy as np
import statsmodels.api as sm
# データirisをインポートする
iris = sm.datasets.get_rdataset("iris", "datasets").data
# 説明変数の行列Xと被説明変数のベクトルyを作る
# sm.add_constant(X)と同じことをするためにiris["const"]を追加
iris["const"] = 1
X = iris[["const", "Sepal.Length", "Petal.Length"]].values
y = iris["Sepal.Width"].values
# 演算子@で内積をとり、np.linalg.solveで引数1の逆行列を引数2にかけたものを計算できる。
# 下の書き方は速度面に配慮した記法で、次の記事を参考にした。
# https://qiita.com/fujiisoup/items/e7f703fc57e2dfc441ad
coef = np.linalg.solve(X.T @ X, X.T @ y)
coef
## array([ 1.03806906,  0.56118597, -0.33526674])

6 Juliaによる推定

Juliaによる回帰係数の推定は、ライブラリのGLMが用意しているlm関数によって行われることが多いようだ。そこで、本資料では下記の2種類の方法で回帰係数を推定してその結果を比較する。

  • パッケージGLMlm関数による推定
  • パッケージLinearAlgebraの行列計算による推定

7 パッケージGLMによる推定

値はいままで推定してきた結果とほとんど変わらないことがわかる。

# インポート
using RDatasets
using GLM
# RDataSetsからirisをロードする
df = dataset("datasets", "iris");
model = lm(@formula(SepalWidth ~ SepalLength + PetalLength), df);
GLM.coef(model)
## 3-element Vector{Float64}:
##   1.0380690570704476
##   0.5611859710980439
##  -0.33526674157885833

8 パッケージLinearAlgebraを使った行列計算による推定

LinearAlgebraによる回帰係数の推定を行い、ほかの値と比較して差がないことを確認している。

# インポート
using RDatasets
using DataFrames
using LinearAlgebra
# RDataSetsからirisをロードする
df = dataset("datasets", "iris");
df[!, :const] = [1 for i = 1:size(df)[1]];
X = Matrix(df[:, [:const, :SepalLength, :PetalLength]]);
y = df[:, :SepalWidth];
# 行列計算を行う。
coef = inv(transpose(X) * X)* transpose(X) * y;
coef
## 3-element Vector{Float64}:
##   1.038069057070371
##   0.5611859710980548
##  -0.33526674157886044