使用 R/Shiny 创建动态数量的输入元素 [英] Create dynamic number of input elements with R/Shiny

查看:27
本文介绍了使用 R/Shiny 创建动态数量的输入元素的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在编写一个 Shiny 应用,用于可视化我公司的保险福利计划.这是我希望发生的事情:

I'm writing a Shiny app for visualizing insurance benefit plans at my company. Here is what I'd like to happen:

  • 我将有一个 selectInputsliderInput,用户将在其中选择他们医疗计划中的人数
  • 将出现匹配数量的双面滑块(每个成员一个)
  • 然后,他们可以为他们的计划中的每个成员输入他们对最佳/最坏情况医疗费用的估计
  • 我的代码将采用这些估算值并创建并排图来说明三种计划产品的预测成本,以便他们可以根据估算值决定哪一种最便宜
  • I'll have a selectInput or sliderInput where the user will choose the number of individuals on their medical plan
  • 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

这是我当前使用硬编码输入的 ui.R 文件,模拟四口之家:

Here's my current ui.R file with hard coded inputs, simulating a family of four:

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"))
  )
)))

这是它的样子(透明的末端部分是两个计划的 HSA 供款的结果.我认为这是一种很好的方式来显示保费和医疗费用,同时显示公司 HSA 供款的影响.因此,您只需比较纯色的长度即可).

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).

我见过一些例子 像这样,其中 UI 输入本身是固定的(在这种情况下,一个 checkboxGroupInput 存在,但它的内容是根据另一个 UI 输入的选择量身定制的),但我还没有看到定制数字的例子(或者说,type) 作为另一个 UI 输入内容的结果产生的输入元素.

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.

对此有何建议(甚至可能)?

Any suggestions on this (is it even possible)?

我最后的选择是创建 15 个输入滑块并将它们初始化为零.我的代码可以正常工作,但我想通过不必为拥有非常大家庭的偶尔用户创建那么多滑块来清理界面.

My last resort will be to create, say, 15 input sliders and initialize them to zero. 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.

根据 Kevin Ushay 的回答更新

我尝试走 server.R 路线并得到这个:

I tried to go the server.R route and have this:

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)
    })

  })

})

紧接着,我尝试从 input 中提取每个人的费用的值:

Immediately afterwards, I try and extract the values out of input for each individual's expenses:

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))
})

最后,我有两个函数可以创建对象来存储数据框,以便根据低医疗费用和高医疗费用估计进行绘图.它们被称为 best_caseworst_case 并且都需要 expenses 对象才能工作,因此我将其称为第一行,因为我从 这个问题

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_case <- reactive({

    expenses <- expenses()

    ...

)}

我遇到了一些错误,所以我使用 browser() 来遍历 expenses 位,并注意到诸如 input$ind1 之类的特殊东西似乎存在于 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.

我还在其中尝试了各种 print() 语句以查看发生了什么.最引人注目的是当我将 print(names(input)) 作为函数的第一行时:

I also played around with various print() statements in it to see what was happening. The most striking is when I do print(names(input)) as the very first line in the function:

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

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

我得到两个输出,我相信这是由于费用的定义和随后的调用.奇怪的是...当 worst_case 使用完全相同的 expenses <-expense() 行时,我没有得到三分之一.

I get two outputs, which I believe is due to the defining of expenses and subsequent calling of it. Strangely... I don't get a third when worst_case uses the exact same expenses <- expense() line.

如果我在我的 expenses 函数中执行诸如 print(expenses) 之类的操作,我也会得到重复项:

If I do something like print(expenses) inside of my expenses function, I also get duplicates:

# the first
  mins maxs
1   NA   NA
2   NA   NA

# the second
  mins maxs
1    0  500
2    0  500

关于为什么我的 ind1ind2input 元素在 expenses 之前不会显示的任何提示第二次调用从而阻止正确创建数据框?

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?

推荐答案

你可以在 server.R 中处理 UI 元素的生成,所以你有类似的东西:

You could handle generation of the UI element in server.R, so you have something like:

ui.R
----

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

server.R
--------

shinyServer( function(input, output, session) {

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


})

当我的 UI 元素依赖于其他 UI 元素的值时,我发现在 server.R 中生成它们最容易.

When I have UI elements that depend on values from other UI elements, I find it easiest to generate them in server.R.

了解所有 _Input 函数只生成 HTML 很有用.当您想要动态生成该 HTML 时,将其移动到 server.R 是有意义的.也许另一件值得强调的事情是,可以在 renderUI 调用中返回 HTML元素"的 list.

It's useful to understand that all of the _Input functions just generate HTML. When you want to generate that HTML dynamically it makes sense to move it to 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.

这篇关于使用 R/Shiny 创建动态数量的输入元素的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆