投手は同じ球種を連投するべきではないのか。

投手は狙いを絞らせないためや打者に球筋を見慣れさせないために同じ球種を連続して投げるべきではないと言われることがあります。しかし実際それは正しいのでしょうか。

球種の投球割合を増やしてもRun Value(得点価値)やSwStr%(空振り率)は変わらない。

上記の記事は投手が球種の割合を増減させた場合にRV/100(100球あたりの球種の得点価値)やSwStr%(空振り/投球)にどのような変化が見られるのかについて検証した記事です。検証した結果、投手がある球種の投球割合を増減させたとしてもRV/100やSwStr%に変化は見られませんでした。投球割合を増やすと自然と球種を連投する機会が増えると思うのですがそれでも球種の価値に変化が見られないのであれば同じ球種の連投にそれほどペナルティがないのではないでしょうか。検証してみます。

検証方法

同じ投手対打者の打席で特定の球種を連投した場合について各種Statsを算出します。このときカウントによって打者のアプローチやRun Valueの変動のしやすさがある程度変わることが予測されるのでその影響を抑えるために初球(一般的にスイング率が低い)は結果から除外し2ストライク時(三振が発生するカウントのためRVの価値が上がりやすい、打者が三振を避けて積極的に振ってくる)と非2ストライク時に結果を分割します。また同じ球種を連投しても抑えられるのは藤川球児やデニス・サファテのフォーシーム、マリアノ・リベラやケンリー・ジャンセンのカットボールのような救援投手の特権の可能性もありそうです。先発投手についても有効なのかを探るために先発と救援で分割します。

検証結果

各球種・各ポジションについて結果を載せていきます。

(2strikesの連投数は2strikesになってからの連投数ではなく2strikesになるまでのカウントからの通算の連投数であることに注意してください。)

フォーシーム

FF連投

シンカー(ツーシーム)

SI連投

カットボール

FC連投

スライダー

SL連投

カーブ

CU連投

チェンジアップ

CH連投

全体の傾向

個々の球種の各種条件ごとの結果はそれぞれ異なるのですがここでは全体の傾向にあてはまりそうなことを書きます。まずRV/100(100球あたりの得点価値、投手にとっては低いほうがいい)とSwStr%(空振り/投球)は1球目よりも2球目や3球目といった連投をした方が優れた値を記録する傾向にあります。これは同じ球種を連投してはいけないという考えに反する結果です。

また球種の連投がプラスの効果を発揮しているのは救援投手だけでなく先発投手にも当てはまります。打者と何巡も対戦しない救援投手はともかく1試合に何度も同じ相手と対戦する先発投手は多くの球種を駆使して抑えるというイメージを私は持っていたのですが先発投手でも球種の連投は効果を発揮しているようです。

もちろんこの結果は何かしらのバイアスを含んでいる可能性があります。例えば同じ球種を連続して投げるのは打者がその球種をよほど苦手にしている場合であったり投手にとっての得意球種なので連投しても打たれない、といったものです。ただ同じ球種を連続して投げると打たれやすくなるというはっきりとしたデメリットも表れておらず同じ打者に同じ球種を何球も投げてはいけないという従来の考えに一考の余地を与えるのではないでしょうか。

将来的には投手はウィニングショットに依存した投球をするようになる?

And that trend will not stop until there is evidence that a pitch will perform worse upon increased usage. Barring that, pitchers across the league will rely on the pitches they deem most dominant.

前述で紹介した記事では特定の球種の使用量が増えるとパフォーマンスが低下するという証拠が出るまでリーグ全体の投手は自分が最も支配的だと考える球種に依存した投球をすることを示唆しています。今回分析した球種の連投という観点から見ても同じ球種を連続して投げることのはっきりとしたデメリットは見出し難く(むしろ1球目より2,3球連続して投げると効果的)投手が自分の得意とする球種に依存したピッチングをすることを否定しない結果になりました。これらの結果から考えると将来的にはわざわざ得意でない球種を使用してまでの多彩な球種や緩急を駆使した投球は失われ投手たちがそれぞれ得意とする球種に依存した投球を行う時代が訪れるのかもしれません。


library(tidyverse)
library(gt)
library(webshot)

df <- df %>%
 select(game_pk,at_bat_number,pitch_number,pitch_type,pitcher,batter,description,woba_denom,
        estimated_woba_using_speedangle,woba_value,type,zone,strikes,inning_topbot,delta_run_exp)

df <- df %>%
 arrange(game_pk,at_bat_number,pitch_number)

player_Top <- df %>%
 filter(inning_topbot == "Top")%>%
 group_by(game_pk,pitcher)%>%
 filter(at_bat_number == min(at_bat_number))%>%
 filter(pitch_number == min(pitch_number))%>%
 select(game_pk,at_bat_number,pitch_number)

SP_Top <- player_Top %>%
 group_by(game_pk)%>%
 filter(at_bat_number == min(at_bat_number))%>%
 filter(pitch_number == min(pitch_number))%>%
 mutate(Top_SP = "SP")%>%
 select(pitcher,game_pk,Top_SP)

df <- left_join(df,SP_Top)

player_Bot <- df %>%
 filter(inning_topbot == "Bot")%>%
 group_by(game_pk,pitcher)%>%
 filter(at_bat_number == min(at_bat_number))%>%
 filter(pitch_number == min(pitch_number))%>%
 select(game_pk,at_bat_number,pitch_number)

SP_Bot <- player_Bot %>%
 group_by(game_pk)%>%
 filter(at_bat_number == min(at_bat_number))%>%
 filter(pitch_number == min(pitch_number))%>%
 mutate(Bot_SP = "SP")%>%
 select(pitcher,game_pk,Bot_SP)

df <- left_join(df,SP_Bot)

df <- df %>%
 mutate(P_POS = case_when(
   Top_SP == "SP" ~ "SP",
   Bot_SP == "SP" ~ "SP",
   TRUE ~ "RP"
 ))

df <- df %>%
 mutate(
   pitch_type = case_when(
     pitch_type == "FF" ~ "FF", 
     pitch_type == "FT" | pitch_type == "SI" ~ "SI",
     pitch_type == "SL"  ~ "SL",
     pitch_type == "FC"  ~ "FC",
     pitch_type == "CU" | pitch_type == "KC" ~ "CU",
     pitch_type == "CH" | pitch_type == "FS" ~ "CH",
     TRUE ~ "ELSE"),
   lag_1_P = lag(pitcher),
   lag_2_P = lag(lag_1_P),
   lag_3_P = lag(lag_2_P),
   lag_4_P = lag(lag_3_P),
   lag_1_B = lag(batter),
   lag_2_B = lag(lag_1_B),
   lag_3_B = lag(lag_2_B),
   lag_4_B = lag(lag_3_B),
   lag_1_PT = lag(pitch_type),
   lag_2_PT = lag(lag_1_PT),
   lag_3_PT = lag(lag_2_PT),
   lag_4_PT = lag(lag_3_PT),
   Pitch_1 = ifelse(pitch_type != lag_1_PT,1,0),
   Pitch_2 = ifelse(pitch_type == lag_1_PT & pitcher == lag_1_P & batter == lag_1_B ,2,0),
   Pitch_3 = ifelse(pitch_type == lag_1_PT & pitcher == lag_1_P & batter == lag_1_B
                    & lag_1_PT == lag_2_PT & lag_1_P == lag_2_P & lag_1_B == lag_2_B ,3,0),
   Pitch_4 = ifelse(pitch_type == lag_1_PT & pitcher == lag_1_P & batter == lag_1_B 
                    & lag_1_PT == lag_2_PT & lag_1_P == lag_2_P & lag_1_B == lag_2_B 
                    & lag_2_PT == lag_3_PT & lag_2_P == lag_3_P & lag_2_B == lag_3_B ,4,0),
   Pitch_5 = ifelse(pitch_type == lag_1_PT & pitcher == lag_1_P & batter == lag_1_B 
                    & lag_1_PT == lag_2_PT & lag_1_P == lag_2_P & lag_1_B == lag_2_B 
                    & lag_2_PT == lag_3_PT & lag_2_P == lag_3_P & lag_2_B == lag_3_B
                    & lag_3_PT == lag_4_PT & lag_3_P == lag_4_P & lag_3_B == lag_4_B ,5,0),
   Pitch_2 = ifelse(Pitch_2 == 2 & Pitch_3 != 3 ,2,0),
   Pitch_3 = ifelse(Pitch_3 == 3 & Pitch_4 != 4 ,3,0),
   Pitch_4 = ifelse(Pitch_4 == 4 & Pitch_5 != 5 ,4,0),
   Continues = case_when(
     Pitch_5 == 5 ~ 5,
     Pitch_4 == 4 ~ 4,
     Pitch_3 == 3 ~ 3,
     Pitch_2 == 2 ~ 2,
     Pitch_1 == 1 ~ 1),
   swing_denom = case_when( 
   description == "swinging_strike" ~ 1,
   description == "swinging_strike_blocked" ~ 1,
   description == "foul_tip" ~ 1,
   description == "foul" ~ 1,
   description == "hit_into_play" ~ 1,
   description == "hit_into_play_score" ~ 1,
   description == "hit_into_play_no_out" ~ 1,
   TRUE ~ 0),
   swstr = case_when( 
     description == "swinging_strike" ~ 1,
     description == "swinging_strike_blocked" ~ 1,
     description == "foul_tip" ~ 1,
     TRUE ~ 0),
   contact = case_when( 
     description == "foul" ~ 1,
     description == "hit_into_play" ~ 1,
     description == "hit_into_play_score" ~ 1,
     description == "hit_into_play_no_out" ~ 1,
     TRUE ~ 0),
   ball_zone = ifelse(zone == 11 | zone == 12 | zone == 13 | zone == 14 ,1,0),
   strike_zone = ifelse(ball_zone == 0,1,0),
   ball_contact = ifelse(ball_zone == 1 & contact == 1 ,1,0),
   strike_contact = ifelse(strike_zone == 1 & contact == 1 ,1,0),
   ball_swing_denom = case_when( 
     ball_zone == 1 & description == "swinging_strike" ~ 1,
     ball_zone == 1 & description == "swinging_strike_blocked" ~ 1,
     ball_zone == 1 & description == "foul_tip" ~ 1,
     ball_zone == 1 & description == "foul" ~ 1,
     ball_zone == 1 & description == "hit_into_play" ~ 1,
     ball_zone == 1 & description == "hit_into_play_score" ~ 1,
     ball_zone == 1 & description == "hit_into_play_no_out" ~ 1,
     TRUE ~ 0),
   strike_swing_denom = case_when( 
     strike_zone == 1 & description == "swinging_strike" ~ 1,
     strike_zone == 1 & description == "swinging_strike_blocked" ~ 1,
     strike_zone == 1 & description == "foul_tip" ~ 1,
     strike_zone == 1 & description == "foul" ~ 1,
     strike_zone == 1 & description == "hit_into_play" ~ 1,
     strike_zone == 1 & description == "hit_into_play_score" ~ 1,
     strike_zone == 1 & description == "hit_into_play_no_out" ~ 1,
     TRUE ~ 0),
   xwoba_value = ifelse(type == "X",estimated_woba_using_speedangle,woba_value),
   strikes2 = ifelse(strikes == 2,"2strikes","non2strikes"),
   strikes2 = 
     factor(strikes2, 
            levels = c("non2strikes", "2strikes")),
   P_POS = 
     factor(P_POS, 
            levels = c("SP", "RP")))

PT_Continuous_Plate <- df %>%
 group_by(pitch_type,P_POS,strikes2,Continues)%>%
 filter(pitch_number >= 2)%>%
 dplyr::summarise(N = n(),
                 Ball_zone = sum(ball_zone ,na.rm = TRUE),
                 Strike_Zone = sum(strike_zone ,na.rm=TRUE),
                 Swing = sum(swing_denom,na.rm =TRUE),
                 O_Swing = sum(ball_swing_denom,na.rm =TRUE),
                 Z_Swing = sum(strike_swing_denom,na.rm=TRUE),
                 Swingpct = round(Swing / N*100,1),
                 O_Swingpct = round(O_Swing / Ball_zone*100,1),
                 Z_Swingpct = round(Z_Swing / Strike_Zone*100,1),
                 Contactpct = round(sum(contact,na.rm=TRUE)/Swing*100,1),
                 O_Contactpct = round(sum(ball_contact,na.rm=TRUE)/O_Swing*100,1),
                 Z_Contactpct = round(sum(strike_contact,na.rm=TRUE)/Z_Swing*100,1),
                 SwStrpct = round(sum(swstr,na.rm=TRUE)/N*100,1),
                 RV_100 = round(mean(delta_run_exp,na.rm = TRUE)*100,2))%>%
 select(pitch_type,Continues,N,Swingpct,O_Swingpct,Z_Swingpct,Contactpct,O_Contactpct,Z_Contactpct,SwStrpct,RV_100)

PT_Continuous_Plate <- subset(PT_Continuous_Plate, !(is.na(PT_Continuous_Plate$Continues)))

PT_Continuous_typeX <- df %>%
 group_by(pitch_type,P_POS,strikes2,Continues)%>%
 filter(pitch_number >= 2)%>%
 dplyr::summarise(wOBAcon_denom = sum(woba_denom,na.rm=TRUE),
                  xwOBAcon = round(sum(xwoba_value,na.rm=TRUE)/wOBAcon_denom,3))
PT_Continuous_typeX <- subset(PT_Continuous_typeX, !(is.na(PT_Continuous_typeX$Continues)))

PT_Continuous <- inner_join(PT_Continuous_Plate,PT_Continuous_typeX)

PT_Continuous <- PT_Continuous %>%
 select(pitch_type,Continues,N,Swingpct,O_Swingpct,Z_Swingpct,Contactpct,O_Contactpct,Z_Contactpct,SwStrpct,xwOBAcon,RV_100)

FF_Continue <- PT_Continuous%>%
 filter(pitch_type == "FF") %>% gt %>%
 tab_header(
   title = "同一球種連投の効果",
   subtitle = md("MLB 15-20 source:statcast")
 )%>%
 tab_footnote(
   footnote = "初球(0-0からの投球)を除く",
   locations = cells_data(
     columns = vars(Continues),
     rows = Continues == 1 & strikes2 == "non2strikes" 
     ))%>%
 tab_footnote(
   footnote = "5球目以降",
   locations = cells_data(
     columns = vars(Continues),
     rows = Continues == 5 
   ))

gtsave(FF_Continue, "FF連投.png")

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