Update app.R
Browse files
app.R
CHANGED
|
@@ -32,59 +32,226 @@ hf_target_for <- function(source) {
|
|
| 32 |
}
|
| 33 |
|
| 34 |
# ============================================================
|
| 35 |
-
#
|
| 36 |
# ============================================================
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
}
|
| 46 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
|
| 48 |
# ============================================================
|
| 49 |
-
#
|
| 50 |
# ============================================================
|
| 51 |
-
clean_college_data <- function(data, teams = NA){
|
| 52 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
data <- data %>%
|
| 54 |
-
mutate(
|
| 55 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
Pitcher = sub("(.*),\\s*(.*)", "\\2 \\1", Pitcher),
|
| 57 |
Catcher = sub("(.*),\\s*(.*)", "\\2 \\1", Catcher))
|
| 58 |
-
|
|
|
|
| 59 |
col <- colnames(data)
|
| 60 |
-
|
| 61 |
-
|
|
|
|
|
|
|
| 62 |
}
|
| 63 |
-
|
|
|
|
| 64 |
numeric_columns <- c("PitchNo", "PAofInning", "PitchofPA", "PitcherId", "BatterId", "Inning", "Outs", "Balls",
|
| 65 |
-
"Strikes", "OutsOnPlay", "RunsScored", "RelSpeed", "VertRelAngle", "HorzRelAngle", "SpinRate",
|
| 66 |
-
"SpinAxis", "RelHeight", "RelSide", "Extension", "VertBreak", "InducedVertBreak", "HorzBreak",
|
| 67 |
-
"PlateLocHeight", "PlateLocSide", "ZoneSpeed", "VertApprAngle", "HorzApprAngle", "ZoneTime",
|
| 68 |
"ExitSpeed", "Angle", "Direction", "HitSpinRate", "Distance", "Bearing", "HangTime",
|
| 69 |
-
"LastTrackedDistance", "pfxx", "pfxz", "x0", "y0", "z0", "vx0", "vz0", "vy0", "ax0", "ay0",
|
| 70 |
"az0", "EffectiveVelo", "MaxHeight", "SpeedDrop", "ContactPositionX", "ContactPositionY",
|
| 71 |
"ContactPositionZ", "HomeTeamForeignID", "AwayTeamForeignID", "CatcherId", "ThrowSpeed",
|
| 72 |
-
"PopTime", "ExchangeTime", "TimeToBase")
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 80 |
mutate(TaggedPitchType = case_when(
|
| 81 |
-
TaggedPitchType == "FourSeamFastBall" ~ "Fastball",
|
| 82 |
-
TaggedPitchType %in% c("TwoSeamFastBall", "OneSeamFastBall") ~ "Sinker",
|
| 83 |
-
TaggedPitchType == "ChangeUp" ~ "Changeup",
|
| 84 |
-
TaggedPitchType == "Undefined" ~ "Other",
|
| 85 |
T ~ TaggedPitchType
|
| 86 |
))
|
| 87 |
-
|
|
|
|
| 88 |
data <- data %>%
|
| 89 |
mutate(
|
| 90 |
is_csw = case_when(
|
|
@@ -92,7 +259,7 @@ clean_college_data <- function(data, teams = NA){
|
|
| 92 |
TRUE ~ 0
|
| 93 |
),
|
| 94 |
is_swing = case_when(
|
| 95 |
-
PitchCall %in% c("StrikeSwinging", "FoulBallNotFieldable", "InPlay",
|
| 96 |
"FoulBallFieldable", "FoulBall") ~ 1,
|
| 97 |
TRUE ~ 0
|
| 98 |
),
|
|
@@ -182,98 +349,116 @@ clean_college_data <- function(data, teams = NA){
|
|
| 182 |
),
|
| 183 |
is_k = case_when(
|
| 184 |
is_at_bat == 1 & KorBB == "Strikeout" ~ 1,
|
| 185 |
-
is_at_bat == 1 & KorBB != "Strikeout" ~ 0,
|
| 186 |
PlayResult %in% c("StrikeoutSwinging", "StrikeoutLooking") ~ 1,
|
| 187 |
TRUE ~ NA_real_
|
| 188 |
-
),
|
| 189 |
is_put_away = case_when(
|
| 190 |
Strikes == 2 & KorBB == "Strikeout" ~ 1,
|
| 191 |
Strikes == 2 & KorBB != "Strikeout" ~ 0,
|
| 192 |
Strikes == 2 & PlayResult %in% c("StrikeoutSwinging", "StrikeoutLooking") ~ 1,
|
| 193 |
TRUE ~ NA_real_
|
| 194 |
-
),
|
| 195 |
OutsOnPlay = ifelse(KorBB == "Strikeout" | PlayResult %in% c("StrikeoutSwinging", "StrikeoutLooking"), OutsOnPlay + 1, OutsOnPlay)
|
| 196 |
)
|
| 197 |
-
|
| 198 |
-
data <- data %>%
|
| 199 |
mutate(event_type = case_when(
|
| 200 |
-
PitchCall %in% c("StrikeSwinging", "StrkeSwinging") ~ "Whiff",
|
| 201 |
-
PitchCall %in% c("StriekC", "StrikeCalled") ~ "Called Strike",
|
| 202 |
PitchCall %in% c("FoulBallFieldable", "FoulBall", "FoulBallNotFieldable",
|
| 203 |
-
"FouldBallNotFieldable") ~ "Foul Ball",
|
| 204 |
-
PitchCall %in% c("BallCalled", "BallinDirt", "BallIntentional", "BalIntentional") ~ "Ball",
|
| 205 |
-
PitchCall == "HitByPitch" ~ "HBP",
|
| 206 |
PitchCall == "InPlay" & PlayResult %in% c("Out", "FieldersChoice",
|
| 207 |
-
"Error", "error",
|
| 208 |
-
"Sacrifice") ~ "Field Out",
|
| 209 |
-
PitchCall == "InPlay" & PlayResult == "Single" ~ "Single",
|
| 210 |
PitchCall == "InPlay" & PlayResult == "Double" ~ "Double",
|
| 211 |
PitchCall == "InPlay" & PlayResult == "Triple" ~ "Triple",
|
| 212 |
-
PitchCall == "InPlay" & PlayResult == "Homerun" ~ "Home Run",
|
| 213 |
T ~ NA
|
| 214 |
))
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 218 |
data <- data %>%
|
| 219 |
dplyr::select(
|
| 220 |
-
-any_of(c(
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
|
|
|
|
|
|
| 226 |
-starts_with("PitchTrajectory"),
|
| 227 |
-starts_with("HitTrajectory")
|
| 228 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 229 |
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
any(TaggedPitchType == "Fastball") ~ "Fastball",
|
| 253 |
-
any(TaggedPitchType == "Sinker") ~ "Sinker",
|
| 254 |
-
TRUE ~ names(sort(table(TaggedPitchType), decreasing = TRUE))[1]
|
| 255 |
-
)
|
| 256 |
-
) %>%
|
| 257 |
-
group_by(Pitcher, GameID, primary_pitch) %>%
|
| 258 |
-
mutate(
|
| 259 |
-
primary_az0 = mean(az0[TaggedPitchType == primary_pitch], na.rm = TRUE),
|
| 260 |
-
primary_velo = mean(RelSpeed[TaggedPitchType == primary_pitch], na.rm = TRUE)
|
| 261 |
-
) %>%
|
| 262 |
-
ungroup() %>%
|
| 263 |
-
mutate(az0_diff = az0 - primary_az0,
|
| 264 |
-
velo_diff = RelSpeed - primary_velo)
|
| 265 |
-
|
| 266 |
-
df_processed <- bake(stuffplus_recipe, new_data = predict_data)
|
| 267 |
-
df_matrix <- as.matrix(df_processed)
|
| 268 |
-
raw_stuff <- predict(stuffplus_model, df_matrix)
|
| 269 |
-
|
| 270 |
-
data$raw_stuff <- raw_stuff
|
| 271 |
data <- data %>%
|
| 272 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 273 |
|
| 274 |
return(data)
|
|
|
|
|
|
|
| 275 |
}
|
| 276 |
|
|
|
|
| 277 |
# ============================================================
|
| 278 |
# CSS
|
| 279 |
# ============================================================
|
|
|
|
| 32 |
}
|
| 33 |
|
| 34 |
# ============================================================
|
| 35 |
+
# READ IN PRIVATE DATASETS AND MODELS FOR CLEANING -- All in a private dataset on the main (HyannisHarborHawksCCBL) Huggingface
|
| 36 |
# ============================================================
|
| 37 |
+
|
| 38 |
+
download_private_parquet <- function(repo_id, filename, max_retries = 3) {
|
| 39 |
+
url <- paste0("https://huggingface.co/datasets/", repo_id, "/resolve/main/", filename)
|
| 40 |
+
api_key <- Sys.getenv("HF_READ_TOKEN")
|
| 41 |
+
if (api_key == "") stop("API key is not set.")
|
| 42 |
+
for (attempt in 1:max_retries) {
|
| 43 |
+
tryCatch({
|
| 44 |
+
response <- GET(url, add_headers(Authorization = paste("Bearer", api_key)), timeout(60))
|
| 45 |
+
if (status_code(response) == 200) {
|
| 46 |
+
tmp <- tempfile(fileext = ".parquet")
|
| 47 |
+
writeBin(content(response, "raw"), tmp)
|
| 48 |
+
return(read_parquet(tmp))
|
| 49 |
+
} else warning(paste("Attempt", attempt, "failed with status:", status_code(response)))
|
| 50 |
+
}, error = function(e) {
|
| 51 |
+
warning(paste("Attempt", attempt, "failed:", e$message))
|
| 52 |
+
if (attempt < max_retries) Sys.sleep(2)
|
| 53 |
+
})
|
| 54 |
}
|
| 55 |
+
stop(paste("Failed to download dataset after", max_retries, "attempts"))
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
download_private_csv <- function(repo_id, filename, max_retries = 3) {
|
| 59 |
+
url <- paste0("https://huggingface.co/datasets/", repo_id, "/resolve/main/", filename)
|
| 60 |
+
api_key <- Sys.getenv("HF_READ_TOKEN")
|
| 61 |
+
if (api_key == "") stop("API key is not set.")
|
| 62 |
+
for (attempt in 1:max_retries) {
|
| 63 |
+
tryCatch({
|
| 64 |
+
response <- GET(url, add_headers(Authorization = paste("Bearer", api_key)), timeout(60))
|
| 65 |
+
if (status_code(response) == 200) {
|
| 66 |
+
tmp <- tempfile(fileext = ".csv")
|
| 67 |
+
writeBin(content(response, "raw"), tmp)
|
| 68 |
+
return(readr::read_csv(tmp, show_col_types = FALSE))
|
| 69 |
+
} else warning(paste("Attempt", attempt, "failed with status:", status_code(response)))
|
| 70 |
+
}, error = function(e) {
|
| 71 |
+
warning(paste("Attempt", attempt, "failed:", e$message))
|
| 72 |
+
if (attempt < max_retries) Sys.sleep(2)
|
| 73 |
+
})
|
| 74 |
+
}
|
| 75 |
+
stop(paste("Failed to download dataset after", max_retries, "attempts"))
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
download_private_xgb <- function(repo_id, filename, max_retries = 3) {
|
| 79 |
+
url <- paste0("https://huggingface.co/datasets/", repo_id, "/resolve/main/", filename)
|
| 80 |
+
api_key <- Sys.getenv("HF_READ_TOKEN")
|
| 81 |
+
if (api_key == "") stop("API key is not set.")
|
| 82 |
+
for (attempt in 1:max_retries) {
|
| 83 |
+
tryCatch({
|
| 84 |
+
response <- GET(url, add_headers(Authorization = paste("Bearer", api_key)), timeout(60))
|
| 85 |
+
if (status_code(response) == 200) {
|
| 86 |
+
tmp <- tempfile(fileext = ".json")
|
| 87 |
+
writeBin(content(response, "raw"), tmp)
|
| 88 |
+
return(xgb.load(tmp))
|
| 89 |
+
} else warning(paste("Attempt", attempt, "failed with status:", status_code(response)))
|
| 90 |
+
}, error = function(e) {
|
| 91 |
+
warning(paste("Attempt", attempt, "failed:", e$message))
|
| 92 |
+
if (attempt < max_retries) Sys.sleep(2)
|
| 93 |
+
})
|
| 94 |
+
}
|
| 95 |
+
stop(paste("Failed to download dataset after", max_retries, "attempts"))
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
download_private_rds <- function(repo_id, filename, max_retries = 3) {
|
| 99 |
+
url <- paste0("https://huggingface.co/datasets/", repo_id, "/resolve/main/", filename)
|
| 100 |
+
api_key <- Sys.getenv("HF_READ_TOKEN")
|
| 101 |
+
if (api_key == "") stop("API key is not set.")
|
| 102 |
+
for (attempt in 1:max_retries) {
|
| 103 |
+
tryCatch({
|
| 104 |
+
response <- GET(url, add_headers(Authorization = paste("Bearer", api_key)), timeout(60))
|
| 105 |
+
if (status_code(response) == 200) {
|
| 106 |
+
tmp <- tempfile(fileext = ".rds")
|
| 107 |
+
writeBin(content(response, "raw"), tmp)
|
| 108 |
+
return(readRDS(tmp))
|
| 109 |
+
} else warning(paste("Attempt", attempt, "failed:", e$message))
|
| 110 |
+
}, error = function(e) {
|
| 111 |
+
warning(paste("Attempt", attempt, "failed:", e$message))
|
| 112 |
+
if (attempt < max_retries) Sys.sleep(2)
|
| 113 |
+
})
|
| 114 |
+
}
|
| 115 |
+
stop(paste("Failed to download dataset after", max_retries, "attempts"))
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
#Stuff Model (XGBoost)
|
| 119 |
+
stuffplus_recipe <- download_private_rds("HyannisHarborHawksCCBL/CleanDataObjects", "stuffplus_recipe.rds")
|
| 120 |
+
stuffplus_model <- download_private_xgb("HyannisHarborHawksCCBL/CleanDataObjects", "stuffplus_xgb.json")
|
| 121 |
+
|
| 122 |
+
#xwOBA model (KNN)
|
| 123 |
+
xwoba_model <- unbundle(download_private_rds("HyannisHarborHawksCCBL/CleanDataObjects", "bundle_college_xwoba_knn.rds"))
|
| 124 |
+
|
| 125 |
+
#Data to join on (non-context run values, conferences, GUTS!, longer team names)
|
| 126 |
+
rv <- download_private_csv("HyannisHarborHawksCCBL/CleanDataObjects", "non_context_run_values.csv")
|
| 127 |
+
team_leagues <- download_private_csv("HyannisHarborHawksCCBL/CleanDataObjects", "college_team_leagues.csv")
|
| 128 |
+
college_guts <- download_private_csv("HyannisHarborHawksCCBL/CleanDataObjects", "college_GUTS.csv")
|
| 129 |
+
college_join <- download_private_csv("HyannisHarborHawksCCBL/CleanDataObjects", "college_join.csv")
|
| 130 |
+
|
| 131 |
+
#Team logo
|
| 132 |
+
logo_b64 <- base64enc::base64encode("HHLogo.png")
|
| 133 |
+
logo_uri <- paste0("data:image/png;base64,", logo_b64)
|
| 134 |
|
| 135 |
# ============================================================
|
| 136 |
+
#Clean data
|
| 137 |
# ============================================================
|
|
|
|
| 138 |
|
| 139 |
+
predict_stuffplus <- function(data) {
|
| 140 |
+
|
| 141 |
+
predict_data <- data %>%
|
| 142 |
+
mutate(RelSide = case_when(
|
| 143 |
+
PitcherThrows == "Right" ~ RelSide,
|
| 144 |
+
PitcherThrows == "Left" ~ -RelSide,
|
| 145 |
+
PitcherThrows %in% c("Both", "Undefined") & RelSide > 0 ~ RelSide,
|
| 146 |
+
PitcherThrows %in% c("Both", "Undefined") & RelSide < 0 ~ -RelSide),
|
| 147 |
+
ax0 = case_when(
|
| 148 |
+
PitcherThrows == "Right" ~ ax0,
|
| 149 |
+
PitcherThrows == "Left" ~ -ax0,
|
| 150 |
+
PitcherThrows %in% c("Both", "Undefined") & ax0 > 0 ~ ax0,
|
| 151 |
+
PitcherThrows %in% c("Both", "Undefined") & ax0 < 0 ~ -ax0),
|
| 152 |
+
ax0 = -ax0) %>%
|
| 153 |
+
group_by(Pitcher, GameID) %>%
|
| 154 |
+
mutate(
|
| 155 |
+
primary_pitch = case_when(
|
| 156 |
+
any(TaggedPitchType == "Fastball") ~ "Fastball",
|
| 157 |
+
any(TaggedPitchType == "Sinker") ~ "Sinker",
|
| 158 |
+
TRUE ~ names(sort(table(TaggedPitchType), decreasing = TRUE))[1]
|
| 159 |
+
)
|
| 160 |
+
) %>%
|
| 161 |
+
ungroup() %>%
|
| 162 |
+
group_by(Pitcher, GameID, primary_pitch) %>%
|
| 163 |
+
mutate(
|
| 164 |
+
primary_az0 = mean(az0[TaggedPitchType == primary_pitch], na.rm = TRUE),
|
| 165 |
+
primary_velo = mean(RelSpeed[TaggedPitchType == primary_pitch], na.rm = TRUE)
|
| 166 |
+
) %>%
|
| 167 |
+
ungroup() %>%
|
| 168 |
+
mutate(az0_diff = az0 - primary_az0,
|
| 169 |
+
velo_diff = RelSpeed - primary_velo)
|
| 170 |
+
|
| 171 |
+
|
| 172 |
+
df_processed <- bake(stuffplus_recipe, new_data = predict_data)
|
| 173 |
+
|
| 174 |
+
df_matrix <- as.matrix(df_processed)
|
| 175 |
+
|
| 176 |
+
raw_stuff <- predict(stuffplus_model, df_matrix)
|
| 177 |
+
|
| 178 |
+
data$raw_stuff <- raw_stuff
|
| 179 |
+
|
| 180 |
+
|
| 181 |
data <- data %>%
|
| 182 |
+
mutate(stuff_plus = ((raw_stuff - 0.004424894) / 0.01010482) * 10 + 100)
|
| 183 |
+
|
| 184 |
+
return(data)
|
| 185 |
+
|
| 186 |
+
}
|
| 187 |
+
|
| 188 |
+
#===================================================================================================
|
| 189 |
+
#Clean College Data Function (includes predicting stuffplus)
|
| 190 |
+
|
| 191 |
+
|
| 192 |
+
clean_college_data <- function(data, teams = NA) {
|
| 193 |
+
|
| 194 |
+
|
| 195 |
+
data <- data %>%
|
| 196 |
+
mutate(PlayResult = ifelse(PlayResult %in% c("HomeRun", "homerun"), "Homerun", PlayResult),
|
| 197 |
+
Batter = sub("(.*),\\s*(.*)", "\\2 \\1", Batter),
|
| 198 |
Pitcher = sub("(.*),\\s*(.*)", "\\2 \\1", Pitcher),
|
| 199 |
Catcher = sub("(.*),\\s*(.*)", "\\2 \\1", Catcher))
|
| 200 |
+
|
| 201 |
+
|
| 202 |
col <- colnames(data)
|
| 203 |
+
|
| 204 |
+
if ("Top/Bottom" %in% col){
|
| 205 |
+
data <- data %>%
|
| 206 |
+
rename(`Top.Bottom` = `Top/Bottom`)
|
| 207 |
}
|
| 208 |
+
|
| 209 |
+
#Fix column types
|
| 210 |
numeric_columns <- c("PitchNo", "PAofInning", "PitchofPA", "PitcherId", "BatterId", "Inning", "Outs", "Balls",
|
| 211 |
+
"Strikes", "OutsOnPlay", "RunsScored", "RelSpeed", "VertRelAngle", "HorzRelAngle", "SpinRate",
|
| 212 |
+
"SpinAxis", "RelHeight", "RelSide", "Extension", "VertBreak", "InducedVertBreak", "HorzBreak",
|
| 213 |
+
"PlateLocHeight", "PlateLocSide", "ZoneSpeed", "VertApprAngle", "HorzApprAngle", "ZoneTime",
|
| 214 |
"ExitSpeed", "Angle", "Direction", "HitSpinRate", "Distance", "Bearing", "HangTime",
|
| 215 |
+
"LastTrackedDistance", "pfxx", "pfxz", "x0", "y0", "z0", "vx0", "vz0", "vy0", "ax0", "ay0",
|
| 216 |
"az0", "EffectiveVelo", "MaxHeight", "SpeedDrop", "ContactPositionX", "ContactPositionY",
|
| 217 |
"ContactPositionZ", "HomeTeamForeignID", "AwayTeamForeignID", "CatcherId", "ThrowSpeed",
|
| 218 |
+
"PopTime", "ExchangeTime", "TimeToBase", "OutsOnPlay")
|
| 219 |
+
data <- data %>%
|
| 220 |
+
mutate(across(any_of(numeric_columns), ~suppressWarnings(as.numeric(.x))))
|
| 221 |
+
|
| 222 |
+
|
| 223 |
+
#Correct for last, first
|
| 224 |
+
data <- data %>%
|
| 225 |
+
mutate(
|
| 226 |
+
Batter = ifelse(str_detect(Batter, ","),
|
| 227 |
+
str_trim(sub("^([^,]*),\\s*(.*)$", "\\2 \\1", Batter)),
|
| 228 |
+
Batter),
|
| 229 |
+
Pitcher = ifelse(str_detect(Pitcher, ","),
|
| 230 |
+
str_trim(sub("^([^,]*),\\s*(.*)$", "\\2 \\1", Pitcher)),
|
| 231 |
+
Pitcher)
|
| 232 |
+
)
|
| 233 |
+
|
| 234 |
+
#Fix plate locations where unit is feet and not inches
|
| 235 |
+
data <- data %>%
|
| 236 |
+
group_by(GameID) %>%
|
| 237 |
+
mutate(
|
| 238 |
+
PlateLocHeight = PlateLocHeight * if (isTRUE(sd(PlateLocHeight, na.rm = TRUE) < 5)) 12 else 1,
|
| 239 |
+
PlateLocSide = PlateLocSide * if (isTRUE(sd(PlateLocSide, na.rm = TRUE) < 5)) 12 else 1
|
| 240 |
+
) %>%
|
| 241 |
+
ungroup()
|
| 242 |
+
|
| 243 |
+
|
| 244 |
+
#fix pitch tagging
|
| 245 |
+
data <- data %>%
|
| 246 |
mutate(TaggedPitchType = case_when(
|
| 247 |
+
TaggedPitchType == "FourSeamFastBall" ~ "Fastball",
|
| 248 |
+
TaggedPitchType %in% c("TwoSeamFastBall", "OneSeamFastBall") ~ "Sinker",
|
| 249 |
+
TaggedPitchType == "ChangeUp" ~ "Changeup",
|
| 250 |
+
TaggedPitchType == "Undefined" ~ "Other",
|
| 251 |
T ~ TaggedPitchType
|
| 252 |
))
|
| 253 |
+
|
| 254 |
+
#Main aggregations
|
| 255 |
data <- data %>%
|
| 256 |
mutate(
|
| 257 |
is_csw = case_when(
|
|
|
|
| 259 |
TRUE ~ 0
|
| 260 |
),
|
| 261 |
is_swing = case_when(
|
| 262 |
+
PitchCall %in% c("StrikeSwinging", "FoulBallNotFieldable", "InPlay",
|
| 263 |
"FoulBallFieldable", "FoulBall") ~ 1,
|
| 264 |
TRUE ~ 0
|
| 265 |
),
|
|
|
|
| 349 |
),
|
| 350 |
is_k = case_when(
|
| 351 |
is_at_bat == 1 & KorBB == "Strikeout" ~ 1,
|
| 352 |
+
is_at_bat == 1 & KorBB != "Strikeout" ~ 0,
|
| 353 |
PlayResult %in% c("StrikeoutSwinging", "StrikeoutLooking") ~ 1,
|
| 354 |
TRUE ~ NA_real_
|
| 355 |
+
),
|
| 356 |
is_put_away = case_when(
|
| 357 |
Strikes == 2 & KorBB == "Strikeout" ~ 1,
|
| 358 |
Strikes == 2 & KorBB != "Strikeout" ~ 0,
|
| 359 |
Strikes == 2 & PlayResult %in% c("StrikeoutSwinging", "StrikeoutLooking") ~ 1,
|
| 360 |
TRUE ~ NA_real_
|
| 361 |
+
),
|
| 362 |
OutsOnPlay = ifelse(KorBB == "Strikeout" | PlayResult %in% c("StrikeoutSwinging", "StrikeoutLooking"), OutsOnPlay + 1, OutsOnPlay)
|
| 363 |
)
|
| 364 |
+
|
| 365 |
+
data <- data %>%
|
| 366 |
mutate(event_type = case_when(
|
| 367 |
+
PitchCall %in% c("StrikeSwinging", "StrkeSwinging") ~ "Whiff",
|
| 368 |
+
PitchCall %in% c("StriekC", "StrikeCalled") ~ "Called Strike",
|
| 369 |
PitchCall %in% c("FoulBallFieldable", "FoulBall", "FoulBallNotFieldable",
|
| 370 |
+
"FouldBallNotFieldable") ~ "Foul Ball",
|
| 371 |
+
PitchCall %in% c("BallCalled", "BallinDirt", "BallIntentional", "BalIntentional") ~ "Ball",
|
| 372 |
+
PitchCall == "HitByPitch" ~ "HBP",
|
| 373 |
PitchCall == "InPlay" & PlayResult %in% c("Out", "FieldersChoice",
|
| 374 |
+
"Error", "error",
|
| 375 |
+
"Sacrifice") ~ "Field Out",
|
| 376 |
+
PitchCall == "InPlay" & PlayResult == "Single" ~ "Single",
|
| 377 |
PitchCall == "InPlay" & PlayResult == "Double" ~ "Double",
|
| 378 |
PitchCall == "InPlay" & PlayResult == "Triple" ~ "Triple",
|
| 379 |
+
PitchCall == "InPlay" & PlayResult == "Homerun" ~ "Home Run",
|
| 380 |
T ~ NA
|
| 381 |
))
|
| 382 |
+
|
| 383 |
+
|
| 384 |
+
if (!"mean_DRE" %in% names(data)) {
|
| 385 |
+
data <- data %>%
|
| 386 |
+
left_join(rv, by = "event_type")
|
| 387 |
+
}
|
| 388 |
+
|
| 389 |
data <- data %>%
|
| 390 |
dplyr::select(
|
| 391 |
+
-any_of(c(
|
| 392 |
+
"PitchLastMeasuredX", "PitchLastMeasuredY", "PitchLastMeasuredZ",
|
| 393 |
+
"HitSpinAxis",
|
| 394 |
+
"PitchReleaseConfidence", "PitchLocationConfidence", "PitchMovementConfidence",
|
| 395 |
+
"HitLaunchConfidence", "HitLandingConfidence",
|
| 396 |
+
"CatcherThrowCatchConfidence", "CatcherThrowReleaseConfidence", "CatcherThrowLocationConfidence",
|
| 397 |
+
"PositionAt110X", "PositionAt110Y", "PositionAt110Z"
|
| 398 |
+
)),
|
| 399 |
-starts_with("PitchTrajectory"),
|
| 400 |
-starts_with("HitTrajectory")
|
| 401 |
)
|
| 402 |
+
|
| 403 |
+
data <- data %>%
|
| 404 |
+
dplyr::select(-any_of(c("HomeLeague", "AwayLeague", "PitcherLeague", "BatterLeague"))) %>%
|
| 405 |
+
left_join(team_leagues, by = "HomeTeam") %>%
|
| 406 |
+
left_join(team_leagues %>% rename(AwayLeague = HomeLeague, AwayTeam = HomeTeam), by = "AwayTeam") %>%
|
| 407 |
+
mutate(PitcherLeague = ifelse(`Top.Bottom` == "Top", HomeLeague, AwayLeague),
|
| 408 |
+
BatterLeague = ifelse(`Top.Bottom` == "Top", AwayLeague, HomeLeague),
|
| 409 |
+
HomeLeague = replace_na(HomeLeague, "Other"),
|
| 410 |
+
AwayLeague = replace_na(AwayLeague, "Other"),
|
| 411 |
+
PitcherLeague = replace_na(PitcherLeague, "Other"),
|
| 412 |
+
BatterLeague = replace_na(BatterLeague, "Other")
|
| 413 |
+
)
|
| 414 |
+
|
| 415 |
|
| 416 |
+
|
| 417 |
+
if (!"stuff_plus" %in% names(data)) {
|
| 418 |
+
data <- predict_stuffplus(data)
|
| 419 |
+
}
|
| 420 |
+
|
| 421 |
+
data <- data %>%
|
| 422 |
+
filter(!(Level %in% c("D2", "JUCO", "NAIA", "D3")))
|
| 423 |
+
|
| 424 |
+
#predict xwoba
|
| 425 |
+
data <- data %>% mutate(row_id = row_number())
|
| 426 |
+
|
| 427 |
+
prob_cols <- c("prob_0", "prob_1", "prob_2", "prob_3", "prob_4")
|
| 428 |
+
|
| 429 |
+
complete <- data %>%
|
| 430 |
+
filter(!is.na(ExitSpeed) & !is.na(Angle)) %>%
|
| 431 |
+
dplyr::select(-any_of(prob_cols))
|
| 432 |
+
|
| 433 |
+
xwoba_preds <- predict(xwoba_model, complete, type = "prob")
|
| 434 |
+
colnames(xwoba_preds) <- prob_cols
|
| 435 |
+
|
| 436 |
+
complete <- complete %>% bind_cols(xwoba_preds)
|
| 437 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 438 |
data <- data %>%
|
| 439 |
+
dplyr::select(-any_of(prob_cols)) %>%
|
| 440 |
+
left_join(complete %>% dplyr::select(row_id, any_of(prob_cols)), by = "row_id") %>%
|
| 441 |
+
dplyr::select(-row_id)
|
| 442 |
+
|
| 443 |
+
data <- data %>%
|
| 444 |
+
dplyr::select(-any_of(c("w1B", "w2B", "w3B", "wHR"))) %>%
|
| 445 |
+
cross_join(college_guts %>% dplyr::select(w1B, w2B, w3B, wHR)) %>%
|
| 446 |
+
mutate(xwOBA = w1B*prob_1 + w2B*prob_2 + w3B*prob_3 + wHR*prob_4 + 0*prob_0,
|
| 447 |
+
xwOBA = case_when(
|
| 448 |
+
KorBB == "Strikeout" ~ 0,
|
| 449 |
+
KorBB == "Walk" ~ .82,
|
| 450 |
+
event_type == "HBP" ~ .85,
|
| 451 |
+
TRUE ~ xwOBA
|
| 452 |
+
))
|
| 453 |
+
|
| 454 |
+
message(nrow(data), " rows cleaned.")
|
| 455 |
|
| 456 |
return(data)
|
| 457 |
+
|
| 458 |
+
|
| 459 |
}
|
| 460 |
|
| 461 |
+
|
| 462 |
# ============================================================
|
| 463 |
# CSS
|
| 464 |
# ============================================================
|