7.4 Where is the Strike Zone?

  • Only part of the ball needs to cross home plate to be a strike
  • Home plate is 17 inches wide and ball’s circumference is 9 inches
  • Outside edges of strike zone vary by plus or minus 0.947 feet
  • Top and bottom of strike zone varies by batter height (Midpoint between top of shoulders and top of players pants down to hollow beneath kneecap)
MLB Strike Zone
MLB Strike Zone
plate_width <- 17 + 2 * (9/pi)
k_zone_plot <- ggplot(
  NULL, aes(x = plate_x, y = plate_z)
) + 
  geom_rect(
    xmin = -(plate_width/2)/12, 
    xmax = (plate_width/2)/12, 
    ymin = 1.5, 
    ymax = 3.6, color = crcblue, alpha = 0
  ) + 
  coord_equal() + 
  scale_x_continuous(
    "Horizontal location (ft.)", 
    limits = c(-2, 2)
  ) + 
  scale_y_continuous(
    "Vertical location (ft.)", 
    limits = c(0, 5)
  )
k_zone_plot %+% 
  sample_n(taken, size = 2000) +
  aes(color = Outcome) +
  geom_point(alpha = 0.2) + 
  scale_color_manual(values = crc_fc)

zones <- taken |>
  group_by(zone) |>
  summarize(
    N = n(), 
    right_edge = min(1.5, max(plate_x)), 
    left_edge = max(-1.5, min(plate_x)),
    top_edge = min(5, quantile(plate_z, 0.95, na.rm = TRUE)), 
    bottom_edge = max(0, quantile(plate_z, 0.05, na.rm = TRUE)),
    strike_pct = sum(Outcome == "called_strike") / n(),
    plate_x = mean(plate_x), 
    plate_z = mean(plate_z)
  )
library(ggrepel)
k_zone_plot %+% zones + 
  geom_rect(
    aes(
      xmax = right_edge, xmin = left_edge,
      ymax = top_edge, ymin = bottom_edge,
      fill = strike_pct, alpha = strike_pct
    ), 
    color = "lightgray"
  ) +
  geom_text_repel(
    size = 3, 
    aes(
      label = round(strike_pct, 2),
      color = strike_pct < 0.5
    )
  ) + 
  scale_fill_gradient(low = "gray70", high = crcblue) + 
  scale_color_manual(values = crc_fc) +
  guides(color = FALSE, alpha = FALSE)