Shiny 反应函数中用户输入计算的意外结果

Unexpected results from user input calculations in Shiny reactive functions

下面 Shiny reprex 的反应环境中的一些计算已经困扰了我一段时间,我希望我能在这里找到解决方案。此应用从侧边栏面板获取四个用户输入,并根据两组乘数 table(在应用顶部创建)计算输出 table。对于这些输入的每组,将根据 sector/subsector 乘数 table 计算三个值 - expenditure_calc、salary_calc 和 emp_calc。对于给定的扇区输入(第一个下拉列表),如果从第二个下拉框中选择“未知子扇区”,则 sector_multiplier_table 用于计算。 And, when one of Subsector 1, Subsector 2... is chosen, the subsector_multiplier_table" is used.

例如,对于以下用户输入,Select扇区:扇区1; Select分部门:分部门1;计算依据:支出;价值:2,000,000,根据子行业乘数table得出的计算结果为:expenditure_calc = 2,000,000; salary_calc = 400,000; emp_calc = 10

在大多数情况下,计算似乎工作正常,但当我选择涉及“未知子部门”的 sectors/subsectors 组合时,它们给出了我无法理解的值。以下是导致计算错误的输入示例:

输入选择1:“Sector 1”,“Subsector 1”,“Expenditure”,1000000 输入选择2:“Sector 1”,“Unknown subsector”,“Emp”,24

感谢任何帮助。

library(tidyverse)
library(DT)
library(magrittr)
library(shiny)
library(shinydashboard)
library(dashboardthemes)
library(shinydashboardPlus)
library(shinyWidgets)

# Sector multiplier table
sector <- c("Sector 1", "Sector 2", "Sector 3")
expenditure <- c(1000000, 1000000, 1000000)
salary <- c(250000, 500000, 160000)
emp <- c(5, 7, 9)
sector_multipliers <- data.frame(sector, expenditure, salary, emp)

# Subsector multiplier table
sector <- c("Sector 1", "Sector 1", "Sector 1", "Sector 1", "Sector 2", "Sector 2", "Sector 2", "Sector 3", "Sector 3")
subsector <- c("Subsector 1", "Subsector 2", "Subsector 3", "Subsector 4", "Subsector 1", "Subsector 2", "Subsector 3", "Subsector 1", "Subsector 2")
expenditure <- c(1000000, 1000000, 1000000, 1000000, 1000000, 1000000, 1000000, 1000000, 1000000)
salary <- c(200000, 250000, 300000, 400000, 425000, 280000, 600000, 170000, 150000)
emp <- c(5, 7, 9, 12, 9, 14, 18, 4, 5)
subsector_multipliers <- data.frame(sector, subsector, expenditure, salary, emp)

# Initialize empty data frame to store user input
input_data <- data.frame(sector = character(), subsector = character(), calc_type = character(), calc_value = double(), stringsAsFactors = FALSE)

# UI component
ui <- dashboardPage(
  header = dashboardHeader(title = "Calculations", titleWidth = 450),
  # UI sidebar panel
  sidebar = dashboardSidebar(
    minified = F, 
    width = 300,
    pickerInput("sector", 
                "Select sector:",
                choices = sector_multipliers$sector,
                options = list(`live-search` = TRUE, title = "Select a sector", selected = NULL)),
    pickerInput("subsector", 
                "Select subsector:", 
                choices = "",
                options = list(`live-search` = TRUE, title = "Select a subsector", selected = NULL)),
    radioButtons("calc_type",
                 label = "Calculation based on:", 
                 choices = list("Expenditure", "Emp"),
                 selected = "Expenditure"),
    numericInput("calc_value", 
                 "Enter value:", 
                 value = "1000000"),
    actionButton("add_btn",
                 "Add Values"),
    actionButton("delete_btn",
                 "Delete Values"),
    actionButton("calculate_btn", 
                 "CALCULATE")
  ),
  
  # UI body
  body = dashboardBody(
    
    tabPanel(
      "Model input and calculations",
      fluidRow(tags$h5("Input selections:"),
               actionButton("reset", "Clear All"),
               DT::dataTableOutput("input_table")),
      br(),
      fluidRow(tags$h5("Sector or subsector table join multipliers that are used:"),
               DT::dataTableOutput("multipliers_join")
      ),
      br(),
      fluidRow(tags$h5("Multiplier calculation output:"), 
               DT::dataTableOutput("calculation_output"))
    )
  )
)

server <- function(input, output, session) {
  # Populating subsector drop-down after a sector is selected  
  observeEvent(
    input$sector,
    updatePickerInput(session, "subsector", "Select Subsector:",
                      choices = c("Unknown subsector", subsector_multipliers$subsector[subsector_multipliers$sector == input$sector]))
  )
  
  # Creating reactive shock table from user inputs
  input_table <- reactiveVal(input_data)
  observeEvent(input$add_btn, {
    # req(input$sector, input$subsector, input$calc_type, input$calc_value)
    t = rbind(input_table(), data.frame(sector = input$sector, subsector = input$subsector, calc_type = input$calc_type, calc_value = input$calc_value))
    input_table(t)
  })
  
  observeEvent(input$delete_btn, {
    t = input_table()
    print(input$input_table_rows_selected)
    if (!is.null(input$input_table_rows_selected)) {
      t <- t[-input$input_table_rows_selected,]
    }
    input_table(t)
  })
  
  observeEvent(input$reset, {
    input_table(input_data)
  })
  
  output$input_table <- DT::renderDataTable({
    datatable(input_table(), selection = 'single', editable = F,
              colnames = c("Sector", "Subsector", "Expenditure/Emp", "Value"),
              options = list(paging = TRUE)) %>% 
      formatCurrency(columns = c(4), currency = "", interval = 3, mark = ",", digits = 0)
  })
  
  
  # Change input multiplier join tables based on subsector selection
  # If subsector is "Unknown subsector" use the sector multiplier table
  sector_multipliers_join <- eventReactive(input$calculate_btn, {
    input_table() %>% 
      filter(input_table()[[2]] == "Unknown subsector") %>% 
      left_join(sector_multipliers)
  })
  
  # If subsector is selected use the subsector multiplier table
  subsector_multipliers_join <- eventReactive(input$calculate_btn, {
    input_table() %>% 
      filter(input_table()[[2]] != "Unknown subsector") %>% 
      left_join(subsector_multipliers)
  })
  
  # Bind shock multiplier tables for unknown and known subsectors
  multipliers_join <- reactive({
    bind_rows(sector_multipliers_join(), subsector_multipliers_join())
  }) 
  output$multipliers_join <- DT::renderDataTable(datatable(multipliers_join()))
  
  # Calculations
  calculation_output <- eventReactive(input$calculate_btn, {
    req(input$sector, input$subsector, input$calc_type, input$calc_value)
    multipliers_join() %>% 
      mutate(
        expenditure_calc = if_else(input_table()[[3]] == "Expenditure", ((calc_value * expenditure) / expenditure),
                                            ((calc_value * expenditure) / emp))) %>%
      mutate(
        salary_calc = if_else(input_table()[[3]] == "Expenditure", ((calc_value * salary) / expenditure),
                                       ((calc_value * salary) / emp))) %>%
      mutate(
        emp_calc = if_else(input_table()[[3]] == "Expenditure", ((calc_value * emp) / expenditure),
                                    ((calc_value * emp) / emp)))
  })
  
  output$calculation_output <- DT::renderDataTable({
    datatable(calculation_output())
  })
  
} # End of server logic

# Run the application 
shinyApp(ui = ui, server = server)

你有几个问题。

  1. 检查向量中的值时,应使用 %in% 而不是 ==
  2. calculation_output 中,您正在尝试将来自不同 table 的向量与特定值进行比较,并将值分配给新变量。它可能不是相同的顺序。由于您在同一个 table 中有一个名为 calc_type 的列,您可以使用该列。

试试这个

server <- function(input, output, session) {
  # Populating subsector drop-down after a sector is selected  
  observeEvent(
    input$sector,
    updatePickerInput(session, "subsector", "Select Subsector:",
                      choices = c("Unknown subsector", subsector_multipliers$subsector[subsector_multipliers$sector == input$sector]))
  )
  
  # Creating reactive shock table from user inputs
  input_table <- reactiveVal(input_data)
  observeEvent(input$add_btn, {
    # req(input$sector, input$subsector, input$calc_type, input$calc_value)
    t = rbind(input_table(), data.frame(sector = input$sector, subsector = input$subsector, calc_type = input$calc_type, calc_value = input$calc_value))
    input_table(t)
  })
  
  observeEvent(input$delete_btn, {
    t = input_table()
    print(input$input_table_rows_selected)
    if (!is.null(input$input_table_rows_selected)) {
      t <- t[-input$input_table_rows_selected,]
    }
    input_table(t)
  })
  
  observeEvent(input$reset, {
    input_table(input_data)
  })
  
  output$input_tabl <- DT::renderDataTable({
    datatable(input_table(), selection = 'single', editable = F,
              colnames = c("Sector", "Subsector", "Expenditure/Emp", "Value"),
              options = list(paging = TRUE)) %>% 
      formatCurrency(columns = c(4), currency = "", interval = 3, mark = ",", digits = 0)
  })
  
  
  ###  Change input multiplier join tables based on subsector selection
  ###  If subsector is "Unknown subsector" use the sector multiplier table
  sector_multipliers_join <- eventReactive(input$calculate_btn, {
    input_table() %>%
      dplyr::filter(input_table()[[2]] %in% "Unknown subsector") %>%
      #dplyr::filter(sum(input_table()[[2]] %in% "Unknown subsector")>0) %>%
      left_join(sector_multipliers)
  })

  ### If subsector is selected use the subsector multiplier table
  subsector_multipliers_join <- eventReactive(input$calculate_btn, {
    input_table() %>%
      dplyr::filter(!input_table()[[2]] %in% "Unknown subsector") %>%
      left_join(subsector_multipliers)
  })

  
  # Bind shock multiplier tables for unknown and known subsectors
  multipliers_join <- reactive({
    bind_rows(sector_multipliers_join(), subsector_multipliers_join())
  }) 
  output$multipliers_join <- DT::renderDataTable(datatable(multipliers_join()))
  
  # Calculations
  calculation_output <- eventReactive(input$calculate_btn, {
    req(input$sector, input$subsector, input$calc_type, input$calc_value)
    multipliers_join()  %>% 
      dplyr::mutate(
        expenditure_calc = ifelse(calc_type %in% "Expenditure", ((calc_value * expenditure) / expenditure),
                                   ((calc_value * expenditure) / emp))) %>%
      dplyr::mutate(
        salary_calc = ifelse(calc_type %in% "Expenditure", ((calc_value * salary) / expenditure),
                              ((calc_value * salary) / emp))) %>%
      dplyr::mutate(
        emp_calc = ifelse(calc_type %in% "Expenditure", ((calc_value * emp) / expenditure),
                           ((calc_value * emp) / emp)))
  })
  
  output$calculation_output <- DT::renderDataTable({
    datatable(calculation_output())
  })
  
} # End of server logic