TimStats's picture
Update app.R
7e1e22e verified
Raw
History Blame
12.2 kB
library(shiny)
library(tidyverse)
data1 <- read.csv("data20-24.csv", header = TRUE, check.names = FALSE, fileEncoding = "UTF-8")
AAaveragePT <- data1 %>%
mutate(arm_angle = round(arm_angle, digits = 0)) %>%
group_by(arm_angle, phand, pitch_name) %>%
summarise(
AvgIVB = mean(IVB, na.rm = TRUE),
AvgHB = mean(HB, na.rm = TRUE),
.groups = 'drop'
)
break_plot_Szn <- function(game,data1,sdate,edate) {
game <- game %>% left_join(pitch_type_lookup, by = c("pitch_name"))
title <- paste0(unique(game$`Pitcher Name`)," ",sdate," to ",edate)
# Get pitcher's handedness
pitcher_hand <- unique(game$phand)
angle_degrees <- round(mean(game$arm_angle,na.rm = TRUE),digits = 1)
# Calculate average movement by arm angle and pitch type from season data
# AAaveragePT <- data1 %>%
# mutate(arm_angle = round(arm_angle, digits = 0)) %>%
# group_by(arm_angle, phand, pitch_name) %>%
# summarise(
# AvgIVB = mean(IVB, na.rm = TRUE),
# AvgHB = mean(HB, na.rm = TRUE),
# .groups = 'drop'
# )
pitch_colors <- c(
"FF" = "#FF4136",
"SI" = "#FF851B",
"FC" = "#FFDC00",
"CH" = "#2ECC40",
"SL" = "#0074D9",
"ST" = "#ED68ED",
"CU" = "#B10DC9",
"FS" = "#01FF70",
"KC" = "#85144b",
"SV" = "#3D9970",
"KN" = "#39CCCC",
"FO" = "#F012BE",
"EP" = "#AAAAAA",
"FA" = "#7FDBFF",
"SC" = "#FF69B4"
)
# Convert angle to radians
angle_radians <- angle_degrees * (pi / 180)
# Legend location based on handedness
if(pitcher_hand == "L") {
leg <- c(0.08, .22)
factor <- -1
} else {
leg <- c(0.92, .22)
factor <- 1
}
# Calculate the endpoint coordinates
x_end <- 50 * cos(angle_radians) * factor
y_end <- 50 * sin(angle_radians)
avg_locations <- game %>%
group_by(pitch_abbr) %>%
summarize(
avg_HB = mean(HB, na.rm = TRUE),
avg_IVB = mean(IVB, na.rm = TRUE)
)
# Get unique pitch types from the game data
game_pitches <- unique(game$pitch_abbr)
# Filter season data for the current arm angle, handedness, and only pitches in the game
arm_angle_data <- data1 %>%
filter(abs(round(arm_angle) - angle_degrees) <= 2,
phand == pitcher_hand) %>%
left_join(pitch_type_lookup, by = c("pitch_name")) %>%
filter(pitch_abbr %in% game_pitches)
ggplot(game, aes(x = HB, y = IVB)) +
# Base layers
geom_vline(xintercept = 0, color = "lightblue", linewidth = 1, linetype = 4) +
geom_hline(yintercept = 0, color = "lightblue", linewidth = 1, linetype = 4) +
# Add ellipses from season data for this arm angle
stat_ellipse(
data = arm_angle_data,
aes(x = HB, y = IVB, fill = pitch_abbr),
geom = "polygon",
alpha = 0.2,
level = 0.68,
show.legend = FALSE
) +
# Individual pitch points from the game
# geom_point(aes(fill = pitch_abbr), color = "#c6c6c6", size = 3, stroke = .5, shape = 21) +
# # Average location points from season data
# geom_point(
# data = AAaveragePT %>%
# filter(arm_angle == angle_degrees,
# phand == pitcher_hand) %>%
# left_join(pitch_type_lookup, by = c("pitch_name")) %>%
# filter(pitch_abbr %in% game_pitches),
# aes(x = AvgHB, y = AvgIVB, fill = pitch_abbr),
# color = "black",
# size = 6,
# stroke = .5,
# shape = 21
# ) +
geom_point(data = avg_locations, aes(x = avg_HB, y = avg_IVB, fill = pitch_abbr),
color = "black", size = 6, stroke = .5, shape = 21) +
# Arm angle line
geom_segment(x = 0, y = 0, xend = x_end, yend = y_end, color = "red", linewidth = 1, linetype = 5) +
# Aesthetics
scale_fill_manual(values = pitch_colors) +
scale_color_manual(values = pitch_colors) +
labs(
x = "Horizontal Break (in)",
y = "Induced Vertical Break (in)",
title = title,
subtitle = paste0("Arm Angle: ", angle_degrees, "\u00b0"),
caption = "Data: MLB | Viz: @TimStats\nEllipses show 1σ of movement for same-handed pitchers at similar arm angles"
) +
xlim(-25, 25) +
ylim(-25, 25) +
theme_minimal() +
theme(
legend.position = leg,
plot.title = element_text(hjust = 0.5, face = "bold", color = "white"),
plot.subtitle = element_text(hjust = 0.5, face = "italic", color = "white"),
aspect.ratio = 1,
plot.background = element_rect(fill = "#333333", color = NA),
panel.background = element_rect(fill = "#333333", color = NA),
axis.text = element_text(color = "white"),
axis.title = element_text(color = "white"),
legend.background = element_rect(fill = "#333333"),
legend.text = element_text(color = "white"),
legend.title = element_blank(),
plot.margin = margin(10, 5, 10, 5),
axis.line = element_blank(),
axis.ticks = element_line(color = "white"),
panel.grid = element_blank(),
legend.key = element_blank(),
plot.caption = element_text(
color = "white",
hjust = 0.5
),
panel.border = element_blank()
)
}
break_plot_tot <- function(game,data1,sdate,edate) {
game <- game %>% left_join(pitch_type_lookup, by = c("pitch_name"))
title <- paste0(unique(game$`Pitcher Name`)," ",sdate," to ",edate)
# Get pitcher's handedness
pitcher_hand <- unique(game$phand)
angle_degrees <- round(mean(game$arm_angle,na.rm = TRUE),digits = 1)
# Calculate average movement by arm angle and pitch type from season data
# AAaveragePT <- data1 %>%
# mutate(arm_angle = round(arm_angle, digits = 0)) %>%
# group_by(arm_angle, phand, pitch_name) %>%
# summarise(
# AvgIVB = mean(IVB, na.rm = TRUE),
# AvgHB = mean(HB, na.rm = TRUE),
# .groups = 'drop'
# )
pitch_colors <- c(
"FF" = "#FF4136",
"SI" = "#FF851B",
"FC" = "#FFDC00",
"CH" = "#2ECC40",
"SL" = "#0074D9",
"ST" = "#ED68ED",
"CU" = "#B10DC9",
"FS" = "#01FF70",
"KC" = "#85144b",
"SV" = "#3D9970",
"KN" = "#39CCCC",
"FO" = "#F012BE",
"EP" = "#AAAAAA",
"FA" = "#7FDBFF",
"SC" = "#FF69B4"
)
# Convert angle to radians
angle_radians <- angle_degrees * (pi / 180)
# Legend location based on handedness
if(pitcher_hand == "L") {
leg <- c(0.08, .22)
factor <- -1
} else {
leg <- c(0.92, .22)
factor <- 1
}
# Calculate the endpoint coordinates
x_end <- 50 * cos(angle_radians) * factor
y_end <- 50 * sin(angle_radians)
avg_locations <- game %>%
group_by(pitch_abbr) %>%
summarize(
avg_HB = mean(HB, na.rm = TRUE),
avg_IVB = mean(IVB, na.rm = TRUE)
)
# Get unique pitch types from the game data
game_pitches <- unique(game$pitch_abbr)
# Filter season data for the current arm angle, handedness, and only pitches in the game
arm_angle_data <- data1 %>%
filter(abs(round(arm_angle) - angle_degrees) <= 2,
phand == pitcher_hand) %>%
left_join(pitch_type_lookup, by = c("pitch_name")) %>%
filter(pitch_abbr %in% game_pitches)
ggplot(game, aes(x = HB, y = IVB)) +
# Base layers
geom_vline(xintercept = 0, color = "lightblue", linewidth = 1, linetype = 4) +
geom_hline(yintercept = 0, color = "lightblue", linewidth = 1, linetype = 4) +
# Add ellipses from season data for this arm angle
stat_ellipse(
data = arm_angle_data,
aes(x = HB, y = IVB, fill = pitch_abbr),
geom = "polygon",
alpha = 0.2,
level = 0.68,
show.legend = FALSE
) +
# Individual pitch points from the game
geom_point(aes(fill = pitch_abbr), color = "#c6c6c6", size = 3, stroke = .5, shape = 21) +
# # Average location points from season data
# geom_point(
# data = AAaveragePT %>%
# filter(arm_angle == angle_degrees,
# phand == pitcher_hand) %>%
# left_join(pitch_type_lookup, by = c("pitch_name")) %>%
# filter(pitch_abbr %in% game_pitches),
# aes(x = AvgHB, y = AvgIVB, fill = pitch_abbr),
# color = "black",
# size = 6,
# stroke = .5,
# shape = 21
# ) +
geom_point(data = avg_locations, aes(x = avg_HB, y = avg_IVB, fill = pitch_abbr),
color = "black", size = 6, stroke = .5, shape = 21) +
# Arm angle line
geom_segment(x = 0, y = 0, xend = x_end, yend = y_end, color = "red", linewidth = 1, linetype = 5) +
# Aesthetics
scale_fill_manual(values = pitch_colors) +
scale_color_manual(values = pitch_colors) +
labs(
x = "Horizontal Break (in)",
y = "Induced Vertical Break (in)",
title = title,
subtitle = paste0("Arm Angle: ", angle_degrees, "\u00b0"),
caption = "Data: MLB | Viz: @TimStats\nEllipses show 1σ of movement for same-handed pitchers at similar arm angles"
) +
xlim(-25, 25) +
ylim(-25, 25) +
theme_minimal() +
theme(
legend.position = leg,
plot.title = element_text(hjust = 0.5, face = "bold", color = "white"),
plot.subtitle = element_text(hjust = 0.5, face = "italic", color = "white"),
aspect.ratio = 1,
plot.background = element_rect(fill = "#333333", color = NA),
panel.background = element_rect(fill = "#333333", color = NA),
axis.text = element_text(color = "white"),
axis.title = element_text(color = "white"),
legend.background = element_rect(fill = "#333333"),
legend.text = element_text(color = "white"),
legend.title = element_blank(),
plot.margin = margin(10, 5, 10, 5),
axis.line = element_blank(),
axis.ticks = element_line(color = "white"),
panel.grid = element_blank(),
legend.key = element_blank(),
plot.caption = element_text(
color = "white",
hjust = 0.5
),
panel.border = element_blank()
)
}
ui <- fluidPage(
# Application title
titlePanel("2020-2024 MLB Pitch Plots"),
sidebarLayout(
sidebarPanel(
width = 3,
selectInput("player", "Select Player:", choices = unique(data1$`Pitcher Name`),
width = "100%"),
dateRangeInput("date1", "Date Range:",
start = "2024-02-09",
end = "2024-09-05",
width = "100%"),
radioButtons("type", "Plot Type",
choices = c("Season Average","All Pitches")),
actionButton("submit", "Update Plot",
class = "btn btn-primary btn-block",
style = "margin-bottom: 10px"),
downloadButton("download", "Download Plot",
class = "btn btn-success btn-block")
),
mainPanel(
width = 9,
plotOutput("Plot")
)
)
)
# Define server
server <- function(input, output) {
filtered_data <- eventReactive(input$submit, {
data1 %>%
filter(`Pitcher Name` == input$player,
between(as.Date(date), input$date1[1], input$date1[2]))
})
current_plot <- reactive({
game <- filtered_data()
season_data <- data1
if(nrow(game) == 0) {
return(ggplot() +
annotate("text", x = 0.5, y = 0.5,
label = "No data available for selected date range",
color = "white") +
theme_void() +
theme(plot.background = element_rect(fill = "#333333", color = NA)))
}
if(input$type == "Season Average"){
break_plot_Szn(game, season_data, input$date1[1], input$date1[2])
} else{
break_plot_tot(game, season_data, input$date1[1], input$date1[2])
}
})
output$Plot <- renderPlot({
current_plot()
}, width = 1000, height = 1000)
output$download <- downloadHandler(
filename = function() {
paste0(
gsub(" ", "_", input$player), "_",
format(input$date1[1], "%Y%m%d"), "_to_",
format(input$date1[2], "%Y%m%d"), ".png"
)
},
content = function(file) {
ggsave(file,
plot = current_plot(),
width = 7,
height = 7,
dpi = 300,
bg = "#333333")
}
)
}
# Run the application
shinyApp(ui = ui, server = server)