TimStats commited on
Commit
96fb294
·
verified ·
1 Parent(s): e24d267

Update app.R

Browse files
Files changed (1) hide show
  1. app.R +84 -86
app.R CHANGED
@@ -2,28 +2,23 @@ library(shiny)
2
  library(dplyr)
3
  library(ggplot2)
4
  library(utils)
 
 
5
 
6
  data1 <- read.csv("data1.csv", header = TRUE, check.names = FALSE, fileEncoding = "UTF-8")
7
  data1 <- data1[,2:54]
8
 
9
- AAaveragePT <- data1 %>%
10
- mutate(arm_angle = round(arm_angle, digits = 0)) %>%
11
- group_by(arm_angle, phand, pitch_name) %>%
12
- summarise(
13
- AvgIVB = mean(IVB, na.rm = TRUE),
14
- AvgHB = mean(HB, na.rm = TRUE),
15
- .groups = 'drop'
16
- )
17
-
18
  pitch_type_lookup <- data.frame(
19
- pitch_name = c("Changeup", "Curveball", "Cutter", "Eephus", "Forkball",
20
- "Four-Seam Fastball", "Knuckle Ball", "Knuckle Curve",
21
- "Screwball", "Sinker", "Slider", "Slurve", "Splitter", "Sweeper"),
22
- pitch_abbr = c("CH", "CU", "FC", "EP", "FO", "FF", "KN", "KC",
23
- "SC", "SI", "SL", "SV", "FS", "ST"),
24
- stringsAsFactors = FALSE
25
  )
26
 
 
27
  pitch_colors <- c(
28
  "FF" = "#FF4136",
29
  "SI" = "#FF851B",
@@ -43,8 +38,6 @@ pitch_colors <- c(
43
  )
44
 
45
  break_plot_Szn <- function(game, data1, sdate, edate) {
46
- # Calculate pitch usage percentages
47
-
48
  game <- game %>% filter(between(as.Date(date), sdate, edate))
49
  total_pitches <- nrow(game)
50
  usage_stats <- game %>%
@@ -58,7 +51,6 @@ break_plot_Szn <- function(game, data1, sdate, edate) {
58
  left_join(pitch_type_lookup, by = c("pitch_name")) %>%
59
  left_join(usage_stats, by = "pitch_name")
60
 
61
-
62
  game <- game %>% filter(!is.na(pitch_abbr))
63
  title <- paste0(unique(game$`Pitcher Name`)," ",sdate," to ",edate)
64
  pitcher_hand <- unique(game$phand)
@@ -77,24 +69,21 @@ break_plot_Szn <- function(game, data1, sdate, edate) {
77
  x_end <- 50 * cos(angle_radians) * factor
78
  y_end <- 50 * sin(angle_radians)
79
 
80
- # Create the base color mapping
81
  game_pitches <- unique(game$pitch_abbr)
82
  used_colors <- pitch_colors[game_pitches]
83
 
84
- # Add usage to the game data
85
  avg_locations <- game %>%
86
  group_by(pitch_abbr) %>%
87
  summarize(
88
  avg_HB = mean(HB, na.rm = TRUE),
89
  avg_IVB = mean(IVB, na.rm = TRUE),
90
- usage = first(usage) # Keep the usage info
 
91
  )
92
 
93
- # Create labels with percentages
94
  legend_labels <- paste0(names(used_colors), " (",
95
  avg_locations$usage[match(names(used_colors), avg_locations$pitch_abbr)], ")")
96
 
97
- # Create the color scale with usage labels
98
  fill_values <- used_colors
99
  names(fill_values) <- legend_labels
100
 
@@ -105,10 +94,9 @@ break_plot_Szn <- function(game, data1, sdate, edate) {
105
  filter(!is.na(pitch_abbr)) %>%
106
  filter(pitch_abbr %in% game_pitches)
107
 
108
- ggplot(game, aes(x = HB, y = IVB)) +
109
  geom_vline(xintercept = 0, color = "lightblue", linewidth = 1, linetype = 4) +
110
  geom_hline(yintercept = 0, color = "lightblue", linewidth = 1, linetype = 4) +
111
- # Ellipses
112
  stat_ellipse(
113
  data = arm_angle_data,
114
  aes(fill = paste0(pitch_abbr, " (",
@@ -118,17 +106,17 @@ break_plot_Szn <- function(game, data1, sdate, edate) {
118
  level = 0.68,
119
  show.legend = FALSE
120
  ) +
121
- # Average points
122
- geom_point(
123
  data = avg_locations,
124
  aes(x = avg_HB, y = avg_IVB,
125
- fill = paste0(pitch_abbr, " (", usage, ")")),
 
 
126
  color = "black",
127
  size = 6,
128
  stroke = .5,
129
  shape = 21
130
  ) +
131
- # Arm angle line
132
  geom_segment(x = 0, y = 0, xend = x_end, yend = y_end,
133
  color = "red", linewidth = 1, linetype = 5) +
134
  scale_fill_manual(values = fill_values) +
@@ -159,16 +147,14 @@ break_plot_Szn <- function(game, data1, sdate, edate) {
159
  axis.ticks = element_line(color = "white"),
160
  panel.grid = element_blank(),
161
  legend.key = element_blank(),
162
- plot.caption = element_text(
163
- color = "white",
164
- hjust = 0.5
165
- ),
166
  panel.border = element_blank()
167
  )
 
 
168
  }
169
 
170
  break_plot_tot <- function(game, data1, sdate, edate) {
171
- # Calculate pitch usage percentages
172
  game <- game %>% filter(between(as.Date(date), sdate, edate))
173
  total_pitches <- nrow(game)
174
  usage_stats <- game %>%
@@ -177,11 +163,11 @@ break_plot_tot <- function(game, data1, sdate, edate) {
177
  count = n(),
178
  usage = sprintf("%.1f%%", (count/total_pitches) * 100)
179
  )
180
-
181
  game <- game %>%
182
  left_join(pitch_type_lookup, by = c("pitch_name")) %>%
183
  left_join(usage_stats, by = "pitch_name")
184
-
185
  game <- game %>% filter(!is.na(pitch_abbr))
186
  title <- paste0(unique(game$`Pitcher Name`)," ",sdate," to ",edate)
187
  pitcher_hand <- unique(game$phand)
@@ -200,24 +186,21 @@ break_plot_tot <- function(game, data1, sdate, edate) {
200
  x_end <- 50 * cos(angle_radians) * factor
201
  y_end <- 50 * sin(angle_radians)
202
 
203
- # Create the base color mapping
204
  game_pitches <- unique(game$pitch_abbr)
205
  used_colors <- pitch_colors[game_pitches]
206
 
207
- # Add usage to the game data
208
  avg_locations <- game %>%
209
  group_by(pitch_abbr) %>%
210
  summarize(
211
  avg_HB = mean(HB, na.rm = TRUE),
212
  avg_IVB = mean(IVB, na.rm = TRUE),
213
- usage = first(usage) # Keep the usage info
 
214
  )
215
 
216
- # Create labels with percentages
217
  legend_labels <- paste0(names(used_colors), " (",
218
  avg_locations$usage[match(names(used_colors), avg_locations$pitch_abbr)], ")")
219
 
220
- # Create the color scale with usage labels
221
  fill_values <- used_colors
222
  names(fill_values) <- legend_labels
223
 
@@ -228,10 +211,9 @@ break_plot_tot <- function(game, data1, sdate, edate) {
228
  filter(!is.na(pitch_abbr)) %>%
229
  filter(pitch_abbr %in% game_pitches)
230
 
231
- ggplot(game, aes(x = HB, y = IVB)) +
232
  geom_vline(xintercept = 0, color = "lightblue", linewidth = 1, linetype = 4) +
233
  geom_hline(yintercept = 0, color = "lightblue", linewidth = 1, linetype = 4) +
234
- # Ellipses
235
  stat_ellipse(
236
  data = arm_angle_data,
237
  aes(fill = paste0(pitch_abbr, " (",
@@ -241,25 +223,26 @@ break_plot_tot <- function(game, data1, sdate, edate) {
241
  level = 0.68,
242
  show.legend = FALSE
243
  ) +
244
- # Individual points
245
- geom_point(
246
- aes(fill = paste0(pitch_abbr, " (", usage, ")")),
 
247
  color = "#c6c6c6",
248
  size = 3,
249
  stroke = .5,
250
  shape = 21
251
  ) +
252
- # Average points
253
- geom_point(
254
  data = avg_locations,
255
  aes(x = avg_HB, y = avg_IVB,
256
- fill = paste0(pitch_abbr, " (", usage, ")")),
 
 
257
  color = "black",
258
  size = 6,
259
  stroke = .5,
260
  shape = 21
261
  ) +
262
- # Arm angle line
263
  geom_segment(x = 0, y = 0, xend = x_end, yend = y_end,
264
  color = "red", linewidth = 1, linetype = 5) +
265
  scale_fill_manual(values = fill_values) +
@@ -290,53 +273,51 @@ break_plot_tot <- function(game, data1, sdate, edate) {
290
  axis.ticks = element_line(color = "white"),
291
  panel.grid = element_blank(),
292
  legend.key = element_blank(),
293
- plot.caption = element_text(
294
- color = "white",
295
- hjust = 0.5
296
- ),
297
  panel.border = element_blank()
298
  )
 
 
299
  }
 
300
  ui <- fluidPage(
301
- # Application title
 
302
  titlePanel("2020-2024 MLB Pitch Plots"),
303
 
304
  sidebarLayout(
305
  sidebarPanel(
306
- width = 3,
307
- selectInput("player", "Select Player:", choices = unique(data1$`Pitcher Name`),
308
- width = "100%"),
309
- dateRangeInput("date1", "Dates: (Be wary of multi year plots as arm angles may vary over years)",
 
 
310
  start = "2024-03-20",
311
  end = "2024-10-01",
312
- width = "100%"),
313
  radioButtons("type", "Plot Type",
314
- choices = c("Season Average","All Pitches")),
315
  actionButton("submit", "Update Plot",
316
- class = "btn btn-primary btn-block",
317
- style = "margin-bottom: 10px"),
318
- downloadButton("download", "Download Plot",
319
- class = "btn btn-success btn-block")
320
  ),
321
 
322
-
323
  mainPanel(
324
- width = 9,
325
- plotOutput("Plot")
326
  )
327
  )
328
  )
329
-
330
- # Define server
331
  server <- function(input, output, session) {
332
- # Create a reactive value to store current plot settings
333
  plotSettings <- reactiveVal(list(
334
  player = NULL,
335
  type = "Season Average",
336
  dates = c(as.Date("2024-03-20"), as.Date("2024-10-01"))
337
  ))
338
 
339
- # Update settings only when submit is clicked
340
  observeEvent(input$submit, {
341
  plotSettings(list(
342
  player = input$player,
@@ -345,28 +326,44 @@ server <- function(input, output, session) {
345
  ))
346
  })
347
 
348
- output$Plot <- renderPlot({
349
  settings <- plotSettings()
350
- req(settings$player) # Wait until we have a player selected
351
 
352
  game <- data1 %>%
353
  filter(`Pitcher Name` == settings$player)
354
 
355
  if(nrow(game) == 0) {
356
- return(ggplot() +
357
- annotate("text", x = 0.5, y = 0.5,
358
- label = "No data available for selected date range",
359
- color = "white") +
360
- theme_void() +
361
- theme(plot.background = element_rect(fill = "#333333", color = NA)))
362
  }
363
 
364
- if(settings$type == "Season Average"){
365
  break_plot_Szn(game, data1, settings$dates[1], settings$dates[2])
366
- } else{
367
  break_plot_tot(game, data1, settings$dates[1], settings$dates[2])
368
  }
369
- }, width = 750, height = 750)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
370
 
371
  output$download <- downloadHandler(
372
  filename = function() {
@@ -383,9 +380,9 @@ server <- function(input, output, session) {
383
  game <- data1 %>%
384
  filter(`Pitcher Name` == settings$player)
385
 
386
- plot <- if(settings$type == "Season Average"){
387
  break_plot_Szn(game, data1, settings$dates[1], settings$dates[2])
388
- } else{
389
  break_plot_tot(game, data1, settings$dates[1], settings$dates[2])
390
  }
391
 
@@ -394,9 +391,10 @@ server <- function(input, output, session) {
394
  width = 10,
395
  height = 10,
396
  dpi = 300,
397
- bg = "#333333")
398
  }
399
  )
400
  }
401
 
 
402
  shinyApp(ui = ui, server = server)
 
2
  library(dplyr)
3
  library(ggplot2)
4
  library(utils)
5
+ library(ggiraph)
6
+ library(htmlwidgets)
7
 
8
  data1 <- read.csv("data1.csv", header = TRUE, check.names = FALSE, fileEncoding = "UTF-8")
9
  data1 <- data1[,2:54]
10
 
11
+ # Sample pitch type lookup - modify if needed
 
 
 
 
 
 
 
 
12
  pitch_type_lookup <- data.frame(
13
+ pitch_name = c("Changeup", "Curveball", "Cutter", "Eephus", "Forkball",
14
+ "Four-Seam Fastball", "Knuckle Ball", "Knuckle Curve",
15
+ "Screwball", "Sinker", "Slider", "Slurve", "Splitter", "Sweeper"),
16
+ pitch_abbr = c("CH", "CU", "FC", "EP", "FO", "FF", "KN", "KC",
17
+ "SC", "SI", "SL", "SV", "FS", "ST"),
18
+ stringsAsFactors = FALSE
19
  )
20
 
21
+ # Define pitch colors
22
  pitch_colors <- c(
23
  "FF" = "#FF4136",
24
  "SI" = "#FF851B",
 
38
  )
39
 
40
  break_plot_Szn <- function(game, data1, sdate, edate) {
 
 
41
  game <- game %>% filter(between(as.Date(date), sdate, edate))
42
  total_pitches <- nrow(game)
43
  usage_stats <- game %>%
 
51
  left_join(pitch_type_lookup, by = c("pitch_name")) %>%
52
  left_join(usage_stats, by = "pitch_name")
53
 
 
54
  game <- game %>% filter(!is.na(pitch_abbr))
55
  title <- paste0(unique(game$`Pitcher Name`)," ",sdate," to ",edate)
56
  pitcher_hand <- unique(game$phand)
 
69
  x_end <- 50 * cos(angle_radians) * factor
70
  y_end <- 50 * sin(angle_radians)
71
 
 
72
  game_pitches <- unique(game$pitch_abbr)
73
  used_colors <- pitch_colors[game_pitches]
74
 
 
75
  avg_locations <- game %>%
76
  group_by(pitch_abbr) %>%
77
  summarize(
78
  avg_HB = mean(HB, na.rm = TRUE),
79
  avg_IVB = mean(IVB, na.rm = TRUE),
80
+ avg_velo = mean(start_speed, na.rm = TRUE),
81
+ usage = first(usage)
82
  )
83
 
 
84
  legend_labels <- paste0(names(used_colors), " (",
85
  avg_locations$usage[match(names(used_colors), avg_locations$pitch_abbr)], ")")
86
 
 
87
  fill_values <- used_colors
88
  names(fill_values) <- legend_labels
89
 
 
94
  filter(!is.na(pitch_abbr)) %>%
95
  filter(pitch_abbr %in% game_pitches)
96
 
97
+ p <- ggplot(game, aes(x = HB, y = IVB)) +
98
  geom_vline(xintercept = 0, color = "lightblue", linewidth = 1, linetype = 4) +
99
  geom_hline(yintercept = 0, color = "lightblue", linewidth = 1, linetype = 4) +
 
100
  stat_ellipse(
101
  data = arm_angle_data,
102
  aes(fill = paste0(pitch_abbr, " (",
 
106
  level = 0.68,
107
  show.legend = FALSE
108
  ) +
109
+ geom_point_interactive(
 
110
  data = avg_locations,
111
  aes(x = avg_HB, y = avg_IVB,
112
+ fill = paste0(pitch_abbr, " (", usage, ")"),
113
+ tooltip = sprintf("Avg Velo: %.1f mph\nAvg IVB: %.1f in\nAvg HB: %.1f in",
114
+ avg_velo, avg_IVB, avg_HB)),
115
  color = "black",
116
  size = 6,
117
  stroke = .5,
118
  shape = 21
119
  ) +
 
120
  geom_segment(x = 0, y = 0, xend = x_end, yend = y_end,
121
  color = "red", linewidth = 1, linetype = 5) +
122
  scale_fill_manual(values = fill_values) +
 
147
  axis.ticks = element_line(color = "white"),
148
  panel.grid = element_blank(),
149
  legend.key = element_blank(),
150
+ plot.caption = element_text(color = "white", hjust = 0.5),
 
 
 
151
  panel.border = element_blank()
152
  )
153
+
154
+ return(p)
155
  }
156
 
157
  break_plot_tot <- function(game, data1, sdate, edate) {
 
158
  game <- game %>% filter(between(as.Date(date), sdate, edate))
159
  total_pitches <- nrow(game)
160
  usage_stats <- game %>%
 
163
  count = n(),
164
  usage = sprintf("%.1f%%", (count/total_pitches) * 100)
165
  )
166
+
167
  game <- game %>%
168
  left_join(pitch_type_lookup, by = c("pitch_name")) %>%
169
  left_join(usage_stats, by = "pitch_name")
170
+
171
  game <- game %>% filter(!is.na(pitch_abbr))
172
  title <- paste0(unique(game$`Pitcher Name`)," ",sdate," to ",edate)
173
  pitcher_hand <- unique(game$phand)
 
186
  x_end <- 50 * cos(angle_radians) * factor
187
  y_end <- 50 * sin(angle_radians)
188
 
 
189
  game_pitches <- unique(game$pitch_abbr)
190
  used_colors <- pitch_colors[game_pitches]
191
 
 
192
  avg_locations <- game %>%
193
  group_by(pitch_abbr) %>%
194
  summarize(
195
  avg_HB = mean(HB, na.rm = TRUE),
196
  avg_IVB = mean(IVB, na.rm = TRUE),
197
+ avg_velo = mean(start_speed, na.rm = TRUE),
198
+ usage = first(usage)
199
  )
200
 
 
201
  legend_labels <- paste0(names(used_colors), " (",
202
  avg_locations$usage[match(names(used_colors), avg_locations$pitch_abbr)], ")")
203
 
 
204
  fill_values <- used_colors
205
  names(fill_values) <- legend_labels
206
 
 
211
  filter(!is.na(pitch_abbr)) %>%
212
  filter(pitch_abbr %in% game_pitches)
213
 
214
+ p <- ggplot(game, aes(x = HB, y = IVB)) +
215
  geom_vline(xintercept = 0, color = "lightblue", linewidth = 1, linetype = 4) +
216
  geom_hline(yintercept = 0, color = "lightblue", linewidth = 1, linetype = 4) +
 
217
  stat_ellipse(
218
  data = arm_angle_data,
219
  aes(fill = paste0(pitch_abbr, " (",
 
223
  level = 0.68,
224
  show.legend = FALSE
225
  ) +
226
+ geom_point_interactive(
227
+ aes(fill = paste0(pitch_abbr, " (", usage, ")"),
228
+ tooltip = sprintf("Velo: %.1f mph\nIVB: %.1f in\nHB: %.1f in",
229
+ start_speed, IVB, HB)),
230
  color = "#c6c6c6",
231
  size = 3,
232
  stroke = .5,
233
  shape = 21
234
  ) +
235
+ geom_point_interactive(
 
236
  data = avg_locations,
237
  aes(x = avg_HB, y = avg_IVB,
238
+ fill = paste0(pitch_abbr, " (", usage, ")"),
239
+ tooltip = sprintf("Avg Velo: %.1f mph\nAvg IVB: %.1f in\nAvg HB: %.1f in",
240
+ avg_velo, avg_IVB, avg_HB)),
241
  color = "black",
242
  size = 6,
243
  stroke = .5,
244
  shape = 21
245
  ) +
 
246
  geom_segment(x = 0, y = 0, xend = x_end, yend = y_end,
247
  color = "red", linewidth = 1, linetype = 5) +
248
  scale_fill_manual(values = fill_values) +
 
273
  axis.ticks = element_line(color = "white"),
274
  panel.grid = element_blank(),
275
  legend.key = element_blank(),
276
+ plot.caption = element_text(color = "white", hjust = 0.5),
 
 
 
277
  panel.border = element_blank()
278
  )
279
+
280
+ return(p)
281
  }
282
+
283
  ui <- fluidPage(
284
+ # Remove useGirafe() line and just have girafeOutput in mainPanel
285
+
286
  titlePanel("2020-2024 MLB Pitch Plots"),
287
 
288
  sidebarLayout(
289
  sidebarPanel(
290
+ width = 3,
291
+ selectInput("player", "Select Player:",
292
+ choices = unique(data1$`Pitcher Name`),
293
+ width = "100%"),
294
+ dateRangeInput("date1",
295
+ "Dates: (Be wary of multi year plots as arm angles may vary over years)",
296
  start = "2024-03-20",
297
  end = "2024-10-01",
298
+ width = "100%"),
299
  radioButtons("type", "Plot Type",
300
+ choices = c("Season Average", "All Pitches")),
301
  actionButton("submit", "Update Plot",
302
+ class = "btn btn-primary btn-block",
303
+ style = "margin-bottom: 10px"),
304
+ downloadButton("download", "Download Plot",
305
+ class = "btn btn-success btn-block")
306
  ),
307
 
 
308
  mainPanel(
309
+ width = 9,
310
+ girafeOutput("Plot", width = "750px", height = "750px")
311
  )
312
  )
313
  )
 
 
314
  server <- function(input, output, session) {
 
315
  plotSettings <- reactiveVal(list(
316
  player = NULL,
317
  type = "Season Average",
318
  dates = c(as.Date("2024-03-20"), as.Date("2024-10-01"))
319
  ))
320
 
 
321
  observeEvent(input$submit, {
322
  plotSettings(list(
323
  player = input$player,
 
326
  ))
327
  })
328
 
329
+ output$Plot <- renderGirafe({
330
  settings <- plotSettings()
331
+ req(settings$player)
332
 
333
  game <- data1 %>%
334
  filter(`Pitcher Name` == settings$player)
335
 
336
  if(nrow(game) == 0) {
337
+ return(girafe(ggobj = ggplot() +
338
+ annotate("text", x = 0.5, y = 0.5,
339
+ label = "No data available for selected date range",
340
+ color = "white") +
341
+ theme_void() +
342
+ theme(plot.background = element_rect(fill = "#333333", color = NA))))
343
  }
344
 
345
+ plot <- if(settings$type == "Season Average") {
346
  break_plot_Szn(game, data1, settings$dates[1], settings$dates[2])
347
+ } else {
348
  break_plot_tot(game, data1, settings$dates[1], settings$dates[2])
349
  }
350
+
351
+ girafe(ggobj = plot,
352
+ width_svg = 10,
353
+ height_svg = 10,
354
+ options = list(
355
+ opts_tooltip(
356
+ opacity = 0.8,
357
+ css = "background-color: #333333; color: white; padding: 5px; border-radius: 3px;"
358
+ ),
359
+ opts_hover(
360
+ css = "fill-opacity: 1; stroke: #FFF; stroke-width: 2;"
361
+ ),
362
+ opts_selection(
363
+ type = "none"
364
+ )
365
+ ))
366
+ })
367
 
368
  output$download <- downloadHandler(
369
  filename = function() {
 
380
  game <- data1 %>%
381
  filter(`Pitcher Name` == settings$player)
382
 
383
+ plot <- if(settings$type == "Season Average") {
384
  break_plot_Szn(game, data1, settings$dates[1], settings$dates[2])
385
+ } else {
386
  break_plot_tot(game, data1, settings$dates[1], settings$dates[2])
387
  }
388
 
 
391
  width = 10,
392
  height = 10,
393
  dpi = 300,
394
+ bg = "#333333")
395
  }
396
  )
397
  }
398
 
399
+ # Run the application
400
  shinyApp(ui = ui, server = server)