7.6 Modeling Catcher Framing

taken <- taken |>
  filter(
    is.na(plate_x) == FALSE,
    is.na(plate_z) == FALSE
  ) |> 
  mutate(
    strike_prob = predict(
      strike_mod, 
      type = "response"
    )
  )

\[\log \frac{p_j}{1 - p_j} = \beta_0 + \beta_1 \cdot strike\_prob_j + \alpha_{c(j)}\]

We fit a generalized linear mixed model using fixed effects from the catcher.

library(lme4)
mod_a <- glmer(
  Outcome == "called_strike" ~ 
    strike_prob + (1|fielder_2_1),
  data = taken, 
  family = binomial
)
fixed.effects(mod_a)

# (Intercept) strike_prob 
#       -4.00        7.67 

VarCorr(mod_a)

# Groups      Name        Std.Dev.
#  fielder_2_1 (Intercept) 0.218  
c_effects <- mod_a |>
  ranef() |>
  as_tibble() |>
  transmute(
    id = as.numeric(levels(grp)), 
    effect = condval
  )
master_id <- baseballr::chadwick_player_lu() |> 
  mutate(
    mlb_name = paste(name_first, name_last),
    mlb_id = key_mlbam
  ) |> 
  select(mlb_id, mlb_name) |> 
  filter(!is.na(mlb_id))

c_effects <- c_effects |>
  left_join(
    select(master_id, mlb_id, mlb_name),
    join_by(id == mlb_id)
  ) |>
  arrange(desc(effect))

c_effects |> slice_head(n = 6)

# A tibble: 6 × 3
#       id effect mlb_name         
#    <dbl>  <dbl> <chr>            
# 1 664848  0.358 Donny Sands      
# 2 669004  0.294 MJ Melendez      
# 3 642020  0.287 Chuckie Robinson 
# 4 672832  0.275 Israel Pineda    
# 5 571912  0.260 Luke Maile       
# 6 575929  0.243 Willson Contreras

c_effects |> slice_tail(n = 6)

# A tibble: 6 × 3
#       id effect mlb_name         
#    <dbl>  <dbl> <chr>            
# 1 664731 -0.293 P. J. Higgins    
# 2 455139 -0.304 Robinson Chirinos
# 3 661388 -0.336 William Contreras
# 4 608360 -0.357 Chris Okey       
# 5 435559 -0.357 Kurt Suzuki      
# 6 595956 -0.390 Cam Gallagher  

\[\log \frac{p_j}{1 - p_j} = \beta_0 + \beta_1 strike\_prob_j + \alpha_{c(j)} + \gamma_{p(j)} + \delta_{b(j)}\]

We add to the model with pitcher and batter effects.

mod_b <- glmer(
  Outcome == "called_strike" ~ strike_prob + 
    (1|fielder_2_1) +
    (1|batter) + (1|pitcher),
  data = taken, 
  family = binomial
)

VarCorr(mod_b)

# Groups      Name        Std.Dev.
#  pitcher     (Intercept) 0.267   
#  batter      (Intercept) 0.251   
#  fielder_2_1 (Intercept) 0.209 
c_effects <- mod_b |>
  ranef() |>
  as_tibble() |>
  filter(grpvar == "fielder_2_1") |>
  transmute(
    id = as.numeric(as.character(grp)), 
    effect = condval
  )
c_effects <- c_effects |>
  left_join(
    select(master_id, mlb_id, mlb_name),
    join_by(id == mlb_id)
  ) |>
  arrange(desc(effect))

c_effects |> slice_head(n = 6)

# A tibble: 6 × 3
#       id effect mlb_name       
#    <dbl>  <dbl> <chr>          
# 1 624431  0.313 Jose Trevino   
# 2 669221  0.277 Sean Murphy    
# 3 425877  0.263 Yadier Molina  
# 4 664874  0.253 Seby Zavala    
# 5 543309  0.229 Kyle Higashioka
# 6 608700  0.221 Kevin Plawecki 

c_effects |> slice_tail(n = 6)

# A tibble: 6 × 3
#       id effect mlb_name         
#    <dbl>  <dbl> <chr>            
# 1 596117 -0.277 Garrett Stubbs   
# 2 435559 -0.281 Kurt Suzuki      
# 3 521692 -0.291 Salvador Perez   
# 4 553869 -0.327 Elias Díaz       
# 5 455139 -0.336 Robinson Chirinos
# 6 669004 -0.347 MJ Melendez