Spaces:
Runtime error
Runtime error
| 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) |