捕手のフレーミング指標の考え方と算出方法
田中将大が捕手にフレーミング技術を習得するように呼び掛けるなど日本でも捕手のフレーミング技術について目をする機会も増えてきている。そんなフレーミングを客観的に評価するためトラッキングデータを用いて指標にする方法を解説する。
フレーミングとは
捕手のフレーミングとは捕手が捕球によって球審にストライクを多くコールさせる技術のことを指す。野球のルール上、ストライクゾーンは定義されているものの実際にストライクかボールかを判断するのは球審に任せられている。球審がどのようなジャッジを下すかについては捕手だけでなく投手や球審ごとの癖といった多くの要素が絡むと思われるが捕手による影響がある程度大きいことが先行研究によって示されている。
このように球審がストライクと多くコールするかは捕手にある程度帰属することからフレーミングは捕手の技術であるとして現在では捕手の守備評価に組み込まれている。
フレーミング指標の考え方
ボールの軌道をトラッキングする技術が確立されてから捕手のフレーミングを客観的に評価することが可能になった。この評価方法について2019年のstatcastのデータとRを用いて実際に計算しながら説明していく。
まずストライクとボールをどれだけ増やしたかと算出する方法についてだが考え方としては投球したコースがどれだけストライクになっていたかという期待値を基に算出する方法を用いる。例えばあるコースのストライク期待値が0.9だとする。このコースに投球されたボールをストライクと判定させた場合、期待値0.9だったストライク期待値を1にしたことで+0.1の評価が与えられる。反対にボールと判定させた場合は0.9あったストライク期待値を0にしたとして-0.9の評価を与える。こうして捕手ごとに期待値と比較して増やしたストライクの数の合計数を算出する。そしてその増やしたストライクの数にボールをストライクに変えた得点価値を乗じることでその捕手がフレーミングによって防いだ失点数を得点化する。
今回は投球コースを縦横5cmずつに刻みストライクの期待値を算出する。(画像はイメージ)
補正方法
ここからは補正方法について説明する。
1つ目は投球のカウントだ。MLBでの先行研究では球審はストライクカウントが増えるとストライクゾーンが縮小しボールカウントが増えるとストライクゾーンが拡大する傾向が確認されている。同じ投球コースでもカウントによってストライクとコールされる期待値は変わってくるためこれを補正する。
2つ目は打席の左右だ。こちらもMLBの先行研究では打席の左右によって同じコースでもストライクの期待値が変動する傾向が確認されている。
3つ目は球種だ。これは球種によってフレーミングが難しい場合があることについて補正を行うのが目的としている。(例えばナックルボールのような球種は捕球が難しいとされている。)
ボールをストライクに変える得点価値
ボールをストライクに変える得点価値については見逃しストライクとボールの1球単位の得点価値の変動(statcastではdelta_run_expと呼ばれている)の平均を用いてそれぞれの得点価値を出し
ストライクの得点価値 - ボールの得点価値
と計算しストライクを1つ増やした時の得点価値を算出する。2019年度の得点価値は-0.125だった。
算出結果
この方法で算出したフレーミング得点の上位10名と下位10名を示す。
Nは捕球機会、CSAAは平均的な捕手と比較して増やしたストライクの数、FRAAはフレーミングによって防いだ失点数(-は失点を増やしたことを意味する)、FRAA_per_5000は捕球機会5000あたりで防いだ失点数だ。FRAAを見ると上位と下位では30点近い差がついていることがわかる。フレーミングが失点抑止に与える影響は捕手によってかなり差が出るようだ。
他の項目と比較したフレーミングが得失点増減に与えるインパクト
捕手のフレーミングがにわかに注目を浴びるようになった要因として得失点増減に与えるインパクトの大きさがある。下記の表はfangraphsとBaseball Prspectusから取得したデータを基に作成した2019年MLB500イニング以上出場した捕手の各項目のRAA(Runs Above Average・リーグ平均と比較した得点創出)のレンジ(最大値と最小値の差)と標準偏差(データのばらつき具合を表す指標)である。
フレーミングは打撃ほどではないものの従来から大事にされてきた盗塁阻止等、他の守備項目と比較するとデータのばらつきが大きく差がつきやすい項目だ。捕手守備において最重要な項目とされるのもわかる結果になった。
Rのコード
以下は実際に今回算出に使用したRのコード。
library(tidyverse)
library(gt)
#dfには2019MLBのstatcastデータが入っている
#フレーミング機会のみにデータを絞る
df <- df %>%
select(description,plate_x,plate_z,balls,strikes,stand,pitch_type,delta_run_exp,fielder_2,batter,player_name)
df <- df %>% filter(description == "called_strike" | description == "ball")
#垂直方向の座標が0未満の投球は評価から除外
df <- df %>% filter(plate_z > 0)
#投球位置データをフィートからセンチメートルにする
#ボールカウントの列を作成
#ifelse関数でストライクを1,ボールを0とする列を作成
df <- df %>%
mutate(plate_x_cm = plate_x * 30.48,
plate_z_cm = plate_z * 30.48,
count = paste(balls,"-",strikes),
called_strike = ifelse(description == "called_strike" ,1,0))
#cut関数で投球コースを縦横5cmずつに刻む
Group <- seq(-60,60,5)
Group_2<- seq(0, 120 , 5)
df$plate_x_bin <- with(df, cut(plate_x_cm, Group))
df$plate_z_bin <- with(df, cut(plate_z_cm, Group_2))
#投球コース、カウント、左右打席ごとのストライク期待値を出す
expected_called_strike <- df %>%
group_by(plate_x_bin,plate_z_bin,count,stand,pitch_type)%>%
dplyr::summarise(N = n(),
sumCS = sum(called_strike, na.rm = TRUE),
xCS = sumCS / N )
#NAになってる行を削除
expected_called_strike <- subset(expected_called_strike, !(is.na(expected_called_strike$plate_x_bin)))
expected_called_strike <- subset(expected_called_strike, !(is.na(expected_called_strike$plate_z_bin)))
#left_joinで各組合せのストライク期待値を結合
df <- df %>%
left_join(expected_called_strike %>% select(plate_x_bin,plate_z_bin,count,stand,pitch_type,xCS))
#ストライク-ストライク期待値の列を作成
df <- df %>% mutate(CS_AA = called_strike - xCS)
df <- df %>%
mutate(called_strike_value = ifelse(description == "called_strike",delta_run_exp,NA),
called_ball_value = ifelse(description == "ball",delta_run_exp,NA))
#ボール→ストライクの得点価値を算出。-0.125点だった。
df <- df %>%
mutate(called_strike_value = ifelse(description == "called_strike",delta_run_exp,NA),
called_ball_value = ifelse(description == "ball",delta_run_exp,NA))
mean(df$called_strike_value, na.rm = TRUE) - mean(df$called_ball_value, na.rm = TRUE)
mean(df$called_strike_value, na.rm = TRUE) - mean(df$called_ball_value, na.rm = TRUE)
#フレーミング機会1000以上の捕手ごとにそれぞれのフレーミング指標を作成
Framing <- df %>%
group_by(fielder_2) %>%
dplyr::summarise(N = n(),
CSAA = sum(CS_AA, na.rm = TRUE),
FRAA = CSAA * 0.125,
FRAA_per_5000 = FRAA / N * 5000) %>%
filter(N >= 1000)
#選手リストを作成して結合
player_list <- df %>%
select(batter,player_name)%>%
rename(fielder_2 = batter)%>%
distinct(fielder_2,.keep_all = TRUE)
Framing <- inner_join(Framing,player_list,by="fielder_2")
#降順で表示
Framing <- Framing[order(Framing$FRAA,decreasing = TRUE),]
#小数点第一位まで四捨五入
Framing[,3:5] <- round(Framing[,3:5], 1)
#並べ替え
Framing <- Framing %>%
select(player_name,N,CSAA,FRAA,FRAA_per_5000)
FRAAtop10_table <- Framing %>%
top_n(10,FRAA)
#テーブルにする
FRAAtop10_table <- FRAAtop10_table %>% gt()
FRAAtop10_table
FRAAbottom10_table <- Framing %>%
top_n(-10,FRAA)
#テーブルにする
FRAAbottom10_table <- FRAAbottom10_table %>% gt()
FRAAbottom10_table
この記事が気に入ったらサポートをしてみませんか?