sugitora's picture
Update app.R
d10bebf verified
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)