TimStats's picture
Update app.R
9913517 verified
Raw
History Blame Contribute Delete
10.3 kB
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)