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)