You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将自定义match_predictor函数集成到R Shiny应用Server端?

解决R Shiny中集成match_predictor函数的问题

核心问题梳理

  • 原函数在内部执行包安装的做法不符合Shiny应用规范,易引发重复安装或加载错误
  • Server端未根据用户选择动态匹配对应联赛数据
  • 未处理函数抛出的异常(如主客场球队相同、球队不存在)
  • Reactive表达式未正确调用预测函数并传递参数

修正后的完整代码

预处理与函数修正

# 提前加载依赖包,避免函数内部重复操作
if (!require("scales")) {
  install.packages("scales")
  library(scales)
}
if (!require("shiny")) {
  install.packages("shiny")
  library(shiny)
}
if (!require("DT")) {
  install.packages("DT")
  library(DT)
}

# 假设你已提前加载各联赛数据:laliga, serieA, bundesliga, ligue1, premierleague

# 移除内部包安装逻辑的修正版match_predictor函数
match_predictor <- function(home_team, away_team, league){
  if (!(home_team %in% league$team)){warning('Home team not found.')}
  if (!(away_team %in% league$team)){stop('Away team not found.')}
  if (home_team == away_team){stop('Home team can not be equal to away team.')}    
  average_goals_scored_in_league <- sum(league$goals_for)/sum(league$matches_played)
  attack_rating <- rep(0, nrow(league))
  defense_rating <- rep(0, nrow(league))
  league$attack_rating <- attack_rating
  league$defense_rating <- defense_rating
  
  for (i in 1:nrow(league)){
    league$attack_rating[i] = (league$goals_for[i]/league$matches_played[i]) / average_goals_scored_in_league
    league$defense_rating[i] = (league$goals_against[i]/league$matches_played[i]) / average_goals_scored_in_league
  }
  
  home_idx <- which(league$team == home_team, arr.ind=TRUE)
  away_idx <- which(league$team == away_team, arr.ind=TRUE)
  
  home_expGoals <- league$attack_rating[home_idx] * league$defense_rating[away_idx] * average_goals_scored_in_league
  away_expGoals <- league$attack_rating[away_idx] * league$defense_rating[home_idx] * average_goals_scored_in_league
  
  possible_goals <- seq(0,15)
  col2 <- rep(0, length(possible_goals))
  col3 <- rep(0, length(possible_goals))
  col4 <- rep(0, length(possible_goals))
  col5 <- rep(0, length(possible_goals))
  pred_df <- data.frame(possible_goals, col2, col3, col4, col5)
  colnames(pred_df) <- c('possible_goals', home_team, away_team, paste0(home_team, "_win_chance"), paste0(away_team, "_win_chance"))
  
  for (i in 1:nrow(pred_df)){
    pred_df[i,2] <- dpois(x = pred_df$possible_goals[i], lambda = home_expGoals)
    pred_df[i,3] <- dpois(x = pred_df$possible_goals[i], lambda = away_expGoals)
  }
  for (j in 2:nrow(pred_df)){
    pred_df[j,4] <- pred_df[j,2] * sum(pred_df[1:(j-1),3])
    pred_df[j,5] <- pred_df[j,3] * sum(pred_df[1:(j-1),2])
  }
  
  column1 <- rep(0,1)
  column2 <- rep(0,1)
  column3 <- rep(0,1)
  result_df <- data.frame(column1, column2, column3)
  colnames(result_df) <- c(paste0(home_team, "_win_chance"), paste0(away_team, "_win_chance"), 'draw_chance')
  result_df[1,1] <- sum(pred_df[,4])
  result_df[1,2] <- sum(pred_df[,5])
  result_df[1,3] <- 1 - result_df[1,1] - result_df[1,2]
  result_df[1,1] <- label_percent()(result_df[1,1])
  result_df[1,2] <- label_percent()(result_df[1,2])
  result_df[1,3] <- label_percent()(result_df[1,3])
  
  list(details = pred_df, prediction = result_df)
}

Shiny UI与Server端

ui <- fluidPage(
  titlePanel("Soccer game prediction model."),
  sidebarLayout(
    sidebarPanel(
      selectInput('league', 'Select league', choices = c('laliga', 'serieA', 'bundesliga', 'ligue1', 'premierleague'), 'laliga'),
      
      conditionalPanel(
        condition = "input.league == 'laliga'",
        selectInput('home_team', 'Select home team', unique(laliga$team)),
        selectInput('away_team', 'Select away team', unique(laliga$team))
      ),
      
      conditionalPanel(
        condition = "input.league == 'serieA'",
        selectInput('home_team', 'Select home team', unique(serieA$team)),
        selectInput('away_team', 'Select away team', unique(serieA$team))
      ),
      
      conditionalPanel(
        condition = "input.league == 'bundesliga'",
        selectInput('home_team', 'Select home team', unique(bundesliga$team)),
        selectInput('away_team', 'Select away team', unique(bundesliga$team))
      ),
      
      conditionalPanel(
        condition = "input.league == 'ligue1'",
        selectInput('home_team', 'Select home team', unique(ligue1$team)),
        selectInput('away_team', 'Select away team', unique(ligue1$team))
      ),
      
      conditionalPanel(
        condition = "input.league == 'premierleague'",
        selectInput('home_team', 'Select home team', unique(premierleague$team)),
        selectInput('away_team', 'Select away team', unique(premierleague$team))
      ),
      
    ),
    mainPanel(
      DT::DTOutput("prediction_table"),
      DT::DTOutput("details_table")
    )
  )
)

server <- function(input, output, session){
  # 动态获取选中的联赛数据
  selected_league <- reactive({
    switch(input$league,
           "laliga" = laliga,
           "serieA" = serieA,
           "bundesliga" = bundesliga,
           "ligue1" = ligue1,
           "premierleague" = premierleague)
  })
  
  # 调用预测函数并捕获异常
  prediction_result <- reactive({
    req(input$home_team, input$away_team, selected_league())
    tryCatch({
      match_predictor(input$home_team, input$away_team, selected_league())
    }, error = function(e) {
      data.frame(Error = e$message)
    })
  })
  
  # 输出预测结果表格
  output$prediction_table <- DT::renderDT({
    res <- prediction_result()
    if(is.data.frame(res) && "Error" %in% colnames(res)){
      res
    } else {
      res$prediction
    }
  }, options = list(pageLength = 1))
  
  # 输出详细概率表格
  output$details_table <- DT::renderDT({
    res <- prediction_result()
    if(is.data.frame(res) && "Error" %in% colnames(res)){
      data.frame(Details = "No details available due to error")
    } else {
      res$details
    }
  })
}

shinyApp(ui = ui, server = server)

关键修正说明

  1. 依赖包管理:将包的安装和加载移至应用开头,避免函数内部重复操作,符合Shiny最佳实践
  2. 动态数据匹配:通过reactive和switch实现联赛数据的动态切换,确保预测函数拿到正确的数据源
  3. 异常处理:用tryCatch捕获函数抛出的错误,将错误信息以表格形式展示,避免应用崩溃
  4. 输入验证:req()确保所有必要输入完成后才执行预测,避免空输入引发的计算错误
  5. 输出增强:同时展示简洁的预测结果和详细的概率计算表格,提升用户体验

额外优化建议

  • 用updateSelectInput替代多个conditionalPanel,动态更新主客场球队列表,简化UI代码
  • 在函数中添加数据列验证,确保联赛数据包含goals_for、matches_played等必要字段
  • 添加加载提示组件,在计算过程中显示加载状态,优化交互体验

内容的提问来源于stack exchange,提问作者Qba Liu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 00:18:13