Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In delayed expression evaluation, R Shiny uses changed values of variables

Tags:

r

shiny

Here is a simple demo of the problem:

library(shiny)

ui <- fluidPage(
    textOutput("Text1"),
    textOutput("Text2")
)

server <- function(input, output) {
    for(i in 1:2) {
        id <- paste0("Text", i)
        output[[id]] <- renderText(paste0("This is text #", i)) # Problem!
    }
}

shinyApp(ui, server)

This program produces output

This is text #2
This is text #2

rather then #1 and #2.

Evidently, Shiny stores the expressions passed to renderText() in the line marked # Problem!, and evaluates them after the for-loop is finished. The expressions depend on variable i, and its final value i = 2 is used in evaluating both expressions.

How can I produce correct output (how can I force Shiny to use different values of i in different expressions), while still using the loop? In my code the loop limits are dynamic, and I cannot replace the loop with several static calls.

like image 674
Alex Fainshtein Avatar asked Jan 07 '18 08:01

Alex Fainshtein


1 Answers

Why the for-loop does not work, check the output of this example:

library(shiny)

ui <- fluidPage(
  textOutput("Text1"),
  textOutput("Text2")
)

server <- function(input, output) {
  for(i in 1:3) {
    id <- paste0("Text", i)
    output[[id]] <- renderText(paste0("This is text #", i)) # Problem!
  }
  i=10 # we set i to 10.

}

shinyApp(ui, server)

As you can see, all renderText elements use the last (global) value for i. This is not the case in the lapply, where an argument is passed to the function, but that argument is not defined in the global environment.

So you could use lapply instead of a for-loop, like this:

library(shiny)

ui <- fluidPage(
  textOutput("Text1"),
  textOutput("Text2")
)

server <- function(input, output) {

  lapply(1:2,function(i){
    id <- paste0("Text", i)
    output[[id]] <- renderText(paste0("This is text #", i)) # Problem!
  })

}

shinyApp(ui, server)

Output:

enter image description here


If you also want the ui to be reactive, you could use renderUI and uiOutput, for example as follows:

library(shiny)

ui <- fluidPage(
  numericInput("number_of_text","Number of text",min=1,max=10,value=3),
  uiOutput('my_text')
)

server <- function(input, output) {


  reactive_text <- reactive({
    all_text <- lapply(1:input$number_of_text,function(i){
      id <- paste0("Text", i)
      renderText(paste0("This is text #", i)) # Problem!
    })
   # do.call(all_text,tagList)
  })

  output$my_text <- renderUI({
    do.call(fluidRow, reactive_text())
  })

}

shinyApp(ui, server)

Output:

enter image description here

Hope this helps!

like image 154
Florian Avatar answered Oct 18 '22 20:10

Florian