6
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

mediba Advent Calendar 2024Advent Calendar 2024

Day 9

RでMLBのMVPプレイヤーのホームラン時の投球コースと球種を可視化する

Last updated at Posted at 2024-12-08

はじめに

mediba Advent Calender 2024の9日目の記事になります。

今回は、R言語のアウトプットとしてbaseballrというライブラリを利用しMLBのデータを可視化することにしました。

具体的には、MLB 2024年シーズンのMVPプレイヤーである以下2選手のホームラン時の投球コースと球種を可視化しました。

  • American League MVP:Aaron Judge(NYY)
  • National League MVP:Shohei Ohtani(LAD)

アウトプット

最終的に、2選手のホームラン時の投球コースと球種を可視化することができました。

本来ストライクゾーンの位置は選手ごとに異なるのですが、以下は一律同じ位置に固定しているため実際のストライクゾーンとは異なります。

image.png

可視化すると、ジャッジ選手は、ある程度コースを絞ってホームランにしているのが分かります。

逆に、大谷選手は特定のコースに絞らず、ホームランにしています。かなり外れたボール球をホームランにしているのも分かりますね。(第52号ホームランの映像

おまけ

ホームランの投球コース別割合も可視化してみました。比較すると、以下の傾向が見えてきました。

以下ストライクゾーンの位置は選手ごとに異なるため、実際のストライクゾーンに近い位置で可視化されています。

  • ジャッジ選手:高めの球のホームランは少なく、真ん中から低めに強い
  • 大谷選手:どの投球コースでもホームランを打っている(中でも高めが少し強い)

image.png

作業工程

今回の大まかな作業の流れは以下の通りです。

  1. データ収集(baseballr)
  2. スキーマを理解する
  3. 前処理(dplyr)
  4. 可視化(ggplot2/patchwork)

データ収集(baseballr)

Rのbaseballrを利用して、Baseball SavantというWebサイトからスクレイピングし必要なデータを取得します。

Baseball Savantには「Statcast」という分析ツールで計測されたMLBの試合に関するあらゆるデータが掲載されています。

image.png
引用 - 「Introducing Statcast 2020: Hawk-Eye and Google Cloud」Ben Jedlovec氏

なお、baseballrにはBaseball Savant以外のWebサイトからもデータをスクレイピングする関数が含まれており、効率的なデータ取得が可能です。

それでは、データをスクレイピングしていきます。

必要パッケージのインストール

CRANに登録されていないbaseballrパッケージをインストールしたいため、devtoolsを利用しています。

install.packages("devtools")
install.packages("tidyverse")
install.packages("dplyr")
install.packages("ggplot2")
install.packages("patchwork")

library(devtools) #CRANに登録されていないパッケージのインストール
library(tidyverse) 
library(dplyr) #前処理
library(ggplot2) #可視化
library(patchwork) #ggplot2で作成された複数のプロットの結合

install_github("billpetti/baseballr", force = TRUE)

余談ですが、pacmanというライブラリを利用すれば、対象パッケージがインストールされていなくても自動でインストールし読み込んでくれるようです。

install.packages("pacman")
library(pacman)

p_load(devtools, tidyverse, dplyr, ggplot2, patchwork)

install_github("billpetti/baseballr", force = TRUE)

Baseball Savantのデータをスクレイピングする

以下の関数で、対象選手の指定期間の打者データのスクレイピングが可能です。

df_judge_raw <- scrape_statcast_savant_batter("2024-01-01",
                                              "2024-12-31",
                                              batterid = 592450)

df_ohtani_raw <- scrape_statcast_savant_batter("2024-01-01",
                                               "2024-12-31",
                                               batterid = 660271)

なお、上記関数の引数に必要なbatteridは、以下2種類の方法で検索します。

  1. baseballrplayerid_lookup関数
  2. razzball.com内での検索

1のplayerid_lookup関数によるbatteridの検索は少々時間がかかるため、2の方法をおすすめします。

playerid_lookup("Judge", "Aaron")
playerid_lookup("Ohtani", "Shohei")

2はrazzball.comにアクセスし、対象選手の氏名でフィルタをかけて検索します。

image.png

データフレームの中身を確認すると、2024年シーズンの対象選手に投じられた1球ごとのデータが格納されています。

打球速度や打球角度、カウントなどあらゆるデータが正確に計測されています。

image.png

補足となりますが、スクレイピングするための関数は投手用など他にもあるようでした。

scrape_statcast_savant(start_date = "yyyy-mm-dd",
                       end_date = "yyyy-mm-dd",
                       playerid = {playerid},
                       player_type = "{pitcher or batter}", ...)

scrape_statcast_savant_batter_all(start_date = "yyyy-mm-dd",
                                  end_date = "yyyy-mm-dd",
                                  batterid = {batterid}, ...)

scrape_statcast_savant_pitcher(start_date = "yyyy-mm-dd",
                               end_date = "yyyy-mm-dd",
                               pitcherid = {pitcherid}, ...)

scrape_statcast_savant_pitcher_all(start_date = "yyyy-mm-dd",
                                   end_date = "yyyy-mm-dd",
                                   pitcherid = {pitcherid}, ...)

スキーマを理解する

可視化に必要なカラムを判断するため、以下のページを参考に先ほどのスクレイピングしたデータのスキーマを理解します。

以下のカラムが、可視化に必要となりそうです。

  • events:打席の結果
  • plate_x:ホームベースを横切った際のボールの水平位置(キャッチャー視点)
    • 単位は「ft」(フィート)/ 1ft = 30.48cm
    • 捕手視点で真ん中を「0」、右打者側を「-」マイナス、左打者側を「+」プラスとする
  • plate_z:ホームベースを横切った際のボールの垂直位置(キャッチャー視点)
    • 単位は「ft」(フィート)
    • 地面を「0」、ワンバウンドを「-」マイナス、地上を「+」プラスとする
  • pitch_name:球種

なお、野球独自の指標などは、以下のページを参考に理解しました。

前処理(dplyr)

簡単な前処理を行います、具体的な内容は以下の通りです。

  • 対象選手それぞれのデータフレームを作成する
  • 可視化に必要なカラムを抽出する
  • レギュラーシーズンのホームランのみのレコードにフィルタする
  • 日付順(昇順)に並び替える
df_judge_hr_result <- df_judge_raw %>% 
  select(pitch_type, game_date, player_name, events, zone, game_type, plate_x, plate_z, pitch_name) %>% 
  filter(events == "home_run", game_type == "R") %>% 
  arrange(game_date)

df_ohtani_hr_result <- df_ohtani_raw %>% 
  select(pitch_type, game_date, player_name, events, zone, game_type, plate_x, plate_z, pitch_name) %>% 
  filter(events == "home_run", game_type == "R") %>% 
  arrange(game_date)

前処理した結果は以下の通りです。

image.png

念のため、対象選手の2024年レギュラーシーズンのホームラン数が合っているか確認しておきます。

まずは、先ほどの2つのデータフレームをrbindで結合します。

df_mvp_hr_result <- rbind(df_judge_hr_result, df_ohtani_hr_result) %>% 
  arrange(player_name, game_date)

次に、以下コードをRStudioのConsole上で実行すると対象選手の2024年レギュラーシーズンのホームラン数が出力されます。

df_mvp_hr_count <- df_mvp_hr_result %>% 
  group_by(player_name) %>% 
  summarize(hr_count = sum(events == "home_run")) %>% 
  ungroup()

# 実行結果
# A tibble: 2 × 2
  player_name    hr_count
  <chr>             <int>
1 Judge, Aaron         58
2 Ohtani, Shohei       54

両者2024年のレギュラーシーズンのホームラン数と一致しているので、問題ないですね。

可視化(ggplot2/patchwork)

それでは、最後に以下の手順に沿って可視化を行います。

  1. 13分割された投球コースとホームベースをプロットする
  2. ホームラン時のボールの投球コースと球種をプロットする

なお、本工程は以下の記事を参考にさせていただきました。

13分割された投球コースとホームベースをプロットする

まずは、13分割された投球コースとホームベースをグラフ上にプロットするために、必要なX、Y座標のデータをデータフレーム化します。

13分割された投球コースのX座標、Y座標は以下の通りとなります。手作業で座標を指定いくのはかなり大変でした。

また、各座標データがどの投球コースに属するか番号を振っておくことが重要です。番号が存在しないと、プロット時に描画が崩れる原因となります。

ids_4 <- 1:9
ids_6 <- 11:14

zone_vector <- c(
  rep(ids_4, each = 4), rep(ids_6, each = 6)
)

positions <- tribble(
  ~ width, ~ height,
  #1
  -0.947, 3.6,
  -0.947, 2.9,
  -0.316, 2.9,
  -0.316, 3.6,
  
  #2
  -0.316, 3.6,
  -0.316, 2.9,
  0.315, 2.9,
  0.315, 3.6,
  
  #3
  0.315, 3.6,
  0.315, 2.9,
  0.947, 2.9,
  0.947, 3.6,
  
  #4
  -0.947, 2.9,
  -0.947, 2.2,
  -0.316, 2.2,
  -0.316, 2.9,
  
  #5
  -0.316, 2.9,
  -0.316, 2.2,
  0.315, 2.2,
  0.315, 2.9,
  
  #6
  0.315, 2.9,
  0.315, 2.2,
  0.947, 2.2,
  0.947, 2.9,
  
  #7
  -0.947, 2.2,
  -0.947, 1.5,
  -0.316, 1.5,
  -0.316, 2.2,
  
  #8
  -0.316, 2.2,
  -0.316, 1.5,
  0.315, 1.5,
  0.315, 2.2,
  
  #9
  0.315, 2.2,
  0.315, 1.5,
  0.947, 1.5,
  0.947, 2.2,
  
  #11
  -1.420, 4.125,
  -1.420, 2.55,
  -0.947, 2.55,
  -0.947, 3.6,
  0, 3.6,
  0, 4.125,
  
  #12
  0, 4.125,
  0, 3.6,
  0.947, 3.6,
  0.947, 2.55,
  1.420, 2.55,
  1.420, 4.125, 
  
  #13
  -1.420, 2.55,
  -1.420, 0.975,
  0, 0.975,
  0, 1.5,
  -0.947, 1.5,
  -0.947, 2.55,
  
  #14
  0, 1.5,
  0, 0.975,
  1.420, 0.975,
  1.420, 2.55,
  0.947, 2.55,
  0.947, 1.5
) %>%
  bind_cols(zone = zone_vector)

データフレームの中身は以下の通りとなります。

image.png

ホームベースのX座標、Y座標は以下の通りとなります。

homebase <- tribble(
  ~ width, ~ height,
  -0.9, 0.5,
  -1.3, 0.2,
  0, 0,
  1.3, 0.2,
  0.9, 0.5
)

次に、以下のコードを実行すると13分割された投球コースとホームベースの可視化の完成です。ホームベースを可視化すれば、キャッチャー視点であることがすぐに分かります。

ポイントは、inherit.aes = Fです。異なるデータフレームを別のレイヤーとして可視化する場合は本記述が必要となります。

plot_judge_hr_result <- ggplot(positions) +
  aes(x = width, y = height, group = zone) +
  geom_polygon(color = "black", fill = "white") +
  geom_polygon(aes(x = width, y = height), data = homebase, inherit.aes = F, color = "black", fill = "white") +
  scale_x_continuous("Horizontal location(ft.)", limits = c(-2, 2)) +
  scale_y_continuous("Vertical location(ft.)", limits = c(0, 5))

plot_judge_hr_result

13分割された投球コースとホームベースを可視化することができました。

image.png

ホームラン時のボールの投球コースと球種をプロットする

最後に、ホームランを打った際のコースと球種を先ほどの13分割された投球コース上にプロットします。

plot_judge_hr_result <- ggplot(positions) +
  aes(x = width, y = height, group = zone) +
  geom_polygon(color = "black", fill = "white") +
  geom_polygon(aes(x = width, y = height), data = homebase, inherit.aes = F, color = "black", fill = "white") + 
  geom_point(aes(x = plate_x, y = plate_z, color = pitch_name), data = df_judge_hr_result, inherit.aes = F) +
  scale_x_continuous("Horizontal location(ft.)", limits = c(-2, 2)) +
  scale_y_continuous("Vertical location(ft.)", limits = c(0, 5)) +
  labs(color = "Pitch Type") +
  ggtitle("Aaron Judge Homerun Result(2024)")

plot_judge_hr_result

plot_ohtani_hr_result <- ggplot(positions) +
  aes(x = width, y = height, group = zone) +
  geom_polygon(colour = "black", fill = "white") +
  geom_polygon(aes(x = width, y = height), data = homebase, inherit.aes = F, colour = "black", fill = "white") +
  geom_point(aes(x = plate_x, y = plate_z, color = pitch_name), data = df_ohtani_hr_result, inherit.aes = F ) +
  scale_x_continuous("Horizontal location(ft.)", limits = c(-2, 2)) +
  scale_y_continuous("Vertical location(ft.)", limits = c(0, 5)) +
  labs(color = "Pitch Type") +
  ggtitle("Shohei Ohtani Homerun Result(2024)")

plot_ohtani_hr_result

添付画像のような形で、可視化することができました。

image.png

加えて、2選手それぞれのグラフを横に並べて比較したいため、patchworkというライブラリを利用します。

凡例ラベルの表示は1つでよいため、片方のグラフで非表示にしておきます。

plot_judge_hr_result <- ggplot(positions) +
  aes(x = width, y = height, group = zone) +
  geom_polygon(color = "black", fill = "white") +
  geom_polygon(aes(x = width, y = height), data = homebase, inherit.aes = F, color = "black", fill = "white") + 
  geom_point(aes(x = plate_x, y = plate_z, color = pitch_name), data = df_judge_hr_result, inherit.aes = F) +
  scale_x_continuous("Horizontal location(ft.)", limits = c(-2, 2)) +
  scale_y_continuous("Vertical location(ft.)", limits = c(0, 5)) +
  ggtitle("Aaron Judge") +
  theme(
    legend.position = "none", # 凡例を非表示
    plot.title = element_text(hjust = 0.5)
  ) 

plot_ohtani_hr_result <- ggplot(positions) +
  aes(x = width, y = height, group = zone) +
  geom_polygon(colour = "black", fill = "white") +
  geom_polygon(aes(x = width, y = height), data = homebase, inherit.aes = F, colour = "black", fill = "white") +
  geom_point(aes(x = plate_x, y = plate_z, color = pitch_name), data = df_ohtani_hr_result, inherit.aes = F ) +
  scale_x_continuous("Horizontal location(ft.)", limits = c(-2, 2)) +
  scale_y_continuous("Vertical location(ft.)", limits = c(0, 5)) +
  labs(color = "Pitch Type") +
  ggtitle("Shohei Ohtani") +
  theme(plot.title = element_text(hjust = 0.5))

plot_mvp_hr_result <- plot_judge_hr_result + plot_ohtani_hr_result +
  plot_annotation(title = "MVP Homerun Results Comparison(2024)")

plot_mvp_hr_result

以上で、可視化の完成となります。

image.png

ホームランのゾーン別割合

詳細な説明は割愛しますが、別の視点でも可視化を行ってみました。よりホームランの傾向が見えてきて、興味深いですね。

image.png

おわりに

今回、Rのアウトプットを行うことで改めてRの基礎(特に前処理、可視化)を復習することができました。

また、私自身興味のあるMLBのデータを利用して、可視化した結果を確認する瞬間が最も楽しかったです。

今後も興味のあるデータで様々な可視化をしてみることで、業務に活用していきたいと思います。

参考

6
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?