← 記事一覧へ

左右の相性

「この打者は左投手に強い」と言います. 実際, 対左と対右の打率を並べれば差は出ます. 問題は, その差がどこまで本人の性質で, どこからが偶然かです.

打席数は限られています. 右打者が対左投手で立つ打席はシーズン150程度で, 打率にすれば偶然だけで3分や4分は簡単に動きます.

retrosheet の打席結果には投手と打者の利き腕が入っているので, これを数えられます.

library(data.table)
library(dplyr)
library(ggplot2)

YEARS <- 2009:2013     # 試合数が動かなくなった年以降 (data/README.md)

cols <- fread("../../data/reference/retrosheet-columns.csv", header = FALSE)[[1]]
KEEP <- c("BAT_ID", "BAT_HAND_CD", "PIT_HAND_CD", "AB_FL", "H_FL", "BAT_EVENT_FL")
idx <- match(KEEP, cols)

read_year <- function(y) {
  d <- fread(sprintf("../../data/all%d.csv", y), select = idx,
             col.names = KEEP, showProgress = FALSE)
  d[, year := y][]
}
dat <- rbindlist(lapply(YEARS, read_year))
# 打席として成立したものだけを見る (走者の盗塁死などで打席が終わる行を落とす)
dat <- dat[BAT_EVENT_FL == "T"]
dat[, ab := as.integer(AB_FL == "T")]
dat[, hit := as.integer(H_FL > 0)]
dat[, 同じ手 := BAT_HAND_CD == PIT_HAND_CD]

スイッチヒッターを外す

BAT_HAND_CDその打席で実際にどちら側に立ったかです. スイッチヒッターは相手投手に応じて立つ側を変えるので, ほぼ全打席が「逆の手」になります. 左右の相性を測る対象にはなりません.

hands <- dat[, .(sides = uniqueN(BAT_HAND_CD)), by = BAT_ID]
switch_ids <- hands[sides > 1, BAT_ID]
dat <- dat[!BAT_ID %in% switch_ids]

打席中に立つ側を変えた打者155人を除きました.

打者ごとの対左右の差

対戦相手の利き腕別に打率を出します. 同じ手 (右対右・左対左) と 逆の手 (右対左・左対右) の差を「逆の手有利」と呼びます.

MIN_AB <- 100     # 各側でこれだけ打数がある打者に限る

sp <- dat[ab == 1, .(打数 = .N, 安打 = sum(hit)), by = .(BAT_ID, year, 同じ手)]
w <- dcast(sp, BAT_ID + year ~ 同じ手, value.var = c("打数", "安打"))
# 同じ手 は論理値なので FALSE 側が「逆の手」。位置ではなく名前で対応させる
setnames(w,
         c("打数_FALSE", "打数_TRUE", "安打_FALSE", "安打_TRUE"),
         c("打数_逆",    "打数_同",   "安打_逆",    "安打_同"))
w <- w[!is.na(打数_逆) & !is.na(打数_同)]
split_of <- function(d) {
  d[, avg_逆 := 安打_逆 / 打数_逆][, avg_同 := 安打_同 / 打数_同]
  d[, 差 := avg_逆 - avg_同]
  # 二項分布から見た, 偶然だけで生じる差のばらつき
  d[, se := sqrt(avg_逆 * (1 - avg_逆) / 打数_逆 + avg_同 * (1 - avg_同) / 打数_同)]
  d[]
}

season <- split_of(copy(w))[打数_逆 >= MIN_AB & 打数_同 >= MIN_AB]
ggplot(season, aes(x = 差)) +
  geom_histogram(bins = 40) +
  geom_vline(xintercept = 0, linetype = "dashed") +
  geom_vline(xintercept = mean_split, colour = "red") +
  xlab("逆の手の打率 − 同じ手の打率") +
  ggtitle(paste0(min(YEARS), "-", max(YEARS), "年の打者シーズン (赤線は全体平均)"))

平均は+0.022で, 逆の手のほうがよく打てるという よく知られた傾向がそのまま出ます. 問題はこの分布の広がりです.

広がりのうち, どれだけが実力か

観測された差のばらつきには, 本人の性質と偶然が両方入っています.

\[ \mathrm{Var}(\text{観測}) = \mathrm{Var}(\text{実力}) + \mathrm{Var}(\text{偶然}) \]

偶然のぶんは打数から計算できるので, 引き算すれば実力のばらつきが出ます.

var_obs   <- var(season$差)
var_noise <- mean(season$se^2)
var_true  <- max(0, var_obs - var_noise)
data.frame(
  項目 = c("観測された差のばらつき", "偶然だけで説明されるぶん", "残り (実力)"),
  標準偏差 = sprintf("%.4f", sqrt(c(var_obs, var_noise, var_true)))) %>%
  knitr::kable(caption = "対左右の差のばらつき (打率)")
対左右の差のばらつき (打率)
項目 標準偏差
観測された差のばらつき 0.0466
偶然だけで説明されるぶん 0.0451
残り (実力) 0.0117

観測されたばらつきの94%は偶然で説明できます. 残るのは標準偏差にして0.012です.

つまり「左に強い打者」は存在はするが, 見た目の順位表よりずっと小さい差です.

縮小推定

個々の打者の推定値を, 全体平均に向けて縮めます. 打数が少ないほど大きく縮みます.

season[, w_shrink := var_true / (var_true + se^2)]
season[, 縮小後 := mean_split + w_shrink * (差 - mean_split)]
rbind(data.frame(値 = season$差,     種類 = "観測"),
      data.frame(値 = season$縮小後, 種類 = "縮小後")) %>%
  ggplot(aes(x = 値, fill = 種類)) +
  geom_histogram(bins = 40, alpha = 0.6, position = "identity") +
  geom_vline(xintercept = 0, linetype = "dashed") +
  xlab("逆の手の打率 − 同じ手の打率") +
  ggtitle("縮小前と縮小後")

観測の標準偏差0.047が, 縮小後は0.003になります. 縮小の重みは中央値0.06で, 個人差として残せるのは観測された差の6%程度です.

本当に当たるのか

縮小推定が正しいなら, 別の年のデータを当てられるはずです. 2009-2011年で推定し, 2012-2013年を予測します.

比べる相手は3つです.

  1. 全員に同じ値 (全体平均を答える) — 一番弱いベースライン
  2. 観測された差をそのまま 使う
  3. 縮小推定
agg <- function(d) {
  x <- d[, .(打数_逆 = sum(打数_逆), 打数_同 = sum(打数_同),
             安打_逆 = sum(安打_逆), 安打_同 = sum(安打_同)), by = BAT_ID]
  split_of(x)
}

train <- agg(w[year <= max(YEARS) - 2])[打数_逆 >= MIN_AB & 打数_同 >= MIN_AB]
test  <- agg(w[year >  max(YEARS) - 2])[打数_逆 >= MIN_AB & 打数_同 >= MIN_AB]

m_tr <- weighted.mean(train$差, train$打数_逆 + train$打数_同)
v_tr <- max(0, var(train$差) - mean(train$se^2))
train[, 縮小後 := m_tr + (v_tr / (v_tr + se^2)) * (差 - m_tr)]

ev <- merge(train[, .(BAT_ID, 予測_観測 = 差, 予測_縮小 = 縮小後)],
            test[, .(BAT_ID, 実測 = 差, 重み = 打数_逆 + 打数_同)], by = "BAT_ID")
ev[, 予測_平均 := m_tr]

rmse <- function(p) sqrt(weighted.mean((ev$実測 - p)^2, ev$重み))
data.frame(
  予測 = c("全員に同じ値 (ベースライン)", "観測された差をそのまま", "縮小推定"),
  RMSE = sprintf("%.4f", c(rmse(ev$予測_平均), rmse(ev$予測_観測), rmse(ev$予測_縮小)))) %>%
  knitr::kable(caption = paste0(max(YEARS)-1, "-", max(YEARS), "年の対左右の差を予測したときの誤差 (",
                                nrow(ev), "人)"))
2012-2013年の対左右の差を予測したときの誤差 (209人)
予測 RMSE
全員に同じ値 (ベースライン) 0.0382
観測された差をそのまま 0.0437
縮小推定 0.0371

観測された差をそのまま使うのは, ベースラインより悪くなります (0.0437 対 0.0382). 「左に強い」という評判を鵜呑みにするより, 全員に平均値を答えるほうがましだということです.

縮小推定は0.0371で, 一番良いのは縮小推定でした.

# 差が偶然かどうかを, 打者を再標本化して確かめる
set.seed(1)
boot_gain <- replicate(2000, {
  i <- sample(nrow(ev), nrow(ev), replace = TRUE)
  b <- ev[i]
  rb <- sqrt(weighted.mean((b$実測 - b$予測_平均)^2, b$重み))
  rs <- sqrt(weighted.mean((b$実測 - b$予測_縮小)^2, b$重み))
  rb - rs
})
gain_ci <- quantile(boot_gain, c(0.025, 0.975))

縮小推定はベースラインを2.8%上回りました. 差は 0.0011, 打者を再標本化した95%区間は 0.0004-0.0017です.

区間は0を含みません. 差は小さいものの, 個人差は実在します.

差が大きいと推定された打者

names_tbl <- fread("../../data/derived/player-names.csv")
season[, .(BAT_ID, year, 打数_逆, 打数_同, 差, 縮小後)][order(-縮小後)][1:10] %>%
  merge(names_tbl, by.x = "BAT_ID", by.y = "retroID", all.x = TRUE) %>%
  .[order(-縮小後)] %>%
  .[, .(name, year, 打数_逆, 打数_同,
        観測 = sprintf("%+.3f", 差), 縮小後 = sprintf("%+.3f", 縮小後))] %>%
  knitr::kable(caption = "逆の手に強いと推定された打者シーズン")
逆の手に強いと推定された打者シーズン
name year 打数_逆 打数_同 観測 縮小後
Adam Lind 2010 432 137 +0.159 +0.035
Shin-Soo Choo 2012 392 206 +0.128 +0.031
Nyjer Morgan 2009 366 103 +0.170 +0.031
Robinson Cano 2012 384 243 +0.121 +0.031
Ryan Howard 2009 394 222 +0.113 +0.030
Carlos Peña 2011 373 120 +0.121 +0.030
Derek Norris 2013 150 114 +0.171 +0.029
Andre Ethier 2009 431 165 +0.108 +0.029
Buster Posey 2012 164 366 +0.141 +0.029
Josh Hamilton 2010 352 166 +0.129 +0.029

観測では+0.177のような大きな差が出ますが, 縮小後は+0.035までしか残りません.

注意

  • 指標は打率です. 四球や長打を含めていないので, 「左投手のときは長打が減る」 といった変化は見えません.
  • 対象は2009-2013年で, 各側100打数以上の打者シーズンです. この足切りは「両方の側でそれなりに立った打者」を作るためですが, 控えの左打者のように片側でしか使われない打者が落ちます.
  • 縮小推定は「実力の分布が正規分布で, 全打者が同じ分布から来る」と仮定しています. 極端なプラトーン打者が別の集団として存在する場合, その打者は縮めすぎになります.
  • 打席の割り当ては監督が決めます. 左に弱い打者は左投手のときに外されるので, 観測される差は実際の弱さより小さく出ているはずです.
  • スイッチヒッターは除外しています. 彼らにとっての「左右の相性」は 別の問いになります.