背景
野球アナリストを目指して一年半。これから夢が現実へと変わってゆく。
野球アナリストを志した当時、何から始めればいいか分からずも、一旦先人の足跡をたどるように分析結果をよく目にしていました。が、データの概要やハンドリング方法が示されておらず、分析しようにもできない状態がしばらく続きました。私は幸運にも周りが野球関係者で溢れていたので、比較的質問しやすい環境で分析を進めることができました。
今回のテーマは、野球分析のフレームワーク化です。この投稿では、**MLBのトラッキングデータの取得からデータの加工までを対象にします。**また、「これまでの野球」と「これからの野球」の変貌を併せて解説することで、実際に使用するトラッキングデータの全貌が理解しやすくなると思います。そして、**次回の投稿は実践編で投手分析と打撃分析です。**完成まで今しばらくお待ちください。
Githubにソースコードを公開していますので、よかったら参考にしてください。
https://github.com/yuuuusuke1997/pybaseball
目的
- 野球分析に興味がある人が、この記事を読んだ直後に分析をスタートできる状態にする
- 主に高校球児に向けて、「野球アナリスト」という職業選択の周知
MLBからオファーをいただきたい
環境
- macOS
- Python 3.7.6
- Jupyter lab
参考サイト
セイバーメトリクス系webサイト
- https://www.baseball-reference.com/
- https://www.fangraphs.com/
- https://www.baseballprospectus.com/
- https://baseballsavant.mlb.com/
- https://www.baseballgeeks.jp/
各指標の解説
その他おすすめ動画
- 球団アナリストによるホークアイ解説!
- Future of the Game: Baseball's Latest Statistical Revolution
- MLB Tonight: Evolution of analytics with Trevor Bauer
スターティングメンバー
1. 「これまでの野球」と「これからの野球」
そもそも我々は何故ゆえ野球分析をするのか?
選手がヒットを多く打ち打率を残すため、三振の山を築きタイトルを総なめするため?
それは手段であって目的ではないような気がする。
我々は「勝利」の可能性をプラス2%ほど高め、優勝へと貢献する、影の立役者です。
では、「勝利」へと近づくにはどうすればいいのか、勝利の根本原理を再認識しよう。
試合に勝つには、相手より一点でも多く得点する。
裏を返せば、相手より一点でも多く失点を抑える。
以上。これだけです。
具体的に述べると、打者は得点効率を高めるために打撃向上を目指し、投手は失点を防ぐために投球能力を磨くのです。これを踏まえた上で、上図を見ながら「これまでの野球」と「これからの野球」を解説します。
まずは、勝利から得点と失点に分岐して、そこから打撃や投球など細かい要素になっています。「これまでの野球」は、一打席ごとの結果ベースの定量評価でした。
例えば、
- この選手は年間どのくらいホームランを打ったのか?
- 三振をいくつ奪ったのか?
など、結果に着目した評価が一般的でした。
一方で、「これからの野球」は、一球ごとの物理的結果を元に過程を評価する時代です。
例えば、
- この選手は年間どのくらいホームランを打ったのか?→〇〇本も打てたのはなぜか?→適切な打球角度と打球速度→打球速度を上げるには、〇〇くらいの筋量が必要で、そのためには体重〇〇キロが最適。
- 三振をいくつ奪ったのか?→〇〇回も奪えたのはなぜか?→投球したボールの回転数と回転軸が〇〇だった→MLB平均から大きく逸脱していたので好成績を残せたのかも。
という具合で、「これからの野球」はよりミクロな視点で評価され、サンプル数が少ない場合やリーグ間での評価が容易になりました。また、細部のデータを取得できるようになったことで、コーチングのアプローチ幅が広がり、各分野に特化した専門家からアドバイスを求めるようになりました。
参考: What is Driveline Baseball All About?
では、どうやって物理的データを分析するのか?
その手助けとなるのが、いま注目を集めているトラッキングデータです。
2. Statcastとは
先にイメージを持っていただくために動画を添付しておきます。まずはこちらをご覧ください。
いかがだったでしょうか?なんとなくイメージを掴めたのではないでしょうか?
スタットキャストとは、ボールを追尾するレーダー「トラックマン」と、人の動きをカメラで捉える「映像解析システム」を統合したITシステムです。
参考: [スタットキャストとは?メジャーの最先端技術を紹介!]
(https://www.baseballgeeks.jp/mlb/%E3%82%B9%E3%82%BF%E3%83%83%E3%83%88%E3%82%AD%E3%83%A3%E3%82%B9%E3%83%88%E3%81%A3%E3%81%A6%E4%BD%95%EF%BC%9F/
)
そのため、トラックマンと映像解析システムでは異なったデータとなります。
以下、それぞれの代表的なデータ項目となります。
トラックマンで取得したデータ
- 打者の打球角度, 打球速度, 打球飛距離
- 投手のリリースポイント, 回転数, 回転軸, 変化量
映像解析システムで取得したデータ
- 守備者の落下地点までの到達距離, 打球を追う最高速度
- 走者の塁間到達時間, ベースランの最適ルート, リード幅
今回はトラックマンで取得するトラッキングデータの取得を行います。
3. データの取得
使用するライブラリはpybaseball
で、MLBの主要データサイトから取得したデータを扱うことができます。また、これまで述べてきた一球ごとのトラッキングデータを使用することで、これまで以上に痒い所に手が届くようになりました。
では、ソースコードをコピペしていただく前に三つ事前準備をしましょう。
まずは、pybaseball
のインストールです。
$ pip install pybaseball
次に、こちらからpeople.csv
をクローンする必要があります。pybaseballで取得したデータに対して、打者名を与えるadd_batter_name
関数に使用します。*この関数はマストではないので、必要なければ適宜コメントアウトに変更してください。その際は、csvファイルのクローンも必要ありません。
最後に、何かお困りでしたらTwitterのDMでご連絡ください。微力ではありますが、お力添えになるかもです。
"""
pybaseballを使用して、一年分のMLBデータ(トラッキングデータ)をcsvで取得する
対象サイト: Baseball Reference, Baseball Savant, FanGraphs
"""
import calendar
import re
import traceback
import pandas as pd
from pybaseball import batting_stats, pitching_stats, statcast
players_info_df = pd.read_csv('people.csv', low_memory=False)
pd.set_option('display.max_columns', None)
# 取得したい年に変換
SELECT_YEAR = 2020
def create_df_pitching_stats(year):
"""
投手のシーズン成績を取得(登板数, 勝利数など)
Parameters
--------------
year: int
対象シーズン
Returns
--------------
output_df: DataFrame
投手のシーズン成績
Examples
--------------
pitching_stats関数の使い方: pitching_stats(start_season, end_season)
"""
# qualは最低インニング数
output_df = pitching_stats(year, qual=1)
return output_df
def create_df_batting_stats(year):
"""
打者のシーズン成績を取得(打席数, 打率など)
Parameters
--------------
year: int
対象シーズン
Returns
--------------
output_df: DataFrame
打者のシーズン成績
Examples
--------------
batting_stats関数の使い方: batting_stats(start_season, end_season)
"""
output_df = batting_stats(year, qual=1)
return output_df
def create_df_statcast(start_year):
"""
トラッキングデータを取得(リリースポイント, 打球角度など)
Parameters
--------------
start_year: int
対象シーズン
Returns
--------------
output_df: DataFrame
トラッキングデータ
Examples
--------------
statcast関数の使い方: statcast(start_dt='YYYY-MM-DD', end_dt='YYYY-MM-DD')
"""
list_df = []
end_year = start_year + 1
start_month = 3
end_month = 11
for year in range(start_year, end_year):
# 2020年の開幕は7月
if year == 2020:
start_month = 7
for month in range(start_month, end_month):
start = str(year) + '-' + str(month).zfill(2) + '-01'
end = str(year) + '-' + str(month).zfill(2) + '-' + str(calendar.monthrange(year, month)[1])
data = statcast(start_dt=start, end_dt=end)
list_df.append(data)
output_df = pd.concat(list_df)
return output_df
def add_batter_name(players_info_df, statcast_df):
"""
打者の名前を追加
Parameters
--------------
players_info_df: DataFrame
選手のプロフィール
statcast_df: DataFrame
トラッキングデータ
Returns
--------------
output_df: DataFrame
トラッキングデータ
"""
players_info_df['name_first'] = players_info_df['name_first'].apply(lambda x: str(x))
players_info_df['name_last'] = players_info_df['name_last'].apply(lambda x: str(x))
players_info_df['batter_name'] = players_info_df.apply(lambda x: x['name_first'] + ' ' + x['name_last'], axis=1)
players_info_df = players_info_df[['key_mlbam', 'batter_name']].dropna()
output_df = pd.merge(statcast_df, players_info_df, left_on="batter", right_on='key_mlbam', how="left").rename(columns={'player_name': 'pitcher_name'})
return output_df
def convert_units(df):
"""
各指標の単位を変換する
Parameters
--------------
df: DataFrame
トラッキングデータ
Returns
--------------
output_df: DataFrame
トラッキングデータ
Examples
--------------
release_speed: マイルからキロメートルに変換(* 1.609)
release_pos_x: フィートからメートルに変換(* 0.3048)
"""
output_df = df.copy()
output_df['release_speed'] = output_df['release_speed'].apply(lambda x: x * 1.609)
output_df['release_pos_x'] = output_df['release_pos_x'].apply(lambda x: x * 0.3048)
output_df['release_pos_y'] = output_df['release_pos_y'].apply(lambda x: x * 0.3048)
output_df['release_pos_z'] = output_df['release_pos_z'].apply(lambda x: x * 0.3048)
output_df['pfx_x'] = output_df['pfx_x'].apply(lambda x: x * 0.3048)
output_df['pfx_z'] = output_df['pfx_z'].apply(lambda x: x * 0.3048)
output_df['plate_x'] = output_df['plate_x'].apply(lambda x: x * 0.3048)
output_df['plate_z'] = output_df['plate_z'].apply(lambda x: x * 0.3048)
output_df['launch_speed'] = output_df['launch_speed'].apply(lambda x: x * 1.609)
output_df['effective_speed'] = output_df['effective_speed'].apply(lambda x: x * 1.609)
output_df['release_extension'] = output_df['release_extension'].apply(lambda x: x * 0.3048)
return output_df
if __name__ == '__main__':
try:
pitching_df = create_df_pitching_stats(SELECT_YEAR).reset_index(drop=True)
pitching_csv_name = 'pitching_' + str(SELECT_YEAR) + ".csv"
pitching_df.to_csv(pitching_csv_name)
batting_df = create_df_batting_stats(SELECT_YEAR).reset_index(drop=True)
batting_csv_name = 'batting_' + str(SELECT_YEAR) + ".csv"
batting_df.to_csv(batting_csv_name)
statcast_df = create_df_statcast(SELECT_YEAR).reset_index(drop=True)
statcast_df = add_batter_name(players_info_df, statcast_df)
# 各指標の単位はそれぞれにお任せするので、今回はコメントアウトにしています
# statcast_df = convert_units(statcast_df)
statcast_csv_name = 'statcast_' + str(SELECT_YEAR) + ".csv"
statcast_df.to_csv(statcast_csv_name)
except Exception as e:
print(traceback.format_exc())
おわりに
今回の記事では、野球の構造理解からトラッキングデータの取得まで、分析前の基礎知識を抑えることができました。次回は取得したデータを用いて、投手・打撃分析を行います。特定のデータの可視化効率を図るために、全体像から具体的な分析までアイデアベースで可視化フレームワークを作成する予定です。この記事を読み、面白かった・参考になったと思う方は、次の投稿も是非ご覧ください。
この投稿で、一人でも多くの方が野球分析に興味を持ち、分析を始めていただけたら目的は達成です。きっと多くの方が、野球分析に前のめりになったと信じ、次の投稿に向け分析・執筆準備を進めていきたいと思います。
ここまで長くなりましたが、読んでくださりありがとうございます。誤っている箇所がございましたら、コメントでご指摘頂けると大変嬉しいです。