「Rによるセイバーメトリクス入門」発売中~mlbrパッケージを添えて~

※これはスポーツアナリティクス Advent Calendar 2020 6日目の記事です。*1


久しぶりのブログ更新になってしまいました…。

公開できるアウトプットを出し続けるのは大変ですね…。


アウトプットするには文にまとめる力が必要で、

まとめるには内容の理解が必要で、

理解するには様々なインプットが必要となるので

アウトプットを習慣にできると強くなれるとわかってはいるのですが、

なかなか実行できないものですね。


「悔しいなぁ、何か一つできるようになっても またすぐ目の前に分厚い壁があるんだ」

と炭治郎も言ってましたが、そのセリフを読んで思わず「それな」と言ってしまったのを思い出しました。

目次

はじめに

さて、本題です。

タイトルにもある通り、先日、私が翻訳者の一人として携わりました「Rによるセイバーメトリクス入門」が発売されました。

原著はこちら


セイバーメトリクスに関する本はこれまでもありましたが、R言語と一緒に書かれた和書はなかったと思いますので、 ぜひこの機会にR言語にもセイバーメトリクスにも入門していただけると嬉しいです。


また、本書の第1章でも紹介していますが、メジャーリーグではLahmanデータベースやRetrosheet、PITCHf/x、(Baseball Savantを経由した)Statcastデータなど多くのデータが公開されていて、さらにFanGraphsBaseball-Referenceには各種成績やセイバーメトリクス指標が掲載されています。


これらのデータはRやPythonから比較的簡単に取得することが出来ます。



ただ、公開されているデータが多い一方、R言語のパッケージもLahman、retrosheet、pitchRx、baseballrなど乱立してしまっているのもまた事実です。



昨今、baseballrパッケージにすべての機能を集約していく流れがあるようには感じますが、baseballrパッケージをいざ使ってみると、似た関数が多く、どの関数がどこからデータを取ってきていて、どのデータならどの関数に適用できるのかがいまいちわかりにくいなぁという印象を受けました(もちろん、便利なパッケージであることのは確かです)。



そこで、前置きは長くなりましたが、baseballrパッケージの理解を深めつつ、自分が使いそうな関数を再編したり、さらに機能を追加したりして、mlbrパッケージというものを作成しましたので、今回はその紹介が出来ればと思います。

※私が以前作成したstatcastrパッケージを使ってくれている方もいらっしゃると思いますが、これからはmlbrパッケージに機能を集約していきます

github.com




mlbrパッケージについて

なにができるか

Baseball Savant(Statcast)

  • 一球データ(MLBのみ)の取得
  • 得点期待値の算出
  • Linear Weightsの算出
  • 投球のスナップショットの算出
  • 平均ストライクゾーンの算出
  • 打撃成績の算出

MLB Stats API(MLB、MiLBなど)

  • 一球データの取得
  • 試合情報の取得
  • 打順(選手交代)情報の取得
  • 球場情報の取得

FanGraphs

  • (指定選手の)試合ごとの成績取得(MLB、MiLB)
  • 選手ごとの年間成績取得

Baseball-Reference

  • チームの試合ごとの成績取得
  • 指定した期間内の成績取得

その他

  • 選手リストの取得

シンプルによく使いそうな関数に絞って、ブラッシュアップしたつもりです。

データハンドリングを不自由なく出来る方は一球データを取得したら、バリバリ分析していただけたらと思います。



関数概要

先述したように「どの関数がどこからデータを取ってきていて、どのデータならどの関数に適用できるのかがいまいちわかりにくいなぁ」と感じたので、 データ取得元によって関数に接頭辞を付けました。

  • sc_*:Statcast
  • statsapi_*:MLB Stats API
  • fg_*:Fangraphs
  • bref_*:Baseball-Reference


各関数を関連図にするとこのようになります。

f:id:tsuyu_pon:20201206105059p:plain



使用例

まずはFanGraphsから。

# devtools::install_github("pontsuyu/mlbr")
library(mlbr)
library(tidyverse)

# 選手情報の取得
playerlist <- get_playerlist()

#### FanGraphs ####
# ダルビッシュ投手のIDを抽出
p_id <- playerlist %>% 
  filter(mlb_name == "Yu Darvish") %>%
  pull(fg_id)
gamelog_mlb  <- fg_mlb_gamelog(p_id, 2020, "pitcher") # MLB成績(2020)
gamelog_milb <- fg_milb_gamelog(p_id, 2018, "pitcher") # MiLB成績(2018)

# 選手ごとの年間成績取得
leader_p <- fg_leaders(2020, pit_bat = "pitcher")
leader_b <- fg_leaders(2020, pit_bat = "batter")

取得したデータ例

> glimpse(gamelog_mlb)
Rows: 12
Columns: 29
$ name     <chr> "Yu Darvish", "Yu Darvish", "Yu Darvish", "Yu Darvish", "Yu Da...
$ playerid <chr> "13074", "13074", "13074", "13074", "13074", "13074", "13074",...
$ Date     <chr> "2020-09-25", "2020-09-20", "2020-09-15", "2020-09-09", "2020-...
$ Team     <chr> "CHC", "CHC", "CHC", "CHC", "CHC", "CHC", "CHC", "CHC", "CHC",...
$ Opp      <chr> "@CHW", "MIN", "CLE", "CIN", "STL", "@CIN", "CHW", "STL", "MIL...
$ GS       <chr> "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1"
$ W        <chr> "1", "0", "0", "0", "1", "1", "1", "1", "1", "1", "1", "0"
$ L        <chr> "0", "1", "0", "1", "0", "0", "0", "0", "0", "0", "0", "1"
$ SV       <chr> "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0"
$ HLD      <chr> "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0"
$ IP       <chr> "7.0", "6.0", "7.0", "6.0", "7.0", "6.0", "7.0", "6.0", "7.0",...
$ TBF      <chr> "25", "28", "30", "23", "22", "27", "27", "25", "25", "25", "2...
$ H        <chr> "3", "9", "9", "2", "1", "7", "6", "8", "1", "5", "2", "6"
$ R        <chr> "0", "4", "3", "3", "1", "0", "1", "1", "1", "1", "0", "3"
$ ER       <chr> "0", "4", "2", "3", "1", "0", "1", "1", "1", "1", "0", "3"
$ HR       <chr> "0", "1", "0", "1", "1", "0", "1", "0", "1", "0", "0", "0"
$ BB       <chr> "1", "1", "1", "3", "0", "2", "1", "1", "2", "1", "1", "0"
$ SO       <chr> "5", "9", "7", "9", "11", "8", "10", "7", "11", "4", "7", "5"
$ K_9      <dbl> 6.43, 13.50, 9.00, 13.50, 14.14, 12.00, 12.86, 10.50, 14.14, 5...
$ BB_9     <dbl> 1.29, 1.50, 1.29, 4.50, 0.00, 3.00, 1.29, 1.50, 2.57, 1.29, 1....
$ HR_9     <dbl> 0.00, 1.50, 0.00, 1.50, 1.29, 0.00, 1.29, 0.00, 1.29, 0.00, 0....
$ BABIP    <chr> ".158", ".471", ".409", ".100", ".000", ".412", ".333", ".471"...
$ LOB_perc <dbl> 1.000, 0.698, 0.700, 0.556, 1.000, 1.000, 1.000, 0.889, 1.000,...
$ GB_perc  <dbl> 0.526, 0.333, 0.476, 0.636, 0.545, 0.375, 0.375, 0.412, 0.364,...
$ HR_FB    <dbl> 0.0, 16.7, 0.0, 50.0, 25.0, 0.0, 16.7, 0.0, 20.0, 0.0, 0.0, 0.0
$ ERA      <chr> "0.00", "6.00", "2.57", "4.50", "1.29", "0.00", "1.29", "1.50"...
$ FIP      <chr> "2.19", "2.86", "1.62", "3.86", "1.91", "1.52", "2.62", "1.36"...
$ xFIP     <chr> "4.12", "2.62", "3.55", "2.33", "1.15", "2.81", "2.42", "2.32"...
$ GSv2     <chr> "80", "48", "61", "61", "83", "67", "70", "63", "79", "72", "7...

選手詳細データが欲しい場合はfg_*を使う想定です。

続いて、Statcastデータ。

#### Statcast ####
# 一球データ取得
sc_data <- sc_pbp("2020-07-23", "2020-10-28")
# 得点期待値
sc_PA <- sc_run_expectancy(sc_data, level = "PA")
glimpse(sc_PA$data)
sc_PA$RE %>% as.data.frame()
sc_Pitch <- sc_run_expectancy(sc_data, level = "Pitch")
glimpse(sc_Pitch$data)
sc_Pitch$RE %>% as.data.frame()

# Linear Weights
sc_lw_PA <- sc_linear_weights(sc_PA$data, level = "PA")
sc_lw_Pitch <- sc_linear_weights(sc_Pitch$data, level = "Pitch")
# 投球系
snap <- sc_snapshots(sc_data)
st_zone <- sc_strikezone_mean(sc_data)
# 打撃成績まとめ
batting_stats <- sc_batting_stats(sc_data, "pa")

取得したデータ例

> glimpse(sc_data)
Rows: 279,660
Columns: 73
$ game_year          <int> 2020, 2020, 2020, 2020, 2020, 2020, 2020, 2020, 2020...
$ game_date          <date> 2020-07-23, 2020-07-23, 2020-07-23, 2020-07-23, 202...
$ game_type          <chr> "R", "R", "R", "R", "R", "R", "R", "R", "R", "R", "R...
$ home_team          <chr> "WSH", "WSH", "WSH", "WSH", "WSH", "WSH", "WSH", "WS...
$ away_team          <chr> "NYY", "NYY", "NYY", "NYY", "NYY", "NYY", "NYY", "NY...
$ game_pk            <dbl> 630851, 630851, 630851, 630851, 630851, 630851, 6308...
$ home_score         <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0...
$ away_score         <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2...
$ bat_score          <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2...
$ fld_score          <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0...
$ post_away_score    <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2...
$ post_home_score    <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0...
$ post_bat_score     <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2...
$ post_fld_score     <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0...
$ inning             <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1...
$ inning_topbot      <chr> "Top", "Top", "Top", "Top", "Top", "Top", "Top", "To...
$ strikes            <int> 0, 0, 0, 1, 2, 0, 0, 1, 0, 1, 1, 2, 2, 0, 1, 1, 0, 0...
$ balls              <int> 0, 1, 2, 2, 2, 0, 1, 1, 0, 0, 1, 1, 2, 0, 0, 1, 0, 1...
$ outs_when_up       <int> 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2...
$ on_1b              <dbl> NA, NA, NA, NA, NA, NA, NA, NA, 592450, 592450, 5924...
$ on_2b              <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, ...
$ on_3b              <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, ...
$ fielder_1          <chr> "453286", "453286", "453286", "453286", "453286", "4...
$ fielder_2          <chr> "435559", "435559", "435559", "435559", "435559", "4...
$ fielder_3          <chr> "519346", "519346", "519346", "519346", "519346", "5...
$ fielder_4          <chr> "516770", "516770", "516770", "516770", "516770", "5...
$ fielder_5          <chr> "452678", "452678", "452678", "452678", "452678", "4...
$ fielder_6          <chr> "607208", "607208", "607208", "607208", "607208", "6...
$ fielder_7          <chr> "664057", "664057", "664057", "664057", "664057", "6...
$ fielder_8          <chr> "645302", "645302", "645302", "645302", "645302", "6...
$ fielder_9          <chr> "594809", "594809", "594809", "594809", "594809", "5...
$ p_throws           <chr> "R", "R", "R", "R", "R", "R", "R", "R", "R", "R", "R...
$ pitch_number       <int> 1, 2, 3, 4, 5, 1, 2, 3, 1, 2, 3, 4, 5, 1, 2, 3, 1, 2...
$ pitch_type         <chr> "FF", "FF", "FF", "CU", "FF", "FF", "SL", "FF", "SL"...
$ pitch_name         <chr> "4-Seam Fastball", "4-Seam Fastball", "4-Seam Fastba...
$ release_speed      <dbl> 95.3, 96.1, 96.7, 79.7, 96.1, 96.6, 86.7, 96.7, 87.4...
$ release_pos_x      <dbl> -3.28, -3.38, -3.21, -3.36, -3.28, -3.17, -3.47, -3....
$ release_pos_z      <dbl> 5.47, 5.33, 5.33, 5.48, 5.13, 5.56, 5.37, 5.44, 5.33...
$ spin_dir           <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, ...
$ pfx_x              <dbl> -0.86, -0.79, -0.78, 0.95, -0.75, -0.84, 0.22, -0.92...
$ pfx_z              <dbl> 1.23, 1.44, 1.27, -0.82, 1.25, 1.39, -0.09, 1.35, 0....
$ plate_x            <dbl> -1.16, -1.17, 0.48, -0.41, -0.19, -0.17, -0.33, -0.6...
$ plate_z            <dbl> 3.06, 2.75, 2.07, 1.85, 2.41, 1.50, 2.34, 2.64, 2.27...
$ vx0                <dbl> 7.498831, 7.600636, 11.547336, 4.691537, 9.899700, 9...
$ vy0                <dbl> -138.7235, -139.7677, -140.2895, -116.0643, -139.626...
$ vz0                <dbl> -3.5069884, -4.4832221, -5.9981937, 0.3199351, -4.47...
$ ax                 <dbl> -12.6991506, -11.9647650, -12.7984804, 7.6807117, -1...
$ ay                 <dbl> 28.23702, 30.15144, 30.88044, 22.13045, 30.12869, 28...
$ az                 <dbl> -15.68936, -12.47690, -14.09971, -39.86721, -14.9623...
$ launch_speed       <dbl> NA, NA, NA, NA, 106.7, NA, NA, 108.0, NA, NA, NA, NA...
$ launch_angle       <dbl> NA, NA, NA, NA, 4, NA, NA, 4, NA, NA, NA, NA, -16, N...
$ effective_speed    <dbl> 95.7, 95.9, 96.4, 79.6, 96.3, 96.7, 87.0, 96.5, 86.9...
$ release_spin_rate  <dbl> 2484, 2477, 2421, 2846, 2450, 2575, 2532, 2618, 2328...
$ release_extension  <dbl> 6.3, 6.1, 6.2, 6.2, 6.4, 6.3, 6.1, 6.1, 6.1, 6.2, 6....
$ release_pos_y      <dbl> -3.28, -3.38, -3.21, -3.36, -3.28, -3.17, -3.47, -3....
$ launch_speed_angle <dbl> NA, NA, NA, NA, 4, NA, NA, 4, NA, NA, NA, NA, 1, NA,...
$ zone               <dbl> 11, 11, 9, 7, 5, 13, 7, 4, 5, 12, 6, 14, 14, 5, 13, ...
$ type               <chr> "B", "B", "S", "S", "X", "B", "S", "X", "S", "B", "S...
$ batter             <int> 543305, 543305, 543305, 543305, 543305, 592450, 5924...
$ batter_name        <chr> "Aaron Hicks", "Aaron Hicks", "Aaron Hicks", "Aaron ...
$ sz_top             <dbl> 3.85, 3.58, 3.33, 3.51, 3.41, 3.99, 4.07, 3.76, 3.31...
$ sz_bot             <dbl> 1.84, 1.75, 1.65, 1.65, 1.56, 1.99, 2.02, 1.75, 1.56...
$ at_bat_number      <int> 1, 1, 1, 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 5, 5...
$ stand              <chr> "L", "L", "L", "L", "L", "R", "R", "R", "R", "R", "R...
$ events             <chr> NA, NA, NA, NA, "field_out", NA, NA, "single", NA, N...
$ description        <chr> "ball", "ball", "called_strike", "called_strike", "h...
$ des                <chr> NA, NA, NA, NA, "Aaron Hicks grounds out sharply, se...
$ bb_type            <chr> NA, NA, NA, NA, "ground_ball", NA, NA, "ground_ball"...
$ hit_location       <chr> NA, NA, NA, NA, "4", NA, NA, "7", NA, NA, NA, NA, "6...
$ hc_x               <dbl> NA, NA, NA, NA, 163.49, NA, NA, 65.36, NA, NA, NA, N...
$ hc_y               <dbl> NA, NA, NA, NA, 140.27, NA, NA, 120.49, NA, NA, NA, ...
$ hit_distance_sc    <dbl> NA, NA, NA, NA, 91, NA, NA, 102, NA, NA, NA, NA, 5, ...
$ barrel             <dbl> NA, NA, NA, NA, 0, NA, NA, 0, NA, NA, NA, NA, 0, NA,...

一球データや得点期待値がすぐ得られます。

Statcast系がこのパッケージのメイン関数群といっても過言ではないです。

続いて、MLB Stats API。

#### MLB Stats API ####
# 試合情報の取得(10分くらいかかるかも)
gameinfo <- statsapi_gameinfo(year = 2019, level_ids = c(1,11)) # MLB and MiLB
gameinfo_R <- gameinfo %>% 
  filter(gameType=="R") # レギュラーシーズンに絞る
# 打順(試合ごと)
b_order <- statsapi_batting_orders(gameinfo_R$game_pk[1])
# 球場情報(試合ごと)
pk_info <- statsapi_gameinfo_pk(gameinfo_R$game_pk[1])

# statsapiデータ(時間かかるのでここでは最初の100試合だけ実行)
statsapi_data <- gameinfo_R %>% 
  head(100) %>% 
  pull(game_pk) %>% 
  unique() %>% 
  map(~ statsapi_pbp(.x)) %>% 
  bind_rows()

取得したデータ例

> glimpse(statsapi_data)
Rows: 32,793
Columns: 157
$ game_pk                         <int> 566083, 566083, 566083, 566083, 566083,...
$ game_date                       <chr> "2019-03-20", "2019-03-20", "2019-03-20...
$ index                           <int> 0, 1, 2, 3, 4, 0, 1, 2, 3, 0, 0, 1, 2, ...
$ startTime                       <chr> "2019-03-20T03:10:48.463Z", "2019-03-20...
$ endTime                         <chr> "2019-03-20T09:24:48.354Z", "2019-03-20...
$ isPitch                         <lgl> FALSE, FALSE, FALSE, TRUE, TRUE, TRUE, ...
$ type                            <chr> "action", "action", "action", "pitch", ...
$ playId                          <chr> NA, NA, NA, "05660836-0016-0013-000c-f0...
$ pitchNumber                     <int> NA, NA, NA, 1, 2, 1, 2, 3, 4, 1, 1, 2, ...
$ details.description             <chr> "Status Change - Pre-Game", "Status Cha...
$ details.event                   <chr> "Game Advisory", "Game Advisory", "Game...
$ details.eventType               <chr> "game_advisory", "game_advisory", "game...
$ details.awayScore               <int> 0, 0, 0, NA, NA, NA, NA, NA, NA, NA, NA...
$ details.homeScore               <int> 0, 0, 0, NA, NA, NA, NA, NA, NA, NA, NA...
$ details.isScoringPlay           <lgl> FALSE, FALSE, FALSE, NA, NA, NA, NA, NA...
$ details.hasReview               <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALS...
$ details.code                    <chr> NA, NA, NA, "B", "X", "C", "C", "F", "X...
$ details.ballColor               <chr> NA, NA, NA, "rgba(39, 161, 39, 1.0)", "...
$ details.isInPlay                <lgl> NA, NA, NA, FALSE, TRUE, FALSE, FALSE, ...
$ details.isStrike                <lgl> NA, NA, NA, FALSE, FALSE, TRUE, TRUE, T...
$ details.isBall                  <lgl> NA, NA, NA, TRUE, FALSE, FALSE, FALSE, ...
$ details.call.code               <chr> NA, NA, NA, "B", "X", "C", "C", "F", "X...
$ details.call.description        <chr> NA, NA, NA, "Ball", "In play, out(s)", ...
$ count.balls.start               <int> 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 2, 2, ...
$ count.strikes.start             <int> 0, 0, 0, 0, 0, 1, 2, 2, 2, 0, 0, 0, 0, ...
$ count.outs.start                <int> 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, ...
$ player.id                       <int> 543829, 543829, 543829, NA, NA, NA, NA,...
$ player.link                     <chr> "/api/v1/people/543829", "/api/v1/peopl...
$ pitchData.strikeZoneTop         <dbl> NA, NA, NA, 3.410, 3.410, 3.467, 3.467,...
$ pitchData.strikeZoneBottom      <dbl> NA, NA, NA, 1.570, 1.570, 1.589, 1.589,...
$ pitchData.coordinates.x         <dbl> NA, NA, NA, 144.71, 91.67, 127.17, 124....
$ pitchData.coordinates.y         <dbl> NA, NA, NA, 127.70, 130.53, 135.71, 161...
$ hitData.trajectory              <chr> NA, NA, NA, NA, "line_drive", NA, NA, N...
$ hitData.hardness                <chr> NA, NA, NA, NA, "hard", NA, NA, NA, "me...
$ hitData.location                <chr> NA, NA, NA, NA, "9", NA, NA, NA, "3", "...
$ hitData.coordinates.coordX      <dbl> NA, NA, NA, NA, 199.00, NA, NA, NA, 166...
$ hitData.coordinates.coordY      <dbl> NA, NA, NA, NA, 109.62, NA, NA, NA, 156...
$ actionPlayId                    <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,...
$ isBaseRunningPlay               <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,...
$ details.fromCatcher             <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,...
$ details.runnerGoing             <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,...
$ isSubstitution                  <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,...
$ position.code                   <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,...
$ position.name                   <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,...
$ position.type                   <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,...
$ position.abbreviation           <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,...
$ battingOrder                    <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,...
$ replacedPlayer.id               <int> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,...
$ replacedPlayer.link             <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,...
$ atBatIndex                      <fct> 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 3, 3, 3, ...
$ result.type                     <chr> "atBat", "atBat", "atBat", "atBat", "at...
$ result.event                    <chr> "Lineout", "Lineout", "Lineout", "Lineo...
$ result.eventType                <chr> "field_out", "field_out", "field_out", ...
$ result.description              <chr> "Dee Gordon lines out sharply to right ...
$ result.rbi                      <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ...
$ result.awayScore                <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ...
$ result.homeScore                <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ...
$ about.atBatIndex                <int> 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 3, 3, 3, ...
$ about.halfInning                <chr> "top", "top", "top", "top", "top", "top...
$ about.isTopInning               <lgl> TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRU...
$ about.inning                    <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, ...
$ about.startTime                 <chr> "2019-03-20T03:10:48.463Z", "2019-03-20...
$ about.endTime                   <chr> "2019-03-20T09:36:53.775Z", "2019-03-20...
$ about.isComplete                <lgl> TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRU...
$ about.isScoringPlay             <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALS...
$ about.hasReview                 <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALS...
$ about.hasOut                    <lgl> TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRU...
$ about.captivatingIndex          <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 33, 14, 14, ...
$ count.balls.end                 <int> 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 2, 2, 2, ...
$ count.strikes.end               <int> 0, 0, 0, 0, 0, 2, 2, 2, 2, 0, 3, 3, 3, ...
$ count.outs.end                  <int> 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, ...
$ matchup.batter.id               <int> 543829, 543829, 543829, 543829, 543829,...
$ matchup.batter.fullName         <fct> Dee Strange-Gordon, Dee Strange-Gordon,...
$ matchup.batter.link             <chr> "/api/v1/people/543829", "/api/v1/peopl...
$ matchup.batSide.code            <chr> "L", "L", "L", "L", "L", "R", "R", "R",...
$ matchup.batSide.description     <chr> "Left", "Left", "Left", "Left", "Left",...
$ matchup.pitcher.id              <int> 571666, 571666, 571666, 571666, 571666,...
$ matchup.pitcher.fullName        <fct> Mike Fiers, Mike Fiers, Mike Fiers, Mik...
$ matchup.pitcher.link            <chr> "/api/v1/people/571666", "/api/v1/peopl...
$ matchup.pitchHand.code          <chr> "R", "R", "R", "R", "R", "R", "R", "R",...
$ matchup.pitchHand.description   <chr> "Right", "Right", "Right", "Right", "Ri...
$ matchup.splits.batter           <chr> "vs_RHP", "vs_RHP", "vs_RHP", "vs_RHP",...
$ matchup.splits.pitcher          <chr> "vs_LHB", "vs_LHB", "vs_LHB", "vs_LHB",...
$ matchup.splits.menOnBase        <chr> "Empty", "Empty", "Empty", "Empty", "Em...
$ matchup.postOnFirst.id          <int> NA, NA, NA, NA, NA, NA, NA, NA, NA, 457...
$ matchup.postOnFirst.fullName    <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, "Ja...
$ matchup.postOnFirst.link        <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, "/a...
$ matchup.postOnSecond.id         <int> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,...
$ matchup.postOnSecond.fullName   <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,...
$ matchup.postOnSecond.link       <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,...
$ matchup.postOnThird.id          <int> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,...
$ matchup.postOnThird.fullName    <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,...
$ matchup.postOnThird.link        <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,...
$ home_team                       <chr> "Oakland Athletics", "Oakland Athletics...
$ home_level_id                   <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, ...
$ home_level_name                 <chr> "Major League Baseball", "Major League ...
$ home_league_id                  <int> 103, 103, 103, 103, 103, 103, 103, 103,...
$ home_league_name                <chr> "American League", "American League", "...
$ away_team                       <chr> "Seattle Mariners", "Seattle Mariners",...
$ away_level_id                   <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, ...
$ away_level_name                 <chr> "Major League Baseball", "Major League ...
$ away_league_id                  <int> 103, 103, 103, 103, 103, 103, 103, 103,...
$ away_league_name                <chr> "American League", "American League", "...
$ batting_team                    <fct> Seattle Mariners, Seattle Mariners, Sea...
$ fielding_team                   <fct> Oakland Athletics, Oakland Athletics, O...
$ last.pitch.of.ab                <fct> NA, NA, NA, NA, NA, false, false, false...
$ base                            <int> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,...
<一部省略>

(球種など一部とれないデータもありますが)マイナーリーグの一球データも取得することが出来ます。

MLBだけのデータを使いたいのであれば、Statcastデータだけで事足りますが、マイナーのデータも欲しい場合はこちらも併用する想定です。

最後に、Baseball-Reference。

#### Baseball-Reference ####
# 指定した期間内の成績取得
bref_p <- bref_daily_Performance("2020-07-23", "2020-10-28", "pitcher")
bref_b <- bref_daily_Performance("2020-07-23", "2020-10-28", "batter")
# チームの試合ごとの成績取得
team_results  <- bref_team_results("CHC", 2020)

取得したデータ例

> glimpse(team_results)
Rows: 60
Columns: 21
$ Gm             <chr> "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11",...
$ Date           <chr> "Friday, Jul 24", "Saturday, Jul 25", "Sunday, Jul 26", ...
$ Tm             <chr> "CHC", "CHC", "CHC", "CHC", "CHC", "CHC", "CHC", "CHC", ...
$ Home_Away      <chr> "H", "H", "H", "A", "A", "A", "H", "H", "H", "H", "H", "...
$ Opp            <chr> "MIL", "MIL", "MIL", "CIN", "CIN", "CIN", "PIT", "PIT", ...
$ Result         <chr> "W", "L", "W", "W", "W", "L", "W", "W", "W-wo", "W", "W"...
$ R              <dbl> 3, 3, 9, 8, 8, 7, 6, 4, 2, 2, 5, 6, 2, 7, 7, 4, 3, 5, 5,...
$ RA             <chr> "0", "8", "1", "7", "5", "12", "3", "3", "1", "0", "4", ...
$ Inn            <chr> "", "", "", "", "", "", "", "", "11", "", "", "", "", ""...
$ Win_Lose       <chr> "1-0", "1-1", "2-1", "3-1", "4-1", "4-2", "5-2", "6-2", ...
$ Rank           <dbl> 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,...
$ GB             <chr> "Tied", "1.0", "Tied", "up 0.5", "up 1.5", "up 1.0", "up...
$ Win            <chr> "Hendricks", "Suter", "Chatwood", "Lester", "Mills", "Gr...
$ Loss           <chr> "Woodruff", "Darvish", "Peralta", "Miley", "Reed", "Hend...
$ Save           <chr> "", "", "", "Jeffress", "", "", "", "Wick", "", "Wick", ...
$ Time           <chr> "2:30", "3:36", "3:04", "3:46", "3:02", "3:23", "3:46", ...
$ Day_Night      <chr> "N", "D", "D", "N", "N", "N", "N", "N", "D", "N", "N", "...
$ Attendance     <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, ...
$ Streak         <dbl> 1, -1, 1, 2, 3, -1, 1, 2, 3, 4, 5, 6, -1, 1, 2, 3, -1, -...
$ Orig_Scheduled <chr> "", "", "", "", "", "", "", "", "", "", "", "", "", "", ...
$ Year           <dbl> 2020, 2020, 2020, 2020, 2020, 2020, 2020, 2020, 2020, 20...


得られるデータ定義については、カラム名で見当がつくものばかりですが、たいていはググれば見つかりますので、ここでは割愛させてください。*2


得点期待値を使った計算例

「Rによるセイバーメトリクス入門」の5章にある"得点期待値を用いたプレーの価値"に沿って計算してみます。

本ではLahmanパッケージを使っていますが、先ほど取得したStatcastデータを使ってもすぐ計算できます。

dat <- sc_PA$data %>% 
  filter(final_pitch_at_bat==1, !is.na(events), 
         !str_detect(events, "caught_stealing")) # 打席結果の行のみに絞る

dat <- dat %>% 
  group_by(batter, batter_name) %>% 
  summarise(RE24 = sum(re24),
                     PA = length(re24),
                     Runs.start = sum(avg_re)) %>% 
  ungroup() %>% 
  filter(PA >= 150) # 150打席以上の選手に絞る

library(ggrepel)
# 各打席時のバッターの得点期待値合計とRE24の散布図
ggplot(dat, aes(Runs.start, RE24)) +
  geom_point() +
  geom_smooth() +
  geom_hline(yintercept = 0) +
  geom_text_repel(data = dat %>% filter(RE24>=20), aes(label = batter_name))

f:id:tsuyu_pon:20201206152456p:plain

2020年のStatcastデータを用いて、各打席時のバッターの得点期待値合計とRE24の散布図を出してみました。

この図から打席時の得点期待値合計が高い選手ほどRE24が高くなっている傾向がわかります。*3(RE24が70~90のところで少し上がっているのは、おそらく代打で結果を残しているバッターの影響でしょう)



まとめ

今回はパッケージの紹介のみに絞って書きましたが、今後はFanGraphsの記事に沿って分析したり、mlbrパッケージに可視化・分析機能を追加したりしていきたいと思っています。

「こんな機能も欲しい」「ここ間違ってない?」「一緒に開発したい」などご意見・ご要望がありましたら、githubのissueに書き込んでください!


では、よいお年を~

ENJOY!!

*1:早いもので昨年のアドカレでframingをGAMで考える - 誰ガ為ニ分析ヲスルを書いてからもう一年経つんですね。よかったら昨年の記事も読んでください。

*2:Statcastデータの定義についてはこちらにあります

*3: Baseball-Referenceの結果とも値がほとんど一致しています。(これが"ほとんど"なのは使っている得点期待値が異なっているのが要因かと思います)