防御率と援護率と勝率の関係を探る

趣味で嗜む野球に関するデータ分析を気まぐれにまとめています。今回はプロ野球における防御率と援護率、そして勝率[^1]の関係を2022年シーズンデータを使って分析し、そこから投打がかみ合っていい感じに勝ち試合を作れた投手を示す指標を作ってみた話です[^2]。

# 概要
「えっ、この防御率でこの勝利数？」「またこの投手援護点ないよ・・・」というのはプロ野球をみてると結構ありますよね。どんなに防御率がよくても援護が少なければ勝ち投手にはなれないし、逆にどんなに援護があってもそれ以上に失点しても勝てない。今回の分析はここの関係性を2022年データを使って見ていきます。

### やったこと
* スクレイピングでデータを取得し、投手別2022年シーズン援護率を算出する
* 防御率および援護率を使い勝率との関係を調べる（プロット、回帰）

# データを取得して投手別2022年シーズン援護率を算出する
### 援護率について
援護率はどのように計算されているのか。[こちらのサイト](https://base-info.com/engoritsu)を参考にしました[^3]

:::note
援護率 =「降板する前に取った味方の得点×9」÷「降板するまでの攻撃回数」
:::
上記の定義にそって援護率を算出するために、各試合から以下データを取得します
* 先発投手（最初に投げた投手と定義）
*  先発投手が降板するまでに味方がとった得点
* 先発投手が降板するまでの攻撃回数[^4]

### 試合別データを取得する
2022年3月～10月のペナント全試合別データがほしかったので、[朝日新聞が公開しているプロ野球の試合結果データ](https://www.asahi.com/sports/baseball/npb/schedule/)を拝借することにしました。

一応コード載せますがもっといい書き方があるはず・・・
まずは、スクレイピング用のurlに埋め込む試合日＋ゲームNo.のリストを作成
```python
#試合がない日もあるし試合数も日によってバラつきがあるので最大リストを作ってurlがエラーになるのはexceptではじく
from calendar import Calendar

cl = Calendar()
#1日最大6試合と思ったらなんか規則性が分からないから15くらいにしとく
game_no_list = ['01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12', '13', '14', '15']
game_list = [j.strftime('%Y%m%d')+str(g) for g in game_no_list for i in range(3, 11) for j in list(cl.itermonthdates(2022, i))]

```
いざデータ取得！

```python
#空のデータフレーム準備しとく
data = pd.DataFrame()

for game in game_list:

  url = 'https://www.asahi.com/sports/baseball/npb/game/CBN'+str(game)+'.html'

  try:
    df = pd.read_html(url, header = 0)

    #イニング累計スコア取得------------------------
    data_score = pd.DataFrame(df[1])

    #雨天中止等でイニングにNaNが入っている場合０に置換
    data_score.fillna(0, inplace=True) 
    #9回裏がなくXが入って文字列になってる場合は修正、延長サヨナラはｘが入らないことは確認済
    data_score['9'] = data_score['9'].apply(lambda x: '0' if x == 'X' else x).astype(int) 

    data_score = data_score.drop(columns = '計').set_index('チーム').T
    data_score = data_score.cumsum()
    #indexをイニングとしてカラムにしておく
    data_score = data_score.reset_index().rename(columns = {'index': 'イニング'})

    #ピッチャーの投球回数と攻撃回数を取得---------------
    for i,j in zip(range(0,2), range(4,6)):
      
      #選手名と投球回数の端数と分けたカラムだけにする
      data_p= pd.concat([df[j]['選手名'], df[j]['投球回数'].str.split(' ', expand = True)], axis = 1)
      #最初に投げた先発ピッチャーだけ抜き出して端数を切り捨てた投球回数カラムだけのこす
      data_p = data_p[data_p.index==0][['選手名', 0]].rename(columns = {0:'攻撃回数'})

      #イニング累計得点とイニングと投球回数で結合する
      data_p = pd.merge(data_p, data_score.iloc[:,[0,i+1]], left_on = '攻撃回数', right_on = 'イニング', how = 'left')

      #チーム名の取得
      data_p['チーム'] = df[1].iat[i,0]
      #援護点カラムにする
      data_p['援護点'] = data_p.iloc[:, 3]
      #必要なカラムだけ残す
      data_p = data_p[['チーム', '選手名', '攻撃回数', '援護点']]
      #試合日入れる
      data_p['日付'] = game

      data = data.append(data_p)

  except:
    pass
```
取得したデータからオープン戦・オールスター・CSを除いたり、重複を除いたり、名前の表記ゆれを修正したりして、1716レコード（143x12）できました。

### 試合別データから投手別援護率を算出する
投手別にgroupbyして集計する
```python
data_rs9 = data3.drop(columns = ['日付2']).groupby(['チーム', '選手名']).agg({'日付':'count', '攻撃回数': 'sum','援護点':'sum'}).reset_index()
data_rs9['援護率'] = ((data_rs9['援護点']*9)/data_rs9['攻撃回数']).round(2)
#微修正
data_rs9.rename(columns = {'日付':'先発試合数'}, inplace=True)
```
### 投球回数100イニング以上の投手を対象にする
本来防御率は（援護率も？）規定にのっていることが前提ですが、対象投手が少ないので100イニング以上投げた投手を対象とします。

いつもお世話になっている[プロ野球データFreak](https://baseball-data.com/stats/pitcher-all/ip3-1.html)から100イニング以上投げた投手と防御率を取得し、上記で作成した投手別援護率データと結合します。
```python
url = 'https://baseball-data.com/stats/pitcher-all/ip3-1.html'
data_freak = pd.read_html(url, header = 1)
target_p = pd.DataFrame(data_freak[0])
target_p = target_p[['選手名', 'チーム', '試合', '防御率', '投球回', '勝利', '勝率']]
#100イニング以上投げた投手に絞る
target_p = target_p.query('投球回>=100')

#援護率データと結合
target_p2 = pd.merge(target_p, data_rs9, on = ['選手名', 'チーム'], how = 'left')
print(target_p2.shape[0])
```
50名が対象です。

# 防御率と援護率、そして勝率との関係を調べる
まずは、防御率と勝率、援護率と勝率、それぞれの関係をプロットしてみます。
防御率は小さいほどいいマイナスに働く指標なので、わかりやすくするため-1をかけて大きいほどいい指標として扱います。

勝率との関係を選手名付きでプロット↓
(選手名が被って見えないのは頑張って読み解く）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/937675/4f4cfb77-c1c9-8285-9659-b5cd625b5faa.png)
左）防御率のみでも勝率と相関関係にありそうなことが分かります。ただ、防御率のみでは説明できないケースも見えてきます（日ハムの加藤投手や阪神の西投手より高い防御率でも勝率が高い投手が結構いたり）
右）援護率単体でも、防御率よりは薄そうながらも右肩上がりの関係に見えます。援護点は投手自身より野手による影響が大きいものですが、投手によるバラつきが結構大きいのが面白いです。といっても、例えば1試合で大量得点があったりすると援護率も高くなるので一概に言えない部分もありますが、広島の森下投手やソフトバンクの大関投手は援護率がすごいです。

このプロットから回帰式を算出し回帰直線を引き、また相関係数を出してみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/937675/6422c85a-f62d-fef1-63f1-561a792e47b5.png)
防御率と勝率は相関係数が0.53とやはりある程度の関係性が見えますが、やはりこれだけでは勝率を表しきれていない感があります。援護率もやはりこれだけでは弱い。野球は投手と野手、どちらも必要なんだ（当たり前）

そこで、防御率と援護率を組み合わせ「(T)投打がかみ合って(K)勝ち試合を作れた(P)ピッチャー（TKP)」指標を作ってみます。
援護点＞防除率の関係であれば勝試合は増えるはずなので、まずは
```math
援護率-防御率　①
```
と置きます。しかしこれだけだと援護率が高かった投手が高く評価されるので、①を防御率で割り、より失点を抑え勝てる試合運びに貢献したピッチャーを高く評価するようにします。
```math
TKP=(援護率-防御率)/防御率　 ②
```

早速TKP（投打がかみ合って勝ち試合を作れたピッチャー）ランキングTOP10を見てみます！
| チーム | 選手名 | 勝利数 | 勝率 | 防御率 | 援護率 | 援護率-防御率 | TKP |
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
|阪神|青柳晃洋|13|0.765|2.05|4.14|2.09|1.02|
|オリックス|山本由伸|15|0.75|1.68|3.09|1.41|0.84|
|ソフトバンク|千賀滉大|11|0.647|1.94|3.52|1.58|0.81|
|ソフトバンク|大関友久|7|0.538|2.93|5.29|2.36|0.81|
|ヤクルト|高橋奎二|8|0.8|2.63|4.5|1.87|0.71|
|広島|森下暢仁|10|0.556|3.17|5.4|2.23|0.7|
|オリックス|田嶋大樹|9|0.75|2.66|4.46|1.8|0.68|
|西武|エンス|10|0.588|2.94|4.93|1.99|0.68|
|DeNA|今永昇太|11|0.733|2.26|3.52|1.26|0.56|
|ロッテ|美馬学|10|0.625|2.91|4.54|1.63|0.56|

トップは阪神の青柳投手。防御率も2.05と失点を抑える青柳投手に対し、援護率4.14と対象ピッチャーの中でも高いほうです（上の方に載せた「援護率と勝率」のプロット参照）まさに勝てる試合って感じがします。私はパリーグファンなのでセリーグの投手はあまり詳しくないのですが、青柳投手は交流戦でも全く打てる気がしなかった覚えがあります。

投手成績では山本由伸投手が最強ですが、そこに援護率が入り「投げた試合で勝つ可能性」という観点となると、必ずしもトップではないのが興味深いです。山本由伸投手の場合、援護率がTKPトップ10の中で最も低くても（対象50名の中でも低い方）、投手力で最多勝をもぎ取ることができるということです！

エースピッチャーは対戦相手もエースピッチャーとあたることが多いので、援護点が少ない傾向にありそうです。そんな中でも投手が根負けせず投げ勝つことで勝星を積み上げるからこそエースなんですね。

①援護率-防御率と②TKPを選手名付きでプロットしたものがこちら。↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/937675/41fe4de4-acc7-d94d-4dd5-fcd4e213fede.png)
TKPはきれいな右肩上がりで結構いい感じに勝率を表してそうです。名前の並びも納得感あります。

このプロットに先ほどと同様に回帰直線と相関係数を入れたものがこちら↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/937675/65db9580-43c9-7160-6085-59a7d2666d79.png)
TKPと勝率の相関係数が0.76と強い相関といえそうなレベルまで上がりました。決定係数も0.578まであがり、回帰式としてのあてはまりもよくなっています。防御率・援護率単体で見るよりも、2つの指標を組み合わせ守りと攻めの両面を勘案した指標とすることで勝率との関係性が強くなることが分かります。

# 参考
appendix的に各指標のトップ10を並べた指標も置いておく
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/937675/74f5e0f3-2dfb-b5c7-7587-bb34c77fcfaf.png)
# おわり
他にも残差プロットや他の指標を入れた重回帰や変数の強さ比較などもやってみましたがまとめ疲れたので今回は書くのを終わります。また気が向いたら書く。
今回の分析では「野球は投手と野手、両方がかみ合った時に勝てる」ということをデータで定量的に比較することでより明確に示せすことができたと思います。
しかしこの「投打がかみ合う」という試合を毎試合するのは難しいし、指標では表すことのできないプレーや流れが勝敗を決めることもあるので、データ分析では語れない部分もたくさんあるのが面白いところです。来シーズンも楽しみ！その前に日本シリーズ！ドラフト！！


[^1]:勝利数と勝率、どちらにするか迷いましたが防御率も援護率も割合指標なので今回は勝率を使うことにしました。勝利数でもいい気もします。100イニング以上投げている投手が対象なので傾向は変わらないはず。
[^2]:所々適当になっているところもありますが趣味なので大目にみてください
[^3]:援護率は公式記録ではないので、計算する人によって変わる場合もあるらしい。
[^4]:「先発投手が降板するまでの攻撃回数」はイニング途中の継投、先行・後攻、9回完投等、場合分けが多く機械的なデータ取得がややこしかったので今回は、先発投手が完投したイニングの投球回数＝攻撃回数とした。微妙な誤差はでるがマイナーケースのため許容した。