2017年11月11日土曜日

カウント毎の得点価値の可視化1

今回はカウント毎の得点価値の可視化について。
最近、Jim Albertがこれの新しい可視化の方法を示してくれています。
https://baseballwithr.wordpress.com/2016/05/09/graphing-pitch-count-effects/
https://bayesball.github.io/VB/Chapter4_Plots.html

カウント毎の得点価値自体は、例えばPitch type linear weights (1.02でPitch Valueと書いているやつ) の基礎でもあり、以前から広く使われている概念だと思われます (注1)。Albertの意図としては、数値のテーブルではなく図を使って可視化することで、より直感的に理解できるはずだ、ということでしょう。

下はMLB2013-2016のデータから計算し、描画した結果です (注2)。データはRetrosheetから。図中のラベルはボール・ストライクカウントを表し、ありうる状態遷移が青の線で結ばれています。図の横軸はボール・ストライクカウントの合計を示しています。状況は図の左に移ることはなく、そこで打席が終わるか、右に進んでいきます。"投球数"とラベルしていますが、2ストライク以後はカウントを変化させないファールもあるので、実際の投球数には必ずしもなっていません。数値の計算としては、そのカウントを経由した全ての打席について、どれだけ得点価値を変動させたかをランナー・アウト状況の得点期待値に基づいてもとめ、その平均を取っています (注3)。

図から幾つかのことが容易に見て取れます。
  1. ストライクが先行するとその打席の得点価値が低下、要するに投手有利になる。ボールが先行すると投手不利になる。
  2. いわゆる並行カウントという表現がありますが、それらの得点価値は同じではなく、ストライクが増えるとより投手有利になる (注4)。2-2は1-1よりも0-1に近い。
  3. ストライクになるか、ボールになるかの得点価値は状況による。例えば初球では1-0と0-1の差から、大体0.07 ~ 0.08程度。2-0から、あるいは2-1からの1球は得点価値の変動が他のカウントより大きく、変動の大きさが初球の2倍程度。
  4. ボールを投げることによる打席の得点価値の増加は特に2ボールから3ボールになる時に高い。ストライクが先行している状態ではボールになってもそれほど得点価値は増加しない (このことが2-2を投手有利なカウントにしている。)。一方、ストライクを取ることの価値は、ボールを投げることに比べると、どのカウントでも比較的似たような数値になっているかもしれません。
これらのポイントは、単にカウント毎の得点価値の平均値を示したテーブルからでもわかるはずですが、Albertの図では横軸で投球数を揃えて、ありうる状態変化を線でつなぐことで直感的に理解しやすくなっているのではないかと思います (テーブルの例としては例えば注1のリンク先を参照)。この方法は投球開始前のカウントが持っているポテンシャル的な部分に注目しているので、ボールが4つになる時の価値やストライクが3つになる時の価値や、安打になったり凡打になったりした時の変動は表現されていません。

得点環境の変化によって、カウント毎の得点価値がどう変化するかも調べてみました。
2000年以後で1試合あたりの得点はこんな感じ (注5)。

これを見ると最近はここ15年程度の中では、かなり投手有利であったことがよくわかります。2000年以後で1試合あたりの得点が最も高いのは2000年 (5.14) で、最も低いのは2014年 (4.06) なので、これらの年のカウント毎の得点価値を計算しました。

得点が低い2014年環境では、2000年環境に比べて全体的に平べったくなっています。これは、得点価値の正の値も負の値も、変化が小さくなっていることを示しています。また、この変化にも関わらず、カウント間の相対的な関係は良く維持されていることがわかります。

という感じで今回はリーグ全体でのカウントごとの得点価値の可視化を試してみました。Albertはこの方法を用いて、近著 "Visualizing Baseball" において、各選手ごとにカウントの得点価値がどう変化するかを示しています (注6)。たぶん次はこれを書きます。
<参考>
Marchi and Albert, Analyzing Baseball Data with R, 2013, CRC press.
http://retrosheet.org
________________________________________________________________________
注1.
John Walsh , Searching for the game’s best pitch, 2008.
https://www.fangraphs.com/tht/searching-for-the-games-best-pitch/

注2.
計算方法はMarchi and AlbertのChapter 7及びappendix A.3を参考にしています。多分Albertの計算方法と同じだと思うんですが、Albertの計算結果とほんの少し一致していない。
2015の結果がココ↓
https://bayesball.github.io/VB/Chapter4_Plots.html
で示されているので、こちらも計算してみると微妙にずれる。原因は不明。差は非常に小さいですし、計算途中も変なことは起こってなさそうので、大したことでは無いと理解しています。
ほぼ完全に一致していますが、気持ち悪いものは気持ち悪い。

注3.
1.02の得点価値の説明 (カウントの話ではなく各イベントの得点価値についてですが)。
http://1point02.jp/op/gnav/glossary/gls_explanation.aspx?eid=20013

また、本文中で軽く説明した、この各カウントについての得点価値の計算方法では、カウントの効果は経路に依存しないことを仮定していると理解できます。実際には、少なくとも表面的には、経路に依存する (おそらく、少なくとも部分的には、経路によって打者の能力にセレクションバイアスがかかるという理由)。
Sal Baxamusa, The Memory Remains, 2006.
https://www.fangraphs.com/tht/the-memory-remains/
やMarchi and Albertの "7.2.5 The importance of the previous count" を参照。

注4.
Tango Tigerが得点価値でなくwOBAからカウントを分類してくれています。
http://www.insidethebook.com/ee/index.php/site/comments/plate_counts/
ここでも0-0や1-1が中立とされているのに対して、2-2は投手有利であるとされています。

注5.
この図はMonkman氏が作成したshiny appである
Per-game run scoring by league
https://monkmanmh.shinyapps.io/MLBrunscoring_shiny/
のスクリーンショットです。

注6.
100ページ程度のわりと薄い本ですが、100程度の図が入っており、ぼんやり眺めるだけでも結構楽しめます。実のところ、中の人も本文はほとんど読んでいない (小声)。お値段もハードカバーでなければ3500円程度と、安くはないかもしれませんがそんなに高くもない。
https://www.amazon.co.jp/Visualizing-Baseball-Jim-Albert-ebook/dp/B0764GCQZ3
一部の図のRコードが公開されています。
https://bayesball.github.io/VB/

使われている指標はわりと古典的なもので、良くも悪くも指標がどうのとかいうものではないです。主なテーマはパターンやばらつきの理解であり、要約統計量のテーブルによる数字の洪水ではなく、可視化することでこの理解を助けたい、というところのようです。

本人のブログでの本の宣伝を含むポストと、Hardball timesに掲載されたDave Studeman による書評も貼っておきます。
https://baseballwithr.wordpress.com/2017/08/28/new-book-and-home-run-hitting/
https://www.fangraphs.com/tht/a-new-classic-in-sabermetric-literature/

11 件のコメント:

  1. お世話になっております。
    平均得点と投球数の図、とても直感的に理解しやすいです。
    カウント間の関係は意外ときれいなひし形になるのだなぁと思いました。
    今日、カウント0-0から0-1や1-0になる確率ってどれくらいなのだろうと思ったのですが、Rを使えばそういうことも計算できるのでしょうか。
    この図の線部分に確率を書いていって、x-2の場合、ファールで繰り返しの確率が出てくると思うのですが、そういうデータってもうどこかに出ているのでしょうか?

    返信削除
  2. 88yasuさま

    > カウント0-0から0-1や1-0になる確率
    もちろん計算できます。試しにStatcastデータで18年の3/26から4/30で計算したところ、初球の結果はストライクが約50%でボールが約40%で、残りがインプレー打球だったようです。

    > x-2の場合、ファールで繰り返しの確率
    > そういうデータってもうどこかに出ているのでしょうか?
    "ファールで繰り返しの確率"がちょっとよくわからないですが、頻度ということでいいでしょうか? たぶん誰かが計算していると思いますが、ググるより計算したほうが早いです。上と同じ期間では2ストライク状態の投球でのファウル (インプレー打球除く) の頻度は、全体では約24%となりました。カウントごとに分けると以下のようになります。
    # BS_CT N Foul S B BIP
    # 1 0-2 8049 0.190 0.183 0.458 0.168
    # 2 1-2 11931 0.215 0.190 0.390 0.204
    # 3 2-2 10408 0.253 0.195 0.310 0.242
    # 4 3-2 6405 0.302 0.174 0.230 0.294
    Sはストライクからファウルを引いたものです。FoulとBIPは似たような振る舞いをしているようです。

    あまりちゃんと確認していないですし、期間も短いので結果は怪しいかもしれません。また、簡単なのでStatcastで計算しましたが、上のエントリで得点価値計算に使ったRetrosheetでも当然計算できるはずです (Marchi et al., 2nd edition, pp.142, pp.299あたりが参考になります)。

    返信削除
  3. お世話になっております。
    返信が遅くなり大変申し訳ございません。
    いただいたプログラムを何度も試しましたがなかなかうまく行かず、1つめは走るようになりました。
    1つめを読んで内容を勉強しているのですが、この行が理解出来ず困っています。
    start.dates <- c(seq(ymd('2018-03-26'), ymd('2018-04-25'), by = 6))
    end.dates <- c(seq(ymd('2018-03-31'), ymd('2018-04-30'), by = 6))
    ここの4-25やby=6や3-31などの記載がなぜ必要なのかを勉強する方法はありませんでしょうか。
    また、例えば、2018年度全てのデータを取得しようとする場合、どう書き替えたら良いのでしょうか。

    色々いじってやっと質問できるようになったというレベルです。

    返信削除
  4. 2つめは、最初の方で、
    > batter <- batter %>%
    + mutate(AB_ID = paste(game_date, game_pk, at_bat_number, sep = "-"),
    <中略>
    + BIP_FL = ifelse(type == "X", 1, 0))
    と入力すると、
    batter %>% mutate(AB_ID = paste(game_date, game_pk, at_bat_number, でエラー: 関数 "%>%" を見つけることができません
    とエラー出てきてしまいます。
    どこでうまく行っていないのかもよく分からない状態です。
    お時間がある時にお教えいただければと思います。

    返信削除
  5. このスクリプト http://rpubs.com/snin/cout_transition_examples を試していただいたことついてのコメントですね。ありがとうございます。

    c(seq(ymd('2018-03-26'), ymd('2018-04-25'), by = 6))
    あたりについてですが、lubridateパッケージ (require(lubridate)で呼び出しているもの) のymd関数を使っています。このパッケージを使ってRにおける日付用のクラスdate classの規則的な列を作っています。ここ http://www.okadajp.org/RWiki/?日付、時間関数Tips大全 の、"時間関数Tips大全 連続する日付の生成"あたりが参考になると思います。要するに、各月ごとの日数の違いを考慮して6日ごとの列を作ってくれと命令しているイメージです。

    1年分と言わず、数年分を取り出す場合、例えばこういうコードがありえます。
    ________________________________
    start.dates <- c(seq(ymd('2015-04-01'),ymd('2015-10-04'), by = 6),
    seq(ymd('2016-04-01'),ymd('2016-09-28'), by = 6),
    seq(ymd('2017-04-01'),ymd('2017-09-28'), by = 6),
    seq(ymd('2018-03-26'), ymd('2018-09-28'), by = 6))

    end.dates <- c(seq(ymd('2015-04-06'),ymd('2015-10-09'), by = 6),
    seq(ymd('2016-04-06'),ymd('2016-10-03'), by = 6),
    seq(ymd('2017-04-06'),ymd('2017-10-03'), by = 6),
    seq(ymd('2018-03-31'), ymd('2018-10-03'), by = 6))

    df.dates <- data.frame(start.dates, end.dates)

    # 以下同文
    ________________________________


    話は変わって、%>%はいわゆるパイプ処理と言われるものです。これはもとのコードでは1つめのチャンク (コードのまとまり; 元のrpubs postではsection1と2それぞれに1つあるまとまり) のrequire(tidyverse)で呼び出しているtidyverseの中に含まれる (正確にはtidyverseで呼び出されるパッケージの一つのmagittr) 方言のようなものですが、1と2で分けている表現になっているのでわかりにくいですね。これは申し訳ないです。

    説明としては、例えば https://r4ds.had.co.nz/pipes.html あたりがわかりやすいかと思います。

    機能としては例えば以下の例がわかりやすいのではないでしょうか。下の2つの例は同じものを示しています。

    1. round(as.numeric("2.71828"), 3)
    2. "2.71828" %>% as.numeric()%>% round(3)

    %>%は左に示されたオブジェクトを"1つ目の引数 (argument)" として、右の関数に渡すという感じす。"2.71828"は例示のためわざわざ数値を文字列で渡していま。いずれも文字列
    "2.71828"を数値に変換し、さらに小数点以下を第三位で丸めています。%>%を使わないと、複数の関数の中に最初のオブジェクトが入れ子状になるため、非常に読みにくくなります (上の1)。一方、%>%を使うと、何をどの関数に渡しているのかがわかりやすくなります (上の2)。基本的には、コードの可読性を高めるための工夫というか、方言というかというところですが、一度覚えると戻れない中毒性があります。

    返信削除
    返信
    1. 書くのを忘れましたが、基本的にレギュラーシーズンに絞っています
      filter(game_type == "R")

      削除
  6. 早速のご返信を頂きありがとうございます。
    頂いた文面をしっかりと読み再度勉強させていただきます。
    少し時間がかかるかもしれませんが、よろしくお願いいたします。

    返信削除
  7. ありがとうございます。
    とりあえず、数日かけて頂いたコードで数年分のデータを取り出せました。
    ノートPCで一気にやったらデータをベクトル化する際にメモリーの不足が起こり、デスクトップでやり直して、csvで出力してみたら、Excelで開くには行数が足りなくなっていました。
    結局、1年ごとにデータを取得して、今後色々分析するためにSQL?化しようと思い、現在、XAMPPというソフトのMYSQLを使ってデータを放り込もうとしています。
    そのソフトもデータファイルの容量が大きいためインポートできなくて、色々方法を調べています。
    また、進んだら報告させて下さい。

    返信削除
  8. お礼が遅くなり申し訳ございません。
    やっと全て理解できました。
    これからいろいろと書き換えたりして応用してみます。
    今後ともよろしくお願いいたします。

    返信削除
  9. このようなカウントごとの得点価値を使えばストライク、ファール、ボールの平均的な得点価値といったものも出せるのでしょうか?

    返信削除
    返信
    1. コメントありがとうございます。

      ご指摘の通り、計算しようと思えば計算できます。実際にはおっしゃるようなストライク、ファール、ボールの平均的な得点価値というのはあまり見ないような気がしますが、単に使いたい場面があまりない、ということだと思います。例えば、各球種のpitch valueの計算などでは、ストライクやボールの平均的な価値ではなく、カウントごとの価値を使ってどれだけ変化させたか、カウントに依存する形で計算します。これは各球種の使われ方がカウントによって違うであろう、ということを考えれば、後者のほうが自然という感じではないかと思います。

      質問していただいたような考え方の平均的な得点価値でよく使われるのは、見逃しストライクとボールの間の平均的な価値、でしょうか。これは捕手のフレーミングの効果を測定するときに使われます。例えば、以下の記事ではNPBにおける得点価値は0.12程度であり、カウントごとのwOBA (もちろんこれは得点価値と変換できます) から計算した、というようなことを書いてくれています。
      八代 久通, 2018, 野手の守備力をデータから分析し評価する[1.02 FIELDING AWARDS 2018]捕手部門
      https://1point02.jp/op/gnav/column/bs/column.aspx?cid=53468
      フレーミングの効果の大きさについて調べる場合、捕手のフレーミング効果がカウントに無関係に起こると考えており、平均的な価値を使ったほうがカウントに依存した極端な結果がでるリスクを抑えることができる、というような感じだと理解しています。

      削除