#' @rdname mlb_pbp_diff
#' @title **Acquire pitch-by-pitch data between two timecodes for Major and Minor League games**
#'
#' @param game_pk The date for which you want to find game_pk values for MLB games
#' @param start_timecode The start time code for the MLB game (format: MMDDYYYY_HHMMSS)
#' @param end_timecode The end time code for the MLB game (format: MMDDYYYY_HHMMSS)
#' @importFrom jsonlite fromJSON
#' @return Returns a tibble that includes over 100 columns of data provided
#' by the MLB Stats API at a pitch level between the start_timecode and end_timecode
#'
#' |col_name |types |
#' |:------------------------------|:---------|
#' |game_pk |numeric |
#' |game_date |character |
#' |index |integer |
#' |startTime |character |
#' |endTime |character |
#' |isPitch |logical |
#' |type |character |
#' |playId |character |
#' |pitchNumber |integer |
#' |details.description |character |
#' |details.event |character |
#' |details.awayScore |integer |
#' |details.homeScore |integer |
#' |details.isScoringPlay |logical |
#' |details.hasReview |logical |
#' |details.code |character |
#' |details.ballColor |character |
#' |details.isInPlay |logical |
#' |details.isStrike |logical |
#' |details.isBall |logical |
#' |details.call.code |character |
#' |details.call.description |character |
#' |count.balls.start |integer |
#' |count.strikes.start |integer |
#' |count.outs.start |integer |
#' |player.id |integer |
#' |player.link |character |
#' |pitchData.strikeZoneTop |numeric |
#' |pitchData.strikeZoneBottom |numeric |
#' |details.fromCatcher |logical |
#' |pitchData.coordinates.x |numeric |
#' |pitchData.coordinates.y |numeric |
#' |hitData.trajectory |character |
#' |hitData.hardness |character |
#' |hitData.location |character |
#' |hitData.coordinates.coordX |numeric |
#' |hitData.coordinates.coordY |numeric |
#' |actionPlayId |character |
#' |details.eventType |character |
#' |details.runnerGoing |logical |
#' |position.code |character |
#' |position.name |character |
#' |position.type |character |
#' |position.abbreviation |character |
#' |battingOrder |character |
#' |atBatIndex |character |
#' |result.type |character |
#' |result.event |character |
#' |result.eventType |character |
#' |result.description |character |
#' |result.rbi |integer |
#' |result.awayScore |integer |
#' |result.homeScore |integer |
#' |about.atBatIndex |integer |
#' |about.halfInning |character |
#' |about.inning |integer |
#' |about.startTime |character |
#' |about.endTime |character |
#' |about.isComplete |logical |
#' |about.isScoringPlay |logical |
#' |about.hasReview |logical |
#' |about.hasOut |logical |
#' |about.captivatingIndex |integer |
#' |count.balls.end |integer |
#' |count.strikes.end |integer |
#' |count.outs.end |integer |
#' |matchup.batter.id |integer |
#' |matchup.batter.fullName |character |
#' |matchup.batter.link |character |
#' |matchup.batSide.code |character |
#' |matchup.batSide.description |character |
#' |matchup.pitcher.id |integer |
#' |matchup.pitcher.fullName |character |
#' |matchup.pitcher.link |character |
#' |matchup.pitchHand.code |character |
#' |matchup.pitchHand.description |character |
#' |matchup.splits.batter |character |
#' |matchup.splits.pitcher |character |
#' |matchup.splits.menOnBase |character |
#' |batted.ball.result |factor |
#' |home_team |character |
#' |home_level_id |integer |
#' |home_level_name |character |
#' |home_parentOrg_id |integer |
#' |home_parentOrg_name |character |
#' |home_league_id |integer |
#' |home_league_name |character |
#' |away_team |character |
#' |away_level_id |integer |
#' |away_level_name |character |
#' |away_parentOrg_id |integer |
#' |away_parentOrg_name |character |
#' |away_league_id |integer |
#' |away_league_name |character |
#' |batting_team |character |
#' |fielding_team |character |
#' |last.pitch.of.ab |character |
#' |pfxId |character |
#' |details.trailColor |character |
#' |details.type.code |character |
#' |details.type.description |character |
#' |pitchData.startSpeed |numeric |
#' |pitchData.endSpeed |numeric |
#' |pitchData.zone |integer |
#' |pitchData.typeConfidence |numeric |
#' |pitchData.plateTime |numeric |
#' |pitchData.extension |numeric |
#' |pitchData.coordinates.aY |numeric |
#' |pitchData.coordinates.aZ |numeric |
#' |pitchData.coordinates.pfxX |numeric |
#' |pitchData.coordinates.pfxZ |numeric |
#' |pitchData.coordinates.pX |numeric |
#' |pitchData.coordinates.pZ |numeric |
#' |pitchData.coordinates.vX0 |numeric |
#' |pitchData.coordinates.vY0 |numeric |
#' |pitchData.coordinates.vZ0 |numeric |
#' |pitchData.coordinates.x0 |numeric |
#' |pitchData.coordinates.y0 |numeric |
#' |pitchData.coordinates.z0 |numeric |
#' |pitchData.coordinates.aX |numeric |
#' |pitchData.breaks.breakAngle |numeric |
#' |pitchData.breaks.breakLength |numeric |
#' |pitchData.breaks.breakY |numeric |
#' |pitchData.breaks.spinRate |integer |
#' |pitchData.breaks.spinDirection |integer |
#' |hitData.launchSpeed |numeric |
#' |hitData.launchAngle |numeric |
#' |hitData.totalDistance |numeric |
#' |injuryType |character |
#' |umpire.id |integer |
#' |umpire.link |character |
#' |about.isTopInning |logical |
#' |matchup.postOnFirst.id |integer |
#' |matchup.postOnFirst.fullName |character |
#' |matchup.postOnFirst.link |character |
#' @export
#' @examples \donttest{
#' try(mlb_pbp_diff(game_pk = 632970,
#' start_timecode = "20210808_231704",
#' end_timecode = "20210808_233711"))
#' }
mlb_pbp_diff <- function(
game_pk,
start_timecode,
end_timecode) {
mlb_endpoint <- mlb_stats_endpoint(glue::glue("v1.1/game/{game_pk}/feed/live/diffPatch"))
query_params <- list(
startTimecode = start_timecode,
endTimecode = end_timecode
)
mlb_endpoint <- httr::modify_url(mlb_endpoint, query = query_params)
tryCatch(
expr = {
payload <- mlb_endpoint %>%
mlb_api_call() %>%
jsonlite::toJSON() %>%
jsonlite::fromJSON(flatten = TRUE)
plays <- payload$liveData$plays$allPlays$playEvents %>%
dplyr::bind_rows()
at_bats <- payload$liveData$plays$allPlays
current <- payload$liveData$plays$currentPlay
game_status <- payload$gameData$status$abstractGameState
home_team <- payload$gameData$teams$home$name
home_level <- payload$gameData$teams$home$sport
home_league <- payload$gameData$teams$home$league
away_team <- payload$gameData$teams$away$name
away_level <- payload$gameData$teams$away$sport
away_league <- payload$gameData$teams$away$league
columns <- lapply(at_bats, function(x) class(x)) %>%
dplyr::bind_rows(.id = "variable")
cols <- c(colnames(columns))
classes <- c(t(unname(columns[1,])))
df <- data.frame(cols, classes)
list_columns <- df %>%
dplyr::filter(.data$classes == "list") %>%
dplyr::pull("cols")
at_bats <- at_bats %>%
dplyr::select(-c(tidyr::one_of(list_columns)))
pbp <- plays %>%
dplyr::left_join(at_bats, by = c("endTime" = "playEndTime"))
pbp <- pbp %>%
tidyr::fill("atBatIndex":"matchup.splits.menOnBase", .direction = "up") %>%
dplyr::mutate(
game_pk = game_pk,
game_date = substr(payload$gameData$datetime$dateTime, 1, 10)) %>%
dplyr::select("game_pk", "game_date", tidyr::everything())
pbp <- pbp %>%
dplyr::mutate(
matchup.batter.fullName = factor(.data$matchup.batter.fullName),
matchup.pitcher.fullName = factor(.data$matchup.pitcher.fullName),
atBatIndex = factor(.data$atBatIndex)
# batted.ball.result = case_when(!result.event %in% c(
# "Single", "Double", "Triple", "Home Run") ~ "Out/Other",
# TRUE ~ result.event),
# batted.ball.result = factor(batted.ball.result,
# levels = c("Single", "Double", "Triple", "Home Run", "Out/Other"))
) %>%
dplyr::mutate(
home_team = home_team,
home_level_id = home_level$id,
home_level_name = home_level$name,
home_parentOrg_id = payload$gameData$teams$home$parentOrgId,
home_parentOrg_name = payload$gameData$teams$home$parentOrgName,
home_league_id = home_league$id,
home_league_name = home_league$name,
away_team = away_team,
away_level_id = away_level$id,
away_level_name = away_level$name,
away_parentOrg_id = payload$gameData$teams$away$parentOrgId,
away_parentOrg_name = payload$gameData$teams$away$parentOrgName,
away_league_id = away_league$id,
away_league_name = away_league$name,
batting_team = factor(ifelse(.data$about.halfInning == "bottom",
.data$home_team,
.data$away_team)),
fielding_team = factor(ifelse(.data$about.halfInning == "bottom",
.data$away_team,
.data$home_team)))
pbp <- pbp %>%
dplyr::arrange(desc(.data$atBatIndex), desc(.data$pitchNumber))
pbp <- pbp %>%
dplyr::group_by(.data$atBatIndex) %>%
dplyr::mutate(
last.pitch.of.ab = ifelse(.data$pitchNumber == max(.data$pitchNumber), "true", "false"),
last.pitch.of.ab = factor(.data$last.pitch.of.ab)) %>%
dplyr::ungroup()
pbp <- dplyr::bind_rows(baseballr::stats_api_live_empty_df, pbp)
check_home_level <- pbp %>%
dplyr::distinct(.data$home_level_id) %>%
dplyr::pull()
# this will need to be updated in the future to properly estimate X,Z coordinates at the minor league level
# if(check_home_level != 1) {
#
# pbp <- pbp %>%
# dplyr::mutate(pitchData.coordinates.x = -pitchData.coordinates.x,
# pitchData.coordinates.y = -pitchData.coordinates.y)
#
# pbp <- pbp %>%
# dplyr::mutate(pitchData.coordinates.pX_est = predict(x_model, pbp),
# pitchData.coordinates.pZ_est = predict(y_model, pbp))
#
# pbp <- pbp %>%
# dplyr::mutate(pitchData.coordinates.x = -pitchData.coordinates.x,
# pitchData.coordinates.y = -pitchData.coordinates.y)
# }
pbp <- pbp %>%
dplyr::rename(
"count.balls.start" = "count.balls.x",
"count.strikes.start" = "count.strikes.x",
"count.outs.start" = "count.outs.x",
"count.balls.end" = "count.balls.y",
"count.strikes.end" = "count.strikes.y",
"count.outs.end" = "count.outs.y") %>%
make_baseballr_data("MLB Play-by-Play diff data from MLB.com",Sys.time())
},
error = function(e) {
message(glue::glue("{Sys.time()}: Invalid arguments provided"))
},
finally = {
}
)
return(pbp)
}
#' @rdname get_pbp_mlb
#' @title **(legacy) Acquire pitch-by-pitch data for Major and Minor League games**
#' @inheritParams mlb_pbp
#' @return Returns a tibble that includes over 100 columns of data provided
#' by the MLB Stats API at a pitch level.
#' @keywords legacy
#' @export
get_pbp_mlb <- mlb_pbp
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.