scikit-learnで線形単回帰

# 1.やること

線形回帰を行う。
説明変数が一つの場合、目的変数$y$と説明変数$x$の間に$y=ax+b$的な関係があるとして、$a$と$b$を求めたい。
説明変数が複数の場合は[sklearn公式](https://scikit-learn.org/stable/modules/linear_model.html)には以下のようにある。

```math
\hat{y}(w, x) = w_0 + w_1 x_1 + ... + w_p x_p
```

データとして、2019年シーズンの北海道日本ハムファイターズの各試合データを用いる。データは一行＝一試合で、各試合のデータが記されている。
今回は「失点」を目的変数、「被安打」のみを説明変数として線形回帰モデルを構築する。

モデル式$y=ax+b$において、$b$は被安打0の時の失点、$a$は1本ヒットを打たれたときに増える失点数ということになりそうだ。

# 2.データ

まず、データ（csvファイル）を``pd.DataFrame``として読み込む。

```py:2-1
from pathlib import Path

import pandas as pd

p = Path("D:/data/")
df = pd.read_csv(p.joinpath("file.csv"))

df.head()
```

>|  試合 | 失点 | 被安打 |
>| ----: | ---: | -----: |
>| **1** |    3 |     10 |
>| **2** |    4 |     11 |
>| **3** |    1 |      6 |
>| **4** |    3 |      8 |
>| **5** |    3 |     10 |

つづいて各列を説明変数・目的変数に代入。
ちなみにここで各変数の型を``pd.DataFrame``（行列的なやつ）ではなく``pd.Series``（ベクトル的なやつ）にしてしまうと後で面倒ということがわかった。

```py:参考資料
print(type(df[["失点"]]))
print(type(df.loc[:,["失点"]]))
print(type(df.iloc[:, 0:1]))
# <class 'pandas.core.frame.DataFrame'>

print(type(df.loc[:, "失点"]))
print(type(df.iloc[:, 0]))
# <class 'pandas.core.series.Series'>
```

つまりこうする。1列の``pd.DataFrame``。

```py:2-2
y = df.loc[:,["失点"]]
x = df.loc[:,["被安打"]]
```

# 3.基礎分析

プロットしてみる。

```py:3-1
import matplotlib.pyplot as plt

plt.scatter(x, y)
```

>![20190426_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/394333/4bb53491-2043-4d09-2027-d4bba6ae4227.png)

x軸（横軸）が被安打、y軸（縦軸）が失点。
被安打が多いほど失点も多いという相関がありそう。

# 4.学習

まずモデルを設定。線形回帰は``sklearn.linear_model.LinearRegression()``らしい。

```py:4-1
from sklearn import linear_model

model = linear_model.LinearRegression()
```

つづいてモデルをデータに合わせて学習させる。sklearnでは``model.fit(x, y)``でこれを行う。

```py:4-2
model.fit(x, y)
```

ここで``x``と``y``の型が``pd.Series``（ベクトル的なやつ）だとエラーがでる。行列計算するときにn行1列にすべきところ1行n列になるためと思われる。

```py:4-2b
y = df.iloc[:, 0]
x = df.loc[:, "被安打"]

model.fit(x, y)
```

>ValueError: Expected 2D array, got 1D array instead:　（以下略）

この場合は``pd.Series.values``で``numpy.ndarray``型にし、``np.reshape(-1, 1)``でn行1列に変形するとうまくいった。

```py:4-2c
y = df.iloc[:, 0]
x = df.loc[:, "被安打"]

model.fit(x.values.reshape(-1, 1), y.values.reshape(-1, 1))

# これもあり
model.fit(pd.DataFrame(x), pd.DataFrame(y))
```

# 5.結果

学習後、モデル式$ax+b$の、$a$は``model.coef_``、$b$は``model.intercept_``で取得できる。

```py:5-1
print(model.coef_, model.intercept_)
```

>[[0.8755288]] [-3.17832737]

（単回帰でも行列（切片はベクトル）で返ってくるのか……）
ともかくこれによると

```math
失点 = 被安打×0.88 - 3.18
```

という式に表すことができるという。
被安打がわかったところでこんな計算してられないが、予測失点数は``model.predict(x)``で求めることができるので数式を立てる必要はない。

モデルの決定係数を``model.score(x, y)``で取得できる。
``model.score(x, y)``は「``x``からの予測値」と「``y``」間のR2を算出するもので、``sklearn.metrics.r2_score(y, model.predict(x))``を返す。引数を間違えそうになった。

```py:5-2
print(model.score(x, y))
```

>0.7048504590959697

ちなみに0.70≒0.84^2。かなり高い。

プロットしてみる。

```py:5-3
lx = [[-100], [100]]

fig = plt.figure(figsize=(4, 4))
ax = fig.add_axes((0, 0, 1, 1), xlim=(-0.5, 23), ylim=(-0.5, 23))
ax.scatter(x, y, color="C0", zorder=10)
ax.plot(lx, model.predict(lx), color="C1", zorder=9)
[ax.spines[side].set_visible(False) for side in ["right", "top"]]
ax.grid(True, axis="both", color="gainsboro", alpha=0.8, zorder=7)
```

>![20190426_02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/394333/b88e1914-8cb9-4cab-b21c-b005f3a313eb.png)

はい。

### でもさ

失点はマイナスにならないという文句が聞こえてくる。
そういうときはモデル設定時に``fit_intercept=False``を与えると切片0モデルになる。

```py:5b
model = linear_model.LinearRegression(fit_intercept=False)
model.fit(x, y)

print(model.coef_, model.intercept_, model.score(x, y))

fig = plt.figure(figsize=(4, 4), facecolor="white")
ax = fig.add_axes((0, 0, 1, 1), xlim=(-0.5, 23), ylim=(-0.5, 23))
ax.scatter(x, y, color="C0", zorder=10)
ax.plot(lx, model.predict(lx), color="C1", zorder=9)
[ax.spines[side].set_visible(False) for side in ["right", "top"]]
ax.grid(True, axis="both", color="gainsboro", alpha=0.8, zorder=7)
```

>[[0.54919786]] 0.0 0.5906048285153787
>![20190426_03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/394333/0d476ad7-db77-16c2-866c-5244c9ceb01d.png)


### 結局

現実的な解釈。

```py:5c
lx = [[(0-model.intercept_)/model.coef_], [100]]

fig = plt.figure(figsize=(4, 4), facecolor="white")
ax = fig.add_axes((0, 0, 1, 1), xlim=(-0.5, 23), ylim=(-0.5, 23))
ax.scatter(x, y, color="C0", zorder=10)
ax.plot([[0]]+lx, [[0]]+model.predict(lx).tolist(), color="C1", zorder=9)
[ax.spines[side].set_visible(False) for side in ["right", "top"]]
ax.grid(True, axis="both", color="gainsboro", alpha=0.8, zorder=7)
```

>![20190426_04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/394333/e2d45a0c-e6e9-3bef-2c04-23bd000be7de.png)

### ちな

今回はGoogle Colaboratoryを使った。