Spaces:
Sleeping
Sleeping
| library(shiny) | |
| library(shinydashboard) | |
| library(ggplot2) | |
| library(dplyr) | |
| library(readr) | |
| library(tidyr) | |
| library(cluster) | |
| library(randomForest) | |
| library(DT) | |
| # データ読み込み | |
| csv_path <- "FullGamePitchData.csv" | |
| df <- read_csv(csv_path, locale = locale(encoding = "UTF-8")) %>% | |
| mutate( | |
| 球速 = as.numeric(gsub("km/h", "", 球速)), | |
| 座標top = as.numeric(座標top), | |
| 座標left = as.numeric(座標left) | |
| ) | |
| ui <- dashboardPage( | |
| dashboardHeader(title = "NPB 投球分析"), | |
| dashboardSidebar( | |
| sidebarMenu( | |
| menuItem("分析結果", tabName = "analysis", icon = icon("chart-area")), | |
| menuItem("データベース", tabName = "data", icon = icon("table")), | |
| radioButtons("role", "分析対象:", choices = c("投手", "打者")), | |
| uiOutput("name_selector"), | |
| actionButton("go", "分析実行"), | |
| br(), | |
| selectInput("filter_date", "日付で絞り込み:", choices = c("すべて", unique(df$日付))), | |
| selectInput("filter_pitcher", "投手で絞り込み:", choices = c("すべて", unique(df$投手_名前))), | |
| selectInput("filter_batter", "打者で絞り込み:", choices = c("すべて", unique(df$打者_名前))) | |
| ) | |
| ), | |
| dashboardBody( | |
| tabItems( | |
| tabItem(tabName = "analysis", | |
| fluidRow( | |
| box(title = "ヒートマップ", width = 6, plotOutput("heatmap")), | |
| box(title = "球種分布", width = 6, tableOutput("pitch_types")) | |
| ), | |
| fluidRow( | |
| box(title = "クラスタリング", width = 6, plotOutput("cluster_plot")), | |
| box(title = "次の球種予測", width = 6, textOutput("next_pitch")) | |
| ) | |
| ), | |
| tabItem(tabName = "data", | |
| fluidRow( | |
| box(title = "全投球データ", width = 12, DTOutput("full_data")) | |
| ) | |
| ) | |
| ) | |
| ) | |
| ) | |
| server <- function(input, output, session) { | |
| output$name_selector <- renderUI({ | |
| choices <- if (input$role == "投手") unique(na.omit(df$投手_名前)) else unique(na.omit(df$打者_名前)) | |
| selectInput("name", paste0(input$role, "名を選択:"), choices = choices) | |
| }) | |
| filtered_data <- eventReactive(input$go, { | |
| col <- if (input$role == "投手") "投手_名前" else "打者_名前" | |
| df %>% filter(!!sym(col) == input$name) | |
| }) | |
| output$heatmap <- renderPlot({ | |
| d <- filtered_data() | |
| if (nrow(d) < 1) return(plot.new()) | |
| ggplot(d, aes(x = 座標left, y = 座標top)) + | |
| stat_density2d(aes(fill = ..level..), geom = "polygon") + | |
| scale_fill_viridis_c() + | |
| ggtitle(paste(input$name, "のヒートマップ")) + | |
| scale_y_reverse() + | |
| theme_minimal() | |
| }) | |
| output$pitch_types <- renderTable({ | |
| d <- filtered_data() | |
| if (nrow(d) < 1) return(NULL) | |
| d %>% count(球種, name = "投球数") | |
| }) | |
| output$cluster_plot <- renderPlot({ | |
| d <- filtered_data() %>% drop_na(球速, 座標top, 座標left) | |
| if (nrow(d) < 3) return(plot.new()) | |
| km <- kmeans(d[, c("球速", "座標top", "座標left")], centers = 3) | |
| d$cluster <- factor(km$cluster) | |
| ggplot(d, aes(x = 座標left, y = 座標top, color = cluster)) + | |
| geom_point(size = 3) + | |
| scale_y_reverse() + | |
| ggtitle(paste(input$name, "のクラスタリング")) + | |
| theme_minimal() | |
| }) | |
| output$next_pitch <- renderText({ | |
| d <- filtered_data() %>% drop_na(球速, 座標top, 座標left, 球種) | |
| if (nrow(d) < 5) return("データ不足") | |
| d$球種 <- as.factor(d$球種) | |
| model <- randomForest(球種 ~ 球速 + 座標top + 座標left, data = d) | |
| newdata <- d[nrow(d), c("球速", "座標top", "座標left")] | |
| pred <- predict(model, newdata) | |
| paste("次の球種予測:", as.character(pred)) | |
| }) | |
| filtered_table_data <- reactive({ | |
| d <- df | |
| if (input$filter_date != "すべて") d <- d[d$日付 == input$filter_date, ] | |
| if (input$filter_pitcher != "すべて") d <- d[d$投手_名前 == input$filter_pitcher, ] | |
| if (input$filter_batter != "すべて") d <- d[d$打者_名前 == input$filter_batter, ] | |
| d | |
| }) | |
| output$full_data <- renderDT({ | |
| datatable(filtered_table_data(), options = list(pageLength = 25, scrollX = TRUE)) | |
| }) | |
| } | |
| shinyApp(ui, server) | |