Spaces:
Sleeping
Sleeping
Update app.R
Browse files
app.R
CHANGED
|
@@ -201,6 +201,104 @@ build_pitcher_stats <- function(df, min_pitches, scores, portal) {
|
|
| 201 |
arrange(desc(Pitches))
|
| 202 |
}
|
| 203 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 204 |
# ββ UI βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 205 |
ui <- fluidPage(
|
| 206 |
useShinyjs(),
|
|
@@ -312,22 +410,38 @@ ui <- fluidPage(
|
|
| 312 |
setTimeout(function() { $('#splash').remove(); }, 800);
|
| 313 |
}, 2800);
|
| 314 |
|
|
|
|
| 315 |
$(document).on('click', '.plot-btn', function() {
|
| 316 |
var pitcher = $(this).data('pitcher');
|
| 317 |
Shiny.setInputValue('selected_pitcher', pitcher, {priority: 'event'});
|
| 318 |
});
|
| 319 |
-
|
| 320 |
$(document).on('click', '#modal_close_btn', function() {
|
| 321 |
$('#modal_wrapper').hide();
|
| 322 |
Shiny.setInputValue('modal_closed', Math.random(), {priority: 'event'});
|
| 323 |
});
|
| 324 |
-
|
| 325 |
$(document).on('click', '#modal_wrapper', function(e) {
|
| 326 |
if ($(e.target).is('#modal_wrapper')) {
|
| 327 |
$('#modal_wrapper').hide();
|
| 328 |
Shiny.setInputValue('modal_closed', Math.random(), {priority: 'event'});
|
| 329 |
}
|
| 330 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 331 |
});
|
| 332 |
"))
|
| 333 |
),
|
|
@@ -390,6 +504,19 @@ ui <- fluidPage(
|
|
| 390 |
DTOutput("pitch_type_table")
|
| 391 |
)
|
| 392 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 393 |
)
|
| 394 |
|
| 395 |
# ββ Server βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
@@ -399,9 +526,10 @@ server <- function(input, output, session) {
|
|
| 399 |
req(input$level)
|
| 400 |
tryCatch({
|
| 401 |
list(
|
| 402 |
-
df
|
| 403 |
-
scores
|
| 404 |
-
portal
|
|
|
|
| 405 |
)
|
| 406 |
}, error = function(e) {
|
| 407 |
showNotification(paste("Failed to load:", e$message), type = "error")
|
|
@@ -426,6 +554,20 @@ server <- function(input, output, session) {
|
|
| 426 |
showNotification(paste("Stats error:", e$message), type = "error")
|
| 427 |
NULL
|
| 428 |
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 429 |
}
|
| 430 |
})
|
| 431 |
|
|
@@ -437,10 +579,47 @@ server <- function(input, output, session) {
|
|
| 437 |
DTOutput("pitcher_table")
|
| 438 |
)
|
| 439 |
} else {
|
| 440 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 441 |
}
|
| 442 |
})
|
| 443 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 444 |
output$pitcher_table <- renderDT({
|
| 445 |
req(stats_table(), nrow(stats_table()) > 0)
|
| 446 |
dt <- stats_table()
|
|
@@ -498,6 +677,174 @@ server <- function(input, output, session) {
|
|
| 498 |
shinyjs::hide("modal_wrapper")
|
| 499 |
})
|
| 500 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 501 |
pitcher_plot_data <- reactive({
|
| 502 |
req(current_pitcher(), raw_data())
|
| 503 |
raw_data()$df %>%
|
|
|
|
| 201 |
arrange(desc(Pitches))
|
| 202 |
}
|
| 203 |
|
| 204 |
+
|
| 205 |
+
## Build Hitter Stats
|
| 206 |
+
|
| 207 |
+
build_hitter_stats <- function(df, min_pa, hitter_portal) {
|
| 208 |
+
|
| 209 |
+
team_lookup <- df %>%
|
| 210 |
+
select(Batter, BatterTeam) %>%
|
| 211 |
+
distinct(Batter, .keep_all = TRUE)
|
| 212 |
+
|
| 213 |
+
df %>%
|
| 214 |
+
mutate(
|
| 215 |
+
IsSwing = PitchCall %in% c("StrikeSwinging","FoulBall",
|
| 216 |
+
"FoulBallNotFieldable","FoulTip","InPlay"),
|
| 217 |
+
IsContact = PitchCall %in% c("FoulBall","FoulBallNotFieldable","FoulTip","InPlay"),
|
| 218 |
+
IsWhiff = PitchCall == "StrikeSwinging",
|
| 219 |
+
IsZone = !is.na(PlateLocSide) & !is.na(PlateLocHeight) &
|
| 220 |
+
abs(PlateLocSide) <= 0.8303 & PlateLocHeight >= 1.5 & PlateLocHeight <= 3.3775,
|
| 221 |
+
IsChase = IsSwing & !IsZone,
|
| 222 |
+
IsOutZone = !IsZone,
|
| 223 |
+
IsZoneSwing = IsSwing & IsZone,
|
| 224 |
+
IsZoneWhiff = IsWhiff & IsZone,
|
| 225 |
+
IsHardHit = !is.na(ExitSpeed) & ExitSpeed >= 95 &
|
| 226 |
+
!PitchCall %in% c("FoulBall","FoulBallNotFieldable","FoulBallFieldable","FoulTip"),
|
| 227 |
+
IsBIP = !is.na(ExitSpeed) &
|
| 228 |
+
!PitchCall %in% c("FoulBall","FoulBallNotFieldable","FoulBallFieldable","FoulTip"),
|
| 229 |
+
IsGB = IsBIP & TaggedHitType %in% c("Groundball","GroundBall"),
|
| 230 |
+
IsFB = IsBIP & TaggedHitType == "FlyBall",
|
| 231 |
+
IsLD = IsBIP & TaggedHitType == "LineDrive",
|
| 232 |
+
IsHit = !is.na(PlayResult) & PlayResult %in% c("Single","Double","Triple","HomeRun"),
|
| 233 |
+
IsAB = !is.na(PlayResult) & PlayResult %in% c("Single","Double","Triple","HomeRun",
|
| 234 |
+
"Out","Error","FieldersChoice"),
|
| 235 |
+
IsPA = (!is.na(KorBB) & KorBB %in% c("Strikeout","Walk")) |
|
| 236 |
+
(!is.na(PitchCall) & PitchCall %in% c("InPlay","HitByPitch")),
|
| 237 |
+
IsHR = !is.na(PlayResult) & PlayResult == "HomeRun",
|
| 238 |
+
IsBB = !is.na(KorBB) & KorBB == "Walk",
|
| 239 |
+
IsK = !is.na(KorBB) & KorBB == "Strikeout"
|
| 240 |
+
) %>%
|
| 241 |
+
group_by(Batter, Level) %>%
|
| 242 |
+
summarise(
|
| 243 |
+
Pitches = n(),
|
| 244 |
+
PA = sum(IsPA, na.rm = TRUE),
|
| 245 |
+
AB = sum(IsAB, na.rm = TRUE),
|
| 246 |
+
H = sum(IsHit, na.rm = TRUE),
|
| 247 |
+
HR = sum(IsHR, na.rm = TRUE),
|
| 248 |
+
BB = sum(IsBB, na.rm = TRUE),
|
| 249 |
+
K = sum(IsK, na.rm = TRUE),
|
| 250 |
+
hh_n = sum(IsHardHit, na.rm = TRUE),
|
| 251 |
+
bip_n = sum(IsBIP, na.rm = TRUE),
|
| 252 |
+
gb_n = sum(IsGB, na.rm = TRUE),
|
| 253 |
+
fb_n = sum(IsFB, na.rm = TRUE),
|
| 254 |
+
ld_n = sum(IsLD, na.rm = TRUE),
|
| 255 |
+
whiff_n = sum(IsWhiff, na.rm = TRUE),
|
| 256 |
+
swing_n = sum(IsSwing, na.rm = TRUE),
|
| 257 |
+
contact_n = sum(IsContact, na.rm = TRUE),
|
| 258 |
+
chase_n = sum(IsChase, na.rm = TRUE),
|
| 259 |
+
outzone_n = sum(IsOutZone, na.rm = TRUE),
|
| 260 |
+
zone_swing_n = sum(IsZoneSwing, na.rm = TRUE),
|
| 261 |
+
zone_whiff_n = sum(IsZoneWhiff, na.rm = TRUE),
|
| 262 |
+
ev_sum = sum(ifelse(IsBIP, ExitSpeed, NA), na.rm = TRUE),
|
| 263 |
+
.groups = "drop"
|
| 264 |
+
) %>%
|
| 265 |
+
left_join(team_lookup, by = "Batter") %>%
|
| 266 |
+
filter(PA >= min_pa) %>%
|
| 267 |
+
left_join(
|
| 268 |
+
hitter_portal %>% select(Batter, Portal, Position, BatSide, BA, OBP, SLG),
|
| 269 |
+
by = "Batter"
|
| 270 |
+
) %>%
|
| 271 |
+
mutate(
|
| 272 |
+
Portal = ifelse(is.na(Portal), "No", as.character(Portal)),
|
| 273 |
+
BA = ifelse(Portal == "No", NA_real_, as.numeric(BA)),
|
| 274 |
+
OBP = ifelse(Portal == "No", NA_real_, as.numeric(OBP)),
|
| 275 |
+
SLG = ifelse(Portal == "No", NA_real_, as.numeric(SLG)),
|
| 276 |
+
Name = sapply(Batter, format_name),
|
| 277 |
+
Team = BatterTeam,
|
| 278 |
+
AVG = round(H / pmax(AB, 1), 3),
|
| 279 |
+
`BB%` = round(BB / pmax(PA, 1) * 100, 1),
|
| 280 |
+
`K%` = round(K / pmax(PA, 1) * 100, 1),
|
| 281 |
+
`Swing%` = round(swing_n / Pitches * 100, 1),
|
| 282 |
+
`Contact%` = round(contact_n / pmax(swing_n, 1) * 100, 1),
|
| 283 |
+
`IZ Whiff%` = round(zone_whiff_n / pmax(zone_swing_n, 1) * 100, 1),
|
| 284 |
+
`Chase%` = round(chase_n / pmax(outzone_n, 1) * 100, 1),
|
| 285 |
+
`Hard Hit%` = round(hh_n / pmax(bip_n, 1) * 100, 1),
|
| 286 |
+
`Avg EV` = round(ev_sum / pmax(bip_n, 1), 1),
|
| 287 |
+
`GB%` = round(gb_n / pmax(bip_n, 1) * 100, 1),
|
| 288 |
+
`FB%` = round(fb_n / pmax(bip_n, 1) * 100, 1),
|
| 289 |
+
`LD%` = round(ld_n / pmax(bip_n, 1) * 100, 1),
|
| 290 |
+
`Twitter` = paste0('<a href="https://www.google.com/search?q=',
|
| 291 |
+
gsub(" ", "+", sapply(Batter, format_name)),
|
| 292 |
+
'+baseball+twitter" target="_blank">🐦 Search</a>')
|
| 293 |
+
) %>%
|
| 294 |
+
filter(!is.na(Name), trimws(Name) != "", grepl(" ", trimws(Name))) %>%
|
| 295 |
+
select(Name, Level, Team, Portal, Position, BatSide, `Twitter`, PA,
|
| 296 |
+
AVG, OBP, SLG, `BB%`, `K%`, `Swing%`, `Contact%`, `IZ Whiff%`,
|
| 297 |
+
`Chase%`, `Hard Hit%`, `Avg EV`, `GB%`, `FB%`, `LD%`,
|
| 298 |
+
) %>%
|
| 299 |
+
arrange(desc(PA))
|
| 300 |
+
}
|
| 301 |
+
|
| 302 |
# ββ UI βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 303 |
ui <- fluidPage(
|
| 304 |
useShinyjs(),
|
|
|
|
| 410 |
setTimeout(function() { $('#splash').remove(); }, 800);
|
| 411 |
}, 2800);
|
| 412 |
|
| 413 |
+
// pitcher modal
|
| 414 |
$(document).on('click', '.plot-btn', function() {
|
| 415 |
var pitcher = $(this).data('pitcher');
|
| 416 |
Shiny.setInputValue('selected_pitcher', pitcher, {priority: 'event'});
|
| 417 |
});
|
|
|
|
| 418 |
$(document).on('click', '#modal_close_btn', function() {
|
| 419 |
$('#modal_wrapper').hide();
|
| 420 |
Shiny.setInputValue('modal_closed', Math.random(), {priority: 'event'});
|
| 421 |
});
|
|
|
|
| 422 |
$(document).on('click', '#modal_wrapper', function(e) {
|
| 423 |
if ($(e.target).is('#modal_wrapper')) {
|
| 424 |
$('#modal_wrapper').hide();
|
| 425 |
Shiny.setInputValue('modal_closed', Math.random(), {priority: 'event'});
|
| 426 |
}
|
| 427 |
});
|
| 428 |
+
|
| 429 |
+
// hitter modal
|
| 430 |
+
$(document).on('click', '.hitter-name-btn', function() {
|
| 431 |
+
var batter = $(this).data('batter');
|
| 432 |
+
Shiny.setInputValue('selected_batter', batter, {priority: 'event'});
|
| 433 |
+
});
|
| 434 |
+
$(document).on('click', '#hitter_modal_close_btn', function() {
|
| 435 |
+
$('#hitter_modal_wrapper').hide();
|
| 436 |
+
Shiny.setInputValue('hitter_modal_closed', Math.random(), {priority: 'event'});
|
| 437 |
+
});
|
| 438 |
+
$(document).on('click', '#hitter_modal_wrapper', function(e) {
|
| 439 |
+
if ($(e.target).is('#hitter_modal_wrapper')) {
|
| 440 |
+
$('#hitter_modal_wrapper').hide();
|
| 441 |
+
Shiny.setInputValue('hitter_modal_closed', Math.random(), {priority: 'event'});
|
| 442 |
+
}
|
| 443 |
+
});
|
| 444 |
+
|
| 445 |
});
|
| 446 |
"))
|
| 447 |
),
|
|
|
|
| 504 |
DTOutput("pitch_type_table")
|
| 505 |
)
|
| 506 |
)
|
| 507 |
+
# hitter modal
|
| 508 |
+
tags$div(
|
| 509 |
+
id = "hitter_modal_wrapper", class = "modal-overlay", style = "display:none;",
|
| 510 |
+
tags$div(
|
| 511 |
+
class = "modal-box",
|
| 512 |
+
tags$button("β", id = "hitter_modal_close_btn", class = "modal-close"),
|
| 513 |
+
uiOutput("hitter_modal_header"),
|
| 514 |
+
tags$div("vs. Handedness", class = "section-label"),
|
| 515 |
+
DTOutput("hitter_hand_table"),
|
| 516 |
+
tags$div("vs. Pitch Type", class = "section-label"),
|
| 517 |
+
DTOutput("hitter_pitch_table")
|
| 518 |
+
)
|
| 519 |
+
)
|
| 520 |
)
|
| 521 |
|
| 522 |
# ββ Server βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
| 526 |
req(input$level)
|
| 527 |
tryCatch({
|
| 528 |
list(
|
| 529 |
+
df = load_data(input$level),
|
| 530 |
+
scores = load_scores(input$level),
|
| 531 |
+
portal = load_portal(),
|
| 532 |
+
hitter_portal = load_hitter_portal()
|
| 533 |
)
|
| 534 |
}, error = function(e) {
|
| 535 |
showNotification(paste("Failed to load:", e$message), type = "error")
|
|
|
|
| 554 |
showNotification(paste("Stats error:", e$message), type = "error")
|
| 555 |
NULL
|
| 556 |
})
|
| 557 |
+
} else {
|
| 558 |
+
tryCatch({
|
| 559 |
+
df <- build_hitter_stats(
|
| 560 |
+
raw_data()$df,
|
| 561 |
+
input$min_pitches,
|
| 562 |
+
raw_data()$hitter_portal
|
| 563 |
+
)
|
| 564 |
+
if (input$portal_only) df <- df %>% filter(Portal == "Yes")
|
| 565 |
+
df
|
| 566 |
+
}, error = function(e) {
|
| 567 |
+
message("build_hitter_stats failed: ", e$message)
|
| 568 |
+
showNotification(paste("Stats error:", e$message), type = "error")
|
| 569 |
+
NULL
|
| 570 |
+
})
|
| 571 |
}
|
| 572 |
})
|
| 573 |
|
|
|
|
| 579 |
DTOutput("pitcher_table")
|
| 580 |
)
|
| 581 |
} else {
|
| 582 |
+
tagList(
|
| 583 |
+
tags$p(id = "loading_msg", "Loading data...",
|
| 584 |
+
style = "color:#888; font-size:14px; padding:10px 0;"),
|
| 585 |
+
DTOutput("hitter_table")
|
| 586 |
+
)
|
| 587 |
}
|
| 588 |
})
|
| 589 |
|
| 590 |
+
output$hitter_table <- renderDT({
|
| 591 |
+
req(stats_table(), nrow(stats_table()) > 0)
|
| 592 |
+
dt <- stats_table()
|
| 593 |
+
|
| 594 |
+
datatable(
|
| 595 |
+
dt,
|
| 596 |
+
rownames = FALSE,
|
| 597 |
+
escape = FALSE,
|
| 598 |
+
selection = "none",
|
| 599 |
+
options = list(
|
| 600 |
+
pageLength = 25,
|
| 601 |
+
lengthMenu = c(25, 50, 100),
|
| 602 |
+
scrollX = TRUE,
|
| 603 |
+
order = list(list(7, "desc")),
|
| 604 |
+
orderMulti = FALSE,
|
| 605 |
+
dom = "lfrtip",
|
| 606 |
+
columnDefs = list(
|
| 607 |
+
list(className = "dt-left", targets = 0:1),
|
| 608 |
+
list(className = "dt-center", targets = 2:21),
|
| 609 |
+
list(orderable = FALSE, targets = 6)
|
| 610 |
+
),
|
| 611 |
+
initComplete = JS("function(settings, json) {
|
| 612 |
+
$('#loading_msg').hide();
|
| 613 |
+
}")
|
| 614 |
+
)
|
| 615 |
+
) %>%
|
| 616 |
+
formatStyle(
|
| 617 |
+
"Portal",
|
| 618 |
+
color = styleEqual(c("Yes","No"), c("#00840D","#aaaaaa")),
|
| 619 |
+
fontWeight = styleEqual(c("Yes","No"), c("700","400"))
|
| 620 |
+
)
|
| 621 |
+
})
|
| 622 |
+
|
| 623 |
output$pitcher_table <- renderDT({
|
| 624 |
req(stats_table(), nrow(stats_table()) > 0)
|
| 625 |
dt <- stats_table()
|
|
|
|
| 677 |
shinyjs::hide("modal_wrapper")
|
| 678 |
})
|
| 679 |
|
| 680 |
+
# ββ Hitter modal βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 681 |
+
current_batter <- reactiveVal(NULL)
|
| 682 |
+
|
| 683 |
+
observeEvent(input$selected_batter, {
|
| 684 |
+
req(input$selected_batter, raw_data())
|
| 685 |
+
current_batter(input$selected_batter)
|
| 686 |
+
shinyjs::delay(100, shinyjs::show("hitter_modal_wrapper"))
|
| 687 |
+
})
|
| 688 |
+
|
| 689 |
+
observeEvent(input$hitter_modal_closed, {
|
| 690 |
+
shinyjs::hide("hitter_modal_wrapper")
|
| 691 |
+
})
|
| 692 |
+
|
| 693 |
+
batter_data <- reactive({
|
| 694 |
+
req(current_batter(), raw_data())
|
| 695 |
+
raw_data()$df %>%
|
| 696 |
+
filter(Batter == current_batter()) %>%
|
| 697 |
+
mutate(
|
| 698 |
+
PitchGroup = case_when(
|
| 699 |
+
TaggedPitchType %in% c("Fastball","FourSeamFastBall","Four-Seam","FourSeam",
|
| 700 |
+
"Sinker","TwoSeamFastBall","OneSeamFastball","Cutter") ~ "Fastball",
|
| 701 |
+
TaggedPitchType %in% c("Slider","Sweeper","Curveball","CurveBall") ~ "Breaker",
|
| 702 |
+
TaggedPitchType %in% c("Changeup","ChangeUp","Splitter") ~ "Offspeed",
|
| 703 |
+
TRUE ~ NA_character_
|
| 704 |
+
)
|
| 705 |
+
)
|
| 706 |
+
})
|
| 707 |
+
|
| 708 |
+
output$hitter_modal_header <- renderUI({
|
| 709 |
+
req(current_batter())
|
| 710 |
+
parts <- strsplit(current_batter(), ", ")[[1]]
|
| 711 |
+
display_name <- if (length(parts)==2) paste(trimws(parts[2]),trimws(parts[1])) else current_batter()
|
| 712 |
+
pdata <- batter_data()
|
| 713 |
+
team <- if (nrow(pdata)>0) pdata$BatterTeam[1] else ""
|
| 714 |
+
tagList(
|
| 715 |
+
tags$p(display_name, class = "modal-title"),
|
| 716 |
+
tags$p(team, class = "modal-subtitle")
|
| 717 |
+
)
|
| 718 |
+
})
|
| 719 |
+
|
| 720 |
+
output$hitter_hand_table <- renderDT({
|
| 721 |
+
req(batter_data(), nrow(batter_data()) > 0)
|
| 722 |
+
pdata <- batter_data()
|
| 723 |
+
|
| 724 |
+
hand_table <- pdata %>%
|
| 725 |
+
mutate(
|
| 726 |
+
IsHit = !is.na(PlayResult) & PlayResult %in% c("Single","Double","Triple","HomeRun"),
|
| 727 |
+
IsAB = !is.na(PlayResult) & PlayResult %in% c("Single","Double","Triple","HomeRun",
|
| 728 |
+
"Out","Error","FieldersChoice"),
|
| 729 |
+
IsPA = (!is.na(KorBB) & KorBB %in% c("Strikeout","Walk")) |
|
| 730 |
+
(!is.na(PitchCall) & PitchCall %in% c("InPlay","HitByPitch")),
|
| 731 |
+
IsHR = !is.na(PlayResult) & PlayResult == "HomeRun",
|
| 732 |
+
IsBB = !is.na(KorBB) & KorBB == "Walk",
|
| 733 |
+
IsK = !is.na(KorBB) & KorBB == "Strikeout",
|
| 734 |
+
IsSwing = PitchCall %in% c("StrikeSwinging","FoulBall",
|
| 735 |
+
"FoulBallNotFieldable","FoulTip","InPlay"),
|
| 736 |
+
IsWhiff = PitchCall == "StrikeSwinging",
|
| 737 |
+
IsHardHit = !is.na(ExitSpeed) & ExitSpeed >= 95 &
|
| 738 |
+
!PitchCall %in% c("FoulBall","FoulBallNotFieldable","FoulBallFieldable","FoulTip"),
|
| 739 |
+
IsBIP = !is.na(ExitSpeed) &
|
| 740 |
+
!PitchCall %in% c("FoulBall","FoulBallNotFieldable","FoulBallFieldable","FoulTip")
|
| 741 |
+
) %>%
|
| 742 |
+
filter(!is.na(PitcherThrows), PitcherThrows != "") %>%
|
| 743 |
+
group_by(Hand = PitcherThrows) %>%
|
| 744 |
+
summarise(
|
| 745 |
+
PA = sum(IsPA, na.rm=TRUE),
|
| 746 |
+
AB = sum(IsAB, na.rm=TRUE),
|
| 747 |
+
H = sum(IsHit, na.rm=TRUE),
|
| 748 |
+
HR = sum(IsHR, na.rm=TRUE),
|
| 749 |
+
BB = sum(IsBB, na.rm=TRUE),
|
| 750 |
+
K = sum(IsK, na.rm=TRUE),
|
| 751 |
+
hh_n = sum(IsHardHit, na.rm=TRUE),
|
| 752 |
+
bip_n = sum(IsBIP, na.rm=TRUE),
|
| 753 |
+
whiff_n = sum(IsWhiff, na.rm=TRUE),
|
| 754 |
+
swing_n = sum(IsSwing, na.rm=TRUE),
|
| 755 |
+
.groups = "drop"
|
| 756 |
+
) %>%
|
| 757 |
+
mutate(
|
| 758 |
+
AVG = round(H / pmax(AB, 1), 3),
|
| 759 |
+
OBP = round((H + BB) / pmax(PA, 1), 3),
|
| 760 |
+
`K%` = round(K / pmax(PA, 1) * 100, 1),
|
| 761 |
+
`BB%` = round(BB / pmax(PA, 1) * 100, 1),
|
| 762 |
+
`Hard Hit%` = round(hh_n / pmax(bip_n, 1) * 100, 1),
|
| 763 |
+
`Whiff%` = round(whiff_n / pmax(swing_n, 1) * 100, 1)
|
| 764 |
+
) %>%
|
| 765 |
+
select(Hand, PA, AVG, OBP, HR, `BB%`, `K%`, `Hard Hit%`, `Whiff%`)
|
| 766 |
+
|
| 767 |
+
datatable(
|
| 768 |
+
hand_table,
|
| 769 |
+
rownames = FALSE,
|
| 770 |
+
selection = "none",
|
| 771 |
+
options = list(
|
| 772 |
+
pageLength = 5, dom = "t", scrollX = TRUE, ordering = TRUE,
|
| 773 |
+
columnDefs = list(
|
| 774 |
+
list(className = "dt-center", targets = 1:8),
|
| 775 |
+
list(className = "dt-left", targets = 0)
|
| 776 |
+
)
|
| 777 |
+
)
|
| 778 |
+
)
|
| 779 |
+
})
|
| 780 |
+
|
| 781 |
+
output$hitter_pitch_table <- renderDT({
|
| 782 |
+
req(batter_data(), nrow(batter_data()) > 0)
|
| 783 |
+
pdata <- batter_data()
|
| 784 |
+
|
| 785 |
+
pitch_table <- pdata %>%
|
| 786 |
+
mutate(
|
| 787 |
+
IsZone = !is.na(PlateLocSide) & !is.na(PlateLocHeight) &
|
| 788 |
+
abs(PlateLocSide) <= 0.8303 & PlateLocHeight >= 1.5 & PlateLocHeight <= 3.3775,
|
| 789 |
+
IsHit = !is.na(PlayResult) & PlayResult %in% c("Single","Double","Triple","HomeRun"),
|
| 790 |
+
IsAB = !is.na(PlayResult) & PlayResult %in% c("Single","Double","Triple","HomeRun",
|
| 791 |
+
"Out","Error","FieldersChoice"),
|
| 792 |
+
IsPA = (!is.na(KorBB) & KorBB %in% c("Strikeout","Walk")) |
|
| 793 |
+
(!is.na(PitchCall) & PitchCall %in% c("InPlay","HitByPitch")),
|
| 794 |
+
IsHR = !is.na(PlayResult) & PlayResult == "HomeRun",
|
| 795 |
+
IsBB = !is.na(KorBB) & KorBB == "Walk",
|
| 796 |
+
IsK = !is.na(KorBB) & KorBB == "Strikeout",
|
| 797 |
+
IsSwing = PitchCall %in% c("StrikeSwinging","FoulBall",
|
| 798 |
+
"FoulBallNotFieldable","FoulTip","InPlay"),
|
| 799 |
+
IsWhiff = PitchCall == "StrikeSwinging",
|
| 800 |
+
IsChase = IsSwing & !IsZone,
|
| 801 |
+
IsOutZone = !IsZone,
|
| 802 |
+
IsHardHit = !is.na(ExitSpeed) & ExitSpeed >= 95 &
|
| 803 |
+
!PitchCall %in% c("FoulBall","FoulBallNotFieldable","FoulBallFieldable","FoulTip"),
|
| 804 |
+
IsBIP = !is.na(ExitSpeed) &
|
| 805 |
+
!PitchCall %in% c("FoulBall","FoulBallNotFieldable","FoulBallFieldable","FoulTip")
|
| 806 |
+
) %>%
|
| 807 |
+
filter(!is.na(PitchGroup)) %>%
|
| 808 |
+
group_by(`Pitch Type` = PitchGroup) %>%
|
| 809 |
+
summarise(
|
| 810 |
+
Pitches = n(),
|
| 811 |
+
PA = sum(IsPA, na.rm=TRUE),
|
| 812 |
+
AB = sum(IsAB, na.rm=TRUE),
|
| 813 |
+
H = sum(IsHit, na.rm=TRUE),
|
| 814 |
+
HR = sum(IsHR, na.rm=TRUE),
|
| 815 |
+
K = sum(IsK, na.rm=TRUE),
|
| 816 |
+
hh_n = sum(IsHardHit, na.rm=TRUE),
|
| 817 |
+
bip_n = sum(IsBIP, na.rm=TRUE),
|
| 818 |
+
whiff_n = sum(IsWhiff, na.rm=TRUE),
|
| 819 |
+
swing_n = sum(IsSwing, na.rm=TRUE),
|
| 820 |
+
chase_n = sum(IsChase, na.rm=TRUE),
|
| 821 |
+
outzone_n = sum(IsOutZone, na.rm=TRUE),
|
| 822 |
+
.groups = "drop"
|
| 823 |
+
) %>%
|
| 824 |
+
mutate(
|
| 825 |
+
AVG = round(H / pmax(AB, 1), 3),
|
| 826 |
+
`K%` = round(K / pmax(PA, 1) * 100, 1),
|
| 827 |
+
`Hard Hit%` = round(hh_n / pmax(bip_n, 1) * 100, 1),
|
| 828 |
+
`Whiff%` = round(whiff_n / pmax(swing_n, 1) * 100, 1),
|
| 829 |
+
`Chase%` = round(chase_n / pmax(outzone_n,1) * 100, 1)
|
| 830 |
+
) %>%
|
| 831 |
+
select(`Pitch Type`, Pitches, PA, AVG, HR, `K%`, `Hard Hit%`, `Whiff%`, `Chase%`) %>%
|
| 832 |
+
arrange(desc(Pitches))
|
| 833 |
+
|
| 834 |
+
datatable(
|
| 835 |
+
pitch_table,
|
| 836 |
+
rownames = FALSE,
|
| 837 |
+
selection = "none",
|
| 838 |
+
options = list(
|
| 839 |
+
pageLength = 5, dom = "t", scrollX = TRUE, ordering = TRUE,
|
| 840 |
+
columnDefs = list(
|
| 841 |
+
list(className = "dt-center", targets = 1:8),
|
| 842 |
+
list(className = "dt-left", positions = 0)
|
| 843 |
+
)
|
| 844 |
+
)
|
| 845 |
+
)
|
| 846 |
+
})
|
| 847 |
+
|
| 848 |
pitcher_plot_data <- reactive({
|
| 849 |
req(current_pitcher(), raw_data())
|
| 850 |
raw_data()$df %>%
|