山本由伸投手の投球分析

## はじめに
　今回はメジャー挑戦1年目でワールドシリーズ優勝に貢献したドジャースの山本由伸投手の投球内容を分析します。シーズン中には怪我での離脱がありましたが、最終的にはレギュラーシーズン18試合、ポストシーズン4試合に先発してワールドシリーズ制覇に貢献したシーズンでした。今回はその22試合で投げた全1751球を対象として、全投球の傾向や怪我の前後での変化などを分析します。

## 投球データの分析

### 全投球データの取得と可視化
　まずはpybaseballをインストールして、山本投手のレギュラーシーズンとポストシーズンの投球データを取得します。
 
```python
# pybaseballのインストール
!pip install pybaseball
import pybaseball as pb
```
```python
# 山本投手のデータを取得
yamamoto_data = pb.playerid_lookup('Yamamoto', 'Yoshinobu')

# 投球のデータを取得
player_id = yamamoto_data.iloc[0, 2] # player_id = 808967
start_date = '2024-01-01'
end_date = '2024-12-31'
yamamoto_pitch = pb.statcast_pitcher(start_date, end_date, player_id)

# レギュラーシーズンとポストシーズンの投球データのみ抽出
yamamoto_pitch = yamamoto_pitch.query("game_type != 'S' and game_type != 'E'").sort_values(by=['game_date', 'inning', 'outs_when_up'])
yamamoto_pitch.head()
```
```
	pitch_type	game_date	release_speed	release_pos_x	release_pos_z	player_name	batter	pitcher	events	description	...	n_thruorder_pitcher	n_priorpa_thisgame_player_at_bat	pitcher_days_since_prev_game	batter_days_since_prev_game	pitcher_days_until_next_game	batter_days_until_next_game	api_break_z_with_gravity	api_break_x_arm	api_break_x_batter_in	arm_angle
1731	FF	2024-03-21	95.4	-1.38	5.42	Yamamoto, Yoshinobu	673490	808967	sac_fly	hit_into_play	...	1	0	NaN	1.0	9.0	7.0	1.11	0.74	0.74	NaN
1732	FC	2024-03-21	89.6	-1.25	5.46	Yamamoto, Yoshinobu	673490	808967	NaN	ball	...	1	0	NaN	1.0	9.0	7.0	2.08	-0.40	-0.40	NaN
1733	FF	2024-03-21	94.7	-1.24	5.60	Yamamoto, Yoshinobu	673490	808967	NaN	ball	...	1	0	NaN	1.0	9.0	7.0	1.26	1.32	1.32	NaN
1734	FF	2024-03-21	96.1	-1.27	5.40	Yamamoto, Yoshinobu	673490	808967	NaN	called_strike	...	1	0	NaN	1.0	9.0	7.0	1.12	0.59	0.59	NaN
1735	CU	2024-03-21	79.3	-1.04	5.63	Yamamoto, Yoshinobu	673490	808967	NaN	ball	...	1	0	NaN	1.0	9.0	7.0	5.27	-1.17	-1.17	NaN
5 rows × 113 columns
```
表示されるデータ項目は以下の通りです。

https://baseballsavant.mlb.com/csv-docs

今回の分析で扱う投球データは、以下のようにレギュラーシーズン(R)の1469球とポストシーズン(ディビジョンシリーズ(D)123球、リーグチャンピオンシップシリーズ(L)73球、ワールドシリーズ(W)86球)の282球の合計1751球となります。
```python
yamamoto_pitch['game_type'].value_counts()
```
```
	count
game_type	
R	1469
D	123
W	86
L	73

dtype: int64
```
また、以下のように試合日でソートして、登板日と投球数を以下のようにまとめました。怪我で緊急降板となった試合の前の試合の106球が最大の球数で、怪我からの復帰後は最後の登板となったワールドシリーズでの86球が最大でした。
```python
yamamoto_pitch['game_date'].value_counts().sort_index()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3840780/31f7982c-4726-f0b3-646e-c3798fc028d1.png)

　ここからは各投球データを可視化して、今季の全投球を分析します。まずは球種についてです。
```python
import pandas as pd
pd.crosstab(yamamoto_pitch['pitch_name'], yamamoto_pitch['type'], margins=True)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3840780/2ac0a8a6-6d4a-72e5-73d6-35f6295db358.png)
Bがボール、Sがストライク、Xがヒットやゴロなどを含めて打者がバットに当てたときを表しています。また、割合は以下のようになります。
```python
import pandas as pd
pd.crosstab(yamamoto_pitch['pitch_name'], yamamoto_pitch['type'], normalize='index')
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3840780/1052219f-3b56-1915-8c46-3353893f193a.png)
フォーシームとカーブはストライク率が50%を超え、ボール率は共に30%程度となっています。スイーパーと判定されている1球を除けば、カーブはバットに当たっている割合が最も低く、ストライクを取るために特に安定して使うことができるボールであったと言えます。また、球種ごとの球速とコースを可視化すると以下のようになります。なお、コースは打者目線となっています。
```python
import matplotlib.pyplot as plt
import seaborn as sns

plt.figure(figsize=(8,4))
sns.boxplot(x='pitch_name', y='release_speed', data=yamamoto_pitch)
plt.show()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3840780/65436e83-7ca8-4685-86da-005607fa47fb.png)

```python
import matplotlib.pyplot as plt
import seaborn as sns

sns.scatterplot(x=yamamoto_pitch['plate_x'], y=yamamoto_pitch['plate_z'], hue=yamamoto_pitch['pitch_name'])
plt.xlim(-3, 6)
plt.ylim(-2, 6)

# ストライクゾーンを描画
x = [-0.83, 0.83, 0.83, -0.83]
y = [1.5, 1.5, 3.5, 3.5]
plt.fill(x, y, color='k', alpha=0.3)

plt.title('pitch course')
plt.show()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3840780/5f4e7bc2-7f95-4fd5-898e-410bff0d67c7.png)

各球種ごとの打席結果は以下のようになっています。
```python
import pandas as pd
pd.crosstab(yamamoto_pitch['pitch_name'], yamamoto_pitch['events'], margins=True)
```
```
events	double	field_error	field_out	fielders_choice	force_out	grounded_into_double_play	hit_by_pitch	home_run	sac_bunt	sac_fly	single	strikeout	triple	truncated_pa	walk	All
pitch_name																
4-Seam Fastball	8	1	78	0	1	2	0	5	1	2	25	29	1	1	18	172
Curveball	6	1	33	1	2	1	0	2	0	0	9	26	0	0	3	84
Cutter	2	0	16	0	2	1	0	1	0	1	5	1	0	0	0	29
Sinker	2	0	6	0	0	0	1	0	0	0	2	1	0	0	0	12
Slider	1	1	7	0	0	1	0	0	0	0	2	15	0	0	2	29
Split-Finger	4	0	39	0	3	1	1	2	0	1	12	48	1	0	5	117
All	23	3	179	1	8	6	2	10	1	4	55	120	2	1	28	443
```
この中で、ヒット(single, double, triple, home_run)となった結果を抽出して、コースを可視化してみます。
```python
import pandas as pd
hit = yamamoto_pitch.query('events=="single" or events=="double" or events=="triple" or events=="home_run"')
pd.crosstab(hit['pitch_name'], hit['events'], margins=True)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3840780/b2ae3306-1abd-42ba-b004-b5a9d603bd8e.png)
以下のようにコースを可視化してみると、ほとんどがストライクゾーン内のボールであるため、どの球種でも甘いボールを逃してくれないという印象を受けます。
```python
import matplotlib.pyplot as plt
import seaborn as sns

sns.scatterplot(x=hit['plate_x'], y=hit['plate_z'], hue=hit['pitch_name'])
plt.xlim(-3, 6)
plt.ylim(-2, 6)
x = [-0.83, 0.83, 0.83, -0.83]
y = [1.5, 1.5, 3.5, 3.5]
plt.fill(x, y, color='k', alpha=0.2)
plt.title('opp hits')
plt.show()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3840780/f3a3fa73-4cf4-4876-90f9-b6daefa5be85.png)

次に、三振を奪った球種についてです。
```python
strikeout = yamamoto_pitch.query('events=="strikeout"')
strikeout['pitch_name'].value_counts()
```
```
count
pitch_name	
Split-Finger	48
4-Seam Fastball	29
Curveball	26
Slider	15
Cutter	1
Sinker	1
```
スプリットで多くの三振を奪っています。また、以下のように可視化してみると、ボール球となるスプリットを振らせて多くの三振を奪っていることがわかります。
```python
import matplotlib.pyplot as plt
import seaborn as sns

sns.scatterplot(x=strikeout['plate_x'], y=strikeout['plate_z'], hue=strikeout['pitch_name'])
plt.xlim(-3, 6)
plt.ylim(-2, 6)
x = [-0.83, 0.83, 0.83, -0.83]
y = [1.5, 1.5, 3.5, 3.5]
plt.fill(x, y, color='k', alpha=0.2)
plt.title('strikeout')
plt.show()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3840780/01ed9d85-56ad-43b5-a51b-7927eee463fb.png)

最後にリリースポイントです。打者目線でどの位置で手からボールが離れたのか球種ごとに可視化します。
```python
import matplotlib.pyplot as plt
import seaborn as sns

plt.figure(figsize=(5,5))
sns.scatterplot(x=yamamoto_pitch['release_pos_x'], y=yamamoto_pitch['release_pos_z'], hue=yamamoto_pitch['pitch_name'])
plt.xlim(-4, 0)
plt.ylim(3, 7)
plt.title('Release point')
plt.show()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3840780/3ef4c21e-4bb8-4823-83c8-565b2869f903.png)

参考までに、大谷翔平投手、ダルビッシュ有投手の2021年から３シーズン分のデータを以下に示します。グラフのスケールは同じなので、山本投手のリリースポイントが球種による差が少ないことがわかります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3840780/1cb0bc96-0700-4ae8-a478-e73e8a029e99.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3840780/efb7ac32-6a50-4a13-8e54-482c71fb0ccc.png)

### 怪我での離脱前後の比較
　次に怪我での離脱前後で投球データがどのように変化していたのか分析をします。以下のように6月15日の試合で28球投げて降板した後、3ヶ月ほど怪我のために実戦から遠ざかっていました。
```python
# 怪我の前後でデータを分割
before = yamamoto_pitch.query('game_date < "2024-06-16"')
after = yamamoto_pitch.query('game_date > "2024-06-16"')
print(before['game_date'].value_counts().sort_index())
print(after['game_date'].value_counts().sort_index())
```
```
game_date
2024-03-21     43
2024-03-30     68
2024-04-06     80
2024-04-12     91
2024-04-19     99
2024-04-25     97
2024-05-01     94
2024-05-07     97
2024-05-13     84
2024-05-20    100
2024-05-26    100
2024-06-01    101
2024-06-07    106
2024-06-15     28
Name: count, dtype: int64
game_date
2024-09-10    59
2024-09-16    72
2024-09-22    79
2024-09-28    71
2024-10-05    60
2024-10-11    63
2024-10-17    73
2024-10-26    86
Name: count, dtype: int64
```
怪我の前後でヒット打たれた球種について比較します。
```python
#怪我前のデータ
import pandas as pd

hit_before = before.query('events=="single" or events=="double" or events=="triple" or events=="home_run"')
b1 = before['pitch_name'].value_counts()
b2 = hit_before['pitch_name'].value_counts()
opp_hit_before = pd.concat([b1, b2], axis=1)
opp_hit_before.columns = ['pitch_count', 'opp_hit']
opp_hit_before['rate'] = opp_hit_before['opp_hit']/opp_hit_before['pitch_count']
opp_hit_before.sort_index()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3840780/a966fa34-6284-43c2-b8ca-6ed602af5a4a.png)

```python
# 怪我後のデータ
import pandas as pd

hit_after = after.query('events=="single" or events=="double" or events=="triple" or events=="home_run"')
a1 = after['pitch_name'].value_counts()
a2 = hit_after['pitch_name'].value_counts()
opp_hit_after = pd.concat([a1, a2], axis=1)
opp_hit_after.columns = ['pitch_count', 'opp_hit']
opp_hit_after['rate'] = opp_hit_after['opp_hit']/opp_hit_after['pitch_count']
opp_hit_after.sort_index()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3840780/32c62143-7f31-4976-92db-ab56c25df375.png)
中継の解説などで、怪我後はスライダーが良くなったというコメントを多数見かけましたが、実際には怪我の前後で投球数も打たれた数もほとんど差はありませんでした。また、各球種を比較すると、シンカーの数が極端に少なくなり、カットボールやスライダーの割合が高くなったことがわかりました。カットやスライダーといった斜めに曲がる球種を投げる割合が増加したものの、打たれた数に大きな差がなかったため、スライダーが良くなったというような印象を受けたのかもしれません。
　最後にリリースポイントの変化を可視化してみます。
```python
# 怪我の前後を示す'before_after'をデータ項目として追加
before_after = yamamoto_pitch.copy()
before_after['before_after'] = before_after['game_date'].apply(lambda x: 'before' if x < '2024-06-16' else 'after')
```
```python
import matplotlib.pyplot as plt
import seaborn as sns

plt.figure(figsize=(5,5))
sns.scatterplot(x=before_after['release_pos_x'], y=before_after['release_pos_z'], hue=before_after['before_after'], alpha=0.07)
plt.xlim(-4, 0)
plt.ylim(3, 7)
plt.title('Release point (before vs after injury)')
plt.show()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3840780/54578e12-63de-4b5d-8939-419a794c206b.png)
怪我の後はボールを離す位置が斜め下方向に変わっていました。この傾向は以下のように各球種でも同様に確認できました。
```python
import matplotlib.pyplot as plt
import seaborn as sns

four_seam = before_after.query('pitch_name=="4-Seam Fastball"')

plt.figure(figsize=(5,5))
sns.scatterplot(x=four_seam['release_pos_x'], y=four_seam['release_pos_z'], hue=four_seam['before_after'], alpha=0.15)
plt.xlim(-4, 0)
plt.ylim(3, 7)
plt.title('Release point (4-seam, before vs after injury)')
plt.show()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3840780/7650f7a7-070f-45e6-8285-3cde20537d0f.png)
以下、コードは割愛しますが、他の球種についてもリリースポイントを可視化してみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3840780/70b3ccf4-c64f-4cee-856a-f340d318c54f.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3840780/46da4773-934f-4657-90a8-e618745e70a8.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3840780/d565d75a-ad3d-45e7-9860-d58dad3b094c.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3840780/6c8e60ce-6066-4112-bb18-31d03c0b20f5.png)
各球種について、怪我の前後で同様の傾向が見られました。

## 分析結果の総括と所感
　分析の結果、フォーシームやカーブで安定的にストライクが取れていたということや、ボール球のスプリットを振らせて多くの三振を奪っていたことがわかりました。また、怪我の前後でリリースポイントに変化が生じていることもわかりました。その変化を加味しても全体としてはリリースポイントが安定していて、打者目線では球種の判別がしにくいということになるため、山本投手の投球スタイルの1つの強みであると思います。

## 終わりに
　今回は、昨シーズンの山本投手の投球データの分析を行いました。怪我の前後でリリースポイントに変化が生じており、これがいよいよ日本で開幕を迎える新シーズンでも継続していくのか、それとも怪我前の位置に戻っていくのか、シーズン終了後にまた比較分析を行いたいと思います。