| | library(dplyr) |
| | library(shiny) |
| | library(DT) |
| | library(RColorBrewer) |
| | library(scales) |
| | library(readr) |
| | library(reactable) |
| | library(fmsb) |
| |
|
| | |
| | |
| |
|
| |
|
| |
|
| | 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] |
| | |
| | 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()) %>% |
| | 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) |
| |
|