Questions - Does coaches challenge review process include “enhanced video”?
Takeaways - - Reviewable out of bounds are incorrectly called 7% of the time - Shooting fouls are missed or incorretly called 6% of the time while other personal fouls are missed or incorrectly called 3% of the time.
library(tidyverse)
library(httr)
library(rvest)
library(dplyr)
library(reactable)
library(reactablefmtr)
Sys.setenv("VROOM_CONNECTION_SIZE" = 131072 * 2)
url <- "https://raw.githubusercontent.com/atlhawksfanatic/L2M/master/0-data/official_nba/official_nba_l2m_api.csv"
data <- read.csv(url)
data$GameDateOut <- as.Date(data$GameDateOut, format = "%B %d, %Y")
data1 <- data %>%
rename(Player = CP,
Opponent = DP,
Date = GameDateOut) %>%
select(-c(ImposibleIndicator, Qualifier,teamIdInFavor, errorInFavor, GameDate, L2M_Comments,))
data1 <- data1 %>%
mutate(type = case_when(
str_detect(CallType, "Foul: Offensive") ~ "Offensive Foul",
str_detect(CallType, "Foul: Loose Ball") ~ "Loose Ball Foul",
str_detect(CallType, "Foul: Personal") ~ "Defensive Foul",
str_detect(CallType, "Foul: Shooting") ~ "Defensive Foul",
str_detect(CallType, "Stoppage: Out-of-Bounds") ~ "Possession",
str_detect(CallType, "Turnover: Traveling") ~ "Possession",
str_detect(CallType, "Turnover: Lost Ball Out of Bounds") ~ "Possession",
str_detect(CallType, "Turnover: Out of Bounds") ~ "Possession",
str_detect(CallType, "Turnover: Traveling") ~ "Possession",
str_detect(CallType, "Foul: Personal Take") ~ "Defensive Foul",
str_detect(CallType, "Turnover: Out of Bounds - Bad Pass Turn") ~ "Possession",
str_detect(CallType, "Turnover: Stepped out of Bounds") ~ "Possession",
str_detect(CallType, "Foul: Away from Play") ~ "Defensive Foul",
str_detect(CallType, "Violation: Defensive Goaltending") ~ "Goaltending",
str_detect(CallType, "Violation: Offensive Goaltending") ~ "Goaltending",
str_detect(CallType, "Foul: Offensive Charge") ~ "Offensive Foul",
str_detect(CallType, "Foul: Flagrant Type 1") ~ "Defensive Foul",
str_detect(CallType, "Foul: Flagrant Type 2") ~ "Defensive Foul",
str_detect(CallType, "Turnover: Lost ball") ~ "Possession",
str_detect(CallType, "Turnover: Bad Pass") ~ "Possession",
str_detect(CallType, "Turnover: Offensive Foul") ~ "Offensive Foul",
TRUE ~ "other"
)) %>%
mutate(result = case_when(
CallRatingName == "IC" ~ 1,
CallRatingName == "INC" ~ 1,
TRUE ~ 0
))
data2 <- data %>%
rename(Player = CP,
Opponent = DP,
Date = GameDateOut) %>%
select(-c(ImposibleIndicator, Qualifier,teamIdInFavor, errorInFavor, GameDate, L2M_Comments,))
data2 <- data2 %>%
mutate(type = case_when(
str_detect(CallType, "Foul: Offensive") ~ "Offensive Foul",
str_detect(CallType, "Foul: Loose Ball") ~ "Loose Ball Foul",
str_detect(CallType, "Foul: Personal") ~ "Defensive Foul",
str_detect(CallType, "Foul: Shooting") ~ "Defensive Foul",
str_detect(CallType, "Stoppage: Out-of-Bounds") ~ "Possession",
str_detect(CallType, "Turnover: Traveling") ~ "Possession",
str_detect(CallType, "Turnover: Lost Ball Out of Bounds") ~ "Possession",
str_detect(CallType, "Turnover: Out of Bounds") ~ "Possession",
str_detect(CallType, "Turnover: Traveling") ~ "Possession",
str_detect(CallType, "Foul: Personal Take") ~ "Defensive Foul",
str_detect(CallType, "Turnover: Out of Bounds - Bad Pass Turn") ~ "Possession",
str_detect(CallType, "Turnover: Stepped out of Bounds") ~ "Possession",
str_detect(CallType, "Foul: Away from Play") ~ "Defensive Foul",
str_detect(CallType, "Violation: Defensive Goaltending") ~ "Goaltending",
str_detect(CallType, "Violation: Offensive Goaltending") ~ "Goaltending",
str_detect(CallType, "Foul: Offensive Charge") ~ "Offensive Foul",
str_detect(CallType, "Foul: Flagrant Type 1") ~ "Defensive Foul",
str_detect(CallType, "Foul: Flagrant Type 2") ~ "Defensive Foul",
str_detect(CallType, "Turnover: Lost ball") ~ "Possession",
str_detect(CallType, "Turnover: Bad Pass") ~ "Possession",
str_detect(CallType, "Turnover: Offensive Foul") ~ "Offensive Foul",
TRUE ~ "other"
)) %>%
mutate(result = case_when(
CallRatingName == "IC" ~ 1,
TRUE ~ 0
))
reg_24_e <- data1 %>%
filter(Date > "2023-10-10") %>%
group_by(type) %>%
summarize(
challenges_won = sum(result),
total_challenges = n()
) %>%
mutate(error_rate = challenges_won/total_challenges) %>%
mutate_at(vars(4), round, digits = 4) %>%
filter(type != "other") %>%
rename('Incorrect/Missed Calls' = challenges_won,
'Occurrences' = total_challenges,
'Error Rate' = error_rate)
reg_24_b <- data2 %>%
filter(Date > "2023-10-10") %>%
group_by(type) %>%
summarize(
challenges_won = sum(result),
total_challenges = n()
) %>%
mutate(error_rate = challenges_won/total_challenges) %>%
mutate_at(vars(4), round, digits = 4) %>%
filter(type != "other") %>%
rename('Missed Calls' = challenges_won,
'Occurrences' = total_challenges,
'Bad Call Rate' = error_rate)
right_join(reg_24_b, reg_24_e) %>%
select(type,
Occurrences,
'Error Rate',
'Bad Call Rate') %>%
reactable( theme = espn()) %>%
add_title("Referee Analysis for the 2024 Regular Season")
data1 %>%
group_by(type) %>%
filter( Date > "2021-10-01") %>%
summarize(
challenges_won = sum(result),
total_challenges = n()
) %>%
mutate(error_rate = challenges_won/total_challenges) %>%
mutate_at(vars(4), round, digits = 4) %>%
filter(type != "other") %>%
rename('Incorrect/Missed Calls' = challenges_won,
'Incidences' = total_challenges,
'Error Rate' = error_rate) %>%
reactable( theme = espn()) %>%
add_title("Summary of 'Last 2 Minute' reports from last 3 Regular Seasons")
data1 %>%
group_by(type) %>%
summarize(
challenges_won = sum(result),
total_challenges = n()
) %>%
mutate(error_rate = challenges_won/total_challenges) %>%
mutate_at(vars(4), round, digits = 4) %>%
filter(type != "other") %>%
rename('Incorrect/Missed Calls' = challenges_won,
'Incidences' = total_challenges,
'Error Rate' = error_rate) %>%
reactable( theme = espn()) %>%
add_title("Error Rate from last 5 Regular Seasons")
data2 %>%
group_by(type) %>%
summarize(
challenges_won = sum(result),
total_challenges = n()
) %>%
mutate(error_rate = challenges_won/total_challenges) %>%
mutate_at(vars(4), round, digits = 4) %>%
filter(type != "other") %>%
rename('Missed Calls' = challenges_won,
'Incidences' = total_challenges,
'Error Rate' = error_rate) %>%
reactable( theme = espn()) %>%
add_title("Bad Call Rate from last 5 Regular Seasons")
data1 %>%
group_by(type, Difficulty) %>%
summarize(
total_challenges = n()
) %>%
filter(Difficulty == c("Difficult", "Observable", "Undetectable")) %>%
mutate(percent = case_when(
type == "Defensive Foul" ~ total_challenges/ (14 + 28270 + 1),
type == "Goaltending" ~ total_challenges/ (192 + 2),
type == "Offensive Foul" ~ total_challenges/ (4 + 9856 ),
type == "Loose Ball Foul" ~ total_challenges/ (1 + 4616),
type == "Possession" ~ total_challenges/ (1 + 2300 + 302)
)) %>%
mutate_at(vars(4), round, digits = 4) %>%
rename('Incidences' = total_challenges) %>%
reactable( theme = espn()) %>%
add_title("Summary of 'Last 2 Minute' reports from the last Regular Season")
data_difficulty <- data1 %>%
filter(Difficulty == "Undetectable")
def_fouls <- data1 %>%
filter(type == "Defensive Foul") %>%
group_by(CallType) %>%
summarize(
challenges_won = sum(result),
total_challenges = n()
) %>%
mutate(error_rate = challenges_won/total_challenges) %>%
mutate_at(vars(4), round, digits = 4) %>%
rename('Incorrect/Missed Calls' = challenges_won,
'Occurrences' = total_challenges,
'Error Rate' = error_rate)
def_fouls2 <- data2 %>%
filter(type == "Defensive Foul") %>%
group_by(CallType) %>%
summarize(
challenges_won = sum(result),
total_challenges = n()
) %>%
mutate(error_rate = challenges_won/total_challenges) %>%
mutate_at(vars(4), round, digits = 4) %>%
rename('Missed Calls' = challenges_won,
'Occurrences' = total_challenges,
'Bad Call Rate' = error_rate)
right_join(def_fouls, def_fouls2) %>%
rename('Specific Call' = CallType) %>%
select('Specific Call',
Occurrences,
'Error Rate',
'Bad Call Rate') %>%
filter(Occurrences > 20) %>%
reactable( theme = espn()) %>%
add_title("Analysis of Defensive Fouls from last 5 Regular Seasons")
off_fouls1 <- data1 %>%
filter(type == "Offensive Foul") %>%
group_by(CallType) %>%
summarize(
challenges_won = sum(result),
total_challenges = n()
) %>%
mutate(error_rate = challenges_won/total_challenges) %>%
mutate_at(vars(4), round, digits = 4) %>%
rename('Incorrect/Missed Calls' = challenges_won,
'Occurences' = total_challenges,
'Error Rate' = error_rate)
off_fouls2 <- data2 %>%
filter(type == "Offensive Foul") %>%
group_by(CallType) %>%
summarize(
challenges_won = sum(result),
total_challenges = n()
) %>%
mutate(error_rate = challenges_won/total_challenges) %>%
mutate_at(vars(4), round, digits = 4) %>%
rename('Missed Calls' = challenges_won,
'Occurences' = total_challenges,
'Bad Call Rate' = error_rate)
right_join(off_fouls1, off_fouls2) %>%
rename('Specific Type' = CallType) %>%
select('Specific Type',
Occurences,
'Error Rate',
'Bad Call Rate') %>%
filter(Occurences > 20) %>%
reactable( theme = espn()) %>%
add_title("Analysis of Offensive Fouls from last 5 Regular Seasons")
poss1 <- data1 %>%
filter(type == "Possession") %>%
group_by(CallType) %>%
summarize(
challenges_won = sum(result),
total_challenges = n()
) %>%
mutate(error_rate = challenges_won/total_challenges) %>%
mutate_at(vars(4), round, digits = 4) %>%
rename('Incorrect/Missed Calls' = challenges_won,
'Occurrences' = total_challenges,
'Error Rate' = error_rate)
poss2 <- data2 %>%
filter(type == "Possession") %>%
group_by(CallType) %>%
summarize(
challenges_won = sum(result),
total_challenges = n()
) %>%
mutate(error_rate = challenges_won/total_challenges) %>%
mutate_at(vars(4), round, digits = 4) %>%
rename('Missed Calls' = challenges_won,
'Occurrences' = total_challenges,
'Bad Call Rate' = error_rate)
right_join(poss1,poss2) %>%
rename('Specific Call' = CallType) %>%
select('Specific Call',
Occurrences,
'Error Rate',
'Bad Call Rate') %>%
filter(Occurrences > 20) %>%
reactable( theme = espn()) %>%
add_title("Analysis of Possesion based calls from last 5 Regular Seasons")
data1 %>%
reactable()