简体   繁体   English

使用R / Shiny创建动态数量的输入元素

[英]Create dynamic number of input elements with R/Shiny

I'm writing a Shiny app for visualizing insurance benefit plans at my company. 我正在写一个Shiny应用程序,用于可视化我公司的保险福利计划。 Here is what I'd like to happen: 这是我想要发生的事情:

  • I'll have a selectInput or sliderInput where the user will choose the number of individuals on their medical plan 我将有一个selectInputsliderInput ,用户将在其中选择他们的医疗计划中的个人数量
  • A matching number of double sided sliders will appear (one for each member) 将出现匹配数量的双面滑块(每个成员一个)
  • They can then input their estimates for best/worst case medical expenses for each member on their plan 然后,他们可以输入他们对计划中每个成员的最佳/最差病例医疗费用的估计
  • I have code that will take those estimates and create side by side plots illustrating the forecast cost on the three plan offerings so they can decide which one is least expensive based on their estimates 我的代码将采用这些估算并创建并排图表,说明三个计划产品的预测成本,以便他们可以根据他们的估算确定哪一个最便宜

Here's my current ui.R file with hard coded inputs, simulating a family of four: 这是我当前的带有硬编码输入的ui.R文件,模拟一个四口之家:

shinyUI(pageWithSidebar(

  headerPanel("Side by side comparison"),

  sidebarPanel(

    selectInput(inputId = "class", label = "Choose plan type:",
                list("Employee only" = "emp", "Employee and spouse" = "emp_spouse",
                     "Employee and child" = "emp_child", "Employee and family" = "emp_fam")),

    sliderInput(inputId = "ind1", label = "Individual 1",
                min = 0, max = 20000, value = c(0, 2500), step = 250),

    sliderInput(inputId = "ind2", label = "Individual 2",
                min = 0, max = 20000, value = c(0, 2500), step = 250),

    sliderInput(inputId = "ind3", label = "Individual 3",
                min = 0, max = 20000, value = c(0, 2500), step = 250),

    sliderInput(inputId = "ind4", label = "Individual 4",
                min = 0, max = 20000, value = c(0, 2500), step = 250)
    ),

  mainPanel(
    tabsetPanel(  
    tabPanel("Side by Side", plotOutput(outputId = "main_plot", width = "100%")),
    tabPanel("Summary", tableOutput(outputId = "summary"))
  )
)))

Here's what it looks like (the transparent end sections are the result of HSA contributions from two of the plans. I thought it was a nice way to show both the premiums and medical expenses while showing the impact of the company HSA contribution. Thus, you'd just compare the length of the solid colors). 这就是它的样子(透明的末端部分是两个计划的HSA贡献的结果。我认为这是一个很好的方式来显示保费和医疗费用,同时显示公司HSA贡献的影响。因此,你我只是比较纯色的长度。

光泽-示例

I've seen examples like this where the UI input itself is fixed (in this case, one checkboxGroupInput exists, but its contents are tailored based on the choice from another UI input), but I've not seen examples of tailoring the number (or, say, type) of input elements spawned as the result of another UI input's contents. 我看到的例子这样其中UI输入本身是固定的(在这种情况下,一个checkboxGroupInput存在,但它的内容是根据从另一个UI输入的选择定制),但我还没有看到剪裁多个示例(或者说,输入元素作为另一个UI输入内容的结果而产生的输入元素。

Any suggestions on this (is it even possible)? 对此有任何建议(甚至可能)?


My last resort will be to create, say, 15 input sliders and initialize them to zero. 我的最后一招是创建15个输入滑块并将它们初始化为零。 My code will work just fine, but I'd like to clean up the interface by not having to create that many sliders just for the occasional user who has a very large family. 我的代码工作得很好,但是我想通过不必为偶尔拥有一个非常大的家庭的用户创建那么多滑块来清理界面。


Update based on Kevin Ushay's answer 根据Kevin Ushay的回答更新

I tried to go the server.R route and have this: 我试着去server.R路由并有这个:

shinyServer(function(input, output) {

  output$sliders <- renderUI({
    members <- as.integer(input$members) # default 2
    max_pred <- as.integer(input$max_pred) # default 5000
    lapply(1:members, function(i) {
      sliderInput(inputId = paste0("ind", i), label = paste("Individual", i),
                  min = 0, max = max_pred, value = c(0, 500), step = 100)
    })

  })

})

Immediately afterwards, I try and extract the values out of input for each individual's expenses: 紧接着,我尝试从每个人的费用中提取input值:

expenses <- reactive({
    members <- as.numeric(input$members)

    mins <- sapply(1:members, function(i) {
      as.numeric(input[[paste0("ind", i)]])[1]
    })

    maxs <- sapply(1:members, function(i) {
      as.numeric(input[[paste0("ind", i)]])[2]
    })

    expenses <- as.data.frame(cbind(mins, maxs))
})

Lastly, I have two functions that create objects to store a data frame for plotting based on the low and high medical expense estimates. 最后,我有两个函数创建对象来存储数据框,以便根据低和高医疗费用估算进行绘图。 They're called best_case and worst_case and both need the expenses object to work, so I call it as my first line as I learned from this question 它们被称为best_caseworst_case并且都需要expenses对象才能工作,因此我将其称为我的第一行,因为我从这个问题中学到了

best_case <- reactive({

    expenses <- expenses()

    ...

)}

I got some errors, so I used browser() to step through the expenses bit and noticed peculiar things like input$ind1 not seeming to exist from within the expenses function. 我有一些错误,所以我使用browser()来逐步完成expenses并注意到input$ind1类的特殊内容似乎不存在于expenses函数中。

I also played around with various print() statements in it to see what was happening. 我还在其中使用各种print()语句来查看发生了什么。 The most striking is when I do print(names(input)) as the very first line in the function: 最引人注目的是当我print(names(input))作为函数的第一行时:

[1] "class"    "max_pred" "members" 

[1] "class"    "ind1"     "ind2"     "max_pred" "members" 

I get two outputs, which I believe is due to the defining of expenses and subsequent calling of it. 我得到两个输出,我认为这是由于expenses的定义和随后的调用。 Strangely... I don't get a third when worst_case uses the exact same expenses <- expense() line. 奇怪的是......当worst_case使用完全相同的expenses <- expense()行时,我没有得到第三个。

If I do something like print(expenses) inside of my expenses function, I also get duplicates: 如果我在expenses函数中执行类似print(expenses)的操作,我也会得到重复:

# the first
  mins maxs
1   NA   NA
2   NA   NA

# the second
  mins maxs
1    0  500
2    0  500

Any tips on why my input elements for ind1 and ind2 wouldn't show up until expenses is called the second time and thus prevent the data frame from being created correctly? 关于为什么我的ind1ind2 input元素在第二次调用expenses之前不会出现的任何提示,从而阻止正确创建数据框?

You could handle generation of the UI element in server.R , so you have something like: 你可以在server.R处理UI元素的server.R ,所以你有类似的东西:

ui.R
----

shinyUI( pageWithSideBar(
    ...
    selectInput("numIndividuals", ...)
    uiOutput("sliders"),
    ...
))

and

server.R
--------

shinyServer( function(input, output, session) {

  output$sliders <- renderUI({
    numIndividuals <- as.integer(input$numIndividuals)
    lapply(1:numIndividuals, function(i) {
      sliderInput(...)
    })
  })


})

When I have UI elements that depend on values from other UI elements, I find it easiest to generate them in server.R . 当我拥有依赖于其他UI元素的值的UI元素时,我发现在server.R生成它们最容易。

It's useful to understand that all of the _Input functions just generate HTML . 理解所有_Input函数只生成HTML是有用的。 When you want to generate that HTML dynamically it makes sense to move it to server.R . 当您想要动态生成HTML时,将其移动到server.R And perhaps the other thing worth emphasizing is that it's okay to return a list of HTML 'elements' in a renderUI call. 也许值得强调的另一件事是可以在renderUI调用中返回HTML'元素' list

You can access dynamically named variables from shiny using this syntax: 您可以使用以下语法从shiny中访问动态命名的变量:

input[["dynamically_named_element"]]

So in your example above, if you initialise your slider elements as so 因此,在上面的示例中,如果您初始化滑块元素

# server.R

output$sliders <- renderUI({
  members <- as.integer(input$members) # default 2
  max_pred <- as.integer(input$max_pred) # default 5000
  lapply(1:members, function(i) {
    sliderInput(inputId = paste0("ind", i), label = paste("Individual", i),
                min = 0, max = max_pred, value = c(0, 500), step = 100)
  })
})

# ui.R

selectInput("num", "select number of inputs", choices = seq(1,10,1))
uiOutput("input_ui")

You can print the values to a table using the following 您可以使用以下方法将值打印到表中

# server.R

output$table <- renderTable({
    num <- as.integer(input$num)

    data.frame(lapply(1:num, function(i) {
      input[[paste0("ind", i)]]
    }))
  })

# ui.R

tableOutput("table")

See here for a working Shiny example. 请看这里有一个有效的例子。 Working gist here . 在这里工作要点。

Source: Joe Cheng's first answer, about half way down this thread 资料来源:Joe Cheng的第一个答案,大约是这个帖子的一半

You could generate the sidebar with do.call and lapply , something like: 您可以使用do.calllapply生成侧边栏,例如:

# create the first input, which isn't dynamic
sel.input = selectInput(inputId = "class", label = "Choose plan type:",
            list("Employee only" = "emp", "Employee and spouse" = "emp_spouse",
                 "Employee and child" = "emp_child", "Employee and family" = "emp_fam"))

num.individuals = 5  # determine the number of individuals here

# concatenate the select input and the other inputs
inputs = c(list(sel.input), lapply(1:num.individuals, function(i) {
    sliderInput(inputId = paste0("ind", i), label = paste("Individual", i), min = 0, max = 20000, value = c(0, 2500), step = 250)
}))

sidebar.panel = do.call(sidebarPanel, inputs)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM