LoginSignup
2

More than 1 year has passed since last update.

重回帰分析(単回帰分析)

Last updated at Posted at 2022-05-13

重回帰分析(単回帰分析)

テストデータ

import pandas as pd

df5 = pd.DataFrame({
  'x1': [2, 2, 3, 1, 2, 3, 2, 2, 2, 3, 2, 2, 1, 3, 2, 2, 1, 2, 1, 1],
  'x2': [2, 2, 3, 2, 2, 1, 2, 3, 2, 3, 2, 1, 3, 2, 2, 1, 1, 3, 2, 1],
   'y': [17, 20, 24, 15, 20, 19, 20, 22, 20, 26, 24, 14, 22, 23, 23, 19, 13, 27, 17, 13]})

1. sklern.linear_model.LinearRegression による分析

from sklearn.linear_model import LinearRegression
lr = LinearRegression()
X = df5.drop('y', axis=1)
y = df5['y']
lr = lr.fit(X, y)

1.1. 偏回帰係数と標準化偏回帰係数

元のデータを使って重回帰分析をした結果で,切片は .intercept_,偏回帰係数は .coef_ で求めることができる。

lr.intercept_
7.076023391812857
lr.coef_
array([2.7251462 , 3.75497076])

よくある記事だが,

各変数がどの程度目的変数に影響しているかを確認するには、各変数を正規化 (標準化) し、平均 = 0, 標準偏差 = 1 になるように変換した上で、重回帰分析を行うと偏回帰係数の大小で比較することができるようになります。

などと書いているが,データを正規化して分析をもう一回する必要はない

1.2. 偏回帰係数を標準化偏回帰係数に変換

標準化したデータで重回帰分析をしたときに得られるのは,標準化偏回帰係数 $b^{*}_i$ である。

元のデータを用いて重回帰分析をして得られる偏回帰係数を $b_i$,$y$ の標準偏差を $S_y$,$X_i$ の標準偏差を $S_{xi}$ とすれば,標準化偏回帰係数は式(1) のようにして求めることができる。

$\hspace{2cm}(1)\hspace{1cm}\displaystyle b_i^{*} = b_i \frac{S_{xi}}{S_y}$

Sy = y.std()
Sy
4.115439478994091
Sxi = X.std(axis=0)
tuple(Sxi)
(0.6863327411532597, 0.7254762501100116)
import numpy as np
tuple(lr.coef_ * (Sxi/Sy)) # 標準化偏回帰係数
(0.45447322703523774, 0.6619322481381958)

疑い深い人は,時間の無駄だが,以下を試せばよい。

更に時間の無駄をいとわない人は,これを証明すればよい。

import scipy.stats as ss
X2 = ss.zscore(X) # 標準化
y2 = ss.zscore(y) # 標準化
lr2 = LinearRegression()
lr2 = lr2.fit(X2, y2)
tuple(lr2.coef_) # 標準化偏回帰係数
(0.45447322703523757, 0.6619322481381954)

ちなみに,定数項は 0 になる。

lr2.intercept_
3.59314957606639e-16

1.3. 標準化偏回帰係数を偏回帰係数に変換

機械学習における重回帰分析の手順書で,

  • データを正規化して
  • 重回帰分析をして(標準化偏回帰係数を求め)
  • 予測値を求めるために
  • 標準化偏回帰係数を偏回帰係数にして(予測式を作り)
  • 予測式で予測値を求める

ということが記述される。最初にデータを正規化するということの意味をよくわかっていない(誤解した)まま手順書に従うことが多いようだ。

式(1) を $b_i$ について解けば,式(2) が得られる。

$\hspace{2cm}(2)\hspace{1cm}\displaystyle b_i = b_i^{*} \frac{S_y}{S_{xi}}$

つまり,標準化偏回帰係数 $b_i^{*}$ を偏回帰係数 $b_i$ に変換するのだ。

tuple(lr2.coef_ * Sy / Sxi)
(2.72514619883041, 3.7549707602339177)

lr.coef_ と一致している。

定数項 $b_0$ は以下の式で求めることができる。

$\hspace{2cm}b_0 = \bar{y} - \sum b_i * \bar{X_i}$

y.mean() - (lr2.coef_ * Sy / Sxi) @ X.mean(axis=0)
7.076023391812864

これは,元のデータを用いて重回帰分析をしたときの切片(定数項) lr.intercept_ と一致している。

元データで重回帰分析をした結果と,標準化データで重回帰分析をした結果は相互に一次式で変換可能である。

その昔,コンピュータによる計算のコスト(時間的にも金銭的にも)が馬鹿にならない時代には32ビット浮動小数点数を使っていた。そのような場合には計算精度の点から「標準化データで重回帰分析をし,(予測値を求めるために)得られた標準化偏回帰係数を偏回帰係数に変換して...」ということが行われていた。計算アルゴリズムも,「逆行列を求めてどうのこうの」という貧弱なものであった可能性もあるが,現在は64ビット浮動小数点数を使うのが当たり前で,逆行列を使わない計算アルゴリズムが採用されることもあり,もはや標準化データで重回帰分析をしなければならないなんてことはない時代になったのである。

1.4. 決定係数

LinearRegression() で得られる数少ない情報が,決定係数である。これは重相関係数 $R$ の二乗,つまり $R^2$ である。

lr.score(X, y) # R2; coefficient of determination
0.7718952965591936

サンプルサイズ $n$,実測値 $y_i$,予測値 $\hat{y_i}$,$k$ 個の独立変数 $X_{i,j}$,$i=1,2,\dots,n$,$j=1,2,\dots,k$ として

$\hspace{2cm}\displaystyle \hat{y_{i}} = b_{0} + \sum_{j=1}^k b_j\ x_{i,j}$

$\hspace{2cm}\displaystyle S_t = \sum_{i = 1}^{n} (y_i - \bar{y})^2$

$\hspace{2cm}\displaystyle S_e = \sum_{i = 1}^{n} (y_i - \hat{y_i})^2$

$\hspace{2cm}\displaystyle R^2 = 1 - \frac{S_e}{S_t}$

重回帰分析による予測値は .predict() で得られる。

y_hat = lr.predict(X)
Se = sum((y - y_hat)**2)
St = sum((y - y.mean())**2)
R2 = 1-Se/St
(St, Se, R2)
(321.79999999999995, 73.40409356725148, 0.7718952965591936)
import matplotlib.pyplot as plt
plt.scatter(y_hat, y)
min0 = np.min(np.vstack((y_hat, y)))
max0 = np.max(np.vstack((y_hat, y)))
plt.plot([min0, max0], [min0, max0], color='red')
plt.xlabel('predicted value')
plt.ylabel('observed value')
plt.show()

output_27_0.png

$R^2$ は 0 〜 1 の値をとり,1 に近いほど(上記散布図において,データ点が原点を通る傾き 1 の直線に近いほど)予測がうまくいっていることを表す。

しかし,説明変数を増やしていけば(たとえどんなに意味のない説明変数でも),$R^2$ はどんどん 1 に近づいていく。

機械学習やさんは,$R^2$ を少しでも上げるために...(惻隠の情で,それ以上言わない)。

$R^2$ が大きくなったのが,説明変数の個数が増えただけなのか,それとも意味のある説明変数が増えたからなのか,そこが知りたい。

そのために,自由度を調整した決定係数 adjusuted R square $R_{adj.}^2$ を使う。

$\hspace{2cm}\displaystyle R_{adj.}^{2} = 1 - \frac{S_e\ /\ (n-p-1)}{S_t\ /\ (n-1)}$

n, p = X.shape
R2adj = 1 - (Se / (n - p - 1)) / (St / (n - 1))
R2adj
0.7450594490955693

$R_{adj.}^2$ が大きくなる限り,追加された独立変数は有効であることを意味する。

$R^2$ の大小だけで一喜一憂するのは意味のないことである。

1.5. 関数を作って,道具箱にしまっておく

$X$, $y$ をデータフレームで与えて,上記の統計情報を返す関数 lm() を定義しておけば,後々まで便利に使うことができる。

from sklearn.linear_model import LinearRegression
import pandas as pd

def lm(X, y):
    lr = LinearRegression()
    lr = lr.fit(X, y)
    b0, b = lr.intercept_, lr.coef_
    beta = tuple(b * X.std(axis=0) / y.std())
    R2 = lr.score(X, y)
    yhat = lr.predict(X)
    Se = sum((y - yhat)**2)
    St = sum((y - y.mean())**2)
    R2adj = 1 - (Se / (n - p - 1)) / (St / (n - 1))
    return(dict(b0=b0, b=b, beta=beta, R2=R2, yhat=yhat, R2adj=R2adj))

X = df5.drop('y', axis=1)
y = df5['y']
results = lm(X, y)
results
{'b0': 7.076023391812857,
 'b': array([2.7251462 , 3.75497076]),
 'beta': (0.45447322703523774, 0.6619322481381958),
 'R2': 0.7718952965591936,
 'yhat': array([20.03625731, 20.03625731, 26.51637427, 17.31111111, 20.03625731,
        19.00643275, 20.03625731, 23.79122807, 20.03625731, 26.51637427,
        20.03625731, 16.28128655, 21.06608187, 22.76140351, 20.03625731,
        16.28128655, 13.55614035, 23.79122807, 17.31111111, 13.55614035]),
 'R2adj': 0.7450594490955693}

2. statsmodels.formula.api を用いて重回帰分析を行う

これは R の lm() や Julia の GLM.lm() と同じように,formula を指定して分析を行うものである。

sklern.linear_model.LinearRegression() を用いる場合よりは,分析の結果としてより多くの情報が得られる。

import pandas as pd
import statsmodels.formula.api as smf

model = smf.ols(formula='y ~ x1 + x2', data=df5)
fitted = model.fit();

2.1. 結果の要約

重回帰分析の結果は .summary() で要約される。

print(fitted.summary())
                            OLS Regression Results                            
==============================================================================
Dep. Variable:                      y   R-squared:                       0.772
Model:                            OLS   Adj. R-squared:                  0.745
Method:                 Least Squares   F-statistic:                     28.76
Date:                Fri, 13 May 2022   Prob (F-statistic):           3.50e-06
Time:                        22:11:51   Log-Likelihood:                -41.381
No. Observations:                  20   AIC:                             88.76
Df Residuals:                      17   BIC:                             91.75
Df Model:                           2                                         
Covariance Type:            nonrobust                                         
==============================================================================
                 coef    std err          t      P>|t|      [0.025      0.975]
------------------------------------------------------------------------------
Intercept      7.0760      1.777      3.983      0.001       3.328      10.824
x1             2.7251      0.711      3.835      0.001       1.226       4.224
x2             3.7550      0.672      5.585      0.000       2.337       5.173
==============================================================================
Omnibus:                        1.132   Durbin-Watson:                   1.928
Prob(Omnibus):                  0.568   Jarque-Bera (JB):                0.975
Skew:                           0.483   Prob(JB):                        0.614
Kurtosis:                       2.512   Cond. No.                         12.3
==============================================================================

Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.

多くの情報を得るために,それぞれメソッドが用意されている。

  • .predict() 予測値
print(fitted.predict())
[20.03625731 20.03625731 26.51637427 17.31111111 20.03625731 19.00643275
 20.03625731 23.79122807 20.03625731 26.51637427 20.03625731 16.28128655
 21.06608187 22.76140351 20.03625731 16.28128655 13.55614035 23.79122807
 17.31111111 13.55614035]
  • .cov_params() パラメータ間の共分散行列
print(fitted.cov_params())
           Intercept        x1        x2
Intercept   3.156351 -0.782775 -0.707023
x1         -0.782775  0.505016 -0.101003
x2         -0.707023 -0.101003  0.451989
  • .params 偏回帰係数
print(fitted.params)
Intercept    7.076023
x1           2.725146
x2           3.754971
dtype: float64
  • .bse 偏回帰係数の標準誤差 Standard Error of B
print(fitted.bse)
Intercept    1.776612
x1           0.710645
x2           0.672302
dtype: float64
  • .tvalues $H_0:\text{偏回帰係数}=0$ の検定統計量

fitted.tvalues = fitted.params / fitted.bse である

print(fitted.tvalues)
Intercept    3.982875
x1           3.834751
x2           5.585247
dtype: float64
  • .pvalues

帰無仮説 $H_0:\text{偏回帰係数}=0$ の検定統計量 $t_0$ に対する $p$ 値である。

print(fitted.pvalues)
Intercept    0.000962
x1           0.001327
x2           0.000033
dtype: float64

サンプルサイズ $n$,独立変数の数 $k$ としたとき,自由度 $df = n - k - 1$ の $t$ 分布における両側有意確率($p$ 値)である。

$\hspace{2cm}p = 2\Pr{|t| > |t_0|}$

from scipy.stats import t as tdist
tuple(2 * tdist.sf(fitted.tvalues, 17))
(0.0009622611349048397, 0.001326876714709681, 3.2835740511070946e-05)

偏回帰係数の信頼区間である。信頼度 $100(1-\alpha)%$ のとき, $\alpha$ で指定する(信頼度 = 0.95 で指定するのではないので注意)。

print(fitted.conf_int()) # デフォルトで .conf_int(0.05) すなわち信頼度 = 0.95
                  0          1
Intercept  3.327699  10.824347
x1         1.225817   4.224476
x2         2.336538   5.173403
  • .t_test() 説明変数ごとの結果

説明変数(指定法に注意)ごとの偏回帰係数,標準誤差,$t$ 値,$p$ 値,信頼区間

print(fitted.t_test('x1 = 0'))
                             Test for Constraints                             
==============================================================================
                 coef    std err          t      P>|t|      [0.025      0.975]
------------------------------------------------------------------------------
c0             2.7251      0.711      3.835      0.001       1.226       4.224
==============================================================================
print(fitted.t_test('x2 = 0'))
                             Test for Constraints                             
==============================================================================
                 coef    std err          t      P>|t|      [0.025      0.975]
------------------------------------------------------------------------------
c0             3.7550      0.672      5.585      0.000       2.337       5.173
==============================================================================
print(fitted.t_test([[1,0,0], [0,1,0], [0,0,1]]))
                             Test for Constraints                             
==============================================================================
                 coef    std err          t      P>|t|      [0.025      0.975]
------------------------------------------------------------------------------
c0             7.0760      1.777      3.983      0.001       3.328      10.824
c1             2.7251      0.711      3.835      0.001       1.226       4.224
c2             3.7550      0.672      5.585      0.000       2.337       5.173
==============================================================================
  • .rsquared 決定係数
print(fitted.rsquared)
0.7718952965591936
  • .rsquared_adj 自由度調整済み決定係数
print(fitted.rsquared_adj)
0.7450594490955693
  • .aic 赤池の情報量 AIC Akaike's Information Criteria
print(fitted.aic)
88.76248995183397
  • .bic AIC の発展型である BIC
print(fitted.bic)
91.74968677249595
  • .llf 対数尤度 log-likelihood
print(fitted.llf)
-41.381244975916985

2.2. 回帰の分散分析

帰無仮説 $H_0:R^2 = 0$

重相関係数の検定である。平たく言えば,「分析に使用した独立変数で,従属変数は説明できない」ということである。

  • .f_test() 回帰の分散分析

回帰の分散分析表の主要部分である。

指定法に注意。

ftest = fitted.f_test([[0,1,0], [0,0,1]])
print(ftest)
<F test: F=28.763589359549435, p=3.5005883503838223e-06, df_denom=17, df_num=2>
ftest = fitted.f_test('x1 = x2 = 0')
print(ftest)
<F test: F=28.76358935954945, p=3.5005883503838113e-06, df_denom=17, df_num=2>

以下で,$F$ 値,$p$ 値,第 1 自由度,第 2 自由度の 4 要素を持つタプルが得られる。

ftest.fvalue, ftest.pvalue, ftest.df_denom, ftest.df_num
(28.76358935954945, 3.5005883503838113e-06, 17.0, 2.0)

$p \lt 0.05$ ゆえ,有意水準 5% で $H_0: R^2 = 0$ という帰無仮説は棄却される($p ≒ 3.5\times 10^{-6}$)。

3. 結論

  • 「変数を標準化(正規化)したから正確な解が得られる」ということではない。「標準化しなければ不正確な解しか得られない」ということもない。
  • 独立変数をむやみに増やしても,意味のないこともあるんですよということを理解したいものである。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2