セイバーメトリクス

Rによるセイバーメトリクス入門 をじっくり学ぶ 得点期待値について①

得点期待値行列

野球において、それぞれの塁にはランナーいる場合と、いない場合があります。するとランナーのパターンは8通りになります(ランナーなしからランナー満塁まで)

一方、アウトカウントは0アウトから2アウトの3通りになります。

そうすると、ランナーとアウトの取りうる組み合わせは8×3で24通りになります(ノーアウトランナーなしから2アウト満塁まで)

野球のゲームというのはこのパターンが変動するゲームであると言えます。

そうすると、過去の試合を遡り、アウトカウントとランナーの組み合わせで、イニングの残り(=3アウトになるまで)での平均得点を計算し求められる値が得点期待値となります。そして、それを表形式に表したものが得点期待値行列となります。

過去のデータから、このアウトカウント・ランナーのパターンの場合、平均何点入るか?ということから今のプレーを分析することができるのです。

データの入手

まずはじめに、2021年のEvent Filesをretrosheetから準備しました。

Retrosheetについては下記のリンクから。

Event Filesは、それぞれの試合の打者ごとのイベントのデータになります。

このデータを上手に加工して、得点期待値を求めていきたいと思います。

Event Filesのイメージ

イニングの残りで記録された得点

イニングの残りでの得点数を計算するには、ある打席までのホーム・ビジター両チームの得点数とあるイニングの終わりにおける両チームの得点数を知る必要があります

RUNS.ROI =(あるイニング終了時の得点)ー(現在の得点)

このRUNS.ROIを求めることを目的として計算を進めます。

まず、2021年のEvent filesのデータに、mutate関数を用いてアウェーチーム、ホームチームの合計得点をRUNSとして計算します。

data2021 %>% 
 mutate(RUNS = AWAY_SCORE_CT + HOME_SCORE_CT) -> data2021

次に、HALF.INNINGとして、試合ID、イニング、表か裏かというデータを作成します

data2021 %>%
 mutate(HALF.INNING = paste(GAME_ID, INN_CT, BAT_HOME_ID))->data2021

そうすると ”ANA202104010 1 0” ”アナハイムで行われた2021年4月1日 1回 表” みたいなデータができます。

各プレーにおける得点を記録するRUNS.SCOREDという変数を作成します

Event FilesにはBAT_DEST_IDという項目があります。これは1なら安打、2なら2塁打、3なら3塁打、4なら本塁打

また、RUN_DEST_IDというのもあり、これは1なら1塁へ進塁、2なら2塁へ、3なら3塁へ、4以上は得点となります。

data2021 %>%
 mutate(RUNS.SCORED = (BAT_DEST_ID>3)+(RUN1_DEST_ID >3)+
                                         (RUN2_DEST_ID>3)+(RUN3_DEST_ID > 3))->data2021

つづいて、HALF.INNING(試合のID+イニング+表か裏かのデータ)で集計をします。

そして、各イニングでの①アウトカウント数(これは大半が3アウト)、②各イニングでの得点数、③イニング開始時の得点、④イニング終了時の得点を算出します。

data2021%>%
group_by(HALF.INNING)%>%
     summarize(Out.Inning = sum(EVENT_OUTS_CT),
                     Runs.Inning = sum(RUNS.SCORED),
                     Runs.Start = first(RUNS), 
                         MAX.RUNS = Runs.Inning + Runs.Start) -> half_innings

このデータをdata2021に結合します。

data2021%>%      
 inner_join(half_innings, by="HALF.INNING")->data2021

そして、RUNS.ROI =(そのイニングの最終的な得点)ー(現在の得点)を算出します。

data2021%>%
 mutate(RUNS.ROI = MAX.RUNS - RUNS) -> data2021 

そうすると、各打席にイニングの残りでの得点を導き出すことができます。

行列の作成

続いて、ランナーの状況を求めていきましょう。

BASE_RUN_IDはランナーにはランナーの名前が入ってきます。ここにデータがあれば1としていきます。

data2021%>%   
 mutate(BASES =
   paste(ifelse(BASE1_RUN_ID > "",1,0),
              ifelse(BASE2_RUN_ID > "",1,0),
              ifelse(BASE3_RUN_ID > "",1,0),sep = "")-> data2021

そうすると結果100=ランナー1塁や、101ランナー1塁3塁みたいなデータになります。

そして、アウトカウントも付け加えます。

data2021%>%
mutate(STATE = paste(BASES, OUTS_CT))->data2021

現在のプレーでのランナー変化を調べます。

RUN_DEST_ID、BAT_DEST_IDは現在のプレーでランナー、バッターがどこに進塁したかを示します。1なら結果1塁に進んだor留まったという意味になります。それを利用して現プレーによって変化したランナーNRUNNERを導きます。

data2021%>%
 mutate(NRUNNER1=
           as.numeric(RUN1_DEST_ID == 1|BAT_DEST_ID ==1),
          NRUNNER2=
           as.numeric(RUN1_DEST_ID ==2|RUN2_DEST_ID ==2|
                          BAT_DEST_ID==2),
          NRUNNER3=
           as.numeric(RUN1_DEST_ID==3|RUN2_DEST_ID==3|
                          RUN3_DEST_ID==3|BAT_DEST_ID==3))->data2021

アウトカウントの変動を算出します。

data2021%>%
 mutate(NOUTS = OUTS_CT + EVENT_OUTS_CT)->data2021

新たなランナーのパターンNEW.BASESとアウトのパターンの状況を加えたものNEW.STATEを作ります。

data2021%>%
 mutate(NEW.BASES = paste(NRUNNER1,NRUNNER2,NRUNNER3,sep=""),
           NEW.STATE = paste(NEW.BASES,NOUTS))->data2021 

そうすると、ノーアウトランナー1塁 ”100 0” から 

送りバントでワンアウト2塁 ”010 1”みたいなのがわかるようになります。

ここで、変化があったプレーのみ抽出します。

data2021%>%
 filter((STATE != NEW.STATE)|(RUNS.SCORED > 0))->data2021

続いて、3アウトまで到達したイニングをのみを抽出します。これで小さなバイアスを除きます。

data2021%>%
 filter(Out.Inning == 3)->data2021C

そうしましたら、ランナー・アウトの状況であるSTATEでグループ化し、ROIの平均を算出します。

data2021%>%
 group_by(STATE)%>%
 summarize(Mean = mean(RUNS.ROI))->RUNS 

アウトカウントを追加し、順番を変更します。

RUNS%>%
mutate(Outs = substr(STATE, 5,5))
arrange(Outs)->RUNS

0アウト1アウト2アウト
ランナー無0.520.270.1
10.920.540.23
21.150.70.33
31.380.950.38
121.540.920.45
131.761.130.49
232.131.40.59
満塁2.461.690.83
2022年得点期待値行列

これで、得点期待値の行列を求めることができましたね!!

最近思うのは、ツーアウト2塁でチャンス!みたいなこと言われるけど、そんなことないよね。

-セイバーメトリクス