Last update: 2018-10-14

背景と目的

2017年, Tangoは打者の適切な打球角度を推定する方法として, 移動平均値を利用する方法を提示している. その方法では, 全打球の1/3のwOBAconの移動平均値を取り, その値を最大化する角度を最適と考える. やや語弊を招く表現だが, 移動平均の値が最大の角度を便宜的に「理想角度」, その範囲を「理想範囲」と呼ぶことにする (正確には最大移動平均wOBAcon角度/範囲とでも言うところだろう).

この方法の有効性を検討する. 結論から言うと, あまり予測の役には立たないだろう. 今回はなぜ役に立たないか, というよりはそもそもこの方法で計算される数値がどのようにして出てくるのか, という点について主に扱う. 読みやすさのため一部の図のコードは省略している. データはStatcst data (15-18レギュラーシーズン).

# 使用するパッケージ: tidyverse, cowplot
# dataは18-05-19取得

df2 <- df2 %>% mutate(# 必要なflagをつくる
             HR_FL = ifelse(events == "home_run", 1, 0),
             # xwOBA計算のための変数を作る
             xwoba_value = ifelse(type == "X", 
                                  estimated_woba_using_speedangle,
                                  woba_value))%>%
  filter(! grepl("bunt", des))# bunt除く失策と野選は除けてない

bat <- df2 %>%
  filter(type == "X")%>% # BBEのみ
  group_by(player_name)%>%
  dplyr::summarise(speed_mean = mean(launch_speed, na.rm = TRUE),
                   angle_mean = mean(launch_angle, na.rm = TRUE),
                   wOBA_value = sum(woba_value, na.rm = TRUE),
                   xwOBA_value = sum(xwoba_value, na.rm = TRUE),
                   wOBA_denom = sum(woba_denom, na.rm = TRUE),
                   wOBA = wOBA_value / wOBA_denom,
                   xwOBA = xwOBA_value / wOBA_denom,
                   iso = sum(iso_value, na.rm = TRUE) / wOBA_denom,
                   HR_bb =sum(HR_FL, na.rm = TRUE) / wOBA_denom 
                   )%>% 
  filter(wOBA_denom >= 200)

Ortiz

まずTangoが具体例として挙げた, Ortizで試す.

ortiz <- df2 %>% filter(player_name == "David Ortiz") %>%
  filter(type == "X") %>% # 打球のみ
  filter(is.na(launch_angle) == FALSE, is.na(launch_speed) == FALSE) %>%
  arrange(launch_angle) # 角度昇順
(width <- round(length(ortiz$woba_value) / 3)) # 移動平均の幅
## [1] 300
# statsのfilter関数を使って移動平均を計算する
ortiz_mov <- stats::filter(ortiz$woba_value, c(rep(1, width)))
# c()の中は重み付け, ここでは均等に足している
# widthで割って平均, つまり打球あたりの値 (wOBAcon/BBE) に戻す 
ortiz_mov <- as.numeric(ortiz_mov/ width) 

# head(ortiz_mov, 150) 
# tail(ortiz_mov, 151)
# 900のうち頭149, 後ろ150がNA
# widthが偶数なので前が1減っている
# widthが奇数なら前後同じ数を拾うはず

# もとのdfに加える
result <- data.frame(ortiz,
                     mov_avg = ortiz_mov)
# 移動平均の値が最大の角度を取得
(max_woba <- result %>% dplyr::arrange(desc(mov_avg))%>%
  dplyr::select(player_name, launch_angle, mov_avg)%>%
  slice(1))
##   player_name launch_angle mov_avg
## 1 David Ortiz       19.515   0.919

launch_angleが理想角度, mov_avgが理想範囲でのwOBAconである. 範囲内wOBAconはTangoの結果とだいたい一致しているが, 角度はやや低めの結果になった. 打球数が違うので含めたデータの範囲の問題かもしれない. 前に書いたブログでは数値は計算していなくて, 23°ぐらいとか適当を書いている. 要修正.

全体をプロット.

MLB全体

性質をなんとなく掴むためMLB全体で描いてみる.

移動平均

MLB全データ, つまり打者ごとに分けていない状態のデータに対して同様の計算を行う.

# 下をコメントアウトして計算.

# all <- df2 %>%
#   filter(type == "X") %>%
#   filter(is.na(launch_angle) == FALSE, is.na(launch_speed) == FALSE) %>%
#   arrange(launch_angle)
# (width <- round(length(all$woba_value) / 3))
# all_mov <- stats::filter(all$woba_value, c(rep(1, width)))
# all_mov <- as.numeric(all_mov/ width)
# # もとのdfに加える
# all_result <- data.frame(all,
#                         mov_avg = all_mov)
# save(list=c("all_result"),
#       file="wobacon_mov_avg_all_result_1.Rdata")

# あとで速度も使うので速度が測定できていないものも弾いている

# 計算した結果をload
load("wobacon_mov_avg_all_result_1.Rdata")

# 移動平均の値が最大の角度を取得
(max_woba <- all_result %>% dplyr::arrange(desc(mov_avg))%>%
  dplyr::select(launch_angle, mov_avg)%>%
  slice(1))
##   launch_angle   mov_avg
## 1       19.143 0.7107267

プロット.

最大化する角度は19°程度でOrtizとそう大差ないが, Ortizでは25°くらいまで打球価値の低下がなだらかだったが, リーグ全体では急激に低下している.

このMLB全体に対する移動平均の形状がどのようにして出てきたかを理解するため, 移動平均値ではなく角度を適当なサイズに分けて, そこの打球数とwOBAconを計算する.

角度グループごとの平均値

角度を2.5°ずつで分割してその中のwOBAconとを計算する.

全体 (-90°~90°).

Group <- seq(-90, 90, 2.5)
df2$angle_bin <- with(df2, cut(launch_angle, Group))
by.angle <- df2 %>%
  group_by(angle_bin)%>%
  filter(type == "X", is.na(launch_angle) == FALSE)%>%
  dplyr::summarise(N = n(),
                   wOBA_value = sum(woba_value, na.rm = TRUE),
                   wOBA = wOBA_value / N)

angle.long <- by.angle %>%
  mutate(N = N / 20000)%>% # 2軸にするためにピークの高さをだいたいで合わせている
  select(-wOBA_value)%>%
  gather(key = Type,
         value = Value,
         N, wOBA)

ggplot(angle.long, 
       aes(x = angle_bin, 
           y = Value,
           group = Type,
           color = Type,
           linetype = Type)) +
  geom_point(size = 1, alpha = 1) +
  geom_line()+
  theme_bw(base_family = "HiraKakuPro-W3") +
  background_grid(major = "y", minor = "y", 
                  size.major = 0.5, colour.major = "gray",
                  size.minor  = 0.3, colour.minor = "gray")+
  theme(axis.text.x = element_text(size=7),
        axis.text.y = element_text(size=10)) +
  theme(axis.text.x = element_text(angle = 90, hjust = 1))+
  scale_y_continuous(sec.axis = sec_axis(~.*20000, name = "N") ) + # 2軸目の設定
  labs(title = "角度グループごとの打球の数と価値 (-90°~90°).",
       subtitle = "左の軸はwOBAcon, 右の軸は打球の数. MLB15-18.",
       x = "角度グループ", y = "wOBAcon")

Nで見られるいくつかの不自然なピークは実測値ではなく, 推定値を入れている部分だと思われる. Nの不自然なピークと一致しないwOBAの不自然なピークもあるがよくわからない.

範囲が広く見づらいので-25°~40°に絞る.

打球の価値 (wOBAcon, 青の破線) に注目すると, 角度グループに注目すると-10~40°あたりのメジャーピーク領域の中で, wOBAconは2つのピークを持つ. おそらく, 単打になりやすいライナーと, 長打になりやすい高めのライナーから低めのフライに相当する. 移動平均wOBAの19°のピークは, この2つのピークの間に位置している. おそらく両方のピークをなるべく広く含めるような値が点推定値として出てきているのだろう.

打球数に関してみると (ピンクっぽい線), こちらも19°あたりにピークが概ね一致し, さらにおおまかに対称という感じ. このため, MLB全体で移動平均を取った場合Nの分布はあまり影響は無いだろう. 個人で計算する場合に, その選手の打球数の分布の歪度の絶対値が大きいければ, Nも影響を及ぼす可能性はあるだろう.

200 BBE以上の全選手

計算

選手ごとに分割して, 15-18で打球数200以上の打者に対して同様に計算.

# 下をコメントアウトして計算

# dfを渡して移動平均値を取得する関数を設定
# あとで個人別に計算する

# compute.player.stats <- function(df = df){
#     data <- df %>%
#       arrange(launch_angle)
#     width <- round(length(data$woba_value) / 3)
#     mov <- stats::filter(data$woba_value, c(rep(1, width)))
#     mov <- as.numeric(mov/ width)
#     result <- data.frame(data,
#                          mov_avg = mov)
#     max_woba <- result %>%
#       arrange(desc(mov_avg))%>%
#       select(player_name, launch_angle, mov_avg)
#     return(max_woba)
# }
# 
# # 要らないデータを捨てる
# df.qualified <- df2 %>%
#   filter(type == "X")%>%
#   filter(is.na(launch_angle) == FALSE, is.na(launch_speed) == FALSE,
#          player_name %in% bat$player_name) # 200 BBE以上
# 
# # 全員に適用
# all.angle <- df.qualified%>%
#   group_by(player_name)%>% # player_nameでグルーピング
#   do(compute.player.stats(df = .)) # 各選手に上で設定した関数を適用し, dfで返す
# 
# best.angle <- all.angle%>%
#   group_by(player_name)%>%
#   slice(., 1) # 同様に各選手のもっとも範囲が高いwOBAを持つ移動平均範の中心を取得
# 
# save(list=c("all.angle", "best.angle"),
#      file="wobacon_mov_avg_result_1.Rdata")

# あらかじめ計算した結果を呼び出す
load("wobacon_mov_avg_result_1.Rdata")

打撃成績に計算した理想角度をjoin.

bbe <- bat %>% select(player_name, angle_mean,speed_mean, 
                      wOBA_denom, wOBA, xwOBA, iso)
names(bbe) <- c("player_name","mean_angle", "mean_velo", 
                "BBE", "wOBAcon", "xwOBAcon","ISOcon")
bbe <- left_join(best.angle, bbe) %>%
  mutate(angle_diff = mean_angle - launch_angle, # 実際 - 理想
         wOBA_diff = wOBAcon - mov_avg )%>%  # 実際 - 理想
  arrange(desc(wOBA_diff))

可視化

各打者における理想角度と実平均角度の関係.

理想角度が複数ある場合は高い方を拾っているはず (たぶん).

全体的に角度が理想より低い. 打球以外のイベントへの影響を考えるとこうなるのかもしれないし (例えば, 三振を増えないようにするという意図があるのかも), そもそもここで計算された理想角度が打球に関しても, 打者自身の感覚と乖離している可能性もある.

打球数が少ない打者では[打球角度 - 理想角度]のばらつきが大きい. 打球数が足りないため, 良い推定が得られていないと思われる (この指標の回帰の大きさについては3で扱う).

軸を理想角度に変更した. 後述するように理想角度が低い打者のほうが角度を上げすぎることのペナルティーが小さいと思われるので, 理にかなった分布である.

理想角度と, 実際の平均角度, そしてwOBAconの関係.

色はwOBAconを示す.
全体的に平均角度の方が小さいが, 理想角度が高いほど平均角度は高い傾向がある.
理想角度の割に角度が低い打者 (青の線形回帰直線の下側) でwOBAconが大きいかというとそうでもない?
これはISOcon, xwOBAconでも同様 (データは示さない).

理想範囲のwOBAconと, 実際のwOBAconの差を取ってみる.

理想角度からの乖離と, 理想範囲wOBAconとの乖離の間には特に関係性は見られない.

平均角度, 理想角度, wOBAconの関係. 見せ方を変える.

理想角度が大きい選手は平均角度のわりに高いwOBAconを示している (回帰直線の上に位置している).

理想角度が高いほど理想範囲のwOBAは大きい.
理想角度が高いほどなんだかんだ実際の角度も大きい.

理想角度からの差と、理想範囲wOBAconと実際のwOBAconの差で線形回帰

もはや計算するまでもないが, wOBAconの差を角度の差で説明させてみる.

linfit <- lm(wOBA_diff ~ angle_diff, data = bbe)
summary(linfit)
## 
## Call:
## lm(formula = wOBA_diff ~ angle_diff, data = bbe)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.28098 -0.03812  0.01049  0.04474  0.16624 
## 
## Coefficients:
##               Estimate Std. Error t value Pr(>|t|)    
## (Intercept) -0.3407292  0.0054070 -63.017   <2e-16 ***
## angle_diff   0.0010795  0.0007641   1.413    0.158    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.06221 on 544 degrees of freedom
## Multiple R-squared:  0.003655,   Adjusted R-squared:  0.001824 
## F-statistic: 1.996 on 1 and 544 DF,  p-value: 0.1583

角度差は, 理想範囲のwOBAconと実際のwOBAconの差に無関係といってよいだろう.

選手具体例

選手ごとのプロットを具体的に確認する. 面白そうな選手を探す.

## # A tibble: 5 x 5
## # Groups:   player_name [5]
##   player_name        BBE 理想角度 実平均角度 角度差
##   <chr>            <dbl>    <dbl>      <dbl>  <dbl>
## 1 Rob Refsnyder      290     18.4        6.1  -12.3
## 2 Coco Crisp         466     21.3       13.5   -7.8
## 3 Erik Kratz         238     16.3       13.9   -2.4
## 4 Carlos Perez       493     15.6       15     -0.7
## 5 Travis Jankowski   589      5.3        2     -3.3
## # A tibble: 5 x 5
## # Groups:   player_name [5]
##   player_name      BBE 理想角度 実平均角度 角度差
##   <chr>          <dbl>    <dbl>      <dbl>  <dbl>
## 1 Juan Centeno     218     23.3        7.5  -15.8
## 2 Dae-Ho Lee       218     22.1        6.3  -15.8
## 3 David Freese    1142     19.6        4.5  -15.1
## 4 Howie Kendrick  1137     16.9        2    -14.9
## 5 Trey Mancini     852     21.1        6.3  -14.8
## # A tibble: 5 x 5
## # Groups:   player_name [5]
##   player_name    BBE 理想角度 実平均角度 角度差
##   <chr>        <dbl>    <dbl>      <dbl>  <dbl>
## 1 Ryan Schimpf   272     25.8       29.7    4  
## 2 Joey Gallo     600     25.9       21.9   -4  
## 3 Rhys Hoskins   539     21.4       21.5    0.1
## 4 Jett Bandy     336     20.5       20.9    0.5
## 5 Adam Duvall   1130     22.4       20.7   -1.7
## # A tibble: 5 x 5
## # Groups:   player_name [5]
##   player_name        BBE 理想角度 実平均角度 角度差
##   <chr>            <dbl>    <dbl>      <dbl>  <dbl>
## 1 Rusney Castillo    224     11.8        0.8  -11  
## 2 Yandy Diaz         212      7.3        1.9   -5.4
## 3 Travis Jankowski   589      5.3        2     -3.3
## 4 Howie Kendrick    1137     16.9        2    -14.9
## 5 Austin Slater      218     10.4        2.2   -8.2
## # A tibble: 5 x 5
## # Groups:   player_name [5]
##   player_name        BBE 理想角度 実平均角度 角度差
##   <chr>            <dbl>    <dbl>      <dbl>  <dbl>
## 1 Daniel Castro      225      5          6      0.9
## 2 Travis Jankowski   589      5.3        2     -3.3
## 3 Luis Sardinas      255      5.7        5.9    0.2
## 4 Christian Colon    244      7.3        7     -0.3
## 5 Yandy Diaz         212      7.3        1.9   -5.4

目についた以下の打者とStantonについて作図.
Dae-Ho Lee: 角度差最大.
Ryan Schimpf: 実際の平均角度最大.
Travis Jankowski: 実際, 理想平均角度がかなり低く, 打球数もそれなりに多い.
Giancarlo Stanton: なんとなく.

Stanton以外は打球が少ない. 極端な角度を示す打者を集めてきたので, 推定の甘い打者が選ばれてきたのは当然.

Jankowskiは形がかなり異なる. 一般性がありそうなので全体的な傾向をあとで確認する.

各選手を理想角度でグループ化

打者の理想角度タイプごとに, 移動平均の形状や, あるいは理想角度と平均角度の差の影響が異なる可能性があるかもしれない. 理想的な角度の違いによって各打者を分類し, その分類で分割してプロット (選手数が少ないグループは除いた).

summary(bbe$launch_angle)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   5.042  15.985  18.211  17.878  20.101  26.217
Group <- c(5, 7.5, 10, 12.5, 15, 17.5, 20, 22.5,25, 27)
# 理想角度でグループ化
bbe$angle_group <- with(bbe, cut(launch_angle, Group))

速度に関して調整された状態では, どの理想角度のグループでも角度差が小さいからwOBAconが高い, というようなことは見られないようだ. あまり関係ないが, 回帰直線の傾きを見ると, 速度の増大の影響は, 理想角度が大きいほど効果的になっているらしいことがわかる (速度による価値の上昇はFBで大きいのでそれらしい結果).

サンプルサイズが最も大きい17.5-20°のグループに絞って、その中での傾向をもう少し詳しく探る. 具体的には理想角度からの乖離が大きい (低く打ちすぎている) 打者をラベルする.

temp <- bbe %>% filter(angle_group == "(17.5,20]")
playername <- temp$player_name

low.anglle.hitter <- temp %>% 
  arrange(angle_diff) %>% 
  head(15)
selected.playername <- as.character(low.anglle.hitter$player_name)
playername <- as.character(playername)  
playername[!playername %in% selected.playername] <- "" 

ggplot(temp, aes(x = mean_velo, 
                 y = wOBAcon,
                 colour = angle_diff,
                 label = playername)) +
  scale_color_gradient(low = "black", high = "skyblue")+
  geom_point(size = 0.8) +
  geom_smooth(method ="lm") + 
  theme_bw(base_family = "HiraKakuPro-W3") +
  background_grid(major = "xy", minor = "y", 
                  size.major = 0.5, colour.major = "gray", 
                  size.minor = 0.25, colour.minor = "gray")+
  theme(axis.text.x = element_text(size=12),
        axis.text.y = element_text(size=12)) +
  geom_label_repel(size = 3, 
                   family = "HiraKakuPro-W3",
                   box.padding = unit(1.5,"lines"),
                   point.padding = unit(0.1,"lines"),
                   segment.color='grey70') +
  labs(title = "理想角度からのずれとwOBAcon.",
       subtitle = "理想角度 = (17.5,20]. 直線は速度に関する単純回帰. MLB15-18で打球200以上 (N = 151).
       実角度 - 理想角度が低い選手15人についてラベル.",
       x = "平均速度 (MPH)", y = "wOBAcon",colour = "角度差")

ラベルされた角度差が大きい選手は, 打球速度のわりにwOBAconが高い選手もいれば, 低い選手もいる. 角度差が大きいからといってwOBAconが低いということはなさそう. また, 角度差が小さい (色が薄いドット) からwOBAconが高い, というような傾向もなさそう. つまり, 理想角度からの乖離はwOBAconに対して, 速度を調整した状態において, 大まかに理想角度を揃えた状態ですら相関を持たない.

つまり打球の中だけに議論を絞っても, 理想角度から乖離しても打撃による得点能力は損なわれていない. なぜこうなったかは色々と調べる必要があるが, 本稿では扱わない (2で扱う).

理想角度で全体をグループ化

選手具体例で見られた形状の違いの一般性を検討したい. 理想角度グループごとにまとめ, そのタイプごとの移動平均を求める. これによりサンプル数を稼いで, その形状の違いを比較する.

# # 元のdfに対して, 各打者の理想角度グループを示す列を付加

# angle.group <- bbe %>% select(player_name, angle_group)
# df3 <- df2 %>% left_join(angle.group)
# 
# df.qualified2 <- df3 %>% filter(type == "X")  %>%
#   filter(is.na(launch_angle) == FALSE,
#          is.na(launch_speed) == FALSE,
#          is.na(angle_group) == FALSE)  # 角度グループが計算された選手の打席のみ
# 
# compute.angle.group.stats <- function(df = df){
#     data <- df %>%
#       arrange(launch_angle)
#     width <- round(length(data$woba_value) / 3)
#     mov <- stats::filter(data$woba_value, c(rep(1, width)))
#     mov <- as.numeric(mov/ width)
#     result <- data.frame(data,
#                          mov_avg = mov)
#     max_woba <- result %>%
#       arrange(desc(mov_avg))%>%
#       select(angle_group, launch_angle, mov_avg) # ここが違うだけ
#     return(max_woba)
# }
# 
# # 全員に適用
# all.angle2 <- df.qualified2%>%
#   group_by(angle_group)%>% # angle_groupでグルーピング
#   do(compute.angle.group.stats(df = .)) # 各グループに上で設定した関数を適用し, dfで返す
# 
# best.angle2 <- all.angle2%>%
#   group_by(angle_group)%>%
#   slice(., 1) # 同様に各グループのもっとも範囲が高いwOBAを持つ移動平均範の中心を取得
# 
# save(list=c("all.angle2", "best.angle2"),
#      file="wobacon_mov_avg_result_for_angle_type_1.Rdata")

load("wobacon_mov_avg_result_for_angle_type_1.Rdata")
best.angle2
## # A tibble: 9 x 3
## # Groups:   angle_group [9]
##   angle_group launch_angle mov_avg
##   <fct>              <dbl>   <dbl>
## 1 (5,7.5]             5.33   0.570
## 2 (7.5,10]           10.1    0.572
## 3 (10,12.5]          12.4    0.609
## 4 (12.5,15]          14.2    0.651
## 5 (15,17.5]          17.0    0.673
## 6 (17.5,20]          19.0    0.732
## 7 (20,22.5]          20.8    0.783
## 8 (22.5,25]          23.4    0.826
## 9 (25,27]            26.3    0.997

形がかなり違う. 最適角度が低い選手たちの場合, 角度を下げた場合に傾きが急である. 一方, 最適角度が大きい選手の場合は, むしろ角度を上げすぎた場合に低下が急である.

なぜこのような形になるのかを調べるため角度2°ごとにわけてその中の平均成績を計算する.

angle.group <- bbe %>% select(player_name, angle_group)
df3 <- df2 %>% left_join(angle.group)
typeX <- df3 %>% filter(type == "X")  %>%
  filter(is.na(launch_angle) == FALSE) %>%
  filter(is.na(launch_speed) == FALSE) %>%
  filter(is.na(angle_group) == FALSE)
# summary(typeX$launch_angle)
angle.bin <- seq(-90, 90, 2)
typeX$angle_bin <- with(typeX, 
                        cut(launch_angle, angle.bin))

angle.bin.df <- group_by(typeX, 
                            angle_bin, 
                            angle_group)%>%
  dplyr::summarise(speed_mean = mean(launch_speed, na.rm = TRUE),
                   angle_mean = mean(launch_angle, na.rm = TRUE),
                   wOBA_value = sum(woba_value, na.rm = TRUE),
                   wOBA_denom = sum(woba_denom, na.rm = TRUE),
                   wOBA = wOBA_value / wOBA_denom,
                   HR_bb =sum(HR_FL, na.rm = TRUE) / wOBA_denom
                   ) 
ggplot(angle.bin.df , 
             aes(x = angle_mean, 
                 y = wOBA)) +
  geom_point(size = 0.3) +
  facet_wrap(~angle_group, ncol = 3)+
  xlim(-30, 50)+
  theme_bw(base_family = "HiraKakuPro-W3") +
  background_grid(major = "xy", minor = "none", 
                  size.major = 0.5, colour.major = "gray")+
  theme(axis.text.x = element_text(size=12),
        axis.text.y = element_text(size=12)) +

  labs(title = "打球角度とwOBAcon (角度を2度ごとにグループ化し、その平均値をプロット).",
       subtitle = "理想角度でグルーピング. 上の数字は理想角度の範囲. -30° ~ 50°のみを示した. " ,
       x = "角度", y = "wOBAcon")

最適角度が低い選手では10°を超えたあたりにピーク. 角度が低いあたり (0°近辺) では最適角度が大きい選手よりもすこし打球の価値が高いかも.

最適角度が高い選手では30°ぐらいまでwOBAconが上がり続けるが、 そこから価値が急激に下がる. かなり長打力の高い選手たちだと思われるが, それでも35°程度までしか打球の価値を維持できない. 40°-50°までくると長打力とはほとんど関連がない.

理想角度タイプによって異なるピークから, 理想角度タイプによってはあまり変わらない点 (とりあえず45°としましょう) への傾きを考える. すると, 最適角度が低い打者では10°-15°のやや低め (wOBA value 0.8強) のピークから45°へ低下していく. 一方, 最適角度が高い打者では30°の高いピーク (wOBA value 1程度) から45°へ低下していく. つまり, 傾きがかなり異なる.

同じ “最適角度ピークからの上げすぎ”でも傾きが違うため, 意味は異なる. 言ってしまえば, 最適角度が低い打者は打ち上げてもまだライナーというだけの話だが.

この打球を少し上げすぎたときのペナルティーの大きさは最適角度を, 特に最適角度が大きい選手で, 単純な平均で得られるピークの位置よりも押し下げているはず. この結果, 単純に打球価値が高い30°よりもかなり下, 25°弱が望ましいということになっているのだろう. 個人では打球数の分布の形も影響しているかもしれないが.

下は打球タイプごとの角度などの目安.

df2 %>% filter(type == "X")%>%
  group_by(bb_type)%>%
dplyr::summarise(BBE = sum(woba_denom, na.rm = TRUE),
                 angle_mean = mean(launch_angle, na.rm = TRUE),
                 wOBA = sum(woba_value, na.rm = TRUE) / BBE)
## # A tibble: 4 x 4
##   bb_type        BBE angle_mean   wOBA
##   <chr>        <dbl>      <dbl>  <dbl>
## 1 fly_ball    110918       36.6 0.399 
## 2 ground_ball 227120      -11.7 0.251 
## 3 line_drive  131198       16.8 0.696 
## 4 popup        35509       63.4 0.0247

まとめ

極めて散らかった話になった. 結論としては, 冒頭で述べたとおり予測の役に立つかどうかという点ではあまり意味がなさそうである.

ここで見てきたように, この方法はおそらく単打になりやすいライナーや, あるいは長打になりやすいライナーから低めのフライなどの打球価値や分布を考慮して決まるのではないかと思われ, 打球価値の中で主要な部分についてそれなりに理に適った方法で考慮しているようにみえる. にも関わらず, 残念な結果になったということは重要だと見るべきだろう. そもそも平均角度に注目することの効果の小ささを示唆しているかもしれない.

他の可能性としては指標自体が非常に不安定であるために, 200打球数以上程度ではサンプルが足りないため推定が甘く, ここでのデータでは意味のある推論ができていない可能性が考えられるかもしれない.