【Python✗Webスクレイピング】プロ野球選手データをもとにHR数を予測する

# はじめに

2022年プロ野球が開幕して早3ヶ月。    
シーズン日程も折り返しましたが、  
みなさま、贔屓球団の調子はいかがでしょうか？

新体制に希望を抱いて迎えた我が贔屓球団は  
**セ・リーグ最下位**です！！！（2022/7/4現在）

そんな貧打に喘ぐ中日ドラゴンズの  
「ホームラン数予測」をやってみました。

プロ野球ファン歴は19年ですが、機械学習経験は4ヶ月の初心者ですので、
どうか温かい目でご覧いただけると幸いでございます。

また、気になる点がありましたら、
ご指摘いただけると明日への励みになりますので、
ぜひコメントお待ちしております！

<dl>
<dt>【データ】</dt>

[プロ野球データFreak様のサイト](https://baseball-data.com/21/stats/hitter-all/hr-1.html)
</dl>

<dl>
  <dt>【ゴール】</dt>
「2021年のプロ野球選手データを使って、中日ドラゴンズの選手のホームラン数を予測する」
</dl>

<dl>
<dt>【タスク】</dt>
・中日ドラゴンズ以外の11球団のデータを使って機械学習

・出場試合数やヒット数などの変数から「ホームラン数」の傾向を学習させる
・学習したモデルを用い、中日ドラゴンズ選手データから「ホームラン数」を予測 
</dl>

<dl>
<dt>【環境】</dt>
・macOS

・Python 3.9.7
・JupyterLab
</dl>

# ライブラリ
最初にまとめて記載する派なので、
こちらに必要ライブラリのインポートをまとめます。
```python:qiita.py
import urllib  #HTMLにアクセス＆取得
from bs4 import BeautifulSoup #HTMLからデータ抽出
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression as LR
from sklearn.metrics import mean_squared_error as MSE
import matplotlib.pyplot as plt
```

# Webスクレイピング
**※Webスクレイピングには著作権侵害等の恐れがあるため、
　規約などを確認して使用するようにしましょう。**
参照記事：[Webスクレイピングの注意事項一覧](https://qiita.com/nezuq/items/c5e827e1827e7cb29011
)


```python:qiita.py
url = 'https://baseball-data.com/21/stats/hitter-all/hr-1.html'
html = urllib.request.urlopen(url)
soup = BeautifulSoup(html, 'html.parser')

table = soup.find_all('table')[0] # HTMLから表部分を全て取得
rows = table.find_all('tr') # 表から行データを取得

# データ格納
head_data = []
player_data = []

for i, row in enumerate(rows):
    if i == 0: # 1行目
        for headerValue in row.find_all('th'):
            head_data.append(headerValue.get_text())
    elif (i > 0) and (i + 1 < len(rows)): # 2行目〜最終行未満
        player_row = []
        for playerValue in row.find_all('td'):
            player_row.append(playerValue.get_text())
        player_data.append(player_row)

# データフレーム型に変換
df = pd.DataFrame(data = player_data, columns = head_data)

# CSV出力
df.to_csv('playerdata.csv', header=True, index=False)
```

# データ分析
今回は、`目的変数「本塁打」`との相関係数が**0.7以上**の
`「打席数」「打数」「安打」「打点」「四球」「三振」`を予測に使う説明変数としました。
```python:qiita.py
# CSV読み込み
df = pd.read_csv('playerdata.csv')

# 中日以外の11球団データを抽出
df11 = df[df['チーム'] != '中日']
# 中日のデータを抽出
df_d = df[df['チーム'] == '中日']

# 本塁打との相関係数
df_homerun = pd.DataFrame(df11.corr()['本塁打'])

# 可視化（相関高い長打率）
plt.scatter(x = df11['長打率'], y = df11['本塁打'])
plt.show()

# 可視化（相関低い盗塁）
plt.scatter(x = df11['盗塁'], y = df11['本塁打'])
plt.show()

# 相関係数が0.7以上の項目を抽出
x_columns = df_homerun[df_homerun['本塁打'] >= 0.7].index.tolist()
# 目的変数を除外
x_columns.remove('本塁打')

# 説明変数と目的変数を定義
X = df11[x_columns]
y = df11['本塁打']
```
# モデル学習・評価
今回はシンプルに`線形回帰モデル`で学習していきます。
```python:qiita.py
# データ分割
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state = 1)

# モデルの学習
lr = LR()
lr.fit(X_train, y_train)

# 予測値の算出
y_pred_test = lr.predict(X_test)
y_pred_test

# MSEの算出
mse_test = MSE(y_test, y_pred_test)
mse_test
```

# 予測
さあ、いよいよ予測です！
```python:qiita.py
# 中日データ（説明変数）
x_d = df_d[x_columns]
# 予測本塁打数の算出
y_pred_d = lr.predict(x_d)
# 予測本塁打列の追加
df_d['予測本塁打'] = np.round(y_pred_d).astype(np.int32)
# 予測値と実測値との比較
print(df_d[['選手名', '本塁打', '予測本塁打']])
```
| 選手名 | 本塁打 | 予測本塁打 |
|:-----------|------------:|:------------:|
| ビシエド       | 17        | 17         |
| 木下拓哉     | 11      | 11       |
| 福田永将       | 8        | 7         |
| 高橋周平       | 5          | 8           |
| 阿部寿樹       | 5       | 3       |
| 堂上直倫    | 5     | 6      |
| 福留孝介    | 4     | 6      |
| 京田陽太    | 3     | 1      |
| 渡辺勝    | 2     | 2      |
| A.マルティネス    | 2     | 2      |
以下略

**数本の誤差はありますが、概ね正確に予測できました。**
それにしてもチーム本塁打数少な、、、（パァン）

# おまけ
今回のコード全文を記しておきます。
```python:qiita.py
# ライブラリ
import urllib  #HTMLにアクセス＆取得
from bs4 import BeautifulSoup #HTMLからデータ抽出
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression as LR
from sklearn.metrics import mean_squared_error as MSE
import matplotlib.pyplot as plt

# スクレイピング
url = 'https://baseball-data.com/21/stats/hitter-all/hr-1.html'
html = urllib.request.urlopen(url)
soup = BeautifulSoup(html, 'html.parser')

table = soup.find_all('table')[0] # HTMLから表部分を全て取得
rows = table.find_all('tr') # 表から行データを取得

# データ格納
head_data = []
player_data = []

for i, row in enumerate(rows):
    if i == 0: # 1行目
        for headerValue in row.find_all('th'):
            head_data.append(headerValue.get_text())
    elif (i > 0) and (i + 1 < len(rows)): # 2行目〜最終行未満
        player_row = []
        for playerValue in row.find_all('td'):
            player_row.append(playerValue.get_text())
        player_data.append(player_row)

# データフレーム型に変換
df = pd.DataFrame(data = player_data, columns = head_data)

# CSV出力
df.to_csv('playerdata.csv', header=True, index=False)

# CSV読み込み
df = pd.read_csv('playerdata.csv')

# 中日以外の11球団データを抽出
df11 = df[df['チーム'] != '中日']
# 中日のデータを抽出
df_d = df[df['チーム'] == '中日']

# 本塁打との相関係数
df_homerun = pd.DataFrame(df11.corr()['本塁打'])

# 可視化（相関高い長打率）
plt.scatter(x = df11['長打率'], y = df11['本塁打'])
plt.show()

# 可視化（相関低い盗塁）
plt.scatter(x = df11['盗塁'], y = df11['本塁打'])
plt.show()

# 相関係数が0.7以上の項目を抽出
x_columns = df_homerun[df_homerun['本塁打'] >= 0.7].index.tolist()
# 目的変数を除外
x_columns.remove('本塁打')

# 説明変数と目的変数を定義
X = df11[x_columns]
y = df11['本塁打']

# モデルの作成・評価
# データ分割
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state = 1)

# モデルの学習
lr = LR()
lr.fit(X_train, y_train)

# 予測値の算出
y_pred_test = lr.predict(X_test)
y_pred_test

# MSEの算出
mse_test = MSE(y_test, y_pred_test)
mse_test

# 予測
# 中日データ（説明変数）
x_d = df_d[x_columns]
# 予測本塁打数の算出
y_pred_d = lr.predict(x_d)
# 予測本塁打列の追加
df_d['予測本塁打'] = np.round(y_pred_d).astype(np.int32)
# 予測値と実測値との比較
print(df_d[['選手名', '本塁打', '予測本塁打']])
```