Create a dynamic number of input elements using R / Shiny

I am writing a Shiny application to visualize insurance plans in my company. Here is what I would like to do:

  • I will have selectInput or sliderInput , where the user will select the number of persons according to their medical plan.
  • The corresponding number of double-sided sliders is displayed (one for each item).
  • They can then enter their estimates for the best / worst medical expenses for each participant according to their plan.
  • I have a code that will take these estimates and create side-by-side graphs illustrating the projected cost of the three proposals of the plan so that they can decide which one is the least expensive based on their estimates.

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

Here's what it looks like (the transparent end sections are the result of two HSA contributions. I thought it was a good way to show both premium and medical expenses by showing the impact of HSA's contribution. So you simply compare the length of the solid colors )

shiny-example

I saw examples like this where the user interface itself is fixed (in this case, one checkboxGroupInput exists, but its contents are configured based on a choice from another user interface input), but I have not seen examples of fitting the number (or, say, type) of input elements, generated as a result of other user interface input content.

Any suggestions on this (is this possible)?




My last resort would be to create, say, 15 input sliders and initialize them to zero. My code will work fine, but I would like to clear the interface without creating as many sliders just for a random user who has a very large family.




Update based on Kevin Wushai's answer

I tried going through the server.R route and got the following:

 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 after that, I try to extract the values ​​from input for each individual flow:

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

Finally, I have two functions that create objects to store a data frame for building on the basis of low and high medical costs. They are called best_case and worst_case , and both require the expenses object to work, so I call it my first line, as I learned from this question

 best_case <- reactive({ expenses <- expenses() ... )} 

I got some errors, so I used browser() to go through the expenses bit and noticed that functions such as input$ind1 do not exist inside the expenses function.

I also played with various print() instructions to see what happens. The brightest when I execute print(names(input)) as the very first line in the function:

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

I get two outputs, which, in my opinion, are determined by the definition of expenses and its subsequent call. Strange ... I don't get the third when worst_case uses the same expenses <- expense() .

If I do something like print(expenses) inside 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 

Any advice on why my input elements for ind1 and ind2 will not be displayed until expenses is called a second time and thus the data frame is correctly created?

+47
input dynamic r shiny
Oct 02 '13 at 5:23
source share
3 answers

You can handle the creation of the user interface element in server.R , so you have something like:

 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 user interface elements that depend on values ​​from other user interface elements, it is easiest for me to generate them in server.R .

It is helpful to understand that all _Input functions simply generate HTML. If you want to dynamically generate this HTML code, it makes sense to move it to server.R . And perhaps another thing worth noting is that in the renderUI order renderUI can return a list of HTML 'elements.

+31
Oct 02 '13 at 6:20
source share

You can access dynamically named variables from brilliant using this syntax:

 input[["dynamically_named_element"]] 

So, in the example above, if you initialize your slider elements like this

 # 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 ​​in the 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 brilliant example. The working gist is here .

Source: Joe Cheng's first answer, about halfway down this stream

+11
Aug 19 '15 at 7:47
source share

You can create a sidebar with do.call and lapply , something like:

 # 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) 
+3
Oct 02 '13 at 5:35 on
source share



All Articles