Rで野球のデータを分析してみよう5(フレーミング得点を算出)

この記事は一連の記事を読んでることを前提としたものです。そのため、まだ未読の方はそちらを読んでから読むことをお勧めします。

フレーミングとは

フレーミングとは捕手が捕球によって球審にストライクを多くコールさせる技術のことを指します。野球のルール上、ストライクゾーンは定義されているものの、実際にストライクかボールかを判断するのは球審に任せられています。球審がどのようなジャッジを下すかについては、捕手だけでなく投手や球審ごとの癖といった多くの要素が絡むと思われますが、捕手による影響がある程度大きいことが先行研究によって示されています。

 今回はそのフレーミングを算出する方法などについて書いていきます。

フレーミングの先行研究

フレーミング指標を算出する前に先行研究について見ていきます。

球審のストライクコール率に影響を与えるもの

ストライクコール率に影響を与えるものについて見ていきます。

1つ目は投球のカウントです。MLBでの先行研究では球審はストライクカウントが増えるとストライクゾーンが縮小し、ボールカウントが増えるとストライクゾーンが拡大する傾向が確認されています。同じ投球コースでもカウントによってストライクとコールされる期待値は変わってくるためこれを補正する必要があります。

2つ目は打席の左右です。こちらもMLBの先行研究では打席の左右によって同じコースでもストライクの期待値が変動する傾向が確認されています。

下準備

まず単純に計算するためにフレーミングの算出に必要な行のみにデータフレームを絞ります。filter関数を用いてdescriptionがcalled strike、またはballとされているもののみにします。

|(または)

 | は論理演算子で「または」を意味します。下のコードでは「descriptionの列が"called strike"またはdescriptionが"ball"の行のみを残します」という意味になります。

#tidyverseを使います
library(tidyverse)

#dfにはStatcast2022のデータが入っている
#descriptionがcalled strikeとballの行のみを残します

df <- df %>%
  filter(description == "called_strike" | description == "ball")

コースを区切る

フレーミング指標の出し方としては、コースを分割してそれぞれの期待値と比較してストライクをどれだけ増減させたかという方法が一般的だと思います。例を示します。

 図はMLB2022の0B0S時の見逃し時のストライク期待値です。ゾーンの中央に近づくほど期待値が高く、離れるほど低くなります。

 増減させたストライクの数はこの期待値を用います。黄枠の部分(期待値0.78)のコースで球審にストライクとコールさせた場合、増やしたストライクの数は 1 - 0.78 = 0.22 です。緑枠の部分(期待値0.35)のコースで球審にストライクとコールさせた場合は 1 - 0.35 = 0.65 です。同じ「見逃しストライク」でも球審によりコールされにくい後者の方が価値が高くなります。このように期待値を用いることによってストライクとコールさせる難易度を補正できます。

というわけで、dfとは別の入れ物に打席の左右、カウント、コースを考慮したストライク期待値表を作成します。

#expected_called_strikeという入れ物にコース、カウント、打席の左右でグループ分け
#ストライク期待値表を作成

expected_called_strike <- df %>%
  group_by(plate_x_bin,plate_z_bin,balls,strikes,stand)%>%
  dplyr::summarise(
    #n()で分母を算出、sumでストライクの総数を算出、sumCS/Nで期待値を算出
    N = n(),
    sumCS = sum(CS, na.rm = TRUE),
    xCS = sumCS / N )

 こんな感じのデータフレームが作成できたはずです。

ストライク期待値表を元のdfにくっつける

ストライク期待値表を作成したら元のdfにくっつけます。
まずストライク期待値表を結合に必要な列だけにします。

select(必要な列だけを選択する)

 selectは必要な列だけを残す関数です。元のdfに結合させるための列(plate_x_bin,plate_z_bin,balls,strikes,stand)と結合させたい列(xCS)に絞ります。

#結合に必要な列だけを残す
expected_called_strike <- expected_called_strike %>%
  select(plate_x_bin,plate_z_bin,balls,strikes,stand,xCS)

left_join(df同士を結合させる)

 left_joinはどちらのdfにもある列名とその列の値が一致する列を結合させる関数です。イメージとしてはこんな感じです。

http://www.f.waseda.jp/sakas/R/Rprogramming.html
#dfとexpected_called_strikeを結合させる

df <- left_join(df,expected_called_strike)

これでさきほど作ったストライク期待値(xCS)と見逃しストライクとボールの行だけが入ったStatcast2022のデータフレームが結合されます。

期待値と比較して増減したストライクの列を作成

CS(ifelse関数で見逃しストライクに1、ボールに0の値を与えた列)からxCS(ストライク期待値)を引いて「増減したストライクの列」を作成します。

#ストライク-ストライク期待値の列を作成
df <- df %>% 
  mutate(
    CS_AA = called_strike - xCS)

捕手ごとにフレーミングで増やしたストライク数、得点を算出

捕手ごとにフレーミング指標を算出します。fielder_2(捕手の選手idが入った列)でgroup_by()でグループ分けして算出します。

フレーミング得点も算出します。ボール→ストライクの得点価値は0.125点なので増減したストライク数に0.125をかけることで算出できます。

Framing <- df %>%
  #group_byで捕手ごとに算出
  group_by(fielder_2) %>%
  dplyr::summarise(
    #n()で捕球機会、CSAAは平均と比較して増減させたストライク数、FRAAはフレーミング得点
    N = n(),
    CSAA = sum(CS_AA, na.rm = TRUE),
    FRAA = CSAA * 0.125)%>%
  filter(N >= 1000)

こんな感じで算出できたはずです。

savantとの値と比較

作ったフレーミング得点をSavantのフレーミング得点と比較します。
(作成したcsvファイルとsavantのcsvファイルを捕手idでVLOOKUPで紐づけました)

決定係数は0.83、精度として大きな問題はなさそうです。

この記事が気に入ったらサポートをしてみませんか?