TimStats commited on
Commit
4eb20fc
·
verified ·
1 Parent(s): cf23fbd

Update app.R

Browse files
Files changed (1) hide show
  1. app.R +278 -47
app.R CHANGED
@@ -1,58 +1,289 @@
 
1
  library(shiny)
2
- library(bslib)
 
3
  library(dplyr)
4
- library(ggplot2)
5
-
6
- df <- readr::read_csv("penguins.csv")
7
- # Find subset of columns that are suitable for scatter plot
8
- df_num <- df |> select(where(is.numeric), -Year)
9
-
10
- ui <- page_sidebar(
11
- theme = bs_theme(bootswatch = "minty"),
12
- title = "Penguins explorer",
13
- sidebar = sidebar(
14
- varSelectInput("xvar", "X variable", df_num, selected = "Bill Length (mm)"),
15
- varSelectInput("yvar", "Y variable", df_num, selected = "Bill Depth (mm)"),
16
- checkboxGroupInput("species", "Filter by species",
17
- choices = unique(df$Species), selected = unique(df$Species)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  ),
19
- hr(), # Add a horizontal rule
20
- checkboxInput("by_species", "Show species", TRUE),
21
- checkboxInput("show_margins", "Show marginal plots", TRUE),
22
- checkboxInput("smooth", "Add smoother"),
23
- ),
24
- plotOutput("scatter")
25
  )
26
 
 
27
  server <- function(input, output, session) {
28
- subsetted <- reactive({
29
- req(input$species)
30
- df |> filter(Species %in% input$species)
31
- })
32
-
33
- output$scatter <- renderPlot(
34
- {
35
- p <- ggplot(subsetted(), aes(!!input$xvar, !!input$yvar)) +
36
- theme_light() +
37
- list(
38
- theme(legend.position = "bottom"),
39
- if (input$by_species) aes(color = Species),
40
- geom_point(),
41
- if (input$smooth) geom_smooth()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  )
43
-
44
- if (input$show_margins) {
45
- margin_type <- if (input$by_species) "density" else "histogram"
46
- p <- p |> ggExtra::ggMarginal(
47
- type = margin_type, margins = "both",
48
- size = 8, groupColour = input$by_species, groupFill = input$by_species
49
- )
50
- }
51
-
52
- p
53
  },
54
- res = 100
 
 
55
  )
56
  }
57
 
58
- shinyApp(ui, server)
 
1
+ # Load required libraries
2
  library(shiny)
3
+ library(DT)
4
+ library(baseballr)
5
  library(dplyr)
6
+ library(xgboost)
7
+
8
+ Sys.setenv(TZ='EST')
9
+ download_private_csv <- function(repo_id, filename) {
10
+ url <- paste0("https://huggingface.co/datasets/", repo_id, "/resolve/main/", filename)
11
+ response <- GET(url, add_headers(Authorization = paste("Bearer", Sys.getenv("GETCSV"))))
12
+
13
+ if (status_code(response) == 200) {
14
+ content <- content(response, "text")
15
+ con <- textConnection(content)
16
+
17
+ # Try different read options
18
+ data <- read.csv(con,
19
+ header = TRUE,
20
+ check.names = FALSE, # This prevents R from modifying column names
21
+ fileEncoding = "UTF-8",
22
+ stringsAsFactors = FALSE)
23
+ close(con)
24
+ return(data)
25
+ } else {
26
+ stop("Failed to download dataset")
27
+ }
28
+ }
29
+ MLB <- download_private_csv("TimStats/StatcastDataAll", "MLB.csv")
30
+ AAA <- download_private_csv("TimStats/StatcastDataAll", "AAA.csv")
31
+ FSL <- download_private_csv("TimStats/StatcastDataAll", "FSL.csv")
32
+
33
+
34
+ MLB <- MLB %>%
35
+ select(
36
+ `Pitcher Name`, `Pitcher ID`, pitch_name, start_speed, spin_rate, extension,
37
+ IVB, HB, x0, z0
38
+ )
39
+
40
+ # Same selection for AAA before rbind
41
+ AAA <- AAA %>%
42
+ select(
43
+ `Pitcher Name`, `Pitcher ID`, pitch_name, start_speed, spin_rate, extension,
44
+ IVB, HB, x0, z0
45
+ )
46
+
47
+ FSL <- FSL %>%
48
+ select(
49
+ `Pitcher Name`, `Pitcher ID`, pitch_name, start_speed, spin_rate, extension,
50
+ IVB, HB, x0, z0
51
+ )
52
+ # Helper functions
53
+ calculate_EAA <- function(extension) {
54
+ extension / 6.3
55
+ }
56
+
57
+ calculate_SADiff <- function(pfxX, pfxZ, spinDirection) {
58
+ inSA <- atan2(pfxZ, pfxX) * 180/pi + 90
59
+ inSA <- ifelse(inSA < 0, inSA + 360, inSA)
60
+ SADiff <- spinDirection - inSA
61
+ SADiff <- ifelse(SADiff > 180, SADiff - 360, SADiff)
62
+ SADiff <- ifelse(SADiff < -180, SADiff + 360, SADiff)
63
+ return(SADiff)
64
+ }
65
+
66
+ calculate_VAA <- function(vz0, ay, az, vy0, y0) {
67
+ -atan((vz0+(az*(-sqrt((vy0*vy0)-(2*ay*(y0-(17/12))))-vy0)/
68
+ ay))/(-sqrt((vy0*vy0)-(2*ay*(y0-(17/12))))))*(180/pi)
69
+ }
70
+
71
+ pitcher_summary <- function(game_pk, date) {
72
+ gdate <- as.Date.character(date)
73
+ gdate <- as.Date(gdate)
74
+ tmilb <- mlb_pbp(game_pk)
75
+ tmilb <- tmilb %>%
76
+ filter(type == "pitch") %>%
77
+ select(matchup.batter.fullName, matchup.batter.id, matchup.pitcher.fullName,
78
+ matchup.pitcher.id, matchup.pitchHand.code, details.type.description,
79
+ pitchData.startSpeed, pitchData.breaks.spinRate, pitchData.extension,
80
+ pitchData.coordinates.x0, pitchData.coordinates.y0, pitchData.coordinates.z0,
81
+ pitchData.coordinates.aX, pitchData.coordinates.aY, pitchData.coordinates.aZ,
82
+ pitchData.coordinates.vX0, pitchData.coordinates.vZ0, pitchData.coordinates.vY0,
83
+ pitchData.coordinates.pfxX, pitchData.coordinates.pfxZ,
84
+ pitchData.breaks.breakVerticalInduced, pitchData.breaks.breakHorizontal,
85
+ pitchData.breaks.spinDirection)
86
+
87
+ colnames(tmilb) <- c("Batter Name", "Batter ID", "Pitcher Name", "Pitcher ID",
88
+ "phand", "pitch_name", "start_speed", "spin_rate", "extension",
89
+ "x0", "y0", "z0", "ax", "ay", "az", "vx0", "vz0", "vy0",
90
+ "pfxX", "pfxZ", "IVB", "HB", "spinDirection")
91
+
92
+ tmilb <- tmilb %>%
93
+ mutate(date = gdate)
94
+
95
+ return(tmilb)
96
+ }
97
+
98
+ calculate_timstuff <- function(game) {
99
+ game <- game %>%
100
+ mutate(VAA = calculate_VAA(vz0, ay, az, vy0, y0),
101
+ EAA = calculate_EAA(extension),
102
+ SADiff = calculate_SADiff(pfxX, pfxZ, spinDirection),
103
+ ishandL = ifelse(phand == "L", 1, 0))
104
+
105
+ feature_vars <- c("ishandL", "start_speed", "IVB", "HB", "EAA", "x0", "z0", "spin_rate", "SADiff")
106
+ complete_rows <- complete.cases(game[, feature_vars])
107
+ game_complete <- game[complete_rows, ]
108
+ game_na <- game[!complete_rows,]
109
+ game_na$TimStuff <- NA
110
+
111
+ game_complete$TimStuff <- scale_TimStuff(
112
+ predict(model, as.matrix(cbind(game_complete$ishandL, game_complete$start_speed,
113
+ game_complete$IVB, game_complete$HB, game_complete$EAA,
114
+ game_complete$x0, game_complete$z0, game_complete$spin_rate,
115
+ game_complete$SADiff))),
116
+ -0.002620635, 0.006021368)
117
+
118
+ game_complete <- rbind(game_complete, game_na)
119
+ return(game_complete)
120
+ }
121
+
122
+ scale_TimStuff <- function(raw_score, model_mean, model_sd) {
123
+ scaled_score <- (raw_score - model_mean) / model_sd
124
+ result <- 100 - (scaled_score * 10)
125
+ return(result)
126
+ }
127
+
128
+ summary_table <- function(data) {
129
+ # Current year summary
130
+ current_summary <- data %>%
131
+ group_by(`Pitcher Name`, `Pitcher ID`, pitch_name) %>%
132
+ summarize(
133
+ Pitches = n(),
134
+ 'Velo' = round(mean(start_speed, na.rm = TRUE), 1),
135
+ 'Spin' = round(mean(spin_rate, na.rm = TRUE), 0),
136
+ 'Ext' = round(mean(extension, na.rm = TRUE), 1),
137
+ 'IVB' = round(mean(IVB, na.rm = TRUE), 1),
138
+ 'HB' = round(mean(HB, na.rm = TRUE), 1),
139
+ 'RelX' = round(mean(x0, na.rm = TRUE), 1),
140
+ 'RelZ' = round(mean(z0, na.rm = TRUE), 1),
141
+ 'TimStuff' = round(mean(TimStuff, na.rm = TRUE), 0),
142
+ .groups = "drop"
143
+ )
144
+
145
+ data_2024 <- rbind(MLB,AAA,FSL) %>%
146
+ group_by(`Pitcher Name`, `Pitcher ID`, pitch_name) %>%
147
+ summarize(
148
+ 'Velo24' = round(mean(start_speed, na.rm = TRUE), 1),
149
+ 'Spin24' = round(mean(spin_rate, na.rm = TRUE), 0),
150
+ 'Ext24' = round(mean(extension, na.rm = TRUE), 1),
151
+ 'IVB24' = round(mean(IVB, na.rm = TRUE), 1),
152
+ 'HB24' = round(mean(HB, na.rm = TRUE), 1),
153
+ 'RelX24' = round(mean(x0, na.rm = TRUE), 1),
154
+ 'RelZ24' = round(mean(z0, na.rm = TRUE), 1),
155
+ .groups = "drop"
156
+ )
157
+
158
+ # Join and calculate differences
159
+ combined_data <- current_summary %>%
160
+ left_join(data_2024, by = c("Pitcher Name", "Pitcher ID", "pitch_name")) %>%
161
+ mutate(
162
+ 'Velo_Diff' = round(Velo - Velo24, 1),
163
+ 'Spin_Diff' = round(Spin - Spin24, 1),
164
+ 'Ext_Diff' = round(Ext - Ext24, 1),
165
+ 'IVB_Diff' = round(IVB - IVB24, 1),
166
+ 'HB_Diff' = round(HB - HB24, 1),
167
+ 'RelX_Diff' = round(RelX - RelX24, 1),
168
+ 'RelZ_Diff' = round(RelZ - RelZ24, 1)
169
+ ) %>%
170
+ arrange(-Pitches)
171
+
172
+ return(combined_data)
173
+ }
174
+
175
+ # Load TimStuff model
176
+ model <- xgb.load('TimStuff2.model')
177
+
178
+ # UI Definition
179
+ ui <- fluidPage(
180
+ titlePanel("MLB/AAA/FSL Pitch Comparison Dashboard"),
181
+ sidebarLayout(
182
+ sidebarPanel(
183
+ width = 2,
184
+ dateInput("date", "Date:"),
185
+ selectizeInput("level", "Level:",
186
+ c("MLB", "AAA", "FSL", "College (Statcast Parks Only)",
187
+ "Futures Game", "AFL")),
188
+ actionButton("submit", "Get Dashboard"),
189
+ downloadButton("download_summary", "Download Summary")
190
  ),
191
+ mainPanel(
192
+ dataTableOutput("schedule")
193
+ )
194
+ )
 
 
195
  )
196
 
197
+ # Server Definition
198
  server <- function(input, output, session) {
199
+ data <- reactiveVal()
200
+ games <- reactiveVal()
201
+ summary <- reactiveVal()
202
+
203
+ observeEvent(input$submit, {
204
+ season <- format(input$date, "%Y")
205
+
206
+ # Get schedule based on selected level
207
+ schedule_data <- switch(input$level,
208
+ "MLB" = mlb_schedule(season = season, level_ids = "1"),
209
+ "AAA" = mlb_schedule(season = season, level_ids = "11"),
210
+ "FSL" = mlb_schedule(season = season, level_ids = "14"),
211
+ "College (Statcast Parks Only)" = mlb_schedule(season = season, level_ids = "22"),
212
+ "Futures Game" = mlb_schedule(season = season, level_ids = "21"),
213
+ "AFL" = {
214
+ sbid <- mlb_schedule(2024, 17) %>%
215
+ filter(teams_away_team_name %in% c("Glendale Desert Dogs", "Mesa Solar Sox",
216
+ "Peoria Javelinas", "Salt River Rafters",
217
+ "Scottsdale Scorpions", "Surprise Saguaros")) %>%
218
+ filter(gameday_type == "E")
219
+ sbid
220
+ }
221
+ )
222
+
223
+ data(schedule_data)
224
+ schedule <- data() %>% filter(date == input$date)
225
+
226
+ # Initialize empty games dataframe
227
+ games_data <- data.frame()
228
+
229
+ # Process each game
230
+ for(n in 1:nrow(schedule)) {
231
+ tryCatch({
232
+ game1 <- pitcher_summary(schedule[n,6], schedule[n,1])
233
+ games_data <- rbind(game1, games_data)
234
+ }, error = function(e) {
235
+ message(paste("Error occurred for game:", schedule[n,6], "on", schedule[n,1]))
236
+ })
237
+ }
238
+
239
+ # Calculate TimStuff and create summary
240
+ games_data <- calculate_timstuff(games_data)
241
+ games(games_data)
242
+ summary_data <- summary_table(games_data)
243
+ summary(summary_data)
244
+
245
+ # Render comparison table
246
+ output$schedule <- renderDT({
247
+ datatable(summary_data,
248
+ options = list(
249
+ pageLength = 10,
250
+ lengthMenu = c(10, 25, 50, 100),
251
+ columnDefs = list(
252
+ list(className = 'dt-center', targets = "_all"),
253
+ # Names and identifiers
254
+ list(width = '150px', targets = c(0, 1)), # Pitcher Name, Pitcher ID
255
+ list(width = '100px', targets = 2), # pitch_name
256
+ list(width = '70px', targets = 3), # Pitches
257
+ # Current year stats
258
+ list(width = '50px', targets = c(4:11)), # Velo, Spin, Ext, IVB, HB, RelX, RelZ, TimStuff
259
+ # 2024 stats
260
+ list(width = '10px', targets = c(12:18)), # Velo_2024, Spin_2024, Ext_2024, IVB_2024, HB_2024, RelX_2024, RelZ_2024
261
+ # Difference columns
262
+ list(width = '50px', targets = c(19:25)) # All _Diff columns
263
+ )
264
+ )
265
+ ) %>%
266
+ formatStyle(
267
+ c('Velo_Diff', 'Spin_Diff', 'Ext_Diff', 'IVB_Diff',
268
+ 'HB_Diff', 'RelX_Diff', 'RelZ_Diff'),
269
+ backgroundColor = styleInterval(
270
+ cuts = 0,
271
+ values = c('#ffcdd2', '#c8e6c9')
272
+ )
273
  )
274
+ })
275
+
276
+ })
277
+
278
+ # Download handler for summary CSV
279
+ output$download_summary <- downloadHandler(
280
+ filename = function() {
281
+ paste("comparison_data_", Sys.Date(), ".csv", sep = "")
 
 
282
  },
283
+ content = function(file) {
284
+ write.csv(summary(), file, row.names = FALSE)
285
+ }
286
  )
287
  }
288
 
289
+ shinyApp(ui, server)