[Python] 単回帰分析

Python で単回帰分析を行ってみます。

numpy でよりプリミティブに行う方法と、sklearn でより簡単に導出する方法を試します。

scipy にも簡単に線形回帰を行うメソッドが用意されています。

単回帰自体については、以下が分かりやすいです。

データの関係を知る (2) - 回帰と決定係数

線形単回帰

散布図上にばらついている データを,\( y = a + bx \)という直線のモデルで表す場合, 結果として、\( \bar{x} \) \( \bar{y} \) をそれぞれ \( x\)と\(y\) の平均、\( \sigma_{xy} \) を\( x\)と\(y\)の共分散、\( {\sigma_{x}}^2 \)を\(x\)の分散として、

$$ b = \frac { \sigma_{xy} } { {\sigma_{x}}^2 }$$

$$ a = \bar{y} – b \bar{x} $$

と表すことができます。

また、回帰直線のデータへの当てはまり具合を示す決定係数\( r^2 \) は、

$$ r^2 = { ( \frac{ \sigma_{xy} } { \sigma_{x} \sigma_{y}} ) }^2 $$

と、相関係数\( r\) の二乗として表すことができます。

データの準備

ipython で進めていきます。

線形回帰と言えば出てくる下記の有名なデータを使います。

sklearn.datasets.load_boston

In [1]: from sklearn.datasets import load_boston
In [2]: boston = load_boston()

データフレームへの変換

読み込んだデータを、データフレームに変換します。

In [8]: import pandas as pd
In [9]: df_boston = pd.DataFrame(boston.data)
In [11]: df_boston.columns = boston.feature_names
In [12]: df_boston['Price'] = boston.target

データの確認

データの内容を確認します。

In [18]: df_boston.head()
Out[18]: 
      CRIM    ZN  INDUS  CHAS  ...    PTRATIO       B  LSTAT  Price        
0  0.00632  18.0   2.31   0.0  ...       15.3  396.90   4.98   24.0        
1  0.02731   0.0   7.07   0.0  ...       17.8  396.90   9.14   21.6        
2  0.02729   0.0   7.07   0.0  ...       17.8  392.83   4.03   34.7        
3  0.03237   0.0   2.18   0.0  ...       18.7  394.63   2.94   33.4        
4  0.06905   0.0   2.18   0.0  ...       18.7  396.90   5.33   36.2        

[5 rows x 14 columns]

In [19]: df_boston.describe()
Out[19]: 
             CRIM          ZN     ...           LSTAT       Price
count  506.000000  506.000000     ...      506.000000  506.000000
mean     3.593761   11.363636     ...       12.653063   22.532806
std      8.596783   23.322453     ...        7.141062    9.197104
min      0.006320    0.000000     ...        1.730000    5.000000
25%      0.082045    0.000000     ...        6.950000   17.025000
50%      0.256510    0.000000     ...       11.360000   21.200000
75%      3.647423   12.500000     ...       16.955000   25.000000
max     88.976200  100.000000     ...       37.970000   50.000000

[8 rows x 14 columns]

In [27]: df_boston.columns
Out[27]: 
Index(['CRIM', 'ZN', 'INDUS', 'CHAS', 'NOX', 'RM', 'AGE', 'DIS', 'RAD', 'TAX',
       'PTRATIO', 'B', 'LSTAT', 'Price'],
      dtype='object')

価格 Price を目的変数に、部屋の数 RM average number of rooms per dwelling を説明変数とします。

In [34]: df_boston = df_boston[['RM', 'Price']]

In [35]: df_boston.head()
Out[35]: 
      RM  Price
0  6.575  24.0
1  6.421  21.6
2  7.185  34.7
3  6.998  33.4
4  7.147  36.2

In [36]: df_boston.describe()
Out[36]: 
               RM       Price
count  506.000000  506.000000
mean   6.284634    22.532806
std    0.702617    9.197104
min    3.561000    5.000000
25%    5.885500    17.025000
50%    6.208500    21.200000
75%    6.623500    25.000000
max    8.780000    50.000000

相関係数を確認します。

In [41]: import numpy as np
In [41]: np.corrcoef(df_boston['RM'], df_boston['Price'])
Out[41]: 
array([[1.        , 0.69535995],
       [0.69535995, 1.        ]])

大体0.7ぐらいで相関はあります。

散布図を確認します。

In [43]: import matplotlib.pyplot as plt
In [44]: plt.scatter(df_boston['RM'], df_boston['Price']);
In [45]: plt.show()

回帰直線の導出

$$ b = \frac { \sigma_{xy} } { {\sigma_{x}}^2 }$$

に基づいて、\( b \) を求めます。

まずは、共分散\( \sigma_{xy} \)を求めます。

# df_boston['RM']の平均
In [30]: mean_rm = np.mean(df_boston['RM'])
# df_boston['Price']の平均
In [31]: mean_price = np.mean(df_boston['Price'])
# 共分散 
In [32]: cov_rm_price = np.sum((df_boston['RM'] - mean_rm)*(df_boston['Price'] - mean_price)) * (1/len(df_boston['RM']))
In [33]: cov_rm_price
Out[33]: 4.484565551719289

実は、numpy は共分散を求める関数 cov があるので、そちらを使ってみます。

# numpy.cov()による共分散
In [34]: cov_rm_price = np.cov(df_boston['RM'], df_boston['Price'])
In [35]: cov_rm_price
Out[35]: 
array([[ 0.49367085,  4.49344588],
       [ 4.49344588, 84.58672359]])

なぜか最初と異なる値が出ました…。

numpy.cov

ドキュメントを読むと、色々とオプションが設定されるようなので、標本共分散を求めるよう設定します。

In [36]: cov_rm_price = np.cov(df_boston['RM'], df_boston['Price'], ddof=0)
In [37]: cov_rm_price
Out[37]: 
array([[ 0.49269522,  4.48456555],
       [ 4.48456555, 84.41955616]])

同じ値になりました。

\( {\sigma_{x}}^2 \) を求めます。といっても、上の標本共分散で出てしまっていますが…一応。

# df_boston['RM'] の分散
In [40]: var_rm = np.sum((df_boston['RM'] - mean_rm)**2) * (1/len(df_boston['RM']))
    ...: var_rm
Out[40]: 0.49269521612976297
# numpy の var() で求めることもできる。
In [41]: np.var(df_boston['RM'])
Out[41]: 0.49269521612976297

b と a を求めます。

In [45]: b = cov_rm_price / var_rm
In [46]: b
Out[46]: 9.102108981180308
In [47]: a = mean_price - b * mean_rm
In [48]: a
Out[48]: -34.670620776438554

\( y = -34.670620776438554 + 9.102108981180308 * x \) の直線が導出されました。

図示してみます。

In [57]: x = np.arange(4, 9, 0.01)

In [58]: plt.scatter(df_boston['RM'], df_boston['Price'])
    ...: plt.plot(x, a + b * x, color='red');

なとなくそれっぽい直線です。

決定係数を計算します。

# 相関係数
In [60]: r = cov_rm_price / (np.std(df_boston['RM']) * np.std(df_boston['Price']))
# 決定係数
In [61]: r**2
Out[61]: 0.4835254559913339

numpy.linalg.lstsq

numpy は最小二乗法を解く関数を持っているので、こちらを使って単回帰の直線を導出してみます。

numpy.linalg.lstsq

ちょっと面倒ですが、ドキュメントによると、導く直線を y = Ap と考えて、 A = [[x, 1]] 、 p = [[m], [c]]としなければなりません。

ドキュメントの真似をして書いてみます。

In [72]: A = np.vstack([df_boston['RM'], np.ones(len(df_boston['RM']))]).T

In [73]: b, a = np.linalg.lstsq(A, df_boston['Price'])[0]

In [74]: b
Out[74]: 9.102108981180315

In [75]: a
Out[75]: -34.670620776438575

上とほぼ同じ値が導出されました。

sklearn.linear_model.LinearRegression

scikit-learn にも、線形回帰による予測を行うクラスとして、sklearn.linear_model.LinearRegression が用意されているので、こちらを使ってみます。

sklearn.linear_model.LinearRegression

これもドキュメント通りに進めてみます。

In [95]: from sklearn.linear_model import LinearRegression

In [96]: x = df_boston[['RM']].values

In [97]: y = df_boston['Price'].values

In [98]: reg = LinearRegression().fit(x, y)

In [99]: reg.coef_[0]
Out[99]: 9.10210898118031

In [100]: reg.intercept_
Out[100]: -34.67062077643857

上とほぼ同じ値が導出されました。