The New MLB Pitch Clock is Fixing Baseball's Pace of Play Crisis

If you tuned into Opening Day last season, you might have been curious where the baseball was! Between the endless batting glove adjustments by hitters and the incredibly slow working pace of many pitchers, games sometimes felt interminable - especially if Pedro Baez was on the mound:

Baseball’s 2023 Reforms

The pace-of-play problem had gotten so bad that the Major League Baseball, a legendarily change-resistant and slow-moving organization, implemented significant reforms for the 2023 season. While I might be forgetting a minor tweak or two, the key changes to the game are as follows:

  • There is now a pitch timer of either fifteen or twenty seconds (longer when runners are on base), which forces pitchers to begin their delivery on time or be penalized with a ball
  • Batters need to be “ready in the box” by the time the pitch timer (which is a countdown) strikes eight seconds, or they will be penalized with a strike
  • Pickoff attempts by pitchers are rate-limited to two per plate appearance

In addition to the pace-of-play-accelerating reforms above, MLB also made two changes attempting to juice offense. These likely won’t help game times but they do address major points of discussion in the sport:

  • Infield shifts are de-facto banned: two infielders need to cover each side of second base
  • The size of the physical bases has been increased, ostensibly to increase safety of baserunners, but also serving to give a tiny advantage to base stealers, who are now three inches closer to being safe

With the 2023 season underway, I thought it would be fun to make an early estimate on how much difference these reforms are actually having in practice.

To do so, I’ve gathered information on all of the games played through Sunday evening (about fifty games so far), and compared them against the first fifty games of the 2022 season.

The difference is pretty incredible - games are running about half an hour quicker YoY, and we see far fewer games exceeding the magic threshold of three hours. Take a peek at the density plot and boxplot below - I felt both visualizations were helpful in conveying the differences we’re observing!

Visualizations

Density Plot

Distribution of MLB Game Times

Box Plot

Boxplot of MLB Game Times

Code to Generate Plots

Fetch 2023 Schedule and Get Completed Games

We use the excellent baseballr package to fetch game durations - many thanks to Bill Petti and the contributors to that project.

library(tidyverse)
library(baseballr)

sch_2023 <- mlb_schedule(season = "2023", level_ids = "1") %>% 
  filter(game_type == "R") %>% 
  filter(!is.na(teams_home_is_winner))
  
runtimes_2023 <- apply(sch_2023 %>% select("game_pk"), 1,  function(x) mlb_game_info(game_pk = x)$elapsed_time)

Fetch the Same Number of Games to Start The 2022 Season

sch_2022 <- mlb_schedule(season = "2022", level_ids = "1") %>% 
  filter(game_type == "R") %>% 
  filter(!is.na(teams_home_is_winner)) %>% 
  head(length(runtimes_2023))

runtimes_2022 <- apply(sch_2022 %>% select("game_pk"), 1,  function(x) mlb_game_info(game_pk = x)$elapsed_time)

Pool the 2022 and 2023 Games for Combined Analysis

df_2022 <- data.frame(runtimes_2022) %>% rename("runtime" = "runtimes_2022") %>% mutate(year = 2022)

df_2023 <- data.frame(runtimes_2023) %>% rename("runtime" = "runtimes_2023") %>% mutate(year = 2023)

df <- rbind(df_2022, df_2023)

df <- df %>% mutate(
  duration_minutes = as.integer(substr(runtime, 0, 1)) * 60 + as.integer(substr(runtime, 3, 4))
)

Summarize the Data by Year

df %>% group_by(year) %>% 
  summarize(
    count = n(),
    avg_duration_min = mean(duration_minutes),
    min_duration_min = min(duration_minutes),
    p25 = quantile(duration_minutes, 0.25),
    p50 = quantile(duration_minutes, 0.50),
    p75 = quantile(duration_minutes, 0.75)
  )

Create the Density Plot

ggplot(df, aes(x = duration_minutes, fill = factor(year))) +
  geom_density(position = "identity", alpha = 0.6, binwidth = 10) + 
  scale_fill_manual(values=rev(c("#69b3a2", "#404080")), name = "Year") +
  scale_x_continuous(breaks = scales::pretty_breaks(n = 10)) + 
  theme_minimal() +
  theme(
    strip.background = element_rect(fill = "grey30"),
    strip.text = element_text(color = "grey97", face = "bold"),
    axis.text.y = element_blank(),
    legend.background = element_rect(color = NA),
    plot.title = element_text(size = 20, face = "bold"),
    plot.subtitle = element_text(size = 12),
    plot.caption = element_text(colour = "grey60")
  ) + 
  labs(
    x = "\nGame Duration (Minutes)",
    y = "",
    title =  "How has the New MLB Pitch Clock Influenced Game Times?",
    subtitle = "The opening weekend of MLB games has seen dramatically shorter game times compared the last season -\nhalf an hour shorter on average! With substantially fewer games exceeding three hours of runtime.",
    caption = "conormclaughlin.net"
  )

Helper Function for Summary Measures in Boxplot

get_box_stats <- function(y, upper_limit = max(df$duration_minutes) * 1.15) {
  return(data.frame(
    y = 0.95 * upper_limit,
    label = paste(
      #"Count:", length(y), "\n",
      "P25:", round(quantile(y, 0.25), 1), "min\n",
      "Median:", round(median(y), 1), "min\n",
      "P75:", round(quantile(y, 0.75), 1), "min\n"
    )
  ))
}

Create the Boxplot

ggplot(df, aes(x = factor(year), y = duration_minutes, fill = factor(year))) +
  geom_boxplot(alpha = 0.6) + 
  scale_fill_manual(values=rev(c("#69b3a2", "#404080")), name = "Year", guide = FALSE) +
  scale_y_continuous(breaks = scales::pretty_breaks(n = 20)) + 
  stat_summary(fun.data = get_box_stats, geom = "text", hjust = 0.5, vjust = 0.8) +
  geom_jitter(size = 0.5, alpha = 0.9) + 
  theme_minimal() +
  theme(
    strip.background = element_rect(fill = "grey30"),
    strip.text = element_text(color = "grey97", face = "bold"),
    legend.position = "none",
    plot.title = element_text(size = 20, face = "bold"),
    plot.subtitle = element_text(size = 12),
    plot.caption = element_text(colour = "grey60"),
    plot.title.position = "plot"
  ) + 
  labs(
    x = "",
    y = "Game Duration (Minutes)\n",
    title =  "MLB Game Times (2022 and 2023)",
    subtitle = "The opening weekend of MLB games has seen notably shorter\ngame times compared to last season - about half an hour quicker!",
    caption = "conormclaughlin.net"
  )