← 記事一覧へ

序論

負け試合で最終回にヒットを打つと, 解説のおじさんがよく言います.

“明日につながるヒット”.

本当につながるのか, 確認します.

何と比べるか

ここが肝心です.

「つながるヒットを打った人の翌日の打率」を出しても, それだけでは何も言えません. 大差で負けている試合の最終打席に立つのは主力打者が多いので, リーグ平均と比べれば当然高く出ます. 打者の質を拾っているだけです.

必要なのは対照群です. 同じ「大差で負けている試合の最終打席」に立って, ヒットが出なかった人の翌日と比べます. こうすれば場面の条件はそろい, 違うのは「その打席で打てたかどうか」だけになります.

library(data.table)
library(dplyr)
library(ggplot2)
source("../../R/retrosheet.R")

dat <- fread("../../data/all2013.csv")
setnames(dat, fread("../../data/reference/retrosheet-columns.csv", header = FALSE)[[1]])

打者ごと・試合ごとの成績

game_stats <-
  dat |>
  as_tibble() |>
  mutate(打席 = as_flag(AB_FL), 安打 = H_FL > 0, 塁打 = H_FL) |>
  summarise(打数 = sum(打席), 安打 = sum(安打 & 打席), 塁打 = sum(塁打),
            .by = c(BAT_ID, GAME_ID)) |>
  mutate(日付 = game_date_key(GAME_ID)) |>
  arrange(BAT_ID, 日付) |>
  mutate(出場順 = row_number(), .by = BAT_ID)

「翌日」はカレンダーの翌日ではなく, その打者の次の出場試合とします. 移動日や休養日があるので, 日付を1足すだけだと取りこぼします. ダブルヘッダーも GAME_ID の末尾で区別されます.

対象の場面

最終回の攻撃で, 打っている側が4点以上負けている場面を取ります.

もとのコードは HOME_SCORE_CT - AWAY_SCORE_CT >= 4 としていましたが, これはホームから見た点差です. ホームが攻撃しているときは 勝っている側を拾ってしまうので, 攻守で符号を変えます.

last_ab <-
  dat |>
  as_tibble() |>
  filter(INN_CT == max(INN_CT), .by = GAME_ID) |>
  filter(BAT_HOME_ID == max(BAT_HOME_ID), .by = GAME_ID) |>
  mutate(ビハインド = if_else(BAT_HOME_ID == 1L,
                              AWAY_SCORE_CT - HOME_SCORE_CT,
                              HOME_SCORE_CT - AWAY_SCORE_CT)) |>
  filter(ビハインド >= 4, as_flag(AB_FL)) |>
  summarise(安打あり = any(H_FL > 0), .by = c(BAT_ID, GAME_ID))

対象は3,478打者×試合, うち安打があったのは810件 (23.3%) です.

この場面の打率0.233は, リーグ全体の 0.253より21ポイント 低くなっています. 大差で負けている終盤なので, 相手は勝ちを固める継投に 入っていることが多く, また打つ側も主力が退いている場合があります. 場面として楽ではない, ということです.

次の出場試合を引く

next_game <-
  last_ab |>
  inner_join(game_stats |> select(BAT_ID, GAME_ID, 出場順), by = c("BAT_ID", "GAME_ID")) |>
  mutate(出場順 = 出場順 + 1L) |>
  inner_join(game_stats |> select(BAT_ID, 出場順, 打数, 安打, 塁打),
             by = c("BAT_ID", "出場順")) |>
  filter(打数 > 0)

次の試合まで追えたのは3,377件です.

結果

result <-
  next_game |>
  summarise(件数 = n(), 打数 = sum(打数), 安打 = sum(安打), 塁打 = sum(塁打),
            .by = 安打あり) |>
  mutate(打率 = 安打 / 打数, 長打率 = 塁打 / 打数) |>
  arrange(desc(安打あり))
result |> knitr::kable(digits = 4)
安打あり 件数 打数 安打 塁打 打率 長打率
TRUE 780 2715 682 1049 0.2512 0.3864
FALSE 2597 8811 2265 3583 0.2571 0.4067

翌試合の打率の差は -0.0059, 95%信頼区間は [-0.0246, +0.0128] です.

区間は0をまたいでいます.

打者の質をそろえる

対照群を置いても, まだ気になることがあります. 最終打席で打てる打者はもともと良い打者かもしれません.

そこで同じ打者の中で比べます. その打者のシーズン打率を引いて, 「いつもの自分と比べてどうだったか」を見ます.

season <-
  game_stats |>
  summarise(季節打数 = sum(打数), 季節安打 = sum(安打), .by = BAT_ID) |>
  filter(季節打数 >= 100) |>
  mutate(季節打率 = 季節安打 / 季節打数)

within <-
  next_game |>
  inner_join(season, by = "BAT_ID") |>
  mutate(翌試合打率 = 安打 / 打数, 差 = 翌試合打率 - 季節打率)

within |>
  summarise(件数 = n(), `自分の平均との差` = mean(差), sd = sd(差), .by = 安打あり) |>
  mutate(se = sd / sqrt(件数)) |>
  arrange(desc(安打あり)) |>
  knitr::kable(digits = 4)
安打あり 件数 自分の平均との差 sd se
TRUE 742 -0.0139 0.2467 0.0091
FALSE 2411 0.0012 0.2581 0.0053

同一打者内で見ても差は-0.0151, 95%信頼区間[-0.0356, +0.0055]です.

within |>
  ggplot(aes(x = 差, fill = 安打あり)) +
  geom_density(alpha = 0.4) +
  geom_vline(xintercept = 0, linetype = "dashed") +
  xlab("翌試合の打率 − その打者のシーズン打率") +
  ggtitle("いつもの自分と比べてどうだったか")

1試合の打率は4打数のうち何本か, という粗い量なので分布は飛び飛びです. 平均で見るしかありません.

結論

「明日につながるヒット」の効果は見つかりませんでした.

大差で負けている試合の最終打席という同じ場面で比べると, 打てた人と 打てなかった人で, 翌試合の打率に意味のある差はありません. 同じ打者の中で比べても同じです.

もとの分析は「つながるヒットを打った人の翌日の打率」をリーグ平均と 比べていましたが, それでは打者の質を見ているだけになります. 対照群を置くと差は消えました.

注意

  • 2013年の1シーズンだけです. 件数は3,377件で, 検出できる差の大きさには限りがあります. 信頼区間の幅がその限界を表します.
  • 「次の出場試合」は同一シーズン内で追いました. シーズン最終戦の場合は 次が無いので除かれます.
  • 打席に立てたかどうか自体が監督の判断で決まります. 大差の試合で最終打席に 立つのは, 主力とは限りません.