Spaces:
Running
Running
Update app.R
Browse files
app.R
CHANGED
|
@@ -498,46 +498,47 @@ server <- function(input, output, session) {
|
|
| 498 |
})
|
| 499 |
|
| 500 |
# Processing summary
|
| 501 |
-
output$process_summary <- renderText({
|
| 502 |
-
|
| 503 |
-
|
| 504 |
-
|
| 505 |
-
|
| 506 |
-
|
| 507 |
-
|
| 508 |
-
|
| 509 |
-
|
| 510 |
-
|
| 511 |
-
|
| 512 |
-
|
| 513 |
-
|
| 514 |
-
|
| 515 |
-
|
| 516 |
-
|
| 517 |
-
|
| 518 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 519 |
} else {
|
| 520 |
-
|
| 521 |
}
|
| 522 |
-
|
| 523 |
-
|
| 524 |
-
|
| 525 |
-
|
| 526 |
-
|
| 527 |
-
|
| 528 |
-
|
| 529 |
-
|
| 530 |
-
|
| 531 |
-
|
| 532 |
-
|
| 533 |
-
|
| 534 |
-
|
| 535 |
-
|
| 536 |
-
|
| 537 |
-
)
|
| 538 |
|
| 539 |
-
return(summary_text)
|
| 540 |
-
})
|
| 541 |
# Preview table
|
| 542 |
output$preview <- DT::renderDataTable({
|
| 543 |
req(processed_data())
|
|
@@ -635,6 +636,10 @@ output$process_summary <- renderText({
|
|
| 635 |
if (min(distances) <= 2) {
|
| 636 |
clicked_pitch <- pitcher_data[closest_idx, ]
|
| 637 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 638 |
# Store the original row index in the full dataset
|
| 639 |
full_data <- plot_data() %>% filter(Pitcher == input$pitcher_select)
|
| 640 |
original_row <- which(full_data$HorzBreak == clicked_pitch$HorzBreak &
|
|
@@ -654,15 +659,6 @@ output$process_summary <- renderText({
|
|
| 654 |
|
| 655 |
# Show modal
|
| 656 |
toggleModal(session, "pitchEditModal", toggle = "open")
|
| 657 |
-
|
| 658 |
-
|
| 659 |
-
if (min(distances) <= 2) {
|
| 660 |
-
clicked_pitch <- pitcher_data[closest_idx, ]
|
| 661 |
-
|
| 662 |
-
# DEBUG: Print to console
|
| 663 |
-
print("Pitch selected!")
|
| 664 |
-
print(clicked_pitch$TaggedPitchType)
|
| 665 |
-
|
| 666 |
}
|
| 667 |
})
|
| 668 |
|
|
@@ -781,97 +777,99 @@ output$process_summary <- renderText({
|
|
| 781 |
}
|
| 782 |
})
|
| 783 |
|
| 784 |
-
|
| 785 |
-
|
| 786 |
-
|
| 787 |
-
data <- plot_data()
|
| 788 |
-
|
| 789 |
-
movement_stats <- data %>%
|
| 790 |
-
filter(Pitcher == input$pitcher_select) %>%
|
| 791 |
-
filter(!is.na(HorzBreak), !is.na(InducedVertBreak), !is.na(TaggedPitchType)) %>%
|
| 792 |
-
mutate(
|
| 793 |
-
pitch_group = case_when(
|
| 794 |
-
TaggedPitchType %in% c("Fastball", "FourSeamFastBall", "FourSeamFastB", "Four-Seam", "4-Seam") ~ "Fastball",
|
| 795 |
-
TaggedPitchType %in% c("OneSeamFastBall", "TwoSeamFastBall", "Sinker", "Two-Seam", "One-Seam") ~ "Sinker",
|
| 796 |
-
TaggedPitchType %in% c("ChangeUp", "Changeup") ~ "Changeup",
|
| 797 |
-
TRUE ~ TaggedPitchType
|
| 798 |
-
),
|
| 799 |
-
# Create necessary indicator variables if they don't exist
|
| 800 |
-
in_zone = ifelse("StrikeZoneIndicator" %in% names(.), StrikeZoneIndicator,
|
| 801 |
-
ifelse(!is.na(PlateLocSide) & !is.na(PlateLocHeight) &
|
| 802 |
-
PlateLocSide >= -0.95 & PlateLocSide <= 0.95 &
|
| 803 |
-
PlateLocHeight >= 1.6 & PlateLocHeight <= 3.5, 1, 0)),
|
| 804 |
-
is_whiff = ifelse("WhiffIndicator" %in% names(.), WhiffIndicator,
|
| 805 |
-
ifelse(!is.na(PitchCall) & PitchCall == "StrikeSwinging", 1, 0)),
|
| 806 |
-
chase = ifelse("Chaseindicator" %in% names(.), Chaseindicator,
|
| 807 |
-
ifelse(!is.na(PitchCall) & !is.na(PlateLocSide) & !is.na(PlateLocHeight) &
|
| 808 |
-
PitchCall %in% c("StrikeSwinging", "FoulBallNotFieldable", "FoulBall", "InPlay") &
|
| 809 |
-
(PlateLocSide < -0.95 | PlateLocSide > 0.95 | PlateLocHeight < 1.6 | PlateLocHeight > 3.5), 1, 0))
|
| 810 |
-
)
|
| 811 |
-
|
| 812 |
-
# Calculate total pitches for usage percentage
|
| 813 |
-
total_pitches <- nrow(movement_stats)
|
| 814 |
-
|
| 815 |
-
summary_stats <- movement_stats %>%
|
| 816 |
-
group_by(`Pitch Type` = pitch_group) %>%
|
| 817 |
-
summarise(
|
| 818 |
-
Count = n(),
|
| 819 |
-
`Usage%` = sprintf("%.1f%%", (n() / total_pitches) * 100),
|
| 820 |
-
`Ext.` = ifelse("Extension" %in% names(movement_stats),
|
| 821 |
-
sprintf("%.1f", mean(Extension, na.rm = TRUE)),
|
| 822 |
-
"β"),
|
| 823 |
-
`Avg Velo` = sprintf("%.1f mph", mean(RelSpeed, na.rm = TRUE)),
|
| 824 |
-
`90th Velo` = sprintf("%.1f mph", quantile(RelSpeed, 0.9, na.rm = TRUE)),
|
| 825 |
-
`Max Velo` = sprintf("%.1f mph", max(RelSpeed, na.rm = TRUE)),
|
| 826 |
-
`Avg IVB` = sprintf("%.1f in", mean(InducedVertBreak, na.rm = TRUE)),
|
| 827 |
-
`Avg HB` = sprintf("%.1f in", mean(HorzBreak, na.rm = TRUE)),
|
| 828 |
-
`Avg Spin` = ifelse("SpinRate" %in% names(movement_stats),
|
| 829 |
-
sprintf("%.0f rpm", mean(SpinRate, na.rm = TRUE)),
|
| 830 |
-
"β"),
|
| 831 |
-
`Rel Height` = ifelse("RelHeight" %in% names(movement_stats),
|
| 832 |
-
sprintf("%.1f", mean(RelHeight, na.rm = TRUE)),
|
| 833 |
-
"β"),
|
| 834 |
-
`Zone%` = sprintf("%.1f%%", round(mean(in_zone, na.rm = TRUE) * 100, 1)),
|
| 835 |
-
`Whiff%` = sprintf("%.1f%%", round(mean(is_whiff, na.rm = TRUE) * 100, 1)),
|
| 836 |
-
`Chase%` = sprintf("%.1f%%", round(mean(chase, na.rm = TRUE) * 100, 1)),
|
| 837 |
-
.groups = "drop"
|
| 838 |
-
) %>%
|
| 839 |
-
arrange(desc(Count))
|
| 840 |
-
|
| 841 |
-
DT::datatable(summary_stats,
|
| 842 |
-
options = list(pageLength = 15, dom = 't', scrollX = TRUE),
|
| 843 |
-
rownames = FALSE) %>%
|
| 844 |
-
DT::formatStyle(columns = names(summary_stats), fontSize = '12px')
|
| 845 |
-
})
|
| 846 |
-
|
| 847 |
-
output$selected_pitch_info <- renderText({
|
| 848 |
-
pitch_info <- selected_pitch()
|
| 849 |
-
if (!is.null(pitch_info)) {
|
| 850 |
-
pitch_data <- pitch_info$data
|
| 851 |
-
|
| 852 |
-
# Build info text with only available fields
|
| 853 |
-
info_lines <- c(
|
| 854 |
-
paste("Pitcher:", pitch_info$pitcher),
|
| 855 |
-
paste("Current Type:", pitch_data$TaggedPitchType),
|
| 856 |
-
paste("Velocity:", round(pitch_data$RelSpeed, 1), "mph"),
|
| 857 |
-
paste("Horizontal Break:", round(pitch_data$HorzBreak, 1), "inches"),
|
| 858 |
-
paste("Induced Vertical Break:", round(pitch_data$InducedVertBreak, 1), "inches")
|
| 859 |
-
)
|
| 860 |
|
| 861 |
-
|
| 862 |
-
if ("SpinRate" %in% names(pitch_data) && !is.na(pitch_data$SpinRate)) {
|
| 863 |
-
info_lines <- c(info_lines, paste("Spin Rate:", round(pitch_data$SpinRate, 0), "rpm"))
|
| 864 |
-
}
|
| 865 |
|
| 866 |
-
|
| 867 |
-
|
| 868 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 869 |
|
| 870 |
-
|
| 871 |
-
|
| 872 |
-
|
| 873 |
-
|
| 874 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 875 |
|
| 876 |
# Update pitch type
|
| 877 |
observeEvent(input$update_pitch, {
|
|
|
|
| 498 |
})
|
| 499 |
|
| 500 |
# Processing summary
|
| 501 |
+
output$process_summary <- renderText({
|
| 502 |
+
if (is.null(input$file)) {
|
| 503 |
+
return("No file uploaded yet.")
|
| 504 |
+
}
|
| 505 |
+
|
| 506 |
+
if (is.null(processed_data())) {
|
| 507 |
+
return("Processing...")
|
| 508 |
+
}
|
| 509 |
+
|
| 510 |
+
df <- processed_data()
|
| 511 |
+
original_df <- read.csv(input$file$datapath, nrows = 1)
|
| 512 |
+
selected_cols_to_remove <- input$columns_to_remove %||% character(0)
|
| 513 |
+
removed_cols <- intersect(selected_cols_to_remove, names(original_df))
|
| 514 |
+
|
| 515 |
+
# Build the removed columns text
|
| 516 |
+
removed_cols_text <- if (length(removed_cols) > 0) {
|
| 517 |
+
cols_display <- if (length(removed_cols) > 5) {
|
| 518 |
+
paste(paste(head(removed_cols, 5), collapse = ", "), "...")
|
| 519 |
+
} else {
|
| 520 |
+
paste(removed_cols, collapse = ", ")
|
| 521 |
+
}
|
| 522 |
+
paste("β Removed columns:", length(removed_cols), "\n -", cols_display)
|
| 523 |
} else {
|
| 524 |
+
"β Removed columns: 0"
|
| 525 |
}
|
| 526 |
+
|
| 527 |
+
summary_text <- paste(
|
| 528 |
+
"β File processed successfully!",
|
| 529 |
+
paste("β Original columns:", ncol(original_df)),
|
| 530 |
+
paste("β Final columns:", ncol(df)),
|
| 531 |
+
paste("β Target columns: 167"),
|
| 532 |
+
paste("β Rows processed:", nrow(df)),
|
| 533 |
+
removed_cols_text,
|
| 534 |
+
"β Duplicates removed",
|
| 535 |
+
paste("β Ready for further processing"),
|
| 536 |
+
sep = "\n"
|
| 537 |
+
)
|
| 538 |
+
|
| 539 |
+
return(summary_text)
|
| 540 |
+
})
|
|
|
|
| 541 |
|
|
|
|
|
|
|
| 542 |
# Preview table
|
| 543 |
output$preview <- DT::renderDataTable({
|
| 544 |
req(processed_data())
|
|
|
|
| 636 |
if (min(distances) <= 2) {
|
| 637 |
clicked_pitch <- pitcher_data[closest_idx, ]
|
| 638 |
|
| 639 |
+
# DEBUG: Print to console
|
| 640 |
+
print("Pitch selected!")
|
| 641 |
+
print(paste("Type:", clicked_pitch$TaggedPitchType))
|
| 642 |
+
|
| 643 |
# Store the original row index in the full dataset
|
| 644 |
full_data <- plot_data() %>% filter(Pitcher == input$pitcher_select)
|
| 645 |
original_row <- which(full_data$HorzBreak == clicked_pitch$HorzBreak &
|
|
|
|
| 659 |
|
| 660 |
# Show modal
|
| 661 |
toggleModal(session, "pitchEditModal", toggle = "open")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 662 |
}
|
| 663 |
})
|
| 664 |
|
|
|
|
| 777 |
}
|
| 778 |
})
|
| 779 |
|
| 780 |
+
# Movement stats table
|
| 781 |
+
output$movement_stats <- DT::renderDataTable({
|
| 782 |
+
req(plot_data(), input$pitcher_select)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 783 |
|
| 784 |
+
data <- plot_data()
|
|
|
|
|
|
|
|
|
|
| 785 |
|
| 786 |
+
movement_stats <- data %>%
|
| 787 |
+
filter(Pitcher == input$pitcher_select) %>%
|
| 788 |
+
filter(!is.na(HorzBreak), !is.na(InducedVertBreak), !is.na(TaggedPitchType)) %>%
|
| 789 |
+
mutate(
|
| 790 |
+
pitch_group = case_when(
|
| 791 |
+
TaggedPitchType %in% c("Fastball", "FourSeamFastBall", "FourSeamFastB", "Four-Seam", "4-Seam") ~ "Fastball",
|
| 792 |
+
TaggedPitchType %in% c("OneSeamFastBall", "TwoSeamFastBall", "Sinker", "Two-Seam", "One-Seam") ~ "Sinker",
|
| 793 |
+
TaggedPitchType %in% c("ChangeUp", "Changeup") ~ "Changeup",
|
| 794 |
+
TRUE ~ TaggedPitchType
|
| 795 |
+
),
|
| 796 |
+
# Create necessary indicator variables if they don't exist
|
| 797 |
+
in_zone = ifelse("StrikeZoneIndicator" %in% names(.), StrikeZoneIndicator,
|
| 798 |
+
ifelse(!is.na(PlateLocSide) & !is.na(PlateLocHeight) &
|
| 799 |
+
PlateLocSide >= -0.95 & PlateLocSide <= 0.95 &
|
| 800 |
+
PlateLocHeight >= 1.6 & PlateLocHeight <= 3.5, 1, 0)),
|
| 801 |
+
is_whiff = ifelse("WhiffIndicator" %in% names(.), WhiffIndicator,
|
| 802 |
+
ifelse(!is.na(PitchCall) & PitchCall == "StrikeSwinging", 1, 0)),
|
| 803 |
+
chase = ifelse("Chaseindicator" %in% names(.), Chaseindicator,
|
| 804 |
+
ifelse(!is.na(PitchCall) & !is.na(PlateLocSide) & !is.na(PlateLocHeight) &
|
| 805 |
+
PitchCall %in% c("StrikeSwinging", "FoulBallNotFieldable", "FoulBall", "InPlay") &
|
| 806 |
+
(PlateLocSide < -0.95 | PlateLocSide > 0.95 | PlateLocHeight < 1.6 | PlateLocHeight > 3.5), 1, 0))
|
| 807 |
+
)
|
| 808 |
|
| 809 |
+
# Calculate total pitches for usage percentage
|
| 810 |
+
total_pitches <- nrow(movement_stats)
|
| 811 |
+
|
| 812 |
+
summary_stats <- movement_stats %>%
|
| 813 |
+
group_by(`Pitch Type` = pitch_group) %>%
|
| 814 |
+
summarise(
|
| 815 |
+
Count = n(),
|
| 816 |
+
`Usage%` = sprintf("%.1f%%", (n() / total_pitches) * 100),
|
| 817 |
+
`Ext.` = ifelse("Extension" %in% names(movement_stats),
|
| 818 |
+
sprintf("%.1f", mean(Extension, na.rm = TRUE)),
|
| 819 |
+
"β"),
|
| 820 |
+
`Avg Velo` = sprintf("%.1f mph", mean(RelSpeed, na.rm = TRUE)),
|
| 821 |
+
`90th Velo` = sprintf("%.1f mph", quantile(RelSpeed, 0.9, na.rm = TRUE)),
|
| 822 |
+
`Max Velo` = sprintf("%.1f mph", max(RelSpeed, na.rm = TRUE)),
|
| 823 |
+
`Avg IVB` = sprintf("%.1f in", mean(InducedVertBreak, na.rm = TRUE)),
|
| 824 |
+
`Avg HB` = sprintf("%.1f in", mean(HorzBreak, na.rm = TRUE)),
|
| 825 |
+
`Avg Spin` = ifelse("SpinRate" %in% names(movement_stats),
|
| 826 |
+
sprintf("%.0f rpm", mean(SpinRate, na.rm = TRUE)),
|
| 827 |
+
"β"),
|
| 828 |
+
`Rel Height` = ifelse("RelHeight" %in% names(movement_stats),
|
| 829 |
+
sprintf("%.1f", mean(RelHeight, na.rm = TRUE)),
|
| 830 |
+
"β"),
|
| 831 |
+
`Zone%` = sprintf("%.1f%%", round(mean(in_zone, na.rm = TRUE) * 100, 1)),
|
| 832 |
+
`Whiff%` = sprintf("%.1f%%", round(mean(is_whiff, na.rm = TRUE) * 100, 1)),
|
| 833 |
+
`Chase%` = sprintf("%.1f%%", round(mean(chase, na.rm = TRUE) * 100, 1)),
|
| 834 |
+
.groups = "drop"
|
| 835 |
+
) %>%
|
| 836 |
+
arrange(desc(Count))
|
| 837 |
+
|
| 838 |
+
DT::datatable(summary_stats,
|
| 839 |
+
options = list(pageLength = 15, dom = 't', scrollX = TRUE),
|
| 840 |
+
rownames = FALSE) %>%
|
| 841 |
+
DT::formatStyle(columns = names(summary_stats), fontSize = '12px')
|
| 842 |
+
})
|
| 843 |
+
|
| 844 |
+
# Selected pitch info in modal
|
| 845 |
+
output$selected_pitch_info <- renderText({
|
| 846 |
+
pitch_info <- selected_pitch()
|
| 847 |
+
if (!is.null(pitch_info)) {
|
| 848 |
+
pitch_data <- pitch_info$data
|
| 849 |
+
|
| 850 |
+
# Build info text with only available fields
|
| 851 |
+
info_lines <- c(
|
| 852 |
+
paste("Pitcher:", pitch_info$pitcher),
|
| 853 |
+
paste("Current Type:", pitch_data$TaggedPitchType),
|
| 854 |
+
paste("Velocity:", round(pitch_data$RelSpeed, 1), "mph"),
|
| 855 |
+
paste("Horizontal Break:", round(pitch_data$HorzBreak, 1), "inches"),
|
| 856 |
+
paste("Induced Vertical Break:", round(pitch_data$InducedVertBreak, 1), "inches")
|
| 857 |
+
)
|
| 858 |
+
|
| 859 |
+
# Add optional fields only if they exist and have values
|
| 860 |
+
if ("SpinRate" %in% names(pitch_data) && !is.na(pitch_data$SpinRate)) {
|
| 861 |
+
info_lines <- c(info_lines, paste("Spin Rate:", round(pitch_data$SpinRate, 0), "rpm"))
|
| 862 |
+
}
|
| 863 |
+
|
| 864 |
+
if ("Date" %in% names(pitch_data) && !is.na(pitch_data$Date)) {
|
| 865 |
+
info_lines <- c(info_lines, paste("Date:", pitch_data$Date))
|
| 866 |
+
}
|
| 867 |
+
|
| 868 |
+
return(paste(info_lines, collapse = "\n"))
|
| 869 |
+
} else {
|
| 870 |
+
return("No pitch selected")
|
| 871 |
+
}
|
| 872 |
+
})
|
| 873 |
|
| 874 |
# Update pitch type
|
| 875 |
observeEvent(input$update_pitch, {
|