忙しくて全然更新できてなかった間に今年のMLBも半分以上が過ぎてしまいました…。

前回はpitchf/xのデータから田中将大投手を見てみましたが、今回はstatcastrパッケージの紹介がてら今年の田中将大投手の投球を見てみたいと思います。

※pitchf/xとstatcastのデータ取得方法の違いはここに、データ内容についてはここが参考になります。
statcastの方が打者や野手の動きまで詳細に把握できる分、便利です。



statcastrパッケージについて

statcastrパッケージは以下に公開しています。

■statcastで使える関数群

  • scrape_statcast
    ∟このパッケージのメインの関数で任意の期間のピッチング・バッティングのデータをスクレイピングする
  • get_snapshots
    ∟scrape_statcastで得たデータから1球ごとの球の軌道を計算する
  • get_strikezones
    ∟打者位置ごとにストライクゾーンの平均位置を計算する
  • team_results_ref
    ∟指定チームのスケジュールと勝敗をスクレイピングする
  • theme_batterbox
    ∟ggplot2と併用する。バッターボックスのlayerを描く



今年の田中将大投手の成績について

以下の表は田中投手のMLBでの成績です。(2018年8月4日現在)

チーム 登板 投球回 完投 自責点 奪三振 セーブ WHIP 防御率
2014 ヤンキース 20 136 1/3 3 42 141 13 5 0 1.06 2.77
2015 ヤンキース 24 154 1 60 139 12 7 0 0.99 3.51
2016 ヤンキース 31 199 2/3 0 68 165 14 4 0 1.08 3.07
2017 ヤンキース 30 178 1/3 1 94 194 13 12 0 1.24 4.74
2018 ヤンキース 17 98 1/3 1 42 100 9 2 0 1.05 3.84

昨年に比べてだいぶ調子を取り戻せてますね。
マーくん、半端ないって…。



今年の田中将大投手の変化球について

# devtools::install_github("pontsuyu/statcastr")
library("statcastr")
library("tidyverse")
library("ggrepel")
# 2015〜2018年のスクレイピングの実行して保存しておく
# data <- list()
# data[[1]] <- scrape_statcast("2015-04-06", "2015-10-04", pit_bat = "pitcher")
# data[[2]] <- scrape_statcast("2016-04-03", "2016-10-02", pit_bat = "pitcher")
# data[[3]] <- scrape_statcast("2017-04-02", "2017-10-01", pit_bat = "pitcher")
# data[[4]] <- scrape_statcast("2018-04-02", "2018-07-30", pit_bat = "pitcher")
# data <- data %>% 
#   bind_rows %>% 
#   left_join(player_ids, by=c("pitcher"="MLBID"))
# write.csv(data, "data.csv", row.names=F)
data <- data.table::fread("data.csv", data.table = F)
use_col <- c("pitch_n", "game_date", "inning",  
             "pitch_name", "pitch_type", "release_speed",
             "release_pos_x", "release_pos_y", "release_pos_z",
             "player_name", "batter", "pitcher",
             "events", "description", "des",
             "spin_dir", "zone",
             "game_type", "stand", "p_throws",
             "home_team", "away_team",
             "type", "hit_location",
             "bb_type", "outs_when_up", "balls", "strikes",
             "pfx_x", "pfx_z",
             "plate_x", "plate_z",
             "on_3b", "on_2b", "on_1b",
             "inning", "inning_topbot",
             "hc_x", "hc_y",
             "vx0", "vy0", "vz0",
             "ax", "ay", "az",
             "sz_top", "sz_bot")

# 見たいピッチャーの名前を入力
p_name <- "Masahiro Tanaka"
pitch <- data %>% 
         filter(PLAYERNAME==p_name, 
                !(pitch_name %in% c("", "Pitch Out"))) %>% 
         select_(.dots = use_col) %>% 
         separate(game_date, c("year", "month", "day"), sep = "-")
# 球種の割合
pt <- pitch %>% 
      group_by(year, stand, pitch_name) %>% 
      summarise(N = n()) %>% 
      group_by(year, stand) %>% 
      mutate(per = N/sum(N)*100) %>% 
      arrange(year, pitch_name) %>% 
      mutate(row = row_number()) %>% 
      arrange(desc(row)) %>% 
      group_by(year, stand) %>% # 以下、可視化用処理
      mutate(cumsum = cumsum(per) - 0.5 * per,
             year_N = paste0(stand, "_", year, "(N=", sum(N), ")")) %>% 
      ungroup %>% 
      as.data.frame

# 年別打者位置ごとの球種割合
p1 <- ggplot(pt, aes(year_N, per, fill = pitch_name)) + 
  geom_bar(stat = "identity") +
  geom_text(aes(label = round(per, digits = 2), y = cumsum), size = 3) +
  labs(title=paste0("Proportion of pitch names (", p_name, ")")) +
  xlab("stand_year_pitching-N") + ylab("percent") + 
  theme(axis.text.x = element_text(angle = 45, hjust = 1),
        plot.title = element_text(hjust = 0.5))

# 年別変化球の変化量
breaking <- pitch %>%
  group_by(year,pitch_name) %>%
  summarise(x=-mean(as.numeric(pfx_x)*30.48, na.rm = T), # 1feet=30.48cm
            z=mean(as.numeric(pfx_z)*30.48, na.rm = T)) %>%
  mutate(angle=ifelse(x<0, pi+atan(z/x), atan(z/x))) %>%
  as.data.frame

p2 <- ggplot(breaking, aes(0, 0)) +
  geom_point(data = pitch, 
             aes(-as.numeric(pfx_x)*30.48, as.numeric(pfx_z)*30.48, color=pitch_name)) +
  geom_spoke(aes(angle = angle, radius = sqrt(x^2+z^2)), arrow = arrow()) +
  geom_label_repel(aes(x=x, y=z+.5, label = pitch_name, color=pitch_name),
                   size= 3, label.size = 0.5,
                   fontface = "bold", segment.size = 1.5) +
  ggtitle("各球種の変化量(投手視点)") +
  xlab("横の変化量(cm)") + ylab("縦の変化量(cm)") +
  theme_bw(base_family = "Osaka") +
  facet_wrap(~year)

前回の記事と比較するとわかりますが、pitchf/xのデータとほぼ一致しています。
ただ2シームがシンカーとして扱われているのは注意です。

各年の平均球速
pitch_name 2015 2016 2017 2018
4-Seam Fastball 149.3 149.1 148.6 147.8
Changeup - - - 141.6
Curveball 124.0 122.5 123.7 123.9
Cutter 144.3 144.0 144.2 143.0
Sinker 147.8 146.5 147.2 146.1
Slider 135.6 136.1 136.3 134.3
Split Finger 141.8 140.3 141.0 139.3

今年は変化球の投球割合もストレート(4-Seam Fastball)主体となり、スライダーやスプリットがより活きる形にできているようです。また、スライダーの横変化が大きくなっているのも特徴的です。



便利な可視化方法について

gganimateパッケージやplotlyパッケージを駆使するといろいろ見ることが出来ます。

xyz <- get_snapshots(pitch %>% filter(year==2018), interval = 0.01)
sz <- get_strikezones(pitch %>% filter(year==2018))
#x:プレートと並行な面
use_col <- colnames(pitch)
x <- cbind(pitch %>% filter(!is.na(release_pos_x),year==2018), xyz[,,1]) %>% 
  gather("time", "X", -use_col) %>% 
  mutate(time = as.numeric(time)) %>% 
  arrange(pitch_n, time)
#y:ピッチャーとバッター間の面
y <- cbind(pitch %>% filter(!is.na(release_pos_x),year==2018), xyz[,,2]) %>% 
  gather("time", "Y", -use_col) %>% 
  mutate(time = as.numeric(time)) %>% 
  arrange(pitch_n, time)
xy <- inner_join(x, y)
#z:地面からの高さ
z <- cbind(pitch %>% filter(!is.na(release_pos_x),year==2018), xyz[,,3]) %>% 
  gather("time", "Z", -use_col) %>% 
  mutate(time = as.numeric(time)) %>% 
  arrange(pitch_n, time)
xz <- inner_join(x, z)
rm(x, y, z)

p1 <- ggplot(data=xz, aes(x=X, y=Z)) +
    geom_rect(data = sz, inherit.aes = F,
              mapping = aes(xmin = Left, xmax=Right,
                            ymin=Bottom, ymax=Top, group=stand),
              fill="transparent", color="black") +
    geom_point(aes(fill=pitch_type), alpha=0.5,color="grey20",
               shape=21,size=2,stroke=1) +
    ggtitle("1球ごとのアニメーション(審判目線)") +
    theme_bw(base_family = "Osaka") + 
    facet_grid(~stand)+ 
    gganimate::transition_manual(time)
print(p1)

p2<- ggplot(data=xy[1:1000,], aes(x=X,y=Y)) +
  geom_point(aes(color=pitch_type), shape=16, size=1, na.rm=TRUE) +
  theme_batterbox()
plotly::plotly_build(p2)

インタラクティブにいろいろ試したいときplotly便利ですよね。



最後に

今回はパッケージの紹介ということでスクレイピング・可視化を中心に書きました。
最近は大谷翔平選手も調子が上がってきて、2打席連続本塁打を打つなど調子がいいので、今度はバッター側の分析もおこなってみたいと思います。