読者です 読者をやめる 読者になる 読者になる

amacbee's blog

Pythonの話とかデータの話とか酒の話とか

MLBの野球データを用いた岩隈投手の分析 ~ セイバーメトリクスを添えて ~ (野球Hack入門編)

※この記事はPython Advent Calendar 2015の19日目の記事です(大遅刻すみません。。。)
※野球データうんぬん書いてありますが@shinyorkeさんの書いた記事ではありません
※元祖野球Hackを求めている方は本家のブログをご覧ください

ソフトバンクホークス優勝おめでとうございます

皆様,2015年の野球生活はどうでしたか?
私はといいますと,馬原選手が引退を表明して寂しい思いもしていますが,ホークスが圧倒的な優勝を果たして,柳田選手はトリプルスリーをとって,例年以上にドラフトもうまくいったし,最初から最後まで満足度の高い一年でした.*1
この盛り上がった一年を締めるのもやはり野球でしょうということで,この記事では野生の野球アナリストである@shinyorkeさんがつい先日整備・公開してくれたMLBの野球データを用いて,2015年の岩隈投手の活躍をセイバーメトリクスを使って分析していきます.*2

shinyorke.hatenablog.com

セイバーメトリクス

@shinyorkeさんのコンテンツを引用しまくってて大変恐縮ですが,以下をご参照下さい(丸投げ)

shinyorke.hatenablog.com

一言でいうと,データをもとに野球選手の能力を分析する指標です.最近はデータを使ってスポーツの戦略をたてたり選手の評価をしたりが流行ってますよね.
面白そう!と思った方には映画「マネーボール」をお勧めします.

データの用意

リポジトリをご参照下さい(丸投げ 2nd)

github.com

手順通りに進めると問題なくデータのdownload・parse処理が行われます.今回は2015年のデータを利用しました.*3

データの前処理

2015年のデータ(csv/events-2015.csv)をpandasのDataFrameに突っ込みます.

>>> import pandas as pd

>>> df = pd.read_csv('csv/events-2015.csv', low_memory=False)
>>> df.shape  # 行数と列数の確認
(189591, 160)
>>> df.columns[:20]  # 具体的な列の例
Index(['GAME_ID', 'AWAY_TEAM_ID', 'INN_CT', 'BAT_HOME_ID', 'OUTS_CT',
       'BALLS_CT', 'STRIKES_CT', 'PITCH_SEQ_TX', 'AWAY_SCORE_CT',
       'HOME_SCORE_CT', 'BAT_ID', 'BAT_HAND_CD', 'RESP_BAT_ID',
       'RESP_BAT_HAND_CD', 'PIT_ID', 'PIT_HAND_CD', 'RESP_PIT_ID',
       'RESP_PIT_HAND_CD', 'POS2_FLD_ID', 'POS3_FLD_ID'],
      dtype='object')

このデータ中には,2015年の試合で発生した様々なイベント(e.g. ピッチャーがストライクをとった,盗塁が行われた,打者がHRを打った)の情報が収められています.df.shapeで確認すると分かりますが列数が160もあります.それぞれの列や値が何を意味するのかを調べる気力がわかなかったので本家サイトをご確認下さい.
参考:Play-by-Play Data Files (Event Files)

これがセイバーメトリクス初心者に立ちはだかる最初の関門です.データの種類が膨大&独自記号で定義されているためぱっと見で何が何だか分からない仕様になっていて大分とっつき辛いです.後ほど調べて分かったのですが,Lahman’s Baseball Databaseというデータもあって,こちらはある程度統計情報まとめられてるようなので,つらさに耐えられなくなったら最初はこちらを触ってみるのも手かもしれません.

岩隈投手の成績を他投手と比較するために,投球回(Inning Pitched)の多い選手上位200名をピックアップしました.

投球回(とうきゅうかい、英: Innings pitched / IP)は、野球における投手記録の一つで投手が登板したイニングの数を表す。イニングの途中で投手が交代した場合には、登板時に取った(攻撃側に記録された)アウト一つにつき1/3ずつを加える。 by 投球回 - Wikipedia

# GAME_ID: 試合ID
# INN_CT: イニング
# PIT_ID: ピッチャーID(岩隈投手のIDはiwakh001)
# EVENT_OUTS_CT:該当イベントでアウトが発生した回数
>>> pitchers_df = pd.DataFrame(df[['GAME_ID', 'INN_CT', 'PIT_ID', 'EVENT_OUTS_CT']].groupby(['PIT_ID']).sum().EVENT_OUTS_CT)
>>> pitchers_df.columns = ['SUM_EVENT_OUT_CT']
>>> pitchers_df['IP'] = pitchers_df.SUM_EVENT_OUT_CT / 3
>>> target_pitchers_df = pitchers_df.sort_values(by='IP', ascending=False).head(200)
>>> target_pitchers_df = target_pitchers_df.reset_index(0)

# 岩隈投手の投球回 (129 2/3)
>>> print(target_pitchers_df[target_pitchers_df.PIT_ID.str.contains('iwak') == True])
       PIT_ID  SUM_EVENT_OUT_CT          IP
106  iwakh001               389  129.666667

(投球回の多い上位200人に限らず)投手全員の投球回のヒストグラムは以下のような感じです.上位200人で見た場合,最大投球回は232 2/3,最小投球回は69でした.

f:id:amacbee:20151222213835p:plain:w400

ついでに岩隈選手の基本的な統計データも確認しておきましょう.EVENT_CDという列に発生したイベントの情報が確認されています.

# ピッチャー別に見た各イベントの統計
>>> event_df = pd.crosstab(target_df.PIT_ID, target_df.EVENT_CD)
>>> event_df = event_df.reset_index(0)
>>> event_df = event_df.merge(target_pitchers_df)

# 各イベントIDをイベント名に変換
# 参考:http://www.retrosheet.org/datause.txt
>>> id2event = {0: "Unknown event", 1: "No event", 2: "Generic out", 3: "Strikeout", 4: "Stolen base", 5: "Defensive indifference", 6: "Caught stealing", 7: "Pickoff error", 8: "Pickoff", 9: "Wild pitch", 10: "Passed ball", 11: "Balk", 12: "Other advance", 13: "Foul error", 14: "Walk", 15: "Intentional walk", 16: "Hit by pitch", 17: "Interference", 18: "Error", 19: "Fielder's choice", 20: "Single", 21: "Double", 22: "Triple", 23: "Home run", 24: "Missing play"}

# 岩隈選手の基本的な統計データを確認
>>> for col, val in event_df[event_df.PIT_ID.str.contains('iwak') == True].iteritems():
>>>    if col in ('PIT_ID', 'SUM_EVENT_OUT_CT', 'IP'):
>>>         continue
>>>    print('{0}: {1}'.format(id2event[col], int(val)))
Generic out: 264
Strikeout: 111
Stolen base: 1
Defensive indifference: 0
Caught stealing: 2
Pickoff: 0
Wild pitch: 1
Passed ball: 0
Balk: 0
Other advance: 1
Foul error: 0
Walk: 20
Intentional walk: 1
Hit by pitch: 1
Interference: 0
Error: 1
Fielder's choice: 1
Single: 75
Double: 23
Triple: 1
Home run: 18

2015年の岩隈投手を分析 with セイバーメトリクス

何となくデータが揃ったところで,投手の代表的な評価指標であるK/BBDIPSWHIPを用いて岩隈投手の2015年の活躍を分析してみます.各評価指標の基準については以下のサイトを参考にしました(※おそらくNPB仕様)

www.softbankhawks.co.jp

K/BB - Strikeout to Walk ratio

K/BBとは,四球を1つ出すまでにいくつの三振を奪っているかを数値化したもので,数値が大きいほどコントロールが良く,なおかつ多くの三振を奪える投手であることを示しています.
以下の式で計算します.

K/BB = 奪三振数 / 与四球数

数値の基準は以下の通り.

  • 4.00前後:球界を代表するクラス
  • 3.00前後:リーグを代表するクラス
  • 2.00前後:平均的なクラス
# K/BB = 奪三振数 / 与四球数
>>> event_df['KBB'] = event_df[3] / event_df[14]
>>> event_df['KBB_RANK'] = event_df['KBB'].rank(ascending=False)

# K/BB値が高い上位5人
# PIT_IDの変換表:http://www.retrosheet.org/retroID.htm
>>> print(event_df.sort_values(by='KBB_RANK').head(5)[['PIT_ID', 'KBB']])
EVENT_CD    PIT_ID        KBB
162       salaf001  10.571429  # Salas, Fernando
169       schem001   8.625000  # Scherzer, Max
145       pinem001   7.428571  # Pineda, Michael
101       kersc001   7.341463  # Kershaw, Clayton
30        colob001   7.157895  # Colon, Bartolo

# 岩隈投手のK/BB(200人中14位)
>>> print(event_df[event_df.PIT_ID.str.contains('iwak') == True][['PIT_ID', 'KBB', 'KBB_RANK']])
EVENT_CD    PIT_ID   KBB  KBB_RANK
92        iwakh001  5.55        14

投手200人のK/BBのヒストグラムは以下の通りです.

f:id:amacbee:20151222214533p:plain:w400

岩隈投手のK/BB値は他投手と比較して高く優秀な成績であることが伺えます.「コントロール・アーティスト」と呼ばれる岩隈投手の制球力の高さがスコアにあらわれていますね(多分)

DIPS - Defence Independent Pitching Statistics

DIPSとは,インプレイ(フィールド内に飛んだ打球)の要素を削除して奪三振・与四球・被本塁打の数値のみを考慮した指標で,投手本来の能力に着目しています.数値がゼロに近いほど四死球が少なく,三振を奪える投手であることを示しています.
以下の式で計算できます.*4

DIPS = {(与四球 - 故意四球 + 死球) * 3 + 被本塁打 * 13 - 奪三振 * 2} / 投球回数 + 3.12

数値の基準は以下の通り.

  • 1.50前後:球界を代表する投手
  • 2.50前後:球団内のエース級
  • 3.50前後:平均的な投手
# DIPS = (与四球 - 故意四球 + 死球) * 3 + 被本塁打 * 13 - 奪三振 * 2} / 投球回 + 3.12
# 故意四球を考慮できていない(要修正)
>>> event_df['DIPS'] = ((event_df[14] + event_df[16]) * 3 + event_df[23] * 13 - event_df[3] * 2) / event_df['IP'] + 3.12
>>> event_df['DIPS_RANK'] = event_df['DIPS'].rank()

# DIPS値が低い上位5人
>>> print(event_df.sort_values(by='DIPS_RANK').head(5)[['PIT_ID', 'DIPS']])
EVENT_CD    PIT_ID      DIPS
0         allec002  1.720962  # Allen, Cody
98        kersc001  1.963840  # Kershaw, Clayton
174       smitc004  1.977143  # Smith, Carson
60        gilek001  2.034286  # Giles, Ken
5         arrij001  2.307773  # Arrieta, Jake

# 岩隈投手のDIPS(200人中85位)
>>> print(event_df[event_df.PIT_ID.str.contains('iwak') == True][['PIT_ID', 'DIPS', 'DIPS_RANK']])
EVENT_CD    PIT_ID      DIPS  DIPS_RANK
89        iwakh001  3.698406         83

投手200人のDIPSのヒストグラムは以下の通りです.

f:id:amacbee:20151222214558p:plain:w400

岩隈投手は平均的なスコアになっています.ただ岩隈投手の特徴として「ゴロクマ」と呼ばれるほど打たせてとるタイプであることがあげられるので,この指標で岩隈投手の能力を測るのは難しいかもしれません.

WHIP - Walks plus Hits Inning Pitched

WHIPとは,一イニングあたり何人の走者を出しているのかを示す指標で,数値がゼロに近いほどピンチを招く頻度の少ない投手であることを示します.
以下の式で計算できます.

WHIP = (被安打 + 与四球) / 投球回

数値の基準は以下の通り.

  • 1.00未満:球界を代表する投手
  • 1.20未満:球団内のエース級
  • 1.40以上:安定度に欠ける投手
# WHIP = (被安打 + 与四球) / 投球回数
# 被安打 = 一塁打 + 二塁打 + 三塁打 + HR

>>> event_df['WHIP'] = (event_df[14] + event_df[20] + event_df[21] + event_df[22] + event_df[23]) / event_df['IP']
>>> event_df['WHIP_RANK'] = event_df['WHIP'].rank()

# WHIP値が低い上位5人
>>> print(event_df.sort_values(by='WHIP_RANK').head(5)[['PIT_ID', 'WHIP']])
EVENT_CD    PIT_ID      WHIP
67        greiz001  0.839820  # Greinke, Zack
5         arrij001  0.855895  # Arrieta, Jake
98        kersc001  0.876791  # Kershaw, Clayton
77        harrw002  0.887324  # Harris, Will
136       osunr001  0.889952  # Osuna, Roberto

# 岩隈投手のWHIP(200人中26位)
>>> print(event_df[event_df.PIT_ID.str.contains('iwak') == True][['PIT_ID', 'WHIP', 'WHIP_RANK']])
EVENT_CD    PIT_ID      WHIP  WHIP_RANK
89        iwakh001  1.056555         26

投手200人のWHIPのヒストグラムは以下の通りです.

f:id:amacbee:20151222214616p:plain:w400

岩隈投手のWHIP値は他投手と比較して低い値となっており,ランナーをあまり出すことのない優れた投手であることが分かります.また,DIPSが比較的平均の値なのに対しWHIPが低いということは,三振よりもゴロを打たせるタイプの投手であるということを示しているのかもしれません.

まとめ

本記事では岩隈投手の2015年の成績にフォーカスをあててセイバーメトリクス入門させて頂きました.MLBのトップ層と比較しても堂々たるパフォーマンスを発揮していて,岩隈投手本当にすごい!ちなみに,Lahman’s Baseball Databaseの方に選手のSalaryのデータがあるので突き合わせてみると面白いかもしれません.

まともにretrosheetを触ったのは今回が初だったのですが,セイバーメトリクスの計算式自体は難しくないけどデータ形式が特殊で,最初の一歩までが大変な印象を受けました.各指標を計算してくれるAPIが欲しいですね(2016年の野球Hackネタなのでは)
正直複雑過ぎて結果の正確性に自信がないので皆様からのご指摘等お待ちしています.あと値の解釈があってるのかもちょっと自信ないです.*5

それにしても野球データは基本的に前処理超辛いのが多いので,すぐにいじれる環境があるってステキです.@shinyorke++
こんな感じでNPBのデータをよしなにいじれるやつも欲しry|д゚)

*1:Baseball Play Study 2015 プロ野球ふりかえりスペシャルに参加できなかったのは悔しい

*2:一部の方に予告していた「MLBの野球データを用いた岩隈の期待年収予測」のネタは引っ込めさせて頂きました.理由についてはお察し下さい

*3:今回はCSV形式のデータをそのまま利用しましたが,MySQL上にデータを持つことが可能になっています

*4:DIPSの計算方法に諸説あるようです.詳しくはリンクを参照して下さい:DIPS (野球)

*5:そもそも正確に値を出していませんが。。。