library(shiny) library(dplyr) library(data.table) library(xgboost) library(caret) library(shinythemes) library(ggplot2) # Add sample pitcher data - you'll want to replace this with your actual data # pitcher_data <- data.frame( # pitcher_name = c("Pitcher A", "Pitcher A", "Pitcher B", "Pitcher B"), # pitch_name = c("Fastball", "Slider", "Fastball", "Curveball"), # velo = c(95, 85, 92, 78), # ivb = c(16, 2, 15, -5), # hb = c(6, 2, 5, -8), # spinrate = c(2300, 2500, 2200, 2400), # ext = c(6.3, 6.2, 6.5, 6.4), # spinaxisdiff = c(0, 5, -2, 8), # x0 = c(-1.5, -1.6, -1.4, -1.3), # z0 = c(5, 4.8, 5.2, 5.1), # phand = c("R", "R", "L", "L") # ) model <- xgb.load('TimStuff2.ubj') calculate_EAA <- function(extension) { extension / 6.3 } calculate_SADiff <- function(pfxX, pfxZ, spinDirection) { inSA <- atan2(pfxZ, pfxX) * 180/pi + 90 inSA <- ifelse(inSA < 0, inSA + 360, inSA) SADiff <- spinDirection - inSA SADiff <- ifelse(SADiff > 180, SADiff - 360, SADiff) SADiff <- ifelse(SADiff < -180, SADiff + 360, SADiff) return(SADiff) } calculate_VAA <- function(vz0, ay, az, vy0, y0) { -atan((vz0+(az*(-sqrt((vy0*vy0)-(2*ay*(y0-(17/12))))-vy0)/ ay))/(-sqrt((vy0*vy0)-(2*ay*(y0-(17/12))))))*(180/pi) } scale_TimStuff <- function(raw_score, model_mean, model_sd) { scaled_score <- (raw_score - model_mean) / model_sd result <- 100 - (scaled_score * 10) return(result) } timstuff <- function(game) { # game <- calculate_primary(game) game <- game %>% mutate(VAA = calculate_VAA(vz0, ay, az, vy0, y0), SADiff = calculate_SADiff(pfxX, pfxZ, spinDirection), EAA = calculate_EAA(extension), #SADiff = calculate_SADiff(pfxX, pfxZ, spinDirection), team_fielding_id = ifelse(description %in% c("Called Strike", "Swinging Strike", "Swinging Strike (Blocked)"), 1, 0), swing = ifelse(description %in% c("Foul", "Foul Pitchout", "In play, no out", "In play, out(s)", "In play, run(s)", "Swinging Strike", "Swinging Strike (Blocked)", "Foul Tip"), 1, 0), is_strike_swinging = ifelse(is_strike_swinging, 1, 0), Pitch = pitch_name, ishandL = ifelse(phand == "L",1,0)) #mutate(SADiff = calculate_SADiff(pfxX, pfxZ,spinDirection)) # game <- calculate_primary(game) feature_vars <- c("ishandL","start_speed", "IVB", "HB", "EAA", "x0", "z0", "spin_rate","SADiff") complete_rows <- complete.cases(game[, feature_vars]) game_complete <- game[complete_rows, ] game_na <- game[!complete_rows,] game_na$TimStuff <- NA rhp <- game_complete # rhp <- game_complete[game_complete$ishandL == 0] # # lhp$TimStuff <- scale_TimStuff(predict(model, as.matrix(cbind(lhp$ishandL,lhp$start_speed, lhp$IVB, lhp$HB, lhp$EAA, lhp$x0, lhp$z0, lhp$spin_rate, lhp$SADiff,lhp$primary_speed,lhp$primary_IVB,lhp$primary_HB))), -0.00249975, 0.007566558) rhp$TimStuff <- scale_TimStuff(predict(model, as.matrix(cbind(rhp$ishandL,rhp$start_speed, rhp$IVB, rhp$HB, rhp$EAA, rhp$x0, rhp$z0, rhp$spin_rate, rhp$SADiff))), -0.002620635, 0.006021368) game_complete <- rbind(rhp,game_na) return(game_complete) } download_private_parquet <- function(repo_id, filename) { library(httr) library(arrow) # Create the direct download URL based on your example url <- paste0("https://huggingface.co/datasets/", repo_id, "/resolve/main/", filename, "?download=true") # Create a temporary file temp_file <- tempfile(fileext = ".parquet") # Download directly to file response <- GET( url, add_headers(Authorization = paste("Bearer", Sys.getenv("GETCSV"))), write_disk(temp_file, overwrite = TRUE) ) # Check if download was successful if (status_code(response) == 200) { tryCatch({ # Read the parquet file data <- read_parquet(temp_file) file.remove(temp_file) return(data) }, error = function(e) { file.remove(temp_file) stop(paste("Error reading parquet file:", e$message)) }) } else { file.remove(temp_file) stop(paste("Failed to download file. Status code:", status_code(response))) } } MLB24T <- download_private_parquet("TimStats/StatcastDataAll", "MLB26.parquet") MLB24T <- timstuff(MLB24T) pitcher_data <- MLB24T %>% group_by(`Pitcher Name`,pitch_name,phand) %>% summarise( "start_speed" = mean(start_speed,na.rm = TRUE), "IVB" = mean(IVB,na.rm = TRUE), "HB" = mean(HB,na.rm = TRUE) , "EAA" = mean(EAA,na.rm = TRUE) , "x0" = mean(x0,na.rm = TRUE) , "z0" = mean(z0, na.rm = TRUE) , "spin_rate" = mean(spin_rate,na.rm = TRUE), "SADiff" = mean(SADiff,na.rm = TRUE) ) calculate_timstuff <- function(data) { data_matrix <- as.matrix(data) mod <- xgb.load('TimStuff2.ubj') prediction <- predict(mod, data_matrix) timstuff <- (100 - (prediction - -0.002620635) / 0.006021368 * 10) return(timstuff) } ui <- fluidPage( theme = shinytheme("flatly"), titlePanel("TimStuff+ Calculator"), sidebarLayout( sidebarPanel( width = 4, # Add pitcher selection selectInput("pitcher_select", "Select Pitcher", choices = c("Custom Input", unique(pitcher_data$`Pitcher Name`))), # Add pitch type selection - will be dynamically updated based on pitcher conditionalPanel( condition = "input.pitcher_select != 'Custom Input'", selectInput("pitch_select", "Select Pitch Type", choices = NULL) # Will be updated in server ), # Original inputs wrapped in conditional panel conditionalPanel( condition = "input.pitcher_select == 'Custom Input'", radioButtons("phand", "Hand", choices = c("R", "L"), inline = TRUE), numericInput("velo", "Velocity (mph)", value = 95, min = 70, max = 110, step = 1), numericInput("ivb", "Induced Vertical Break (in.) (Pitcher's Perspective)", value = 16, min = -30, max = 30, step = 0.1), numericInput("hb", "Horizontal Break (in.) (Pitcher's Perspective)", value = 6, min = -25, max = 25, step = 0.1), numericInput("spinrate", "Spin Rate (rpm)", value = 2300, min = 1000, max = 3500, step = 10), numericInput("ext", "Extension (ft)", value = 6.3, min = 5, max = 8, step = 0.1), numericInput("spinaxisdiff", "Spin Axis Difference (°)", value = 0, min = -180, max = 180, step = 1), numericInput("x0", "Horizontal Release Point (ft)", value = -1.5, min = -5, max = 5, step = 0.1), numericInput("z0", "Vertical Release Point (ft)", value = 5, min = 3, max = 8, step = 0.1) ), selectInput("feature_to_vary", "Select Feature to Vary:", choices = c("Velocity" = "start_speed", "Induced Vertical Break" = "IVB", "Horizontal Break" = "HB", "Spin Rate" = "spin_rate", "Extension" = "EAA", "Spin Axis Difference" = "SADiff", "Horz. Release Point" = "x0")) ), mainPanel( width = 8, wellPanel( h3("Results"), verbatimTextOutput("stuff"), plotOutput("feature_impact", height = "400px") ) ) ) ) server <- function(input, output, session) { # Update pitch selections based on pitcher observe({ if(input$pitcher_select != "Custom Input") { pitcher_pitches <- pitcher_data %>% filter(`Pitcher Name` == input$pitcher_select) %>% pull(pitch_name) %>% unique() updateSelectInput(session, "pitch_select", choices = pitcher_pitches) } }) # Reactive values for all inputs reactive_data <- reactive({ if(input$pitcher_select == "Custom Input") { # Use manual inputs data.frame( ishandL = ifelse(input$phand == "L", 1, 0), start_speed = input$velo, IVB = input$ivb, HB = input$hb, EAA = input$ext / 6.3, x0 = input$x0, z0 = input$z0, spin_rate = input$spinrate, SADiff = input$spinaxisdiff ) } else { # Use selected pitcher data pitch_data <- pitcher_data %>% filter(`Pitcher Name` == input$pitcher_select, pitch_name == input$pitch_select) %>% select(-`Pitcher Name`, -pitch_name) data.frame( ishandL = ifelse(pitch_data$phand == "L", 1, 0), start_speed = pitch_data$start_speed, IVB = pitch_data$IVB, HB = pitch_data$HB, EAA = pitch_data$EAA, x0 = pitch_data$x0, z0 = pitch_data$z0, spin_rate = pitch_data$spin_rate, SADiff = pitch_data$SADiff ) } }) output$stuff <- renderText({ data <- reactive_data() TimStuff <- calculate_timstuff(data) paste("TimStuff+:", round(TimStuff, 2), "\nTimStuff+ scale is 100 is Average, 1 SD is 10") }) feature_range <- reactive({ ranges <- list( start_speed = c(70, 110), IVB = c(-20, 30), HB = c(-20, 20), spin_rate = c(1000, 3500), EAA = c(5/6.3, 8/6.3), SADiff = c(-180, 180), x0 = c(-4,4) ) ranges[[input$feature_to_vary]] }) output$feature_impact <- renderPlot({ base_data <- reactive_data() feature_seq <- seq(feature_range()[1], feature_range()[2], length.out = 100) varied_data <- do.call(rbind, replicate(100, base_data, simplify = FALSE)) varied_data[[input$feature_to_vary]] <- feature_seq timstuff <- calculate_timstuff(varied_data) plot_data <- data.frame( Feature = names(base_data)[which(names(base_data) == input$feature_to_vary)], Value = feature_seq, TimStuff = timstuff ) ggplot(plot_data, aes(x = Value, y = TimStuff)) + geom_line(color = "blue", size = 1) + geom_point(aes(x = base_data[[input$feature_to_vary]], y = calculate_timstuff(base_data)), color = "red", size = 3) + theme_minimal() + ylim(50,150) + labs(title = paste("Impact of", input$feature_to_vary, "on TimStuff"), x = input$feature_to_vary, y = "TimStuff") + theme(text = element_text(size = 14)) }) } shinyApp(ui = ui, server = server)