Load Packages

library(tidyverse)
## Warning: package 'tidyverse' was built under R version 4.4.3
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.1.4     ✔ readr     2.1.5
## ✔ forcats   1.0.0     ✔ stringr   1.5.1
## ✔ ggplot2   3.5.1     ✔ tibble    3.2.1
## ✔ lubridate 1.9.3     ✔ tidyr     1.3.1
## ✔ purrr     1.0.2     
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(nflfastR)
## Warning: package 'nflfastR' was built under R version 4.4.3
library(formattable)
## Warning: package 'formattable' was built under R version 4.4.3
library(circlize)
## Warning: package 'circlize' was built under R version 4.4.3
## ========================================
## circlize version 0.4.16
## CRAN page: https://cran.r-project.org/package=circlize
## Github page: https://github.com/jokergoo/circlize
## Documentation: https://jokergoo.github.io/circlize_book/book/
## 
## If you use it in published research, please cite:
## Gu, Z. circlize implements and enhances circular visualization
##   in R. Bioinformatics 2014.
## 
## This message can be suppressed by:
##   suppressPackageStartupMessages(library(circlize))
## ========================================

Load Play-By-Play

pbp <- load_pbp(2025)

Get Receivers

receivers <- c("L.McConkey", "K.Allen", "Q.Johnston", "T.Harris", "W.Dissly", "T.Conklin", "O.Gadsden", "O.Hampton")

Create Receiver Data Function

get_receiver_data <- function(receiver_input, pbp) {
  receiver_data <- pbp %>%
    filter(posteam == "LAC", receiver_player_name == receiver_input) %>%
    group_by(receiver_player_name) %>%
    summarize(targets = n(), mean_epa = round(mean(epa, na.rm = TRUE), 3), suc_rate = round(mean(success == 1, na.rm = TRUE), 3)) %>%
    mutate(receiver = receiver_player_name) %>%
    select(receiver, targets, mean_epa, suc_rate)
  
  return(receiver_data)
}

Get Results

results <- tibble()
for (receiver in receivers) {
  results <- bind_rows(results, get_receiver_data(receiver, pbp))
}
results <- results %>% arrange(-mean_epa)

Calculate League Data

target_data <- pbp %>%
  filter(pass == 1, !is.na(receiver_player_name))

epaQ1 <- quantile(target_data$epa, probs = 0.25)
epaMean <- mean(target_data$epa)
epaQ3 <- quantile(target_data$epa, probs = 0.75)

successQ1 <- quantile(target_data$success, probs = 0.25)
successMean <- mean(target_data$success)
successQ3 <- quantile(target_data$success, probs = 0.75)

Format Data and Print

epa_ramp <- colorRamp2(
  breaks = c(epaQ1, epaMean, epaQ3),
  colors = c("red", "white", "green")
)

success_ramp <- colorRamp2(
  breaks = c(successQ1, successMean, successQ3),
  colors = c("red", "white", "green")
)

formattable(results, list(
  mean_epa = formatter(
    "span",
    style = ~ style(
      display = "inline-block",
      width = "70px",
      padding = "0 4px",
      `border-radius` = "4px",
      `background-color` = epa_ramp(mean_epa),
      `text-align` = "center"
    )
  ),
  suc_rate = formatter(
    "span",
    style = ~ style(
      display = "inline-block",
      width = "60px",
      padding = "0 4px",
      `border-radius` = "4px",
      `background-color` = success_ramp(suc_rate),
      `text-align` = "center"
    )
  )
))
receiver targets mean_epa suc_rate
K.Allen 28 0.584 0.607
W.Dissly 2 0.551 0.500
Q.Johnston 24 0.460 0.417
L.McConkey 21 0.448 0.619
T.Harris 5 0.311 0.600
T.Conklin 4 0.265 0.500
O.Hampton 11 0.142 0.545
O.Gadsden 7 0.059 0.571