Last update: 18-10-14.

背景と目的

Tangoの移動平均で推定された理想角度からの乖離は, wOBAconの改善や悪化と相関が見られなかった. なぜそうなったのか? ここでは以下の方針で, 原因について調べてみる.

  • ある程度狭い範囲の理想角度で選手をグループ化して, サンプル数が最大のグループに注目
  • その中で実際の平均打球角度でさらに, グループ化. 理想角度に近いグループと, そうでないグループ (実際の角度が低い) との間で比較を試みる.
  • 対象とする理想角度グループ全体において, 打球を角度で細かく分けて (bin), それぞれの範囲での平均価値を計算. さらに, 実際の平均打球角度グループについて, それぞれの角度の分布を計算. それらを利用して実際の平均打球角度グループごとに推定wOBAconを計算してみる.
  • どのbinでどれだけの打球価値が生み出されているのか? 実際の平均角度によって, 打球価値が生み出されている角度帯が異なるのか? 結果として, トータルの打球角度はなぜ同じくらいになっているのか?

つまり, 平均角度が異なる打者が, それぞれどの角度でwOBAconを稼いで, 結果的に理想角度から乖離しても差が出なかったのかを見てみましょう, ということ.

計算

この辺は1と同じ.

# dataは18-10-02取得
names(df2)
##  [1] "player_name"                     "pitcher_name"                   
##  [3] "events"                          "type"                           
##  [5] "bb_type"                         "launch_speed"                   
##  [7] "launch_angle"                    "iso_value"                      
##  [9] "hit_distance_sc"                 "woba_value"                     
## [11] "estimated_woba_using_speedangle" "woba_denom"                     
## [13] "barrel"                          "babip_value"                    
## [15] "game_year"                       "des"
# 要らないのも結構あるがどのへんを使ったかの目安で 
# pitcher_nameはいりません

# 使用したパッケージ: tidyverse, knitr, kableExtra, reshape2, cowplot

sac_bunts <- c("sac_bunt", "sac_bunt_double_play")

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

# 各選手の指標を計算する
# 使わないものも結構ある
bat <- df2 %>%
  filter(type == "X")%>%
  group_by(player_name)%>%
  dplyr::summarise(speed_mean = mean(launch_speed, na.rm = TRUE),
                   speed_sd = sd(launch_speed, na.rm = TRUE),
                   angle_mean = mean(launch_angle, na.rm = TRUE),
                   angle_sd = sd(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,
                   GB_bb = sum(GB_FL, na.rm = TRUE)/ wOBA_denom,
                   FB_bb = sum(FB_FL, na.rm = TRUE)/ wOBA_denom,
                   LD_bb = sum(LD_FL, na.rm = TRUE)/ wOBA_denom,
                   PU_bb = sum(PU_FL, na.rm = TRUE)/ wOBA_denom
                   )%>% 
  filter(wOBA_denom >= 200)
# 3分クッキング方式
# 1と同じ

# 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_15-18.Rdata")

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

最もサンプル数が多い理想角度が17.5以上20未満 (angle_group == “(17.5,20]”)に注目する. これは理想角度が近ければ,

  • 角度にそって評価した打球価値がある程度近いだろう
  • そのためグループ内では比較に適しているだろう

というぼんやりした予測に基づく. 1でみた限り, 少なくとも主要なピーク部分での打球価値が近い可能性は高く, それなりに悪くない仮定だと思われる (あとで実際の平均角度グループごとに打球価値を確認する).

理想的な角度, その範囲でのwOBAcon, 実際の平均角度, 実際のwOBAconなどを選手ごとにまとめる.

# 実際の平均角度などをbat (typeXのみ)から取得する
# それを最適角度のdfに加える
bbe <- bat %>% 
  select(player_name, angle_mean,speed_mean, 
         wOBA_denom, wOBA, xwOBA, 
         iso, angle_sd, speed_sd,
         GB_bb, LD_bb, FB_bb, PU_bb)
names(bbe) <- c("player_name","mean_angle", 
                "mean_speed" , "N", 
                "wOBAcon", "xwOBAcon","ISO", 
                "sd_angle","sd_speed",
                "GB_bb", "LD_bb", "FB_bb", "PU_bb")
# 実際には大部分は使わない. 

# 各選手の最適角度そのwOBAが記録されたbest_angleに
# 実際の角度とwOBAconを付加する
bbe <- left_join(best.angle, bbe) %>%
  mutate(angle_diff = mean_angle - launch_angle, # 実際 - 理想
         wOBA_diff = wOBAcon - mov_avg)%>%  # 実際 - 理想
  arrange(desc(wOBA_diff))

bbe %>%
  head(5)%>%
  mutate_if(is.numeric, funs(round), 3)%>%
  select(1:7)%>%
  rename("理想角度" = launch_angle,
         "理想範囲wOBAcon" =  mov_avg,
         "平均角度" = mean_angle,
         "平均速度" = mean_speed,
         "BBE" = N)%>%
  kable()%>%
  kable_styling(bootstrap_options = "striped", full_width = F)
player_name 理想角度 理想範囲wOBAcon 平均角度 平均速度 BBE wOBAcon
Rob Refsnyder 18.400 0.493 6.085 88.061 290 0.305
Coco Crisp 21.098 0.546 13.485 85.159 466 0.320
Erik Kratz 16.706 0.524 13.950 89.517 238 0.291
Carlos Perez 15.370 0.534 14.954 85.317 493 0.298
Alexi Amarista 8.469 0.521 10.227 85.185 518 0.283

適当に5人示した (BBEは打球数). ここの理想角度で選手をグルーピングする. 以後は最も選手数の多い17.5°-20°のグループについて調べていく.

# 理想角度でグループを分ける
# summary(bbe$launch_angle)
Group <- c(5, 7.5, 10, 12.5, 15, 17.5, 20, 22.5,25, 27)

# angle_dataには理想角度があるのでそれにcut関数を適用して対応付ける
bbe$angle_group <- with(bbe, cut(launch_angle, Group))

# 同じ理想角度グループの選手は似た性質をもっていると期待できる?
# とりあえずサイズが大きいグループをとりだす
group.of.interest <- bbe %>% filter(angle_group == "(17.5,20]")

角度binごとの打球価値

注目した理想角度グループ (17.5°-20°) の全打球を角度2°ごとのbinにわけ, そのbin内の平均打球パラメータを計算.

names <- group.of.interest$player_name # filter(angle_group == "(17.5,20]")したものである
# pitch-by-pitch dataを注目しているグループの選手のものの打球結果だけに絞る
data <- df2 %>% filter(player_name %in% names, type == "X") %>%
  filter(events != "sac_bunt")
# 打球の角度を2°ずつでグループ化する
angle.bin <- seq(-90, 90, 2)
data$angle_bin <- with(data, cut(launch_angle, angle.bin))

bbe.by.angle <- data %>%
  filter(is.na(launch_angle) == FALSE)%>% # 打球角度が無いデータを除いている
  group_by(angle_bin)%>%
  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)

bbe.by.angle %>%
  arrange(desc(wOBA))%>%
  slice(1:5)%>%
  mutate_if(is.numeric, funs(round), 3)%>%
  kable()%>%
  kable_styling(bootstrap_options = "striped", full_width = F)
angle_bin speed_mean angle_mean wOBA_value wOBA_denom wOBA
(22,24] 92.489 22.998 3708.05 4673 0.794
(12,14] 93.495 13.000 3929.70 4988 0.788
(24,26] 91.850 24.998 3470.40 4443 0.781
(10,12] 93.528 11.001 3712.05 4860 0.764
(26,28] 91.676 26.991 3206.45 4230 0.758

wOBAconの降順にしてtop5を示した.

これの各binのwOBAの期待値としてあとで利用し, 打球数の分布と掛け合わせる. グラフにしておく.

ピークは20°あたり. 角度が上がっても下がっても価値は下がるが, 上がりすぎたときのほうが価値の毀損は急峻である. この集団では40°を超えるとほとんど打球価値を維持できない.

各選手の打球平均角度でグループ化したときの打撃成績

打者の平均角度ごとにグループ化して, その成績や打球角度の分布を計算する.

# 実際の角度でのグループ化
# summary(bbe$mean_angle)
Group2 <- seq(-1, 32, 3)

# angle_dataには理想角度があるのでそれにcut関数を適用して対応付ける
bbe$angle_group <- with(bbe,  cut(mean_angle, Group2))
# これをdataに加える
data <- left_join(data, bbe %>% select(player_name, angle_group))
# 成績を集計しておく
bbe.by.angle_group <- data %>%
  group_by(angle_group)%>%
  dplyr::summarise(N = length(player_name),
                   speed_mean = mean(launch_speed, na.rm = TRUE),
                   speed_sd = sd(launch_speed, na.rm = TRUE),
                   angle_mean = mean(launch_angle, na.rm = TRUE),
                   angle_sd = sd(launch_angle, na.rm = TRUE),
                   wOBA_value = sum(woba_value, na.rm = TRUE),
                   xwOBA_value = sum(estimated_woba_using_speedangle, na.rm = TRUE),
                   wOBA_denom = sum(woba_denom, na.rm = TRUE),
                   wOBA = wOBA_value / wOBA_denom,
                   xwOBA = xwOBA_value / wOBA_denom)

人数をカウントしておく.

# 人数は?
data %>%
  group_by(angle_group)%>%
  dplyr::summarise(Player_N = length(unique(player_name)),
                   BBE  =  n())%>%
  kable()%>%
  kable_styling(bootstrap_options = "striped", full_width = F)
angle_group Player_N BBE
(2,5] 6 5325
(5,8] 12 8373
(8,11] 55 48899
(11,14] 63 55203
(14,17] 37 42515
(17,20] 7 5581

この理想角度内では, 実際の平均打球角度が5より小さい, あるいは17より大きい選手は6, 7人しかいない. 4年分あるので打球数としてはそれなりだが, 選手が少ないため, これらのグループに関しては一般的な傾向を捉えるのは困難だろう.

推定wOBAconの計算

打球角度グループについて, 角度binで分割し打球数と打球価値を計算.

woba.by.angle <- data %>%
  group_by(angle_bin, angle_group)%>%
  dplyr::summarise(N = n(),
                   wOBA_value = sum(woba_value, na.rm = TRUE),
                   wOBA_denom = sum(woba_denom, na.rm = TRUE),
                   wOBA = wOBA_value / wOBA_denom)%>%
  select(angle_bin, angle_group, N, wOBA)

woba.by.angle
## # A tibble: 511 x 4
## # Groups:   angle_bin [89]
##    angle_bin angle_group     N  wOBA
##    <fct>     <fct>       <int> <dbl>
##  1 (-90,-88] (2,5]           1 0    
##  2 (-86,-84] (2,5]           1 0    
##  3 (-86,-84] (8,11]          3 0.3  
##  4 (-86,-84] (14,17]         2 0    
##  5 (-84,-82] (2,5]           1 0    
##  6 (-84,-82] (8,11]          3 0    
##  7 (-84,-82] (11,14]         3 0.3  
##  8 (-84,-82] (14,17]         2 0    
##  9 (-82,-80] (8,11]          8 0.225
## 10 (-82,-80] (11,14]         3 0.3  
## # ... with 501 more rows

ここで求めた打球角度の分布と, 上で計算した角度binの平均打球価値を利用して, 推定wOBAconを計算する.

total <- data %>%
  group_by(angle_group)%>%
  dplyr::summarise(Total = n()) # 各グループにおける全打球数

ex.woba <- woba.by.angle %>% 
  select(angle_bin, angle_group, N)%>% # 各グループにおいて, bin内の打球数
  left_join(total)%>% # 各グループにおける全打球数をjoin
  left_join(bbe.by.angle) # angle_binについてwOBA(con)などの打球パラメータをjoin

ex.woba <- ex.woba %>%
  mutate(Freq = N / Total, # binの打球数が全体で占める割合
         wOBAcon = wOBA * Freq) # 各binの期待される打球価値 x 打球内に占める割合 
ex.woba %>%
  select(angle_bin, angle_group, N, Total, wOBA, wOBAcon)%>%
  mutate_at(5:6, funs(round), 3)%>%
  arrange(desc(wOBA))%>%
  rename("角度bin" = angle_bin,
         "打球角度グループ" = angle_group,
         "bin内BBE" = N,
         "総BBE" =  Total,
         "期待wOBAcon/BBE" = wOBA,
         "bin内wOBAcon" =  wOBAcon)%>%
  head(6)%>%
  kable()%>%
  kable_styling(bootstrap_options = "striped", full_width = F)
角度bin 打球角度グループ bin内BBE 総BBE 期待wOBAcon/BBE bin内wOBAcon
(22,24] (2,5] 143 5325 0.794 0.021
(22,24] (5,8] 226 8373 0.794 0.021
(22,24] (8,11] 1294 48899 0.794 0.021
(22,24] (11,14] 1592 55203 0.794 0.023
(22,24] (14,17] 1227 42515 0.794 0.023
(22,24] (17,20] 191 5581 0.794 0.027

打球価値の高いbin22°-24°の打球における, 各打球グループの結果を示した. 他のbinについても同様に計算し, 各打球角度グループですべてのbin内wOBAconを足し合わせて, 推定wOBAconとする.

dplyr::summarise(group_by(ex.woba, angle_group),
                 estimated_wOBAcon = sum(wOBAcon, na.rm = TRUE))%>%
  mutate_if(is.numeric, funs(round), 3)%>%
  kable()%>%
  kable_styling(bootstrap_options = "striped", full_width = F)
angle_group estimated_wOBAcon
(2,5] 0.389
(5,8] 0.384
(8,11] 0.388
(11,14] 0.392
(14,17] 0.393
(17,20] 0.394

実際のwOBAconと比較.

bbe.by.angle_group %>%
  select(angle_group, wOBA, speed_mean)%>%
  mutate_if(is.numeric, funs(round), 3)%>%
  rename(wOBAcon = wOBA)%>%
  kable()%>%
  kable_styling(bootstrap_options = "striped", full_width = F)
angle_group wOBAcon speed_mean
(2,5] 0.413 89.766
(5,8] 0.383 88.257
(8,11] 0.398 88.724
(11,14] 0.382 88.418
(14,17] 0.396 88.600
(17,20] 0.368 86.379

各グループでwOBAconに多少差はあるが, 平均角度が高いからと言ってwOBAconが高いとは言えないようだ.

(2,5], (17,20]で乖離している. 打球数や人数が少ないので極端な結果になって, うまく推定できなかったのかもしれない. 実際の打球速度が異なることが影響している可能性もあるかもしれない?

作図

角度のbinごとのトータルの推定打球価値をプロットする.

見づらいので絞る. サンプルが最低限あり, 推定値と実際のwOBAconが比較的一致している中で, 極端な角度である“(5,8]”, “(14,17]”に注目する. “(5,8]”はwOBAconがやや低めである.

平均角度が低い“(5,8]”では, 20°あたりのピーク部分ではあまり稼げていないが, 角度が低い領域で取り返している. 特に-20°あたりにピークがあり, そこでは打球角度が低い選手が大きく差を付けている. たぶんここは角度に関しては実測値では無い部分だろうと思うがよくわからない. 大変扱いにくい. 全体的に見ると, 角度が低い部分はそれなりに打球価値が維持されやすいことも関わっているかもしれない?

20°あたりピークでの価値の違いは打球数の分布の差によるだろう. 打球の分布を確認する. とりあえず各グループすべて.

概ね全体的にシフトしている様子が見受けられる. 目的のグループに絞って詳しく見る.

打球角度が低い選手はやはり価値のピークである20°あたりではやや打球が少ない. しかし, 打球角度が高い打者では, 40°を超えるような打球価値が非常に小さい部分でも打球が多い. 角度が低い選手では低い打球が多く, これらの打球は上がりすぎた打球よりは価値がマシである. このため, メジャーピークだけで見ればある程度の差となるが, 全体としての打球価値は, 平均角度が上がっても下がっても相殺されてあまり変わらないのではないか.

あとで見るように理想角度の計算では, 角度が理想角度から大きく離れた部分の打球価値は, 最終的には考慮されていない.

ところで, ここまでのwOBAconの推定ではbin内の打球価値が, 同じ理想角度を持つグループでは一定であることを前提としているが, 妥当か?

major peak部分あたり (20°あたり) の打球価値は概ね一致しているが, 角度が非常に低い打球 (-25°以下あたり)では, 平均角度が低い打者で高いのではないか. これらの結果は下で議論するように, Tangoの理想角度の計算では, 非常に角度が低い, あるいは高い部分の個性が反映されないということと整合的である.

理想角度からの乖離が打球価値の低下につながっていなかったが, これには打球角度を下げてもあまり困らない打者が, 選択的に打球角度を下げていたという部分も影響があるだろう.

理想範囲wOBAcon計算の上限/下限

話は変わる. Tangoの理想角度の計算で, 理想範囲wOBAconの計算は, 理想角度の前後どれぐらいの角度で行われているのか? この幅の広さはこれまで計算している理想角度を計算仮定を理解する上で重要な要素である.

# Tangoの方法で理想角度範囲の最大角度と最小角度を取得する
# 全員に適用する. やはり3分クッキング方式

# typeX <- df2 %>% filter(type == "X")%>%
#   filter(is.na(launch_angle) == FALSE,  is.na(launch_speed) == FALSE)
# player_names <- unique(bat$player_name) # 200BBE以上
# # p_name <- unique(typeX$player_name)[1]
# # これをloopで回す
# ideal_angle_data <- vector("list", length(player_names)) #ここにいれる
# 
# for(i in seq_along(player_names)){
#   p_name <- player_names[i]
#   data <- typeX %>% dplyr::filter(player_name == p_name) %>%
#     dplyr::arrange(launch_angle)
# 
#   width <- round(length(data$woba_value) / 3) # 打球数の1/3
# 
#   moving <- as.numeric(stats::filter(data$woba_value, c(rep(1, width)))/width)
#   result <- data.frame(moving = moving)  # c()の中は重み付け
#   result <- cbind(data, result) %>%
#     dplyr::select(player_name, launch_angle, moving)%>%
#     dplyr::mutate(N = length(data$woba_value),
#                   Width = width)
# 
#   # wOBAcon移動平均値が最大となった行番号を取得
#   max_woba <- max(result$moving, na.rm = TRUE)
#   result$moving <- ifelse(is.na(result$moving) == TRUE,
#                          0,
#                          result$moving) # 下の検索でNAがhitするので0にしている
#   max_woba_df <- result[result$moving == max_woba,]
# 
#   # 複数あった場合角度が高い方を取得
#   max_woba_df$row_num <- as.numeric(dimnames(max_woba_df)[[1]][length(max_woba_df$launch_angle)])
#   max_woba_df <- max_woba_df %>%
#     dplyr::arrange(desc(launch_angle))%>%
#     head(1)
# 
#   # 中心となる行の上下にwidth/2がupper, lower limits.
#   # 下で面倒なのでwidthをroundしてしまっているのでずれてるかも. まあ誤差だよ誤差.
#   max_woba_df <- max_woba_df %>%
#     dplyr::mutate(upper = round(width / 2) + row_num,
#                   lower = - round(width / 2) + row_num)
#   max_woba_df$upper_limit = as.numeric(result[max_woba_df$upper,][2])
#   max_woba_df$lower_limit = as.numeric(result[max_woba_df$lower,][2])
#   ideal_angle_data[[i]] <- max_woba_df
# }
# ideal_angle_data <- bind_rows(ideal_angle_data)
# 
# save(list=c("ideal_angle_data"),
#       file="wobacon_mov_avg_ranges_1.Rdata")

load("wobacon_mov_avg_ranges_1.Rdata")

ideal_angle_data <- ideal_angle_data%>% 
  mutate(ideal_angle = round(launch_angle, 1),
         mov_avg = round(moving, 3),
         up_range = upper_limit - launch_angle,
         low_range = launch_angle - lower_limit,
         range_diff = up_range - low_range)%>%
  mutate_at(3, funs(round), 3)%>%
  mutate_at(vars(matches("limit")), funs(round), 1)%>%
  mutate_at(vars(matches("range")), funs(round), 1)

ideal_angle_data%>%
  arrange(desc(mov_avg))%>%
  head(10)%>%
  select(player_name, ideal_angle, upper_limit, lower_limit, range_diff, mov_avg)%>%
  rename("理想範囲内wOBA" = mov_avg)%>%
  kable()%>%
  kable_styling(bootstrap_options = "striped", full_width = F)
player_name ideal_angle upper_limit lower_limit range_diff 理想範囲内wOBA
Joey Gallo 25.9 37.3 14.0 -0.5 1.157
Ryan Schimpf 25.8 35.2 13.4 -3.0 1.064
Giancarlo Stanton 18.9 33.3 6.9 2.4 1.045
Aaron Judge 20.1 31.3 10.1 1.1 1.040
Miguel Sano 20.8 33.7 10.3 2.3 1.010
Tyler Austin 22.3 34.9 11.8 2.1 1.007
Khris Davis 22.1 33.1 10.4 -0.7 0.994
Daniel Palka 18.7 33.5 5.0 1.1 0.991
Gary Sanchez 21.1 35.2 9.9 2.9 0.991
Shohei Ohtani 23.0 36.3 11.3 1.6 0.987

計算結果のうち10人を示した.

プロットして傾向を確認.

理想角度を中心として, だいたい十数度程度上下に理想範囲として含まれている. 例えば, 推定された理想角度が20°の選手では7°-33°くらいを主に含んでいることになる. 基本的にほとんどの打者は低い単打性のライナー (10°あたり) か, 長打力が一定以上あれば長打性のライナー or フライ (20°~30°) あたりに打球価値のピークがあることを考えると, 0°以下, 40°以上あたりは最終的には反映されないことが多いと思われる (一応書いておくと, 含める打球数を増やしても, おそらく角度が低い方向に引っ張られるだけで, ある意味解決はしない).

上のテーブルのrange_diffは理想角度から上限までの角度の大きさから, 理想角度から下限までの角度の大きさを引いたもの. つまり上下の非対称性を調べている. ついでに角度に関して上下の非対称性があるかどうか調べる.

y軸は上限までの角度 - 下限までの角度. 全体的に0より高いような感じ. 打球数が小さい打者で極端な数値が出ている. 推定が甘いのだろう.

上限までの角度と下限までの角度を, 箱ひげ図にしておく.

rangeはupper側に広い. つまり, 全体的にみると各打者の打球は理想角度に対して, 低角度部分でやや多い傾向 (低角度側に分布が歪んでいる) があるのだろうと思われる.

まとめ

Tangoの方法による理想角度からの乖離が大きい場合, 主要なピークでの打球価値はやや低下するが, その分極端に価値が低い上がりすぎた打球が減少し, また低角度領域で打球価値を稼いでいた. 打球価値はある程度以上ピークから離れた角度領域ではこの方法では捉えられない. 低角度領域で高い価値を持っている打者は, 自身の特徴を認識し, 打球をやや低めに打っていたのかもしれない. これは価値が低いフライを減らす効果がありそうだった. 結果として, 理想的な角度からの乖離はwOBAの変化につながっていなかった可能性がありそうである.