Rで野球のデータを分析してみよう 3(打球の速度と角度の価値を算出)

前準備

とりあえず上記を参考に2022年のStatcastデータを集めておきます。

上記の記事は読んだことを前提で進めます。上記の記事で既に説明した関数の詳しい説明はしません。

打球の速度や角度別の価値とは?

今回は打球の速度と角度別の価値を出していきます。
statcastには以下の列があります。

launch_speed…打球速度。mph表記。
launch_angle…打球角度。

これらを一定の値で区切ることで打球価値を算出します。

下準備

まず下準備。

#statcast2022のデータをdfに入れる
df <- read.csv("statcast2022.csv")

#launch_speedをメートル法に変換
df <- df %>%
  mutate(launch_speed_km = launch_speed*1.609)

cut関数(値を区切る)

cut関数は値を区切る関数です。

seq関数(連続値をとる)

seq関数は連続値をとる関数です。
例えば(x,y,z)とした場合、xから開始し、yまでを、zの値ごとにとる、といった感じ。具体的に数字を出せば(0,21,3)なら
0,3,6,9,12,15,18,21 って感じです。

#cut(区切りたい値の入ってる列名,どんな間隔で区切るか、ここではseq関数で指定します)
#seq(開始の値、最後の値、開始の値から最後の値までをどんな間隔で区切るか)
#とりえあず速度は0,201,3、角度は-60,90,3で設定

df <- df %>%
  mutate(
    EV_bin = cut(launch_speed_km,seq(0,201,3)),
    Angle_bin = cut(launch_angle,seq(-60,90,3)))

各速度、各角度ごとの価値を算出する

打球の速度、角度ごとにどの程度の価値があるかを算出します。ここではwOBAを使います。wOBAの詳しい日本語の説明はこちら。

OPSの精度をより高くしたもの、みたいな認識でも大雑把にはOKです。

statcastには以下の列があるのでこれを使います。

woba_denom…wOBAの分母
woba_value…wOBAの分子

まず打球の速度別の価値を出してみましょう。

filter関数(フィルターをかける)

その前にfilter関数を説明します。filterは条件を付けたうえでフィルターをかけるものです。少数のサンプルは極端な結果を出すので結果を除くことにします。とりあえず200打席に満たないものは除外します。

filter(条件)

打球速度別の価値を出す

#group_byで区切った打球速度別の価値を出します。filterで200打席に満たないものは除外

EV_value <- df %>%
  group_by(EV_bin)%>%
  dplyr::summarise(PA=sum(woba_denom,na.rm=TRUE),
                   wOBA = sum(woba_value,na.rm=TRUE)/PA)%>%
  filter(PA>=200)

write.csv(EV_value,"EV_value.csv")

こんな感じで出力されればOKです。
ついでにExcelでグラフを書きます。(Rでもggplotを使えば書けますが今回は割愛)

このグラフからわかることは
・打球は速くなればなるほど価値が高い
・153キロを超えたあたりから価値がどんどん上がっていく。
・打球速度は153キロ未満では価値は横這い。
・153キロ未満で打った場合は遅くても速くても価値に大差はない。

MLBではHard Hit%(153キロ以上で打った打球率)という指標があります。打球速度は153キロ以上から価値を持つ、という上記のグラフを見れば納得できると思います。
ちなみに153未満の打球を全て0として扱い、153以上の打球は(153-打球速度)、として平均値を取るのもありだと思います。(tangotigerのEscape Velocityがそんな感じの指標です)

打球角度別の価値を出す

打球角度も同様に。

Angle_value <- df %>%
  group_by(Angle_bin)%>%
  dplyr::summarise(PA=sum(woba_denom,na.rm=TRUE),
                   wOBA = sum(woba_value,na.rm=TRUE)/PA)%>%
  filter(PA>=200)

write.csv(Angle_value,"Angle_value.csv")

打球角度は0-39度あたりは価値を持ちますが、それ以下や以上は価値がかなり低いです。打球角度は極端に低い・高い角度は価値が低いということがわかります。ちなみに9-33度は価値が高めなこともわかります。
Sweet Spot%(打球に占める8-32度の打球率)という指標がありますがこのグラフを見ると納得できるのではないでしょうか。

速度と角度をクロス集計する

ここまでは速度と角度をそれぞれ算出しましたが、実際はどちらのパラメータも重要です。よってクロス集計を試みます。
(なんかフィルターを200以上にしたらスカスカになったので今回は100にします。本当は数年分のサンプルを使うべきでしょうが今回は練習用なのでこれでよしとします)

EV_Angle_value <- df %>%
  group_by(Angle_bin,EV_bin)%>%
  dplyr::summarise(PA=sum(woba_denom,na.rm=TRUE),
                   wOBA = sum(woba_value,na.rm=TRUE)/PA)%>%
  filter(PA>=100)

write.csv(EV_Angle_value,"Angle_EV_value.csv")

クロス集計すると異常に価値が高い、いわゆるBarrelっぽい箇所があります。

Barrelの定義をwOBAcon >0.950と定義した場合こんな感じになります。

なんかそれっぽくなりました。

ここまでのコードまとめ

#statcast2022のデータをdfに入れる
df <- read.csv("statcast2022.csv")

#launch_speedをメートル法に変換
df <- df %>%
  mutate(launch_speed_km = launch_speed*1.609)

#cut(区切りたい値の入ってる列名,どんな間隔で区切るか、ここではseq関数で指定します)
#seq(開始の値、最後の値、開始の値から最後の値までをどんな間隔で区切るか)
#とりえあず速度は0,201,3、角度は-60,201,3で設定

df <- df %>%
  mutate(
    EV_bin = cut(launch_speed_km,seq(0,201,3)),
    Angle_bin = cut(launch_angle,seq(-60,90,3)))

#group_byで区切った打球速度別の価値を出します。filterで200打席に満たないものは除外

#速度別value

EV_value <- df %>%
  group_by(EV_bin)%>%
  dplyr::summarise(PA=sum(woba_denom,na.rm=TRUE),
                   wOBA = sum(woba_value,na.rm=TRUE)/PA)%>%
  filter(PA>=200)

write.csv(EV_value,"EV_value.csv")

#角度別value

Angle_value <- df %>%
  group_by(Angle_bin)%>%
  dplyr::summarise(PA=sum(woba_denom,na.rm=TRUE),
                   wOBA = sum(woba_value,na.rm=TRUE)/PA)%>%
  filter(PA>=200)

write.csv(Angle_value,"Angle_value.csv")

#速度と角度をクロス集計

EV_Angle_value <- df %>%
  group_by(Angle_bin,EV_bin)%>%
  dplyr::summarise(PA=sum(woba_denom,na.rm=TRUE),
                   wOBA = sum(woba_value,na.rm=TRUE)/PA)%>%
  filter(PA>=100)

write.csv(EV_Angle_value,"Angle_EV_value.csv")




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