Update app.R
Browse files
app.R
CHANGED
|
@@ -1,58 +1,568 @@
|
|
| 1 |
library(shiny)
|
| 2 |
-
library(
|
|
|
|
|
|
|
| 3 |
library(dplyr)
|
|
|
|
|
|
|
| 4 |
library(ggplot2)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
ui <- page_sidebar(
|
| 11 |
-
|
| 12 |
-
|
| 13 |
sidebar = sidebar(
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
),
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
),
|
| 24 |
-
|
|
|
|
|
|
|
| 25 |
)
|
| 26 |
|
| 27 |
server <- function(input, output, session) {
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
})
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
list(
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
)
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
)
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
},
|
| 54 |
-
res = 100
|
| 55 |
-
)
|
| 56 |
}
|
| 57 |
|
| 58 |
-
|
|
|
|
|
|
| 1 |
library(shiny)
|
| 2 |
+
library(DT)
|
| 3 |
+
library(rsconnect)
|
| 4 |
+
library(baseballr)
|
| 5 |
library(dplyr)
|
| 6 |
+
library(tidyverse)
|
| 7 |
+
library(rvest)
|
| 8 |
library(ggplot2)
|
| 9 |
+
library(janitor)
|
| 10 |
+
library(ggthemes)
|
| 11 |
+
library(jsonlite)
|
| 12 |
+
library(utils)
|
| 13 |
+
library(data.table)
|
| 14 |
+
library(xgboost)
|
| 15 |
+
library(httr)
|
| 16 |
+
library(bslib)
|
| 17 |
+
library(rtabulator)
|
| 18 |
+
|
| 19 |
+
download_private_csv <- function(repo_id, filename) {
|
| 20 |
+
url <- paste0("https://huggingface.co/datasets/", repo_id, "/resolve/main/", filename)
|
| 21 |
+
response <- GET(url, add_headers(Authorization = paste("Bearer", Sys.getenv("GETCSV"))))
|
| 22 |
+
|
| 23 |
+
if (status_code(response) == 200) {
|
| 24 |
+
content <- content(response, "text")
|
| 25 |
+
con <- textConnection(content)
|
| 26 |
+
|
| 27 |
+
# Try different read options
|
| 28 |
+
data <- read.csv(con,
|
| 29 |
+
header = TRUE,
|
| 30 |
+
check.names = FALSE, # This prevents R from modifying column names
|
| 31 |
+
fileEncoding = "UTF-8",
|
| 32 |
+
stringsAsFactors = FALSE)
|
| 33 |
+
close(con)
|
| 34 |
+
return(data)
|
| 35 |
+
} else {
|
| 36 |
+
stop("Failed to download dataset")
|
| 37 |
+
}
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
MLB <- download_private_csv("TimStats/StatcastDataAll", "MLB.csv")
|
| 41 |
+
AAA <- download_private_csv("TimStats/StatcastDataAll", "AAA.csv")
|
| 42 |
+
FSL <- download_private_csv("TimStats/StatcastDataAll", "FSL.csv")
|
| 43 |
+
|
| 44 |
+
is_barrel <- function(df) {
|
| 45 |
+
df$hit_speedr <- round(df$hit_speed)
|
| 46 |
+
df <- df |>
|
| 47 |
+
mutate(barrel = ifelse((hit_speedr >= 124) &
|
| 48 |
+
(hit_angle >= 0 & hit_angle <= 50),1,0)) |>
|
| 49 |
+
mutate(barrel = ifelse((hit_speedr == 123) &
|
| 50 |
+
(hit_angle >= 1 & hit_angle <= 50),1,barrel)) |>
|
| 51 |
+
mutate(barrel = ifelse((hit_speedr == 122) &
|
| 52 |
+
(hit_angle >= 2 & hit_angle <= 50),1,barrel)) |>
|
| 53 |
+
mutate(barrel = ifelse((hit_speedr == 121) &
|
| 54 |
+
(hit_angle >= 3 & hit_angle <= 50),1,barrel)) |>
|
| 55 |
+
mutate(barrel = ifelse((hit_speedr == 120) &
|
| 56 |
+
(hit_angle >= 4 & hit_angle <= 50),1,barrel)) |>
|
| 57 |
+
mutate(barrel = ifelse((hit_speedr == 119) &
|
| 58 |
+
(hit_angle >= 5 & hit_angle <= 50),1,barrel)) |>
|
| 59 |
+
mutate(barrel = ifelse((hit_speedr == 118) &
|
| 60 |
+
(hit_angle >= 6 & hit_angle <= 50),1,barrel)) |>
|
| 61 |
+
mutate(barrel = ifelse((hit_speedr == 117) &
|
| 62 |
+
(hit_angle >= 7 & hit_angle <= 50),1,barrel)) |>
|
| 63 |
+
mutate(barrel = ifelse((hit_speedr == 116) &
|
| 64 |
+
(hit_angle >= 8 & hit_angle <= 50),1,barrel)) |>
|
| 65 |
+
mutate(barrel = ifelse((hit_speedr == 115) &
|
| 66 |
+
(hit_angle >= 9 & hit_angle <= 50),1,barrel)) |>
|
| 67 |
+
mutate(barrel = ifelse((hit_speedr == 114) &
|
| 68 |
+
(hit_angle >= 10 & hit_angle <= 50),1,barrel)) |>
|
| 69 |
+
mutate(barrel = ifelse((hit_speedr == 113) &
|
| 70 |
+
(hit_angle >= 11 & hit_angle <= 50),1,barrel)) |>
|
| 71 |
+
mutate(barrel = ifelse((hit_speedr == 112) &
|
| 72 |
+
(hit_angle >= 12 & hit_angle <= 50),1,barrel)) |>
|
| 73 |
+
mutate(barrel = ifelse((hit_speedr == 111) &
|
| 74 |
+
(hit_angle >= 13 & hit_angle <= 50),1,barrel)) |>
|
| 75 |
+
mutate(barrel = ifelse((hit_speedr == 110) &
|
| 76 |
+
(hit_angle >= 14 & hit_angle <= 48),1,barrel)) |>
|
| 77 |
+
mutate(barrel = ifelse((hit_speedr == 109) &
|
| 78 |
+
(hit_angle >= 15 & hit_angle <= 46),1,barrel)) |>
|
| 79 |
+
mutate(barrel = ifelse((hit_speedr == 108) &
|
| 80 |
+
(hit_angle >= 16 & hit_angle <= 45),1,barrel)) |>
|
| 81 |
+
mutate(barrel = ifelse((hit_speedr == 107) &
|
| 82 |
+
(hit_angle >= 17 & hit_angle <= 43),1,barrel)) |>
|
| 83 |
+
mutate(barrel = ifelse((hit_speedr == 106) &
|
| 84 |
+
(hit_angle >= 18 & hit_angle <= 42),1,barrel)) |>
|
| 85 |
+
mutate(barrel = ifelse((hit_speedr == 105) &
|
| 86 |
+
(hit_angle >= 19 & hit_angle <= 40),1,barrel)) |>
|
| 87 |
+
mutate(barrel = ifelse((hit_speedr == 104) &
|
| 88 |
+
(hit_angle >= 20 & hit_angle <= 39),1,barrel)) |>
|
| 89 |
+
mutate(barrel = ifelse((hit_speedr == 103) &
|
| 90 |
+
(hit_angle >= 21 & hit_angle <= 37),1,barrel)) |>
|
| 91 |
+
mutate(barrel = ifelse((hit_speedr == 102) &
|
| 92 |
+
(hit_angle >= 22 & hit_angle <= 36),1,barrel)) |>
|
| 93 |
+
mutate(barrel = ifelse((hit_speedr == 101) &
|
| 94 |
+
(hit_angle >= 23 & hit_angle <= 34),1,barrel)) |>
|
| 95 |
+
mutate(barrel = ifelse((hit_speedr == 100) &
|
| 96 |
+
(hit_angle >= 24 & hit_angle <= 33),1,barrel)) |>
|
| 97 |
+
mutate(barrel = ifelse((hit_speedr == 99) &
|
| 98 |
+
(hit_angle >= 25 & hit_angle <= 31),1,barrel)) |>
|
| 99 |
+
mutate(barrel = ifelse((hit_speedr == 98) &
|
| 100 |
+
(hit_angle >= 26 & hit_angle <= 30),1,barrel)) |>
|
| 101 |
+
select(-hit_speedr)
|
| 102 |
+
return(df)
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
VAA <- function(milbtotal){
|
| 106 |
+
milbtotal <- milbtotal |>
|
| 107 |
+
mutate(vaa = -atan((vz0+(az*(-sqrt((vy0*vy0)-(2*ay*(y0-(17/12))))-vy0)/
|
| 108 |
+
ay))/(-sqrt((vy0*vy0)-(2*ay*(y0-(17/12))))))*(180/pi))
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
calculate_EAA <- function(extension) {
|
| 112 |
+
extension / 6.3
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
calculate_SADiff <- function(pfxX, pfxZ, spinDirection) {
|
| 116 |
+
inSA <- atan2(pfxZ, pfxX) * 180/pi + 90
|
| 117 |
+
inSA <- ifelse(inSA < 0, inSA + 360, inSA)
|
| 118 |
+
SADiff <- spinDirection - inSA
|
| 119 |
+
SADiff <- ifelse(SADiff > 180, SADiff - 360, SADiff)
|
| 120 |
+
SADiff <- ifelse(SADiff < -180, SADiff + 360, SADiff)
|
| 121 |
+
return(SADiff)
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
calculate_VAA <- function(vz0, ay, az, vy0, y0) {
|
| 125 |
+
-atan((vz0+(az*(-sqrt((vy0*vy0)-(2*ay*(y0-(17/12))))-vy0)/
|
| 126 |
+
ay))/(-sqrt((vy0*vy0)-(2*ay*(y0-(17/12))))))*(180/pi)
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
+
calculate_timstuff <- function(game) {
|
| 130 |
+
# game <- calculate_primary(game)
|
| 131 |
+
game <- game %>%
|
| 132 |
+
mutate(
|
| 133 |
+
#VAA = calculate_VAA(vz0, ay, az, vy0, y0),
|
| 134 |
+
EAA = calculate_EAA(extension),
|
| 135 |
+
SADiff = calculate_SADiff(pfxX, pfxZ, spinDirection),
|
| 136 |
+
#team_fielding_id = ifelse(description %in% c("Called Strike", "Swinging Strike", "Swinging Strike (Blocked)"), 1, 0),
|
| 137 |
+
# 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),
|
| 138 |
+
#is_strike_swinging = ifelse(is_strike_swinging, 1, 0),
|
| 139 |
+
#Pitch = pitch_name,
|
| 140 |
+
ishandL = ifelse(phand == "L",1,0))
|
| 141 |
+
# game <- calculate_primary(game)
|
| 142 |
+
|
| 143 |
+
feature_vars <- c("ishandL","start_speed", "IVB", "HB", "EAA", "x0", "z0", "spin_rate","SADiff")
|
| 144 |
+
complete_rows <- complete.cases(game[, feature_vars])
|
| 145 |
+
game_complete <- game[complete_rows, ]
|
| 146 |
+
game_na <- game[!complete_rows,]
|
| 147 |
+
game_na$TimStuff <- NA
|
| 148 |
+
|
| 149 |
+
rhp <- game_complete
|
| 150 |
+
|
| 151 |
+
# rhp <- game_complete[game_complete$ishandL == 0]
|
| 152 |
+
#
|
| 153 |
+
# 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)
|
| 154 |
+
|
| 155 |
+
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)
|
| 156 |
+
|
| 157 |
+
game_complete <- rbind(rhp,game_na)
|
| 158 |
+
return(game_complete)
|
| 159 |
+
}
|
| 160 |
+
|
| 161 |
+
scale_TimStuff <- function(raw_score, model_mean, model_sd) {
|
| 162 |
+
scaled_score <- (raw_score - model_mean) / model_sd
|
| 163 |
+
result <- 100 - (scaled_score * 10)
|
| 164 |
+
return(result)
|
| 165 |
+
}
|
| 166 |
+
|
| 167 |
+
model <- xgb.load('TimStuff2.model')
|
| 168 |
+
#######
|
| 169 |
|
| 170 |
+
Swing <- function(milbtotal){
|
| 171 |
+
milbtotal <- milbtotal %>%
|
| 172 |
+
mutate(swing = ifelse(description == "Foul" |
|
| 173 |
+
description == "Foul Pitchout" |
|
| 174 |
+
description == "In play, no out" |
|
| 175 |
+
description == "In play, out(s)" |
|
| 176 |
+
description == "In play, run(s)" |
|
| 177 |
+
description == "Swinging Strike" |
|
| 178 |
+
description == "swinging Strike (Blocked)" |
|
| 179 |
+
description == "Foul Tip",1,0))
|
| 180 |
+
}
|
| 181 |
+
|
| 182 |
+
addChecks <- function(df){
|
| 183 |
+
df <- is_barrel(df)
|
| 184 |
+
df %>% mutate(
|
| 185 |
+
InPlayCheck = case_when(description %in% c('In play, run(s)','In play, out(s)','In play, no out') ~ TRUE, TRUE ~ FALSE),
|
| 186 |
+
SwingCheck = case_when(description %in% c('Foul','Foul Bunt','Foul Pitchout','Foul Tip',
|
| 187 |
+
'In play, run(s)','In play, out(s)','In play, no out',
|
| 188 |
+
'Swinging Strike','Swinging Strike (Blocked)',
|
| 189 |
+
'Missed Bunt') ~ TRUE, TRUE ~ FALSE),
|
| 190 |
+
ConCheck = case_when(description %in% c('In play, run(s)','In play, out(s)','In play, no out',
|
| 191 |
+
'Foul','Foul Bunt','Foul Pitchout') ~ TRUE, TRUE ~ FALSE),
|
| 192 |
+
WhiffCheck = case_when(description %in% c('Swinging Strike','Swinging Strike (Blocked)',
|
| 193 |
+
'Missed Bunt','Foul Tip') ~ TRUE, TRUE ~ FALSE),
|
| 194 |
+
CalledStrikeCheck = case_when(description %in% c('Called Strike') ~ TRUE, TRUE ~ FALSE),
|
| 195 |
+
CSWCheck = case_when(description %in% c('Swinging Strike','Swinging Strike (Blocked)',
|
| 196 |
+
'Missed Bunt','Foul Tip','Called Strike') ~ TRUE, TRUE ~ FALSE),
|
| 197 |
+
StrikeCheck = case_when(description %in% c('Called Strike','Foul','Foul Bunt','Foul Pitchout',
|
| 198 |
+
'Foul Tip','In play, no out','In play, out(s)',
|
| 199 |
+
'In play, run(s)','Missed Bunt','Pitchout',
|
| 200 |
+
'Swinging Stike','Swinging Strike (Blocked)') ~ TRUE, TRUE ~ FALSE),
|
| 201 |
+
BallCheck = case_when(description %in% c('Ball','Ball In Dirt','Hit By Pitch') ~ TRUE, TRUE ~ FALSE),
|
| 202 |
+
SweetSpotCheck = case_when(between(hit_angle,10,30) ~ TRUE, TRUE ~ FALSE),
|
| 203 |
+
HardHitCheck = case_when(hit_speed >= 95 ~ TRUE, TRUE ~ FALSE),
|
| 204 |
+
ZoneCheck = ifelse(zone <= 9, TRUE, FALSE),
|
| 205 |
+
Single = case_when(result == "Single" & InPlayCheck == TRUE ~ TRUE, TRUE ~ FALSE),
|
| 206 |
+
Double = case_when(result == "Double" & InPlayCheck == TRUE ~ TRUE, TRUE ~ FALSE),
|
| 207 |
+
Triple = case_when(result == "Triple" & InPlayCheck == TRUE ~ TRUE, TRUE ~ FALSE),
|
| 208 |
+
`Home Run` = case_when(result == "Home Run" & InPlayCheck == TRUE ~ TRUE, TRUE ~ FALSE),
|
| 209 |
+
WalkCheck = case_when(balls >= 4 & result == "Walk" ~ TRUE, TRUE ~ FALSE),
|
| 210 |
+
HBPCheck = case_when(description == "Hit By Pitch" & result == "Hit By Pitch" ~ TRUE, TRUE ~ FALSE),
|
| 211 |
+
StrikeoutCheck = case_when(strikes >= 3 & result %in% c("Strikeout",'Stikeout Double Play') ~ TRUE, TRUE ~ FALSE),
|
| 212 |
+
SacrificeCheck = case_when(InPlayCheck == TRUE & result %in% c('Sac Fly','Sac Bunt',
|
| 213 |
+
'Sac Fly Double Play','Sac Bunt Double Play') ~ TRUE, TRUE ~ FALSE),
|
| 214 |
+
IBBCheck = case_when(pitchNum == 1 & result == "Intent Walk" ~ TRUE, TRUE ~ FALSE),
|
| 215 |
+
ABCheck = StrikeoutCheck + InPlayCheck - SacrificeCheck,
|
| 216 |
+
PACheck = ABCheck + WalkCheck + HBPCheck,
|
| 217 |
+
TopZoneCheck = if_else(zone < 4,TRUE,FALSE),
|
| 218 |
+
BotZoneCheck = if_else(zone > 6 & zone < 10,TRUE,FALSE),
|
| 219 |
+
CompSwingCheck = if_else(bat_speed >= 60 & hit_speed >= 90,TRUE,FALSE),
|
| 220 |
+
CompSwingCheck = ifelse(bat_speed >= quantile(bat_speed,.1,na.rm = TRUE),1,0)
|
| 221 |
+
)
|
| 222 |
+
}
|
| 223 |
+
#' test <- addChecks(MLB) %>% group_by(`Pitcher Name`,season,pitch_name) %>%
|
| 224 |
+
#' summarise(
|
| 225 |
+
#' Pitches = n(),
|
| 226 |
+
#' 'Avg Velo' = mean(start_speed,na.rm = TRUE),
|
| 227 |
+
#' 'Top Velo' = max(start_speed,na.rm = TRUE),
|
| 228 |
+
#' #'TimStuff+' = mean(TimStuff,na.rm = TRUE),
|
| 229 |
+
#' 'Max EV' = max(hit_speed,na.rm = TRUE),
|
| 230 |
+
#' 'Avg EV' = mean(hit_speed,na.rm = TRUE),
|
| 231 |
+
#' 'EV90' = quantile(hit_speed,0.9,na.rm = TRUE),
|
| 232 |
+
#' 'Avg LA' = mean(hit_angle,na.rm = TRUE),
|
| 233 |
+
#' 'stdevLA' = sd(hit_angle,na.rm = TRUE),
|
| 234 |
+
#' 'HardHit%' = mean(HardHitCheck[InPlayCheck == TRUE],na.rm = TRUE),
|
| 235 |
+
#' 'Barrel%' = mean(barrel[InPlayCheck == TRUE],na.rm = TRUE),
|
| 236 |
+
#' 'Sweet Spot%' = mean(SweetSpotCheck[InPlayCheck == TRUE],na.rm = TRUE),
|
| 237 |
+
#' 'xwOBA' = mean(expected_woba,na.rm = TRUE),
|
| 238 |
+
#' 'xwOBACON' = mean(expected_woba[InPlayCheck == TRUE],na.rm = TRUE),
|
| 239 |
+
#' 'Contact%' = mean(ConCheck[SwingCheck == TRUE],na.rm = TRUE),
|
| 240 |
+
#' 'ZCon%' = mean(ConCheck[ZoneCheck == TRUE] & SwingCheck == TRUE,na.rm = TRUE),
|
| 241 |
+
#' 'ZSwing%' = mean(SwingCheck[ZoneCheck == TRUE],na.rm = TRUE),
|
| 242 |
+
#' 'OCon%' = mean(ConCheck[ZoneCheck == FALSE] & SwingCheck == TRUE,na.rm = TRUE),
|
| 243 |
+
#' 'Chase%' = mean(SwingCheck[ZoneCheck == FALSE],na.rm = TRUE),
|
| 244 |
+
#' 'SwStr%' = mean(WhiffCheck,na.rm = TRUE),
|
| 245 |
+
#' 'Whiff%' = mean(WhiffCheck[SwingCheck == TRUE],na.rm = TRUE),
|
| 246 |
+
#' 'Zone%' = mean(ZoneCheck,na.rm = TRUE),
|
| 247 |
+
#' 'Strike%' = mean(StrikeCheck,na.rm = TRUE),
|
| 248 |
+
#' 'Swing%' = mean(SwingCheck,na.rm = TRUE),
|
| 249 |
+
#' 'Spin Rate' = mean(spin_rate,na.rm = TRUE),
|
| 250 |
+
#' 'Extension' = mean(extension,na.rm = TRUE),
|
| 251 |
+
#' 'IVB' = mean(IVB,na.rm = TRUE),
|
| 252 |
+
#' 'HB' = mean(HB,na.rm = TRUE),
|
| 253 |
+
#' # 'VAA' = mean(vaa,na.rm = TRUE),
|
| 254 |
+
#' # 't3VAA' = mean(vaa[TopZoneCheck == TRUE],na.rm = TRUE),
|
| 255 |
+
#' # 'b3VAA' = mean(vaa[BotZoneCheck == TRUE],na.rm = TRUE),
|
| 256 |
+
#' 'CSW%' = mean(CSWCheck,na.rm = TRUE),
|
| 257 |
+
#' 'Arm Angle' = mean(arm_angle,na.rm = TRUE),
|
| 258 |
+
#' 'Bat Speed' = mean(bat_speed[CompSwingCheck == TRUE],na.rm = TRUE)
|
| 259 |
+
#' )
|
| 260 |
+
MLB_processed <- MLB %>%
|
| 261 |
+
calculate_timstuff() %>%
|
| 262 |
+
addChecks() %>%
|
| 263 |
+
VAA() %>%
|
| 264 |
+
mutate('Pitch Name' = pitch_name) %>%
|
| 265 |
+
mutate('Season' = season) %>%
|
| 266 |
+
mutate('Level' = level) %>%
|
| 267 |
+
mutate('Pitch Type' = case_when(
|
| 268 |
+
pitch_name %in% c("Four-Seam Fastball", "Sinker", "Cutter", "Fastball") ~ "Fastball",
|
| 269 |
+
pitch_name %in% c("Slider", "Sweeper", "Slurve", "Curveball", "Screwball",
|
| 270 |
+
"Knuckle Curve", "Slow Curve", "Eephus") ~ "Breaking",
|
| 271 |
+
pitch_name %in% c("Changeup", "Splitter", "Forkball") ~ "Offspeed",
|
| 272 |
+
TRUE ~ NA_character_ # Corrected default case
|
| 273 |
+
)) %>%
|
| 274 |
+
mutate('Batter Side' = bside) %>%
|
| 275 |
+
mutate('Pitcher Hand' = phand) %>%
|
| 276 |
+
mutate('Stadium' = venue_name) %>%
|
| 277 |
+
mutate('Batter Home/Away' = ifelse(`Batter Team` == teams_home_team_name,"Home","Away")) %>%
|
| 278 |
+
mutate('Pitcher Home/Away' = ifelse(`Pitcher Team` == teams_home_team_name,"Home","Away"))
|
| 279 |
+
|
| 280 |
+
AAA_processed <- AAA %>%
|
| 281 |
+
calculate_timstuff() %>%
|
| 282 |
+
addChecks()%>%
|
| 283 |
+
VAA()%>%
|
| 284 |
+
mutate('Pitch Name' = pitch_name) %>%
|
| 285 |
+
mutate('Season' = season) %>%
|
| 286 |
+
mutate('Level' = level) %>%
|
| 287 |
+
mutate('Pitch Type' = case_when(
|
| 288 |
+
pitch_name %in% c("Four-Seam Fastball", "Sinker", "Cutter", "Fastball") ~ "Fastball",
|
| 289 |
+
pitch_name %in% c("Slider", "Sweeper", "Slurve", "Curveball", "Screwball",
|
| 290 |
+
"Knuckle Curve", "Slow Curve", "Eephus") ~ "Breaking",
|
| 291 |
+
pitch_name %in% c("Changeup", "Splitter", "Forkball") ~ "Offspeed",
|
| 292 |
+
TRUE ~ NA_character_ # Corrected default case
|
| 293 |
+
))
|
| 294 |
+
|
| 295 |
+
FSL_processed <- FSL %>%
|
| 296 |
+
calculate_timstuff() %>%
|
| 297 |
+
addChecks()%>%
|
| 298 |
+
VAA()%>%
|
| 299 |
+
mutate('Pitch Name' = pitch_name) %>%
|
| 300 |
+
mutate('Season' = season) %>%
|
| 301 |
+
mutate('Level' = level) %>%
|
| 302 |
+
mutate('Pitch Type' = case_when(
|
| 303 |
+
pitch_name %in% c("Four-Seam Fastball", "Sinker", "Cutter", "Fastball") ~ "Fastball",
|
| 304 |
+
pitch_name %in% c("Slider", "Sweeper", "Slurve", "Curveball", "Screwball",
|
| 305 |
+
"Knuckle Curve", "Slow Curve", "Eephus") ~ "Breaking",
|
| 306 |
+
pitch_name %in% c("Changeup", "Splitter", "Forkball") ~ "Offspeed",
|
| 307 |
+
TRUE ~ NA_character_ # Corrected default case
|
| 308 |
+
))
|
| 309 |
|
| 310 |
+
# Available stats for the UI
|
| 311 |
+
available_stats <- c("Pitches", "Avg Velo", "Top Velo", "TimStuff", "Max EV", "Avg EV",
|
| 312 |
+
"EV90", "Avg LA", "stdevLA", "HardHit%", "Barrel%", "Sweet Spot%",
|
| 313 |
+
"xwOBA", "xwOBACON", "Contact%", "ZCon%", "ZSwing%", "OCon%",
|
| 314 |
+
"Chase%", "SwStr%", "Whiff%", "Zone%", "Strike%", "Swing%",
|
| 315 |
+
"Spin Rate", "Extension", "IVB", "HB", "CSW%", "Arm Angle", "Bat Speed")
|
| 316 |
+
|
| 317 |
+
# Then your UI and server code
|
| 318 |
ui <- page_sidebar(
|
| 319 |
+
title = "MLB/AAA/FSL Statcast Data",
|
| 320 |
+
theme = bs_theme(preset = "united"),
|
| 321 |
sidebar = sidebar(
|
| 322 |
+
width = 300,
|
| 323 |
+
p("X: ", a("(@TimStats)", href = "https://twitter.com/timstats")),
|
| 324 |
+
p("Data Updated To:", Sys.Date() - 1),
|
| 325 |
+
|
| 326 |
+
card(
|
| 327 |
+
class = "mb-3",
|
| 328 |
+
selectInput("level", "Level:",
|
| 329 |
+
c("MLB", "AAA", "FSL"),
|
| 330 |
+
multiple = TRUE
|
| 331 |
+
),
|
| 332 |
+
selectInput("group", "Group By:",
|
| 333 |
+
c("Pitcher Name", "Pitcher ID", "Pitch Name","Batter Name",
|
| 334 |
+
"Batter ID","Season","Level","Pitch Type","Stadium",
|
| 335 |
+
"Batter Home/Away","Pitcher Home/Away","Pitcher Team",
|
| 336 |
+
"Batter Team"),
|
| 337 |
+
multiple = TRUE
|
| 338 |
+
),
|
| 339 |
+
selectInput("stats", "Select Stats:",
|
| 340 |
+
choices = available_stats,
|
| 341 |
+
multiple = TRUE
|
| 342 |
+
),
|
| 343 |
+
dateRangeInput("dates", "Date Range:",
|
| 344 |
+
start = "2024-02-23",
|
| 345 |
+
end = "2024-09-30"
|
| 346 |
+
)
|
| 347 |
),
|
| 348 |
+
actionButton("add_filter", "Add Filter", class = "btn-primary mb-3"),
|
| 349 |
+
uiOutput("filter_container"),
|
| 350 |
+
hr(),
|
| 351 |
+
actionButton("update_table", "Update Table", class = "btn-success")
|
| 352 |
),
|
| 353 |
+
mainPanel(
|
| 354 |
+
tabulatorOutput("table", height = "800px", width = "1600px")
|
| 355 |
+
)
|
| 356 |
)
|
| 357 |
|
| 358 |
server <- function(input, output, session) {
|
| 359 |
+
filters <- reactiveVal(list())
|
| 360 |
+
counter <- reactiveVal(0)
|
| 361 |
+
table_trigger <- reactiveVal(0)
|
| 362 |
+
last_click <- reactiveVal(0)
|
| 363 |
+
|
| 364 |
+
available_columns <- reactive({
|
| 365 |
+
stat_cols <- setNames(
|
| 366 |
+
input$stats,
|
| 367 |
+
input$stats
|
| 368 |
+
)
|
| 369 |
+
c(stat_cols)
|
| 370 |
})
|
| 371 |
+
|
| 372 |
+
removeClicks <- reactiveVal(0)
|
| 373 |
+
|
| 374 |
+
# observeEvent(input$update_table, {
|
| 375 |
+
# table_trigger(table_trigger() + 1)
|
| 376 |
+
# })
|
| 377 |
+
|
| 378 |
+
processed_data <- eventReactive(input$update_table, {
|
| 379 |
+
req(input$level, input$stats, input$group)
|
| 380 |
+
|
| 381 |
+
# Combine preprocessed data from selected levels
|
| 382 |
+
combined_data <- bind_rows(
|
| 383 |
+
if ("MLB" %in% input$level) MLB_processed,
|
| 384 |
+
if ("AAA" %in% input$level) AAA_processed,
|
| 385 |
+
if ("FSL" %in% input$level) FSL_processed
|
| 386 |
+
)
|
| 387 |
+
|
| 388 |
+
# First group and calculate all stats
|
| 389 |
+
grouped_data <- combined_data %>%
|
| 390 |
+
group_by(across(all_of(input$group))) %>%
|
| 391 |
+
summarise(
|
| 392 |
+
Pitches = n(),
|
| 393 |
+
'Avg Velo' = mean(start_speed, na.rm = TRUE),
|
| 394 |
+
'Top Velo' = max(start_speed, na.rm = TRUE),
|
| 395 |
+
'TimStuff' = mean(TimStuff, na.rm = TRUE),
|
| 396 |
+
'Max EV' = max(hit_speed, na.rm = TRUE),
|
| 397 |
+
'Avg EV' = mean(hit_speed, na.rm = TRUE),
|
| 398 |
+
'EV90' = quantile(hit_speed, 0.9, na.rm = TRUE),
|
| 399 |
+
'Avg LA' = mean(hit_angle, na.rm = TRUE),
|
| 400 |
+
'stdevLA' = sd(hit_angle, na.rm = TRUE),
|
| 401 |
+
'HardHit%' = mean(HardHitCheck[InPlayCheck == TRUE], na.rm = TRUE) * 100,
|
| 402 |
+
'Barrel%' = mean(barrel[InPlayCheck == TRUE], na.rm = TRUE) * 100,
|
| 403 |
+
'Sweet Spot%' = mean(SweetSpotCheck[InPlayCheck == TRUE], na.rm = TRUE) * 100,
|
| 404 |
+
'xwOBA' = mean(expected_woba, na.rm = TRUE),
|
| 405 |
+
'xwOBACON' = mean(expected_woba[InPlayCheck == TRUE], na.rm = TRUE),
|
| 406 |
+
'Contact%' = mean(ConCheck[SwingCheck == TRUE], na.rm = TRUE) * 100,
|
| 407 |
+
'ZCon%' = mean(ConCheck[ZoneCheck == TRUE & SwingCheck == TRUE], na.rm = TRUE) * 100,
|
| 408 |
+
'ZSwing%' = mean(SwingCheck[ZoneCheck == TRUE], na.rm = TRUE) * 100,
|
| 409 |
+
'OCon%' = mean(ConCheck[ZoneCheck == FALSE & SwingCheck == TRUE], na.rm = TRUE) * 100,
|
| 410 |
+
'Chase%' = mean(SwingCheck[ZoneCheck == FALSE], na.rm = TRUE) * 100,
|
| 411 |
+
'SwStr%' = mean(WhiffCheck, na.rm = TRUE) * 100,
|
| 412 |
+
'Whiff%' = mean(WhiffCheck[SwingCheck == TRUE], na.rm = TRUE) * 100,
|
| 413 |
+
'Zone%' = mean(ZoneCheck, na.rm = TRUE) * 100,
|
| 414 |
+
'Strike%' = mean(StrikeCheck, na.rm = TRUE) * 100,
|
| 415 |
+
'Swing%' = mean(SwingCheck, na.rm = TRUE) * 100,
|
| 416 |
+
'Spin Rate' = mean(spin_rate, na.rm = TRUE),
|
| 417 |
+
'Extension' = mean(extension, na.rm = TRUE),
|
| 418 |
+
'IVB' = mean(IVB, na.rm = TRUE),
|
| 419 |
+
'HB' = mean(HB, na.rm = TRUE),
|
| 420 |
+
'VAA' = mean(vaa, na.rm = TRUE),
|
| 421 |
+
't3VAA' = mean(vaa[TopZoneCheck == TRUE], na.rm = TRUE),
|
| 422 |
+
'b3VAA' = mean(vaa[BotZoneCheck == TRUE], na.rm = TRUE),
|
| 423 |
+
'CSW%' = mean(CSWCheck, na.rm = TRUE) * 100,
|
| 424 |
+
'Arm Angle' = mean(arm_angle, na.rm = TRUE),
|
| 425 |
+
'Bat Speed' = mean(bat_speed[CompSwingCheck == TRUE], na.rm = TRUE),
|
| 426 |
+
.groups = 'drop'
|
| 427 |
+
) %>%
|
| 428 |
+
mutate(
|
| 429 |
+
across(c('xwOBA', 'xwOBACON'), ~round(., 3)), # wOBA metrics to 3 decimals
|
| 430 |
+
across(where(is.numeric) & !c('xwOBA', 'xwOBACON'), ~round(., 1)) # everything else to 1 decimal
|
| 431 |
+
)
|
| 432 |
+
|
| 433 |
+
# Then apply filters to the summarized data
|
| 434 |
+
filtered_data <- grouped_data
|
| 435 |
+
current_filters <- filters()
|
| 436 |
+
|
| 437 |
+
for (filter in current_filters) {
|
| 438 |
+
column <- input[[filter$column_id]]
|
| 439 |
+
operator <- input[[filter$operator_id]]
|
| 440 |
+
value <- input[[filter$value_id]]
|
| 441 |
+
|
| 442 |
+
if (!is.null(column) && !is.null(operator) && !is.null(value) && value != "") {
|
| 443 |
+
filtered_data <- switch(operator,
|
| 444 |
+
"eq" = filtered_data %>% filter(!!sym(column) == value),
|
| 445 |
+
"like" = filtered_data %>% filter(grepl(value, !!sym(column), ignore.case = TRUE)),
|
| 446 |
+
"gt" = filtered_data %>% filter(!!sym(column) > as.numeric(value)),
|
| 447 |
+
"lt" = filtered_data %>% filter(!!sym(column) < as.numeric(value)),
|
| 448 |
+
"gte" = filtered_data %>% filter(!!sym(column) >= as.numeric(value)),
|
| 449 |
+
"lte" = filtered_data %>% filter(!!sym(column) <= as.numeric(value)),
|
| 450 |
+
filtered_data
|
| 451 |
+
)
|
| 452 |
+
}
|
| 453 |
+
}
|
| 454 |
+
|
| 455 |
+
# Finally select only the requested columns
|
| 456 |
+
filtered_data %>%
|
| 457 |
+
select(all_of(c(input$group, input$stats)))
|
| 458 |
+
})
|
| 459 |
+
|
| 460 |
+
# Tabulator rendering with formatting
|
| 461 |
+
output$table <- renderTabulator({
|
| 462 |
+
req(processed_data())
|
| 463 |
+
column_defs <- c(
|
| 464 |
+
# Group columns
|
| 465 |
+
map(input$group, function(col) {
|
| 466 |
+
list(field = col, title = col, width = 175) # reduced from 250
|
| 467 |
+
}),
|
| 468 |
+
# Stat columns
|
| 469 |
+
map(input$stats, function(col) {
|
| 470 |
list(
|
| 471 |
+
field = col,
|
| 472 |
+
title = col,
|
| 473 |
+
width = 110, # reduced from 150
|
| 474 |
+
formatter = if(col %in% c("xwOBA", "xwOBACON")) "number" else "number",
|
| 475 |
+
formatterParams = if(col %in% c("xwOBA", "xwOBACON")) {
|
| 476 |
+
list(precision = 3)
|
| 477 |
+
} else {
|
| 478 |
+
list(precision = 1)
|
| 479 |
+
}
|
| 480 |
)
|
| 481 |
+
})
|
| 482 |
+
)
|
| 483 |
+
tabulator(processed_data(),
|
| 484 |
+
options = list(
|
| 485 |
+
selectable = TRUE,
|
| 486 |
+
layout = "fitColumns",
|
| 487 |
+
columns = column_defs
|
| 488 |
+
)
|
| 489 |
+
)
|
| 490 |
+
})
|
| 491 |
+
# Add observers for filter management and other reactive elements
|
| 492 |
+
observeEvent(input$add_filter, {
|
| 493 |
+
isolate({
|
| 494 |
+
current_counter <- counter()
|
| 495 |
+
filter_id <- paste0("filter_", current_counter)
|
| 496 |
+
|
| 497 |
+
new_filter <- list(
|
| 498 |
+
id = filter_id,
|
| 499 |
+
column_id = paste0("column_", filter_id),
|
| 500 |
+
operator_id = paste0("operator_", filter_id),
|
| 501 |
+
value_id = paste0("value_", filter_id)
|
| 502 |
+
)
|
| 503 |
+
|
| 504 |
+
current_filters <- filters()
|
| 505 |
+
filters(c(current_filters, list(new_filter)))
|
| 506 |
+
counter(current_counter + 1)
|
| 507 |
+
})
|
| 508 |
+
})
|
| 509 |
+
|
| 510 |
+
observe({
|
| 511 |
+
current_filters <- filters()
|
| 512 |
+
|
| 513 |
+
lapply(current_filters, function(filter) {
|
| 514 |
+
observeEvent(input[[paste0("remove_", filter$id)]], {
|
| 515 |
+
isolate({
|
| 516 |
+
removeClicks(removeClicks() + 1)
|
| 517 |
+
new_filters <- current_filters[sapply(current_filters, function(f) f$id != filter$id)]
|
| 518 |
+
filters(new_filters)
|
| 519 |
+
})
|
| 520 |
+
}, ignoreInit = TRUE, ignoreNULL = TRUE)
|
| 521 |
+
})
|
| 522 |
+
})
|
| 523 |
+
|
| 524 |
+
# Filter container UI
|
| 525 |
+
output$filter_container <- renderUI({
|
| 526 |
+
removeClicks()
|
| 527 |
+
current_filters <- filters()
|
| 528 |
+
|
| 529 |
+
lapply(current_filters, function(filter) {
|
| 530 |
+
div(
|
| 531 |
+
class = "mb-1",
|
| 532 |
+
div(
|
| 533 |
+
style = "display: flex; gap: 10px; align-items: center;",
|
| 534 |
+
selectInput(filter$column_id, "Column",
|
| 535 |
+
choices = available_columns(),
|
| 536 |
+
width = "200px",
|
| 537 |
+
selected = input[[filter$column_id]]
|
| 538 |
+
),
|
| 539 |
+
selectInput(filter$operator_id, "Stat",
|
| 540 |
+
choices = c(
|
| 541 |
+
"=" = "eq",
|
| 542 |
+
"contains" = "like",
|
| 543 |
+
">" = "gt",
|
| 544 |
+
"<" = "lt",
|
| 545 |
+
">=" = "gte",
|
| 546 |
+
"<=" = "lte"
|
| 547 |
+
),
|
| 548 |
+
width = "100px",
|
| 549 |
+
selected = input[[filter$operator_id]]
|
| 550 |
+
),
|
| 551 |
+
textInput(filter$value_id, "Value",
|
| 552 |
+
value = input[[filter$value_id]],
|
| 553 |
+
width = "150px"
|
| 554 |
+
),
|
| 555 |
+
actionButton(
|
| 556 |
+
paste0("remove_", filter$id),
|
| 557 |
+
icon("trash"),
|
| 558 |
+
class = "btn-danger btn-sm",
|
| 559 |
+
style = "margin-top: 22px;"
|
| 560 |
+
)
|
| 561 |
)
|
| 562 |
+
)
|
| 563 |
+
})
|
| 564 |
+
})
|
|
|
|
|
|
|
|
|
|
| 565 |
}
|
| 566 |
|
| 567 |
+
# Run the application
|
| 568 |
+
shinyApp(ui = ui, server = server)
|