【问题标题】:In R Shiny, how to correctly link matrix values?在 R Shiny 中,如何正确链接矩阵值?
【发布时间】:2026-02-11 22:15:01
【问题描述】:

在下面的 MWE 代码中,用户可以选择通过一系列逐步矩阵输入(在本例中为 3 个)来优化模型假设/输入。第一个矩阵,在第一次调用应用程序时默认显示,是 X 个周期内的统一利率 Y。通过单击“显示”调用的第二个矩阵允许用户在 X 周期内改变 Y(构建“曲线”)。通过单击“添加场景”操作按钮调用的第三个矩阵允许用户添加弯曲的场景(请注意,为简化 MWE,此第三个矩阵当前不会水平扩展)。

矩阵在 1 个方向上链接:第一个矩阵中的值馈入第二个矩阵,第二个矩阵馈入第三个矩阵,最后一个矩阵在所有计算中优先。 (请注意,由于 shinyMatrix 包中的一个小错误,我尚未下载补丁,因此对可扩展矩阵的任何输入可能必须从右到左。

MWE 中的矩阵 1 id 是 flatInput,矩阵 2 是 curveBaseRate,矩阵 3 是 curveBaseRateAdd

我遇到的问题是,如果第二个矩阵发生了变化(添加了任何行),它不会正确反映在第三个矩阵中(如底部的第三个图像所示)。如果第二个矩阵没有改变而只是显示出来,那么第二个矩阵正确地输入到第三个矩阵中(如底部的第二个图像所示)。如何解决这个问题?我相信我在模态对话框中呈现的矩阵 3 中对矩阵 2 行/列的引用有问题(行 input$curveBaseRate[,1]input$curveBaseRate[,2],但我不知道我做错了什么。

MWE 代码:

library(shiny);library(shinyMatrix);library(shinyjs)

f <- function(action,i){as.character(checkboxInput(paste0(action,i),label=NULL))}
actions       <- c("show", "reset")
tbl           <- t(outer(actions, c(1,2), FUN = Vectorize(f)))
colnames(tbl) <- c("Show", "Reset")
rownames(tbl) <- c("Base", "Spread")

xDflt <- .05 # << default value for x column of input matrix
yDflt <- .01  # << default value for y column of input matrix

flatRate <- function(inputId,x,y){
  matrixInput(inputId, 
              value = matrix(c(x,y), 1, 1, dimnames = list(c("Base rate"),NULL)),
              rows =  list(extend = FALSE, names = TRUE),
              cols =  list(extend = FALSE, names = FALSE, editableNames = FALSE),
              class = "numeric")
} 

curveRate <- function(inputId,w,x,y){
  matrixInput(inputId,
              label = w,
              value = matrix(c(x,y),1,2,dimnames=list(NULL,c("Period (X)","Curved (Y)"))),
              rows =  list(extend = TRUE,  names = FALSE),
              cols =  list(extend = FALSE, names = TRUE, editableNames = FALSE),
              class = "numeric")
}  

ui <- 
  fluidPage(
    tags$head( 
      tags$style(HTML(
        "td .checkbox {margin-top: 0; margin-bottom: 0;}
       td .form-group {margin-bottom: 0;}"
      ))
    ),
    titlePanel("Model"),
    sidebarLayout(
      sidebarPanel(uiOutput("Panel")),
      mainPanel(tabsetPanel(tabPanel("Rates", value=2),id = "tabselected"))
    ) 
  ) 

server <- function(input,output,session)({
  output$Panel <- renderUI({
      conditionalPanel(
          condition = "input.tabselected == 2",
          useShinyjs(),
          helpText("Modeled periods (X):"),
          sliderInput('periods','',min=2,max=120,value=60),
          helpText("Initial rates (Y):"),
          flatRate("flatInput",xDflt,yDflt), 
          helpText("Generate curves (Y|X):"),
          tableOutput("checkboxes"),
          hidden(uiOutput("curveBaseRate")),
          actionButton("addScenarios","Add scenarios")
      )
  }) 
  
  ### Begin checkbox matrix ###
  output[["checkboxes"]] <- 
    renderTable({tbl}, 
                rownames = TRUE, align = "c",
                sanitize.text.function = function(x) x
    )
  
  observeEvent(input[["show1"]], {
    if(input[["show1"]]){
      shinyjs::show("curveBaseRate")
    } else {
      shinyjs::hide("curveBaseRate")
    }
  })
  ### End checkbox matrix ###
  
  output$curveBaseRate <- renderUI({
    req(input$periods,input$flatInput)
    input[["reset1"]]
    curveRate("curveBaseRate",
              "Base rates curve (Y|X):",
              input$periods,
              input$flatInput[1,1])
  })
  
  outputOptions(output, "curveBaseRate", suspendWhenHidden = FALSE)
  
  observeEvent(input$addScenarios, {
    showModal(
      modalDialog(
        curveRate("curveBaseRateAdd",
                  "Base rates curve (Y|X):",
                  input$curveBaseRate[,1],
                  input$curveBaseRate[,2]),
        footer =  modalButton("Close")
      ))
  }) 
  
}) 

shinyApp(ui, server)

说明图:

  1. 第一张图片显示了第一个用户输入矩阵。
  2. 第二张图片显示了第二个用户输入矩阵(点击“显示”后)渲染并正确 在模态对话框中输入第三个用户输入矩阵,没有用户更改第二个输入矩阵。
  3. 在用户对第二个输入矩阵进行更改后,第三个图像显示模式对话框中第三个输入矩阵的渲染不正确。

【问题讨论】:

    标签: r matrix shiny shiny-reactivity


    【解决方案1】:

    在上面的代码中,您只传递了第二个矩阵的前 2 列:

              input$curveBaseRate[,1],
              input$curveBaseRate[,2])
    

    说实话,我会摆脱所有至少不会被调用两次的自定义函数(否则没有附加值 - 只是混乱)。

    请检查以下内容:


    library(shiny);library(shinyMatrix);library(shinyjs)
    
    f <- function(action,i){as.character(checkboxInput(paste0(action,i),label=NULL))}
    actions       <- c("show", "reset")
    tbl           <- t(outer(actions, c(1,2), FUN = Vectorize(f)))
    colnames(tbl) <- c("Show", "Reset")
    rownames(tbl) <- c("Base", "Spread")
    
    xDflt <- .05 # << default value for x column of input matrix
    yDflt <- .01  # << default value for y column of input matrix
    
    flatRate <- function(inputId,x,y){
      matrixInput(inputId, 
                  value = matrix(c(x,y), 1, 1, dimnames = list(c("Base rate"),NULL)),
                  rows =  list(extend = FALSE, names = TRUE),
                  cols =  list(extend = FALSE, names = FALSE, editableNames = FALSE),
                  class = "numeric")
    } 
    
    curveRate <- function(inputId,w,x,y){
      matrixInput(inputId,
                  label = w,
                  value = matrix(c(x,y),1,2,dimnames=list(NULL,c("Period (X)","Curved (Y)"))),
                  rows =  list(extend = TRUE,  names = FALSE),
                  cols =  list(extend = FALSE, names = TRUE, editableNames = FALSE),
                  class = "numeric")
    }  
    
    ui <- 
      fluidPage(
        tags$head( 
          tags$style(HTML(
            "td .checkbox {margin-top: 0; margin-bottom: 0;}
           td .form-group {margin-bottom: 0;}"
          ))
        ),
        titlePanel("Model"),
        sidebarLayout(
          sidebarPanel(uiOutput("Panel")),
          mainPanel(tabsetPanel(tabPanel("Rates", value=2),id = "tabselected"))
        ) 
      ) 
    
    server <- function(input,output,session)({
      output$Panel <- renderUI({
        conditionalPanel(
          condition = "input.tabselected == 2",
          useShinyjs(),
          helpText("Modeled periods (X):"),
          sliderInput('periods','',min=2,max=120,value=60),
          helpText("Initial rates (Y):"),
          flatRate("flatInput",xDflt,yDflt), 
          helpText("Generate curves (Y|X):"),
          tableOutput("checkboxes"),
          hidden(uiOutput("curveBaseRate")),
          actionButton("addScenarios","Add scenarios")
        )
      }) 
      
      ### Begin checkbox matrix ###
      output[["checkboxes"]] <- 
        renderTable({tbl}, 
                    rownames = TRUE, align = "c",
                    sanitize.text.function = function(x) x
        )
      
      observeEvent(input[["show1"]], {
        if(input[["show1"]]){
          shinyjs::show("curveBaseRate")
        } else {
          shinyjs::hide("curveBaseRate")
        }
      })
      ### End checkbox matrix ###
      
      output$curveBaseRate <- renderUI({
        req(input$periods,input$flatInput)
        input[["reset1"]]
        curveRate("curveBaseRate",
                  "Base rates curve (Y|X):",
                  input$periods,
                  input$flatInput[1,1])
      })
      
      outputOptions(output, "curveBaseRate", suspendWhenHidden = FALSE)
      
      observeEvent(input$addScenarios, {
        showModal(
          modalDialog(
            matrixInput(inputId = "curveBaseRateAdd",
                        label = "Base rates curve (Y|X):",
                        value = input$curveBaseRate,
                        rows =  list(extend = TRUE,  names = FALSE),
                        cols =  list(extend = FALSE, names = TRUE, editableNames = FALSE),
                        class = "numeric"),
            footer =  modalButton("Close")
          ))
      }) 
      
    }) 
    
    shinyApp(ui, server)
    

    使用do.call 的示例 - 请参阅flatInputArgs

    library(shiny);library(shinyMatrix);library(shinyjs)
    
    f <- function(action,i){as.character(checkboxInput(paste0(action,i),label=NULL))}
    actions       <- c("show", "reset")
    tbl           <- t(outer(actions, c(1,2), FUN = Vectorize(f)))
    colnames(tbl) <- c("Show", "Reset")
    rownames(tbl) <- c("Base", "Spread")
    
    xDflt <- .05 # << default value for x column of input matrix
    yDflt <- .01  # << default value for y column of input matrix
    
    flatInputArgs <- list(inputId = "flatInput", 
                value = matrix(c(xDflt,yDflt), 1, 1, dimnames = list(c("Base rate test"),NULL)),
                rows =  list(extend = FALSE, names = TRUE),
                cols =  list(extend = FALSE, names = FALSE, editableNames = FALSE),
                class = "numeric")
    
    curveRate <- function(inputId,w,x,y){
      matrixInput(inputId,
                  label = w,
                  value = matrix(c(x,y),1,2,dimnames=list(NULL,c("Period (X)","Curved (Y)"))),
                  rows =  list(extend = TRUE,  names = FALSE),
                  cols =  list(extend = FALSE, names = TRUE, editableNames = FALSE),
                  class = "numeric")
    }  
    
    ui <- 
      fluidPage(
        tags$head( 
          tags$style(HTML(
            "td .checkbox {margin-top: 0; margin-bottom: 0;}
           td .form-group {margin-bottom: 0;}"
          ))
        ),
        titlePanel("Model"),
        sidebarLayout(
          sidebarPanel(uiOutput("Panel")),
          mainPanel(tabsetPanel(tabPanel("Rates", value=2),id = "tabselected"))
        ) 
      ) 
    
    server <- function(input,output,session)({
      output$Panel <- renderUI({
        conditionalPanel(
          condition = "input.tabselected == 2",
          useShinyjs(),
          helpText("Modeled periods (X):"),
          sliderInput('periods','',min=2,max=120,value=60),
          helpText("Initial rates (Y):"),
          do.call(matrixInput, flatInputArgs),
          helpText("Generate curves (Y|X):"),
          tableOutput("checkboxes"),
          hidden(uiOutput("curveBaseRate")),
          actionButton("addScenarios","Add scenarios")
        )
      }) 
      
      ### Begin checkbox matrix ###
      output[["checkboxes"]] <- 
        renderTable({tbl}, 
                    rownames = TRUE, align = "c",
                    sanitize.text.function = function(x) x
        )
      
      observeEvent(input[["show1"]], {
        if(input[["show1"]]){
          shinyjs::show("curveBaseRate")
        } else {
          shinyjs::hide("curveBaseRate")
        }
      })
      ### End checkbox matrix ###
      
      output$curveBaseRate <- renderUI({
        req(input$periods,input$flatInput)
        input[["reset1"]]
        curveRate("curveBaseRate",
                  "Base rates curve (Y|X):",
                  input$periods,
                  input$flatInput[1,1])
      })
      
      outputOptions(output, "curveBaseRate", suspendWhenHidden = FALSE)
      
      observeEvent(input$addScenarios, {
        showModal(
          modalDialog(
            matrixInput(inputId = "curveBaseRateAdd",
                        label = "Base rates curve (Y|X):",
                        value = input$curveBaseRate,
                        rows =  list(extend = TRUE,  names = FALSE),
                        cols =  list(extend = FALSE, names = TRUE, editableNames = FALSE),
                        class = "numeric"),
            footer =  modalButton("Close")
          ))
      }) 
      
    }) 
    
    shinyApp(ui, server)
    

    【讨论】:

    • 您的解决方案完美运行。正如你所建议的,我删除了所有那些自定义的“整合”功能:它们给我带来的麻烦比它们的价值还要大。
    • 听起来不错。使用预设参数的包装函数的替代方法是保存参数列表并通过do.call 将它们传递给matrixInput
    • 我使用参数列表flatInputArgs添加了一个关于do.call的示例