Spaces:
Sleeping
Sleeping
Update app.R
Browse files
app.R
CHANGED
|
@@ -1,4 +1,3 @@
|
|
| 1 |
-
# Load required libraries
|
| 2 |
library(shiny)
|
| 3 |
library(plotly)
|
| 4 |
library(gridlayout)
|
|
@@ -19,27 +18,9 @@ library(grid)
|
|
| 19 |
library(gridExtra)
|
| 20 |
library(png)
|
| 21 |
library(xgboost)
|
| 22 |
-
library(httr)
|
| 23 |
-
library(magick)
|
| 24 |
-
|
| 25 |
pdf(file = NULL)
|
| 26 |
Sys.setenv(TZ='EST')
|
| 27 |
|
| 28 |
-
# Helper functions
|
| 29 |
-
download_and_process_image <- function(url) {
|
| 30 |
-
tryCatch({
|
| 31 |
-
temp_file <- tempfile(fileext = ".jpg")
|
| 32 |
-
download.file(url, temp_file, mode = "wb")
|
| 33 |
-
img <- image_read(temp_file)
|
| 34 |
-
png_file <- tempfile(fileext = ".png")
|
| 35 |
-
image_write(img, path = png_file, format = "png")
|
| 36 |
-
return(png_file)
|
| 37 |
-
}, error = function(e) {
|
| 38 |
-
warning(paste("Error processing image:", e$message))
|
| 39 |
-
return(NULL)
|
| 40 |
-
})
|
| 41 |
-
}
|
| 42 |
-
|
| 43 |
is_barrel <- function(df) {
|
| 44 |
df$barrel <- with(df, ifelse(hit_angle <= 50 & hit_speed >= 97 & hit_speed * 1.5 -
|
| 45 |
hit_angle >= 117 & hit_speed + hit_angle >= 123, 1, 0))
|
|
@@ -47,7 +28,7 @@ is_barrel <- function(df) {
|
|
| 47 |
}
|
| 48 |
|
| 49 |
VAA <- function(milbtotal){
|
| 50 |
-
milbtotal <- milbtotal
|
| 51 |
mutate(VAA = -atan((vz0+(az*(-sqrt((vy0*vy0)-(2*ay*(y0-(17/12))))-vy0)/
|
| 52 |
ay))/(-sqrt((vy0*vy0)-(2*ay*(y0-(17/12))))))*(180/pi))
|
| 53 |
}
|
|
@@ -56,9 +37,9 @@ pitcher_summary <- function(game_pk,date){
|
|
| 56 |
gdate <- as.Date.character(date)
|
| 57 |
gdate <- as.Date(gdate)
|
| 58 |
tmilb <- mlb_pbp(game_pk)
|
| 59 |
-
tmilb <- tmilb
|
| 60 |
filter(type == "pitch")
|
| 61 |
-
tmilb <- tmilb
|
| 62 |
select(matchup.batter.fullName,matchup.batter.id,matchup.pitcher.fullName,
|
| 63 |
matchup.pitcher.id,result.event,details.description,details.type.description,
|
| 64 |
result.description,pitchData.startSpeed,pitchData.plateTime,pitchData.zone,
|
|
@@ -77,10 +58,10 @@ pitcher_summary <- function(game_pk,date){
|
|
| 77 |
"IVB","HB","hit_speed","hit_angle","hit_distance","inPlay",
|
| 78 |
"lastPitch","spinDirection","phand")
|
| 79 |
tmilb <- is_barrel(tmilb)
|
| 80 |
-
tmilb <- tmilb
|
| 81 |
mutate(is_strike_swinging = ifelse(description == "Swinging Strike" |
|
| 82 |
description == "Foul Tip",TRUE,FALSE))
|
| 83 |
-
tmilb <- tmilb
|
| 84 |
mutate(date = gdate)
|
| 85 |
return(tmilb)
|
| 86 |
}
|
|
@@ -123,46 +104,69 @@ pitch_plot <- function(game){
|
|
| 123 |
axis.ticks.y=element_blank())
|
| 124 |
}
|
| 125 |
|
| 126 |
-
#
|
| 127 |
-
FB <- xgb.load('FB.model')
|
| 128 |
-
Off <- xgb.load('Off.model')
|
| 129 |
-
Break <- xgb.load('Break.model')
|
| 130 |
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
|
|
|
| 134 |
}
|
| 135 |
|
|
|
|
| 136 |
calculate_EAA <- function(extension) {
|
| 137 |
extension / 6.3
|
| 138 |
}
|
| 139 |
|
|
|
|
| 140 |
calculate_SADiff <- function(pfxX, pfxZ, spinDirection) {
|
|
|
|
| 141 |
inSA <- atan2(pfxZ, pfxX) * 180/pi + 90
|
|
|
|
|
|
|
| 142 |
inSA <- ifelse(inSA < 0, inSA + 360, inSA)
|
|
|
|
|
|
|
| 143 |
SADiff <- spinDirection - inSA
|
|
|
|
|
|
|
| 144 |
SADiff <- ifelse(SADiff > 180, SADiff - 360, SADiff)
|
| 145 |
SADiff <- ifelse(SADiff < -180, SADiff + 360, SADiff)
|
|
|
|
| 146 |
return(SADiff)
|
| 147 |
}
|
| 148 |
|
|
|
|
| 149 |
scale_TimStuff <- function(raw_score, model_mean, model_sd) {
|
| 150 |
scaled_score <- (raw_score - model_mean) / model_sd
|
| 151 |
result <- 50 - (scaled_score * 10)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 152 |
return(result)
|
| 153 |
}
|
| 154 |
-
|
|
|
|
|
|
|
|
|
|
| 155 |
summary_table <- function(game) {
|
| 156 |
rows <- nrow(game)
|
| 157 |
-
sumtable <- game
|
| 158 |
mutate(
|
| 159 |
-
VAA = calculate_VAA(vz0, ay,
|
| 160 |
EAA = calculate_EAA(extension),
|
| 161 |
SADiff = calculate_SADiff(pfxX, pfxZ,spinDirection)
|
| 162 |
-
)
|
| 163 |
mutate(team_fielding_id = ifelse(description == "Called Strike" |
|
| 164 |
description == "Swinging Strike" |
|
| 165 |
-
description == "Swinging Strike (Blocked)", 1, 0))
|
| 166 |
mutate(swing = ifelse(description == "Foul" |
|
| 167 |
description == "Foul Pitchout" |
|
| 168 |
description == "In play, no out" |
|
|
@@ -170,10 +174,10 @@ summary_table <- function(game) {
|
|
| 170 |
description == "In play, run(s)" |
|
| 171 |
description == "Swinging Strike" |
|
| 172 |
description == "swinging Strike (Blocked)" |
|
| 173 |
-
description == "Foul Tip", 1, 0))
|
| 174 |
-
mutate(is_strike_swinging = ifelse(is_strike_swinging == TRUE, 1, 0))
|
| 175 |
-
mutate(Pitch = pitch_name)
|
| 176 |
-
rowwise()
|
| 177 |
mutate(TimStuff = if (phand == 'L') {
|
| 178 |
case_when(
|
| 179 |
Pitch %in% c("Four-Seam Fastball", "Sinker", "Cutter", "Fastball") ~
|
|
@@ -212,8 +216,8 @@ summary_table <- function(game) {
|
|
| 212 |
-0.004822031, 0.007912765)),
|
| 213 |
TRUE ~ NA_real_
|
| 214 |
)
|
| 215 |
-
})
|
| 216 |
-
group_by(Pitch)
|
| 217 |
summarize(
|
| 218 |
Pitches = n(),
|
| 219 |
'Pitch%' = round(sum(Pitches)/sum(rows) * 100, digits = 1),
|
|
@@ -223,34 +227,39 @@ summary_table <- function(game) {
|
|
| 223 |
'IVB' = round(mean(IVB, na.rm = TRUE), digits = 1),
|
| 224 |
'HB' = round(mean(HB, na.rm = TRUE), digits = 1),
|
| 225 |
'VAA' = round(mean(VAA, na.rm = TRUE), digits = 1),
|
|
|
|
|
|
|
|
|
|
|
|
|
| 226 |
'CSW%' = round(sum(team_fielding_id, na.rm = TRUE) / sum(!is.na(team_fielding_id)) * 100, digits = 1),
|
| 227 |
'Whiff%' = round(sum(is_strike_swinging, na.rm = TRUE) / sum(swing, na.rm = TRUE) * 100, digits = 1),
|
| 228 |
'TimStuff' = round(mean(TimStuff, na.rm = TRUE), digits = 0)
|
| 229 |
-
)
|
| 230 |
arrange(-Pitches)
|
| 231 |
|
| 232 |
return(sumtable)
|
| 233 |
}
|
| 234 |
|
| 235 |
-
#
|
|
|
|
| 236 |
mlbid <- mlb_schedule(season = 2024, level_ids = "1")
|
| 237 |
-
mlbteamH <- mlbid
|
| 238 |
select(teams_home_team_name)
|
| 239 |
mlbteamH <- distinct(mlbteamH)
|
| 240 |
-
mlbteamA <- mlbid
|
| 241 |
select(teams_away_team_name)
|
| 242 |
mlbteamA <- distinct(mlbteamA)
|
| 243 |
|
| 244 |
aaaid <- mlb_schedule(season = 2024, level_ids = "11")
|
| 245 |
-
aaateamH <- aaaid
|
| 246 |
select(teams_home_team_name)
|
| 247 |
aaateamH <- distinct(aaateamH)
|
| 248 |
-
aaateamA <- aaaid
|
| 249 |
select(teams_away_team_name)
|
| 250 |
aaateamA <- distinct(aaateamA)
|
| 251 |
|
| 252 |
fslid <- mlb_schedule(season = 2024, level_ids = "14")
|
| 253 |
-
fslid <- fslid
|
| 254 |
filter(teams_home_team_name == "Daytona Tortugas" |
|
| 255 |
teams_home_team_name == "Jupiter Hammerheads" |
|
| 256 |
teams_home_team_name == "Palm Beach Cardinals" |
|
|
@@ -261,22 +270,21 @@ fslid <- fslid %>%
|
|
| 261 |
teams_home_team_name == "Fort Myers Mighty Mussels" |
|
| 262 |
teams_home_team_name == "Lakeland Flying Tigers" |
|
| 263 |
teams_home_team_name == "Tampa Tarpons")
|
| 264 |
-
fslteamH <- fslid
|
| 265 |
select(teams_home_team_name)
|
| 266 |
fslteamH <- distinct(fslteamH)
|
| 267 |
-
fslteamA <- fslid
|
| 268 |
select(teams_away_team_name)
|
| 269 |
fslteamA <- distinct(fslteamA)
|
| 270 |
|
| 271 |
sbid <- mlb_schedule(season = 2024, level_ids = "22")
|
| 272 |
-
sbteamH <- sbid
|
| 273 |
select(teams_home_team_name)
|
| 274 |
sbteamH <- distinct(sbteamH)
|
| 275 |
-
sbteamA <- sbid
|
| 276 |
select(teams_away_team_name)
|
| 277 |
sbteamA <- distinct(sbteamA)
|
| 278 |
|
| 279 |
-
# UI Definition
|
| 280 |
ui <- fluidPage(
|
| 281 |
tags$head(
|
| 282 |
tags$style(HTML("
|
|
@@ -301,6 +309,7 @@ ui <- fluidPage(
|
|
| 301 |
actionButton("update", "Find Pitcher", icon("magnifying-glass"),
|
| 302 |
style = "color: #FFFFFF; background-color: #0077B6; width: 100%;"),
|
| 303 |
selectizeInput("pitcher", "Pitcher Name:", c(" ")),
|
|
|
|
| 304 |
textInput("title", "Card Title"),
|
| 305 |
actionButton("update1", "Make Card", icon("plus"),
|
| 306 |
style = "color: #FFFFFF; background-color: #0077B6; width: 100%;"),
|
|
@@ -311,6 +320,7 @@ ui <- fluidPage(
|
|
| 311 |
)
|
| 312 |
)
|
| 313 |
)
|
|
|
|
| 314 |
server <- function(input, output, session) {
|
| 315 |
observeEvent(input$level, {
|
| 316 |
if(input$level == "AAA"){
|
|
@@ -332,162 +342,156 @@ server <- function(input, output, session) {
|
|
| 332 |
})
|
| 333 |
|
| 334 |
observeEvent(input$update, {
|
| 335 |
-
|
| 336 |
-
|
| 337 |
-
|
| 338 |
-
|
| 339 |
-
|
| 340 |
-
|
| 341 |
-
|
| 342 |
-
|
| 343 |
-
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
|
| 348 |
-
|
| 349 |
-
|
| 350 |
-
|
| 351 |
-
|
| 352 |
-
|
| 353 |
-
|
| 354 |
-
|
| 355 |
-
|
| 356 |
-
|
| 357 |
-
|
| 358 |
-
|
| 359 |
-
}
|
| 360 |
-
|
| 361 |
-
|
| 362 |
-
|
| 363 |
-
|
| 364 |
-
|
| 365 |
-
|
| 366 |
-
|
| 367 |
-
|
| 368 |
-
|
| 369 |
-
|
| 370 |
-
|
| 371 |
-
} else {
|
| 372 |
-
updateSelectizeInput(session = getDefaultReactiveDomain(),"pitcher","Pitcher:",choices = pname[,3])
|
| 373 |
-
}
|
| 374 |
-
}, error = function(e) {
|
| 375 |
-
showNotification(paste("Error finding pitchers:", e$message), type = "error")
|
| 376 |
-
})
|
| 377 |
})
|
| 378 |
|
| 379 |
combinedPlot <- reactiveVal()
|
| 380 |
observeEvent(input$update1, {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 381 |
tryCatch({
|
| 382 |
-
|
| 383 |
-
|
| 384 |
-
|
| 385 |
-
|
| 386 |
-
|
| 387 |
-
|
| 388 |
-
|
| 389 |
-
|
| 390 |
-
|
| 391 |
-
|
| 392 |
-
|
| 393 |
-
if(input$level == "FSL"){
|
| 394 |
-
id <- fslid %>%
|
| 395 |
-
filter(date == as.character.Date(input$date)) %>%
|
| 396 |
-
filter(teams_home_team_name == input$homeT) %>%
|
| 397 |
-
filter(teams_away_team_name == input$awayT) %>%
|
| 398 |
-
filter(game_number == input$gamenum)
|
| 399 |
-
game <- pitcher_summary(id[,6],input$date)
|
| 400 |
-
game <- game %>%
|
| 401 |
-
filter(`Pitcher Name` == input$pitcher)
|
| 402 |
-
id <- as.character(game[1,4])
|
| 403 |
-
}
|
| 404 |
-
if(input$level == "AAA"){
|
| 405 |
-
id <- aaaid %>%
|
| 406 |
-
filter(date == as.character.Date(input$date)) %>%
|
| 407 |
-
filter(teams_home_team_name == input$homeT) %>%
|
| 408 |
-
filter(teams_away_team_name == input$awayT) %>%
|
| 409 |
-
filter(game_number == input$gamenum)
|
| 410 |
-
game <- pitcher_summary(id[,6],input$date)
|
| 411 |
-
game <- game %>%
|
| 412 |
-
filter(`Pitcher Name` == input$pitcher)
|
| 413 |
-
id <- as.character(game[1,4])
|
| 414 |
-
}
|
| 415 |
-
if(input$level == "College (Statcast Parks Only)"){
|
| 416 |
-
id <- sbid %>%
|
| 417 |
-
filter(date == as.character.Date(input$date)) %>%
|
| 418 |
-
filter(teams_home_team_name == input$homeT) %>%
|
| 419 |
-
filter(teams_away_team_name == input$awayT) %>%
|
| 420 |
-
filter(game_number == input$gamenum)
|
| 421 |
-
game <- pitcher_summary(id[,6],input$date)
|
| 422 |
-
game <- game %>%
|
| 423 |
-
filter(`Pitcher Name` == input$pitcher)
|
| 424 |
-
id <- as.character(game[1,4])
|
| 425 |
-
}
|
| 426 |
-
|
| 427 |
-
if(nrow(game) == 0) {
|
| 428 |
-
showNotification("No data available for the selected pitcher.", type = "warning")
|
| 429 |
-
return()
|
| 430 |
-
}
|
| 431 |
-
|
| 432 |
-
break_plot <- break_plot(game)
|
| 433 |
-
pitch_plot <- pitch_plot(game)
|
| 434 |
-
|
| 435 |
-
table_plot <- tableGrob(summary_table(game), theme = ttheme_default(
|
| 436 |
-
core = list(fg_params = list(cex = 1.7)),
|
| 437 |
-
colhead = list(fg_params = list(cex = 1.2)),
|
| 438 |
-
rowhead = list(fg_params = list(cex = 2.5))))
|
| 439 |
-
table_plot$widths[[1]] <- unit(0, "cm")
|
| 440 |
-
|
| 441 |
-
text_grobs <- which(sapply(table_plot$grobs, function(g) {
|
| 442 |
-
g$name == "core-fg-1" && inherits(g$children[[1]], "text")
|
| 443 |
-
}))
|
| 444 |
-
|
| 445 |
-
for (i in text_grobs) {
|
| 446 |
-
text <- table_plot$grobs[[i]]$children[[1]]$label
|
| 447 |
-
table_plot$grobs[[i]]$children[[1]]$label <- sub("^\\d+\\s*", "", text)
|
| 448 |
-
}
|
| 449 |
-
|
| 450 |
-
grid.newpage()
|
| 451 |
-
grid.draw(table_plot)
|
| 452 |
-
|
| 453 |
-
mlb_url <- paste0("https://midfield.mlbstatic.com/v1/people/", id, "/mlb/300?circle=false")
|
| 454 |
-
milb_url <- paste0("https://midfield.mlbstatic.com/v1/people/", id, "/milb/300?circle=false")
|
| 455 |
-
|
| 456 |
-
img_file <- tryCatch({
|
| 457 |
-
download_and_process_image(mlb_url)
|
| 458 |
}, error = function(e) {
|
| 459 |
-
|
|
|
|
| 460 |
})
|
| 461 |
-
|
| 462 |
-
|
| 463 |
-
|
| 464 |
-
|
| 465 |
-
|
| 466 |
-
|
| 467 |
-
|
| 468 |
-
|
| 469 |
-
|
| 470 |
-
|
| 471 |
-
|
| 472 |
-
|
| 473 |
-
|
| 474 |
-
|
| 475 |
-
|
| 476 |
-
|
| 477 |
-
|
| 478 |
-
|
| 479 |
-
arrangeGrob(break_plot, pitch_plot, ncol = 2),
|
| 480 |
-
nrow = 2,
|
| 481 |
-
top = textGrob(paste(input$title, "Made by @TimStats", sep = " "), gp = gpar(fontsize = 20, font = 2))
|
| 482 |
-
)
|
| 483 |
-
|
| 484 |
-
combinedPlot(combined)
|
| 485 |
-
|
| 486 |
-
output$combinedPlot <- renderPlot({
|
| 487 |
-
grid.draw(combinedPlot())
|
| 488 |
-
})
|
| 489 |
-
}, error = function(e) {
|
| 490 |
-
showNotification(paste("Error generating card:", e$message), type = "error")
|
| 491 |
})
|
| 492 |
})
|
| 493 |
|
|
@@ -496,9 +500,10 @@ server <- function(input, output, session) {
|
|
| 496 |
paste("baseball_card_", Sys.Date(), ".png", sep = "")
|
| 497 |
},
|
| 498 |
content = function(file) {
|
| 499 |
-
ggsave(file, plot = combinedPlot(), width =
|
| 500 |
}
|
| 501 |
)
|
| 502 |
}
|
| 503 |
|
|
|
|
| 504 |
shinyApp(ui, server)
|
|
|
|
|
|
|
| 1 |
library(shiny)
|
| 2 |
library(plotly)
|
| 3 |
library(gridlayout)
|
|
|
|
| 18 |
library(gridExtra)
|
| 19 |
library(png)
|
| 20 |
library(xgboost)
|
|
|
|
|
|
|
|
|
|
| 21 |
pdf(file = NULL)
|
| 22 |
Sys.setenv(TZ='EST')
|
| 23 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
is_barrel <- function(df) {
|
| 25 |
df$barrel <- with(df, ifelse(hit_angle <= 50 & hit_speed >= 97 & hit_speed * 1.5 -
|
| 26 |
hit_angle >= 117 & hit_speed + hit_angle >= 123, 1, 0))
|
|
|
|
| 28 |
}
|
| 29 |
|
| 30 |
VAA <- function(milbtotal){
|
| 31 |
+
milbtotal <- milbtotal |>
|
| 32 |
mutate(VAA = -atan((vz0+(az*(-sqrt((vy0*vy0)-(2*ay*(y0-(17/12))))-vy0)/
|
| 33 |
ay))/(-sqrt((vy0*vy0)-(2*ay*(y0-(17/12))))))*(180/pi))
|
| 34 |
}
|
|
|
|
| 37 |
gdate <- as.Date.character(date)
|
| 38 |
gdate <- as.Date(gdate)
|
| 39 |
tmilb <- mlb_pbp(game_pk)
|
| 40 |
+
tmilb <- tmilb |>
|
| 41 |
filter(type == "pitch")
|
| 42 |
+
tmilb <- tmilb |>
|
| 43 |
select(matchup.batter.fullName,matchup.batter.id,matchup.pitcher.fullName,
|
| 44 |
matchup.pitcher.id,result.event,details.description,details.type.description,
|
| 45 |
result.description,pitchData.startSpeed,pitchData.plateTime,pitchData.zone,
|
|
|
|
| 58 |
"IVB","HB","hit_speed","hit_angle","hit_distance","inPlay",
|
| 59 |
"lastPitch","spinDirection","phand")
|
| 60 |
tmilb <- is_barrel(tmilb)
|
| 61 |
+
tmilb <- tmilb |>
|
| 62 |
mutate(is_strike_swinging = ifelse(description == "Swinging Strike" |
|
| 63 |
description == "Foul Tip",TRUE,FALSE))
|
| 64 |
+
tmilb <- tmilb |>
|
| 65 |
mutate(date = gdate)
|
| 66 |
return(tmilb)
|
| 67 |
}
|
|
|
|
| 104 |
axis.ticks.y=element_blank())
|
| 105 |
}
|
| 106 |
|
| 107 |
+
# Assume FB.model, Off.model, and Break.model are already loaded in the environment
|
|
|
|
|
|
|
|
|
|
| 108 |
|
| 109 |
+
# Function to calculate VAA (Vertical Approach Angle)
|
| 110 |
+
calculate_VAA <- function(vz0, ay, vy0, y0) {
|
| 111 |
+
-atan((vz0 + (ay * (-sqrt((vy0*vy0) - (2*ay*(y0-(17/12)))) - vy0) / ay)) /
|
| 112 |
+
(-sqrt((vy0*vy0) - (2*ay*(y0-(17/12)))))) * (180/pi)
|
| 113 |
}
|
| 114 |
|
| 115 |
+
# Function to calculate EAA (Effective Approach Angle)
|
| 116 |
calculate_EAA <- function(extension) {
|
| 117 |
extension / 6.3
|
| 118 |
}
|
| 119 |
|
| 120 |
+
# Function to calculate SADiff (Spin Axis Differential)
|
| 121 |
calculate_SADiff <- function(pfxX, pfxZ, spinDirection) {
|
| 122 |
+
# Calculate initial inSA
|
| 123 |
inSA <- atan2(pfxZ, pfxX) * 180/pi + 90
|
| 124 |
+
|
| 125 |
+
# Adjust inSA if it's negative
|
| 126 |
inSA <- ifelse(inSA < 0, inSA + 360, inSA)
|
| 127 |
+
|
| 128 |
+
# Calculate SADiff
|
| 129 |
SADiff <- spinDirection - inSA
|
| 130 |
+
|
| 131 |
+
# Adjust SADiff to be within -180 to 180 range
|
| 132 |
SADiff <- ifelse(SADiff > 180, SADiff - 360, SADiff)
|
| 133 |
SADiff <- ifelse(SADiff < -180, SADiff + 360, SADiff)
|
| 134 |
+
|
| 135 |
return(SADiff)
|
| 136 |
}
|
| 137 |
|
| 138 |
+
# Function to scale TimStuff
|
| 139 |
scale_TimStuff <- function(raw_score, model_mean, model_sd) {
|
| 140 |
scaled_score <- (raw_score - model_mean) / model_sd
|
| 141 |
result <- 50 - (scaled_score * 10)
|
| 142 |
+
|
| 143 |
+
# Add logging
|
| 144 |
+
# cat("Raw score:", raw_score, "\n")
|
| 145 |
+
# cat("Model mean:", model_mean, "\n")
|
| 146 |
+
# cat("Model SD:", model_sd, "\n")
|
| 147 |
+
# cat("Scaled score:", scaled_score, "\n")
|
| 148 |
+
# cat("Final result:", result, "\n")
|
| 149 |
+
|
| 150 |
+
# Ensure the result is within a reasonable range
|
| 151 |
+
# result <- max(min(result, 100), 0)
|
| 152 |
+
|
| 153 |
return(result)
|
| 154 |
}
|
| 155 |
+
FB <- xgb.load('FB.model')
|
| 156 |
+
Off <- xgb.load('Off.model')
|
| 157 |
+
Break <- xgb.load('Break.model')
|
| 158 |
+
# Modify the summary_table function
|
| 159 |
summary_table <- function(game) {
|
| 160 |
rows <- nrow(game)
|
| 161 |
+
sumtable <- game |>
|
| 162 |
mutate(
|
| 163 |
+
VAA = calculate_VAA(vz0, ay, vy0, y0),
|
| 164 |
EAA = calculate_EAA(extension),
|
| 165 |
SADiff = calculate_SADiff(pfxX, pfxZ,spinDirection)
|
| 166 |
+
) |>
|
| 167 |
mutate(team_fielding_id = ifelse(description == "Called Strike" |
|
| 168 |
description == "Swinging Strike" |
|
| 169 |
+
description == "Swinging Strike (Blocked)", 1, 0)) |>
|
| 170 |
mutate(swing = ifelse(description == "Foul" |
|
| 171 |
description == "Foul Pitchout" |
|
| 172 |
description == "In play, no out" |
|
|
|
|
| 174 |
description == "In play, run(s)" |
|
| 175 |
description == "Swinging Strike" |
|
| 176 |
description == "swinging Strike (Blocked)" |
|
| 177 |
+
description == "Foul Tip", 1, 0)) |>
|
| 178 |
+
mutate(is_strike_swinging = ifelse(is_strike_swinging == TRUE, 1, 0)) |>
|
| 179 |
+
mutate(Pitch = pitch_name) |>
|
| 180 |
+
rowwise() |>
|
| 181 |
mutate(TimStuff = if (phand == 'L') {
|
| 182 |
case_when(
|
| 183 |
Pitch %in% c("Four-Seam Fastball", "Sinker", "Cutter", "Fastball") ~
|
|
|
|
| 216 |
-0.004822031, 0.007912765)),
|
| 217 |
TRUE ~ NA_real_
|
| 218 |
)
|
| 219 |
+
}) |>
|
| 220 |
+
group_by(Pitch) |>
|
| 221 |
summarize(
|
| 222 |
Pitches = n(),
|
| 223 |
'Pitch%' = round(sum(Pitches)/sum(rows) * 100, digits = 1),
|
|
|
|
| 227 |
'IVB' = round(mean(IVB, na.rm = TRUE), digits = 1),
|
| 228 |
'HB' = round(mean(HB, na.rm = TRUE), digits = 1),
|
| 229 |
'VAA' = round(mean(VAA, na.rm = TRUE), digits = 1),
|
| 230 |
+
# 'EAA' = round(mean(EAA, na.rm = TRUE), digits = 2),
|
| 231 |
+
# 'x0' = round(mean(x0, na.rm = TRUE), digits = 2),
|
| 232 |
+
# 'z0' = round(mean(z0, na.rm = TRUE), digits = 2),
|
| 233 |
+
# 'SADiff' = round(mean(SADiff, na.rm = TRUE), digits = 2),
|
| 234 |
'CSW%' = round(sum(team_fielding_id, na.rm = TRUE) / sum(!is.na(team_fielding_id)) * 100, digits = 1),
|
| 235 |
'Whiff%' = round(sum(is_strike_swinging, na.rm = TRUE) / sum(swing, na.rm = TRUE) * 100, digits = 1),
|
| 236 |
'TimStuff' = round(mean(TimStuff, na.rm = TRUE), digits = 0)
|
| 237 |
+
) |>
|
| 238 |
arrange(-Pitches)
|
| 239 |
|
| 240 |
return(sumtable)
|
| 241 |
}
|
| 242 |
|
| 243 |
+
# The rest of your code remains the same
|
| 244 |
+
|
| 245 |
mlbid <- mlb_schedule(season = 2024, level_ids = "1")
|
| 246 |
+
mlbteamH <- mlbid |>
|
| 247 |
select(teams_home_team_name)
|
| 248 |
mlbteamH <- distinct(mlbteamH)
|
| 249 |
+
mlbteamA <- mlbid |>
|
| 250 |
select(teams_away_team_name)
|
| 251 |
mlbteamA <- distinct(mlbteamA)
|
| 252 |
|
| 253 |
aaaid <- mlb_schedule(season = 2024, level_ids = "11")
|
| 254 |
+
aaateamH <- aaaid |>
|
| 255 |
select(teams_home_team_name)
|
| 256 |
aaateamH <- distinct(aaateamH)
|
| 257 |
+
aaateamA <- aaaid |>
|
| 258 |
select(teams_away_team_name)
|
| 259 |
aaateamA <- distinct(aaateamA)
|
| 260 |
|
| 261 |
fslid <- mlb_schedule(season = 2024, level_ids = "14")
|
| 262 |
+
fslid <- fslid |>
|
| 263 |
filter(teams_home_team_name == "Daytona Tortugas" |
|
| 264 |
teams_home_team_name == "Jupiter Hammerheads" |
|
| 265 |
teams_home_team_name == "Palm Beach Cardinals" |
|
|
|
|
| 270 |
teams_home_team_name == "Fort Myers Mighty Mussels" |
|
| 271 |
teams_home_team_name == "Lakeland Flying Tigers" |
|
| 272 |
teams_home_team_name == "Tampa Tarpons")
|
| 273 |
+
fslteamH <- fslid |>
|
| 274 |
select(teams_home_team_name)
|
| 275 |
fslteamH <- distinct(fslteamH)
|
| 276 |
+
fslteamA <- fslid |>
|
| 277 |
select(teams_away_team_name)
|
| 278 |
fslteamA <- distinct(fslteamA)
|
| 279 |
|
| 280 |
sbid <- mlb_schedule(season = 2024, level_ids = "22")
|
| 281 |
+
sbteamH <- sbid |>
|
| 282 |
select(teams_home_team_name)
|
| 283 |
sbteamH <- distinct(sbteamH)
|
| 284 |
+
sbteamA <- sbid |>
|
| 285 |
select(teams_away_team_name)
|
| 286 |
sbteamA <- distinct(sbteamA)
|
| 287 |
|
|
|
|
| 288 |
ui <- fluidPage(
|
| 289 |
tags$head(
|
| 290 |
tags$style(HTML("
|
|
|
|
| 309 |
actionButton("update", "Find Pitcher", icon("magnifying-glass"),
|
| 310 |
style = "color: #FFFFFF; background-color: #0077B6; width: 100%;"),
|
| 311 |
selectizeInput("pitcher", "Pitcher Name:", c(" ")),
|
| 312 |
+
#selectizeInput("league", "MLB or MiLB Photo", c("MLB", "MiLB")),
|
| 313 |
textInput("title", "Card Title"),
|
| 314 |
actionButton("update1", "Make Card", icon("plus"),
|
| 315 |
style = "color: #FFFFFF; background-color: #0077B6; width: 100%;"),
|
|
|
|
| 320 |
)
|
| 321 |
)
|
| 322 |
)
|
| 323 |
+
|
| 324 |
server <- function(input, output, session) {
|
| 325 |
observeEvent(input$level, {
|
| 326 |
if(input$level == "AAA"){
|
|
|
|
| 342 |
})
|
| 343 |
|
| 344 |
observeEvent(input$update, {
|
| 345 |
+
if(input$level == "AAA"){
|
| 346 |
+
pname <- aaaid |>
|
| 347 |
+
filter(date == input$date) |>
|
| 348 |
+
filter(teams_home_team_name == input$homeT) |>
|
| 349 |
+
filter(teams_away_team_name == input$awayT) |>
|
| 350 |
+
filter(game_number == input$gamenum)
|
| 351 |
+
pname <- tryCatch({pitcher_summary(pname[,6],input$date)},error=function(e){cat("ERROR :",conditionMessage(e), "\n")})
|
| 352 |
+
updateSelectizeInput(session = getDefaultReactiveDomain(),"pitcher","Pitcher:",choices = pname[,3])
|
| 353 |
+
}
|
| 354 |
+
if(input$level == "FSL"){
|
| 355 |
+
pname <- fslid |>
|
| 356 |
+
filter(date == input$date) |>
|
| 357 |
+
filter(teams_home_team_name == input$homeT) |>
|
| 358 |
+
filter(teams_away_team_name == input$awayT) |>
|
| 359 |
+
filter(game_number == input$gamenum)
|
| 360 |
+
pname <- tryCatch({pitcher_summary(pname[,6],input$date)},error=function(e){cat("ERROR :",conditionMessage(e), "\n")})
|
| 361 |
+
updateSelectizeInput(session = getDefaultReactiveDomain(),"pitcher","Pitcher:",choices = pname[,3])
|
| 362 |
+
}
|
| 363 |
+
if(input$level == "MLB"){
|
| 364 |
+
pname <- mlbid |>
|
| 365 |
+
filter(date == as.character.Date(input$date)) |>
|
| 366 |
+
filter(teams_home_team_name == input$homeT) |>
|
| 367 |
+
filter(teams_away_team_name == input$awayT) |>
|
| 368 |
+
filter(game_number == input$gamenum)
|
| 369 |
+
pname <- tryCatch({pitcher_summary(pname[,6],input$date)},error=function(e){cat("ERROR :",conditionMessage(e), "\n")})
|
| 370 |
+
updateSelectizeInput(session = getDefaultReactiveDomain(),"pitcher","Pitcher:",choices = pname[,3])
|
| 371 |
+
}
|
| 372 |
+
if(input$level == "College (Statcast Parks Only)"){
|
| 373 |
+
pname <- sbid |>
|
| 374 |
+
filter(date == input$date) |>
|
| 375 |
+
filter(teams_home_team_name == input$homeT) |>
|
| 376 |
+
filter(teams_away_team_name == input$awayT) |>
|
| 377 |
+
filter(game_number == input$gamenum)
|
| 378 |
+
pname <- tryCatch({pitcher_summary(pname[,6],input$date)},error=function(e){cat("ERROR :",conditionMessage(e), "\n")})
|
| 379 |
+
updateSelectizeInput(session = getDefaultReactiveDomain(),"pitcher","Pitcher:",choices = pname[,3])
|
| 380 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 381 |
})
|
| 382 |
|
| 383 |
combinedPlot <- reactiveVal()
|
| 384 |
observeEvent(input$update1, {
|
| 385 |
+
if(input$level == "MLB"){
|
| 386 |
+
id <- mlbid |>
|
| 387 |
+
filter(date == as.character.Date(input$date)) |>
|
| 388 |
+
filter(teams_home_team_name == input$homeT) |>
|
| 389 |
+
filter(teams_away_team_name == input$awayT) |>
|
| 390 |
+
filter(game_number == input$gamenum)
|
| 391 |
+
game <- tryCatch({pitcher_summary(id[,6],input$date)},error=function(e){cat("ERROR :",conditionMessage(e), "\n")})
|
| 392 |
+
game <- game |>
|
| 393 |
+
filter(`Pitcher Name` == input$pitcher)
|
| 394 |
+
id <- as.character(game[1,4])
|
| 395 |
+
}
|
| 396 |
+
if(input$level == "FSL"){
|
| 397 |
+
id <- fslid |>
|
| 398 |
+
filter(date == as.character.Date(input$date)) |>
|
| 399 |
+
filter(teams_home_team_name == input$homeT) |>
|
| 400 |
+
filter(teams_away_team_name == input$awayT) |>
|
| 401 |
+
filter(game_number == input$gamenum)
|
| 402 |
+
game <- tryCatch({pitcher_summary(id[,6],input$date)},error=function(e){cat("ERROR :",conditionMessage(e), "\n")})
|
| 403 |
+
game <- game |>
|
| 404 |
+
filter(`Pitcher Name` == input$pitcher)
|
| 405 |
+
id <- as.character(game[1,4])
|
| 406 |
+
}
|
| 407 |
+
if(input$level == "AAA"){
|
| 408 |
+
id <- aaaid |>
|
| 409 |
+
filter(date == as.character.Date(input$date)) |>
|
| 410 |
+
filter(teams_home_team_name == input$homeT) |>
|
| 411 |
+
filter(teams_away_team_name == input$awayT) |>
|
| 412 |
+
filter(game_number == input$gamenum)
|
| 413 |
+
game <- tryCatch({pitcher_summary(id[,6],input$date)},error=function(e){cat("ERROR :",conditionMessage(e), "\n")})
|
| 414 |
+
game <- game |>
|
| 415 |
+
filter(`Pitcher Name` == input$pitcher)
|
| 416 |
+
id <- as.character(game[1,4])
|
| 417 |
+
}
|
| 418 |
+
if(input$level == "College (Statcast Parks Only)"){
|
| 419 |
+
id <- sbid |>
|
| 420 |
+
filter(date == as.character.Date(input$date)) |>
|
| 421 |
+
filter(teams_home_team_name == input$homeT) |>
|
| 422 |
+
filter(teams_away_team_name == input$awayT) |>
|
| 423 |
+
filter(game_number == input$gamenum)
|
| 424 |
+
game <- tryCatch({pitcher_summary(id[,6],input$date)},error=function(e){cat("ERROR :",conditionMessage(e), "\n")})
|
| 425 |
+
game <- game |>
|
| 426 |
+
filter(`Pitcher Name` == input$pitcher)
|
| 427 |
+
id <- as.character(game[1,4])
|
| 428 |
+
}
|
| 429 |
+
|
| 430 |
+
break_plot <- break_plot(game)
|
| 431 |
+
pitch_plot <- pitch_plot(game)
|
| 432 |
+
|
| 433 |
+
# Create table plot
|
| 434 |
+
# Update the tableGrob in the observeEvent(input$update1, {...}) block
|
| 435 |
+
table_plot <- tableGrob(summary_table(game), theme = ttheme_default(
|
| 436 |
+
core = list(fg_params = list(cex = 1.7)),
|
| 437 |
+
colhead = list(fg_params = list(cex = 1.2)),
|
| 438 |
+
rowhead = list(fg_params = list(cex = 2.5))))
|
| 439 |
+
table_plot$widths[[1]] <- unit(0, "cm")
|
| 440 |
+
|
| 441 |
+
# Find the text grobs in the first column
|
| 442 |
+
text_grobs <- which(sapply(table_plot$grobs, function(g) {
|
| 443 |
+
g$name == "core-fg-1" && inherits(g$children[[1]], "text")
|
| 444 |
+
}))
|
| 445 |
+
|
| 446 |
+
# Remove the numbers from these text grobs
|
| 447 |
+
for (i in text_grobs) {
|
| 448 |
+
text <- table_plot$grobs[[i]]$children[[1]]$label
|
| 449 |
+
table_plot$grobs[[i]]$children[[1]]$label <- sub("^\\d+\\s*", "", text)
|
| 450 |
+
}
|
| 451 |
+
|
| 452 |
+
# Draw the modified table
|
| 453 |
+
grid.newpage()
|
| 454 |
+
grid.draw(table_plot)
|
| 455 |
+
# Download and process image
|
| 456 |
+
# if(input$league == "MLB"){
|
| 457 |
+
# y <- paste("https://midfield.mlbstatic.com/v1/people/","/mlb/300?circle=false",sep = id)
|
| 458 |
+
# } else {
|
| 459 |
+
# y <- paste("https://midfield.mlbstatic.com/v1/people/","/milb/300?circle=false",sep = id)
|
| 460 |
+
# }
|
| 461 |
tryCatch({
|
| 462 |
+
# Try MLB URL first
|
| 463 |
+
y <- paste0("https://midfield.mlbstatic.com/v1/people/", id, "/mlb/300?circle=false")
|
| 464 |
+
# Attempt to use the URL (replace this with your actual usage code)
|
| 465 |
+
# For example: response <- httr::GET(y)
|
| 466 |
+
# If it succeeds, it will use this URL
|
| 467 |
+
}, error = function(e) {
|
| 468 |
+
# If MLB URL fails, try MiLB URL
|
| 469 |
+
tryCatch({
|
| 470 |
+
y <- paste0("https://midfield.mlbstatic.com/v1/people/", id, "/milb/300?circle=false")
|
| 471 |
+
# Attempt to use the MiLB URL
|
| 472 |
+
# For example: response <- httr::GET(y)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 473 |
}, error = function(e) {
|
| 474 |
+
# If both fail, you can handle it here
|
| 475 |
+
stop("Both MLB and MiLB URLs failed")
|
| 476 |
})
|
| 477 |
+
})
|
| 478 |
+
photo <- tempfile()
|
| 479 |
+
download.file(y, photo, mode = 'wb')
|
| 480 |
+
img <- readPNG(photo)
|
| 481 |
+
img_grob <- rasterGrob(img, interpolate = TRUE)
|
| 482 |
+
|
| 483 |
+
# Combine all elements into one plot
|
| 484 |
+
combined <- grid.arrange(
|
| 485 |
+
arrangeGrob(table_plot, img_grob, ncol = 2, widths = c(2/3, 1/3)),
|
| 486 |
+
arrangeGrob(break_plot, pitch_plot, ncol = 2),
|
| 487 |
+
nrow = 2,
|
| 488 |
+
top = textGrob(paste(input$title, "Made by @TimStats", sep = " "), gp = gpar(fontsize = 20, font = 2))
|
| 489 |
+
)
|
| 490 |
+
|
| 491 |
+
combinedPlot(combined)
|
| 492 |
+
|
| 493 |
+
output$combinedPlot <- renderPlot({
|
| 494 |
+
grid.draw(combinedPlot())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 495 |
})
|
| 496 |
})
|
| 497 |
|
|
|
|
| 500 |
paste("baseball_card_", Sys.Date(), ".png", sep = "")
|
| 501 |
},
|
| 502 |
content = function(file) {
|
| 503 |
+
ggsave(file, plot = combinedPlot(), width = 19, height = 12, dpi = 300)
|
| 504 |
}
|
| 505 |
)
|
| 506 |
}
|
| 507 |
|
| 508 |
+
|
| 509 |
shinyApp(ui, server)
|