4月20日時点の成績を見て, その年の首位打者を当てられるかを考えます.
打率の推定では, 序盤の打率をどう縮小すれば シーズン打率に近づくかを見ました. ここでは一歩進めて, 「誰が一番打つか」という順位の問題を扱います.
点推定では答えられません. 事後分布が要ります.
library(data.table)
library(dplyr)
library(ggplot2)
library(knitr)
panel <- fread("../../data/derived/batters-through-apr20.csv")
draws <- fread("../../data/derived/posterior-samples.csv")
est <- fread("../../data/derived/batting-estimates.csv")
139人 (2013年に規定打席に到達した打者) の,
4月20日までの成績とシーズン打率です. draws
は階層ベイズの事後分布から取った 4,000サンプル × 139人です. 作り方は
estimate-rstanarm.R にあります.
## 列と打者の対応を確かめておく。draws の j 列目は panel の j 行目の打者。
## ずれていると以降が全部おかしくなるので、中央値で突き合わせる。
stopifnot(identical(panel$BAT_ID, est$BAT_ID))
stopifnot(max(abs(apply(draws, 2, median) - est$MCMC)) < 1e-9)
dat <- data.table(
選手 = est$FULLNAME,
打数 = panel$ATBAT,
安打 = panel$HITS,
四月打率 = panel$AVG,
推定打率 = est$MCMC,
シーズン打率 = panel$AVG_SEASON
)
dat[order(-四月打率)][1:6] |> kable(digits = 3)
| 選手 | 打数 | 安打 | 四月打率 | 推定打率 | シーズン打率 |
|---|---|---|---|---|---|
| David Ortiz | 4 | 2 | 0.500 | 0.271 | 0.309 |
| Chris Johnson | 55 | 23 | 0.418 | 0.284 | 0.321 |
| Freddie Freeman | 17 | 7 | 0.412 | 0.274 | 0.319 |
| Torii Hunter | 70 | 28 | 0.400 | 0.285 | 0.304 |
| Jed Lowrie | 66 | 26 | 0.394 | 0.284 | 0.290 |
| Joe Mauer | 61 | 24 | 0.393 | 0.283 | 0.324 |
首位はDavid Ortizの0.500です. ただし4打数しかありません. 2本打っただけでこの位置に来ています.
打数はばらついています (4から79, 中央値60). 打率の順位表は, 打数の少ない打者が上に来やすい仕組みになっています.
階層ベイズの推定値を見ます.
dat |>
ggplot(aes(x = 四月打率, y = 推定打率, size = 打数)) +
geom_abline(slope = 1, intercept = 0, linetype = "dashed") +
geom_point(alpha = 0.6) +
ggtitle("縮小すると, 推定値はほとんど同じ値に潰れる")

推定打率は0.253 〜 0.285の範囲に収まっています. 幅は0.032しかありません.
この順に並べても意味がありません. 1位と10位の差が 打率にして数厘では, 順位は誤差で入れ替わります. 縮小推定は「値」を良くしますが, 「誰が一番か」には答えていません.
点推定を1つ選ぶ代わりに, サンプルごとに誰が最高かを数えます.
事後分布から取った1回のサンプルは「あり得た世界」1つぶんです. その世界で真の打率が一番高いのが誰かを見て, 4,000回ぶん集計すれば, その打者がリーグ最高である確率になります.
M <- as.matrix(draws)
winner <- max.col(M, ties.method = "first") # 各サンプルでの最高打率の打者
dat[, 最高である確率 := tabulate(winner, ncol(M)) / nrow(M)]
dat[order(-最高である確率)][1:10] |> kable(digits = 3)
| 選手 | 打数 | 安打 | 四月打率 | 推定打率 | シーズン打率 | 最高である確率 |
|---|---|---|---|---|---|---|
| Torii Hunter | 70 | 28 | 0.400 | 0.285 | 0.304 | 0.043 |
| Chris Johnson | 55 | 23 | 0.418 | 0.284 | 0.321 | 0.034 |
| Joe Mauer | 61 | 24 | 0.393 | 0.283 | 0.324 | 0.032 |
| Jed Lowrie | 66 | 26 | 0.394 | 0.284 | 0.290 | 0.032 |
| Adrian Gonzalez | 61 | 23 | 0.377 | 0.281 | 0.293 | 0.030 |
| Jean Segura | 57 | 22 | 0.386 | 0.281 | 0.294 | 0.027 |
| Jose Altuve | 70 | 27 | 0.386 | 0.284 | 0.283 | 0.025 |
| Chris Davis | 58 | 22 | 0.379 | 0.280 | 0.286 | 0.022 |
| Miguel Cabrera | 72 | 25 | 0.347 | 0.278 | 0.348 | 0.020 |
| Chris Denorfia | 48 | 18 | 0.375 | 0.278 | 0.279 | 0.020 |
最も高い打者でも4.2%です (Torii Hunter).
dat |>
ggplot(aes(x = reorder(選手, -最高である確率), y = 最高である確率)) +
geom_col() +
scale_y_continuous(labels = scales::percent) +
theme(axis.text.x = element_blank(), axis.ticks.x = element_blank()) +
xlab(sprintf("%d人の打者 (確率順)", nrow(dat))) +
ggtitle("誰も抜け出していない")

確率を半分集めるのに26人, 9割集めるのに93人必要です. 139人全員に0でない確率が付いています.
4月20日の時点では, 首位打者はほとんど絞り込めていません. これが正直な答えです.
4月打率が高い打者を, 打数と一緒に並べます.
dat[四月打率 >= 0.35][order(-四月打率)][
, .(選手, 打数, 四月打率, 推定打率, 最高である確率)] |>
head(8) |> kable(digits = 3)
| 選手 | 打数 | 四月打率 | 推定打率 | 最高である確率 |
|---|---|---|---|---|
| David Ortiz | 4 | 0.500 | 0.271 | 0.012 |
| Chris Johnson | 55 | 0.418 | 0.284 | 0.034 |
| Freddie Freeman | 17 | 0.412 | 0.274 | 0.010 |
| Torii Hunter | 70 | 0.400 | 0.285 | 0.043 |
| Jed Lowrie | 66 | 0.394 | 0.284 | 0.032 |
| Joe Mauer | 61 | 0.393 | 0.283 | 0.032 |
| Jean Segura | 57 | 0.386 | 0.281 | 0.027 |
| Jose Altuve | 70 | 0.386 | 0.284 | 0.025 |
David Ortizは打率0.500で全体1位ですが, 確率は1.1%です. 一方Torii Hunterは打率0.400と下ですが, 70打数あるので確率は4.2%, 3.7倍あります.
打率だけ見るとDavid Ortizが上, 確率で見るとTorii Hunterが上です. 順位表は打数を見ていませんが, 事後分布は見ています.
dat |>
ggplot(aes(x = 打数, y = 最高である確率, colour = 四月打率)) +
geom_point(size = 2) +
scale_colour_viridis_c() +
scale_y_continuous(labels = scales::percent) +
ggtitle("同じ打率でも, 打数が少ないと確率は上がらない")

dat[, `:=`(四月順位 = frank(-四月打率), 確率順位 = frank(-最高である確率))]
dat[order(-シーズン打率)][1:5,
.(選手, シーズン打率, 四月打率, 四月順位, 最高である確率, 確率順位)] |>
kable(digits = 3)
| 選手 | シーズン打率 | 四月打率 | 四月順位 | 最高である確率 | 確率順位 |
|---|---|---|---|---|---|
| Miguel Cabrera | 0.348 | 0.347 | 17.0 | 0.020 | 9 |
| Michael Cuddyer | 0.331 | 0.357 | 14.0 | 0.017 | 14 |
| Joe Mauer | 0.324 | 0.393 | 6.0 | 0.032 | 3 |
| Mike Trout | 0.323 | 0.304 | 43.5 | 0.004 | 79 |
| Chris Johnson | 0.321 | 0.418 | 2.0 | 0.034 | 2 |
首位打者はMiguel Cabreraの0.348でした.
4月20日時点では打率0.347で 17位, 確率で見ると9位 (2.0%) です.
4月の首位だったDavid Ortizのシーズン打率は 0.309で, 首位打者ではありませんでした.
ここで欲張ってはいけません. 「確率のほうが当たる」と言えるかを確かめます.
tibble::tibble(
指標 = c("4月打率", "推定打率 (階層ベイズ)", "最高である確率"),
`シーズン打率との順位相関` = c(
cor(dat$四月打率, dat$シーズン打率, method = "spearman"),
cor(dat$推定打率, dat$シーズン打率, method = "spearman"),
cor(dat$最高である確率, dat$シーズン打率, method = "spearman"))
) |> kable(digits = 3)
| 指標 | シーズン打率との順位相関 |
|---|---|
| 4月打率 | 0.483 |
| 推定打率 (階層ベイズ) | 0.449 |
| 最高である確率 | 0.343 |
確率の順位相関は0.343で, 4月打率の0.483より低いです.
意外ではありません. 確率はほとんどの打者で0に近い値に潰れるので, 下位の並び順に情報が残りません. 順位を当てる道具ではないのです.
事後分布から得られるのは順位ではなく, 「どれくらい分からないか」です. 「最も有力な打者でも4%」という言い方は, 点推定からは出てきません.