jameson-bodenburg's picture
Update app.R
2de8a1e verified
library(dplyr)
library(shiny)
library(DT)
library(RColorBrewer)
library(scales)
library(readr)
library(reactable)
library(fmsb)
#This project has the goal of creating a shiny app to give the most similar current NBA player to current college players,
#with the hopes of determining logical player comps for draft prospects.
stats25 <- read_csv("stats25.csv")
stats_past <- read_csv("stats_past.csv")
rd_bu_colors <- colorRampPalette(rev(brewer.pal(11, "RdBu")))
palette_100 <- rd_bu_colors(100)
ui <- fluidPage(
titlePanel("Find the Most Similar College Player"),
sidebarLayout(
sidebarPanel(
selectizeInput("selected_player", "Select a Player:", choices = NULL, multiple = FALSE, options = list(maxOptions = 1000)),
actionButton("find_button", "Find Most Similar Players")
),
mainPanel(
tabsetPanel(
tabPanel("Similar Players", reactableOutput("comparison_table")),
tabPanel("Radar Plot",
plotOutput("radar_plot", height = "600px"))
)
)
)
)
server <- function(input, output, session) {
observe({
updateSelectizeInput(session, "selected_player", choices = unique(stats25$name), server = TRUE)
})
find_most_similar_players <- function(input_player_name) {
stat_cols <- c("ppg", "apg", "rpg", "spg", "bpg", "fg_pct", "fg3_pct", "to", "fgm", "fga", "fg3m", "fg3a")
available_cols <- intersect(c("name", "season", "team", stat_cols), colnames(stats25))
available_cols_past <- intersect(c("name", "season", "team", stat_cols), colnames(stats_past))
input_player <- stats25 %>%
filter(name == input_player_name) %>%
select(all_of(available_cols))
scaled_past_stats <- stats_past %>%
mutate(across(all_of(stat_cols), ~ (.-mean(., na.rm = TRUE)) / sd(., na.rm = TRUE))) %>%
select(name, all_of(stat_cols))
input_player_scaled <- (input_player[, stat_cols] - colMeans(stats_past[stat_cols], na.rm = TRUE)) /
apply(stats_past[stat_cols], 2, sd, na.rm = TRUE)
input_player_scaled[is.na(input_player_scaled)] <- 0
input_player_scaled <- as.numeric(input_player_scaled)
euclidean_distances <- rowSums((as.matrix(scaled_past_stats[, stat_cols]) -
matrix(input_player_scaled, nrow = nrow(scaled_past_stats),
ncol = length(input_player_scaled), byrow = TRUE))^2)
top_5_indices <- order(euclidean_distances)[1:5]
top_5_similar_players <- stats_past[top_5_indices, available_cols_past] # Use actual stats
comparison_df <- bind_rows(input_player, top_5_similar_players) %>%
mutate(Player_Type = ifelse(name == input_player_name, "Selected", "Similar")) %>%
mutate(across(c("ppg", "apg", "rpg", "spg", "bpg", "to", "fgm", "fga", "fg3m", "fg3a"), ~ round(., 1))) %>%
mutate(across(c("fg_pct", "fg3_pct"), ~ round(., 3))) %>%
select(name, season, team, Player_Type, everything()) %>% # Reorder columns here
rename(PPG = ppg,
APG = apg,
RPG = rpg,
SPG = spg,
BPG = bpg,
`FG%` = fg_pct,
`3P%` = fg3_pct,
TO = to,
FGM = fgm,
FGA = fga,
FG3M = fg3m,
FG3A = fg3a)
return(comparison_df)
}
observeEvent(input$find_button, {
stat_cols <- c("PPG", "APG", "RPG", "SPG", "BPG", "FG%", "3P%", "TO", "FGM", "FGA", "FG3M", "FG3A")
comparison_df <- find_most_similar_players(input$selected_player)
output$comparison_table <- renderReactable({
reactable(
comparison_df,
columns = c(
list(
Player_Type = colDef(show = FALSE),
name = colDef(name = "Player"),
season = colDef(name = "Season"),
team = colDef(name = "Team")
),
setNames(lapply(stat_cols, function(stat) {
colDef(
style = function(value, index) {
if (comparison_df$Player_Type[index] == "Selected") return(NULL)
selected_value <- comparison_df %>% filter(Player_Type == "Selected") %>% pull(!!sym(stat))
diff <- value - selected_value
if (stat %in% c("FG%", "3P%")) {
rescaled_value <- rescale(diff, from = c(-0.1, 0.1), to = c(0, 1))
} else {
rescaled_value <- rescale(diff, from = c(-2, 2), to = c(0, 1))
}
rescaled_value <- pmin(pmax(rescaled_value, 0), 1)
color <- palette_100[round(rescaled_value * 99) + 1]
rgb_values <- col2rgb(color) / 255
luminance <- 0.2126 * rgb_values[1] + 0.7152 * rgb_values[2] + 0.0722 * rgb_values[3]
text_color <- ifelse(luminance > 0.5, "black", "white")
list(background = color, color = text_color)
}
)
}), stat_cols)
),
bordered = TRUE,
striped = TRUE,
highlight = TRUE
)
})
output$radar_plot <- renderPlot({
req(input$selected_player)
stats_columns <- c("mpg", "ppg", "apg", "rpg", "spg", "bpg", "fg_pct", "fg3_pct")
percentile_data <- stats25 %>%
ungroup() %>%
mutate(across(all_of(stats_columns), ~ percent_rank(.) * 100))
player_percentiles <- percentile_data %>%
filter(name == input$selected_player) %>%
select(all_of(stats_columns))
if (nrow(player_percentiles) == 0) {
return(NULL)
}
radar_df <- rbind(rep(100, length(stats_columns)),
rep(0, length(stats_columns)),
player_percentiles)
colnames(radar_df) <- stats_columns
radar_df <- radar_df %>%
rename(MPG = mpg,
PPG = ppg,
APG = apg,
RPG = rpg,
SPG = spg,
BPG = bpg,
`FG%` = fg_pct,
`3P%` = fg3_pct)
radarchart(
radar_df,
axistype = 1,
pcol = "darkgreen",
pfcol = rgb(0, 0.5, 0, 0.3),
plwd = 2,
cglcol = "gray",
cglty = 1,
axislabcol = "black",
cglwd = 0.8,
vlcex = 1.5,
title = paste("Radar Plot for", input$selected_player)
)
})
})
}
shinyApp(ui, server)