Shiny: use of `reactive()` inside loops?

Created on 4 Jul 2014  ·  12Comments  ·  Source: rstudio/shiny

BLUF: reactive({...}) is working for me on individual uses, but I cannot get them to work in a loop.

Situation: I have a session-specific variable called models that contains one or more sub-lists, each of which I'll refer to as a model. Each model is a list of multiple various things (data.frame, matrices, etc), and each of model have the identical structure of subcomponents. A severely truncated example of this:

str(models)
##  List of 2
##   $ :List of 8
##    ..$ name       : chr "model"
##    ..$ description: chr ""
##    ..$ config     :List of 10
##    .. ..$ subelem1    : num 0.2
##    .. ..$ subelem2    :'data.frame': 9 obs. of  3 variables:
##    .. .. .. <snip>
##    .. ..$ <more stuff here>
##    ..$ objs       :List of 2
##    .. ..$ obj1:List of 7
##    .. .. ..$ defn     :'data.frame': 9 obs. of  9 variables:
##    .. .. .. .. <snip>
##    .. .. ..$ curves   : num [1:9, 1:9] 0 0 0 0 -1 1 1 1 -1 1 ...
##    .. .. ..$ <more stuff here>
##    .. ..$ obj2:List of 7
##    .. .. .. <snip>
##    ..$ scens      :List of 2
##    .. .. <lots of stuff>
##   $ :List of 8
##    .. <repeat all of the above>

The user selects which model to use based on a dynamically-created and -maintained pulldown. My intent is to be able to track dependencies among numerous other regions in the shiny app. Currently, when the pulldown is changed, another session-specific variable this has a copy of that model.

If an html output is reactive to this then the chaining works fine. However, I want a little finer-grained dependency so that an html element will react to changes to a specific component of the model.

I can manually create numerous variables that track individual components, but since there are more than just a few I thought I'd automate the process in a loop. Here's some close-enough code to demonstrate. The following works:

this <- reactive({
    names <- sapply(models, `[[`, 'name')
    idx <- which(input$modelSelect == names)
    if (length(idx)) models[[idx]] else NULL
})

The reacting output elements check for NULL.

The following does not produce the intended reactions (also no errors):

this <- list()
for (el in c('name', 'description', 'objs', <more stuff>))
    this[[el]] <- reactive({
        names <- sapply(models, `[[`, 'name')
        idx <- which(input$modelSelect == names)
        if (length(idx)) models[[idx]][[el]] else NULL
    })

It is safe to assume that the model "always" contains the specific elements. I also tried (with equally unsuccessful luck) using assign(el, reactive({ ... })) inside the for loop.

In addition to helping me route out the problem, can you think of a better way to handle this (admittedly non-trivial) dependency?

Thanks! (BTW: I had a great time talking with you today about this at the useR conference, Joe and Jeff, thanks for the help!)

Most helpful comment

You're the second person at useR that I talked to that was bitten by this behavior in R. It's because all the iterations of the for loop share the same reference to el. So when any of the created reactive expressions execute, they're using whatever the final value of el was.

You can fix this either by 1) using lapply instead of a for loop; since each iteration executes as its own function call, it gets its own reference to el; or 2) using a for loop but introducing a local({...}) inside of there, and creating a local variable in there whose value is assigned to el outside of the reactive.

for (el in whatever) {
  local({
    thisEl <- el
    ...
 })
}

All 12 comments

You're the second person at useR that I talked to that was bitten by this behavior in R. It's because all the iterations of the for loop share the same reference to el. So when any of the created reactive expressions execute, they're using whatever the final value of el was.

You can fix this either by 1) using lapply instead of a for loop; since each iteration executes as its own function call, it gets its own reference to el; or 2) using a for loop but introducing a local({...}) inside of there, and creating a local variable in there whose value is assigned to el outside of the reactive.

for (el in whatever) {
  local({
    thisEl <- el
    ...
 })
}

If you define a function, you can use it either in lapply or a for loop. But you have to make sure to pass each el value as an argument to the function -- otherwise they'll share the same el from the outer environment, as before.

With lapply:

els <- c('name', 'description', 'objs')
this <- lapply(els, function(el) {
    names <- sapply(models, `[[`, 'name')
    idx <- which(input$modelSelect == names)
    if (length(idx)) models[[idx]][[el]] else NULL
})

With a for loop:

do_stuff <- function(el) {
    names <- sapply(models, `[[`, 'name')
    idx <- which(input$modelSelect == names)
    if (length(idx)) models[[idx]][[el]] else NULL
}

for (el in c('name', 'description', 'objs')) {
    this[[el]] <- do_stuff(el)
}

This is a pretty common issue, but it takes some advanced R knowledge to really understand why it happens and how to solve it. It makes me wonder if we should create a convenient wrapper function to capture variables, maybe something like capture(el)(reactive({ .... }).

@wch this is just lazy evaluation right?

library(shiny)

# Doesn't work
out1 <- vector("list", 3)
for(i in 1:3) {
  out[[i]] <- reactive(i)
}
isolate(out1[[1]]())

# Still doesn't work
out2 <- lapply(1:3, function(i) reactive(i))
isolate(out2[[1]]())

# Works!
out3 <- lapply(1:3, function(i) {
  force(i)
  reactive(i)
})
isolate(out3[[1]]())

@hadley I meant that it would be nice to have a way for people to not have to use local or define a function (and also use force) in order to capture variables locally. The idea is that they could do something like:

for (i in 1:3) {
  out[[i]] <- capture(i)(reactive(i))
}

(I'm not wedded to this particular syntax, but this is the general idea.)

@wch I wonder if it could be an argument to exprToFunction, or if the default behaviour could be tweaked

@jcheng5, it's funny, because I thought I was doing the right thing avoiding the lapply implementation, thinking that the env scope could be causing problems. Bad on me (again) for not testing it just to make sure.

@wch, your suggestion of using a function mostly makes sense to me, though it reminds me that I need to improve my understanding of lazy evaluation; I understand the concept and some of the implications but obviously am not anticipating it or even capitalizing on it in my code. (In this context, "_I am only an egg_.")

@hadley, thanks for your further explanation/demonstration of the lazy eval. I think I need to reread the "hadley-verse" (as Hilary coined it) weekly to incrementally absorb it.

Hi all,
I am stuck with a similar problem. I am trying to create a simple app for plate randomization. It takes some parameters as input from users (like number of subjects, visits, replicates).

In server.R I need to have a loop that created plates with random subject ID numbers. I can't get this function to work in Shiny. It works well otherwise. Code doesn't work starting with plates.w.subjects <- list()

Here is the server.R code:

n.subjects    <- reactive({ as.numeric(input$n.subjects) })
n.visits      <- reactive({ as.numeric(input$n.visits) })     
n.replicates  <- reactive({ as.numeric(input$n.replicates) }) 
n.buffers     <- reactive({ as.numeric(input$n.buffers) })    
n.ipc         <- reactive({ as.numeric(input$n.ipc) })
n.plates      <- reactive({ ceiling((n.subjects()*n.visits()*n.replicates())/(n.wells-sum(n.buffers(),n.ipc()))) })
subject.ids   <- reactive({ seq(1, n.subjects()) })

## other calculations here 

plates.w.subjects <- list()
plates.data <- reactive({

  for(i in 1:n.plates()) {
    local({
      if (i == 1) {
        used.samples <- NA
        left.samples <- subject.ids()
      } else {
        used.samples <- melt(plates.w.subjects)[,1]
        left.samples <- setdiff(subject.ids(), used.samples)
      }

      if(length(left.samples) > max.subjects.plate()) {
        c <- sample(left.samples, max.subjects.plate(), replace=FALSE, prob=NULL)
      } else {
        c <- sample(left.samples, length(left.samples), replace=FALSE, prob=NULL)
      }

      name <- paste0('Plate.', i)
      tmp <- list(subj.ids = c)
      plates.w.subjects[[name]] <- tmp

      #rm(tmp,name,c,used.samples,left.samples)
    })
  }
 })

output$result <- renderPrint({
    plates.data()$plates.w.subjects[[1]]
  })

Ideally the result should be:
$Plate.1
$Plate.1$subj.ids
[1] 23 16 13 20 24 10 19 25 3 21 4 12 9
$Plate.2
$Plate.2$subj.ids
[1] 14 22 8 18 2 17 6 11 1 15 5 7

I saw from the answers above that you can use 'local' or 'lapply' but I cant get either one to work..
Would really appreciate any help!
Thanks!
Maria

The line

plates.w.subjects <- list()

must go into the body of the plates.data reactive expression (make it the
first line). Then the last line of the plates.data reactive should be

plates.w.subjects

And finally, the renderPrint() should just use plates.data()[[1]] (drop the
$plates.w.subject part).

The point of a reactive() expression is to _return_ the value you're
interested in. What you were doing was changing some other variable (and
actually not even that was working) and then returning nothing. The changes
above cause the correct data to be returned from the plates.data reactive
expression.

I highly recommend taking the time to watch my "Effective reactive
programming" tutorial here:
https://www.rstudio.com/resources/webinars/shiny-developer-conference/

Hope that helps!

Thanks Joe!
The videos are really helpful.
I had to break the code into more pieces to get it do what i need (and of course incorporate your suggestions). I ended up separating if i == 1 into a separate chunk. Here is what works. Not sure how efficient it is in term of programming though..

  plates.w.subjects.1 <- reactive({
    sample(subject.ids(), max.subjects.plate(), replace=FALSE, prob=NULL) 
    })


  plates.w.subjects <- reactive({

    plates.data <- list()
    plates.data$Plate.1 <- list(subj.ids = plates.w.subjects.1())

       for(i in 2:n.plates()) {

         used.samples <- melt(plates.data)[,1]
         left.samples <- setdiff(subject.ids(), used.samples)

         if(length(left.samples) > max.subjects.plate()) {
           z <- sample(left.samples, max.subjects.plate(), replace=FALSE, prob=NULL)
           } else {
           z <- sample(left.samples, length(left.samples), replace=FALSE, prob=NULL)
          }

         tmp <- list(subj.ids = z)
         plates.data[[paste0('Plate.', i)]] <- tmp
       } 
      plates.data
    })


  output$result <- renderPrint({
    plates.w.subjects()
  })

Many thanks!
Maria

Hi all,

I seem to be having a similar issue to that of r2evans (which I see was over 3 years ago), but I can't seem to figure out how to correct my code so it works.
I use the list_graphVarPlot created in the portion of code beneath to later plot all the variables contained in the list, but it only takes into the account the last element of this list.
I confirmed this was the issue by changing the for loop to go from 1 to length(list_mod_Variables)-1, and I only got the before last element.
I'm not sure if the issue is coming from the x, or from the i of the for loop.
At first, I tried with x a variable that was supposed to change at every iteration, but I understand from your comments above that this will not work since x will be written over every time. This is why I now have created x as a list, but despite the x[[i]] that I thought should deal with this issue, the problem persists.

I would be very grateful if someone could help me out on this !

Many thanks

list_graphVarPlot <- list()
  x <- list(x1=0,x2=0,x3=0,x4=0)
  for (i in 1:(length(list_mod_Variables))){
      list_graphVarPlot[[i]] <- reactive({
        t <- table_input()
        if(!is.null(t)){
            x[[i]] <- as.vector(t[,list_mod_Variables[[i]]$varSelected()])
            x[[i]] <- eki_lag(x[[i]], list_mod_Variables[[i]]$graphLag())
            x[[i]] <- eki_adstock(x[[i]], list_mod_Variables[[i]]$graphAsInt(), list_mod_Variables[[i]]$graphAsLen())
            name <-paste("dataexpGraphVarPlot",i,sep = "")
            assign(name,x[[i]])
          } 
      })
    names(list_graphVarPlot)[i] = paste("graphVarPlot",i,sep = "")
  }

I have a similar problem for a matrix multiplication.
I try to propose a generalization of the problem here below
If I have an array a (3,4,2) and a matrix b (3,4) and I want to multiply each element of the array for the corresponding element of the matrix (like as I had two matrix instead of 1 arrays; in my real case I have an array (101,2,51) so I cannot simply split the array in 2 matrix) I can do in two ways:
`a<-array(1:24, dim=c(3,4,2))
b<-matrix(c(110,120,130,210,220,230,310,320,330,410,420,430),nrow=3, ncol=4)
c1<-array(NA, dim=c(3,4,2))c2<-array(NA, dim=c(3,4,2))

Two ways

for (h in 1:2) {c1[,,h]<-a[,,h]b}
c2[]<-apply(a, 3, function(x) x
b)

test

c1==c2`

If the matrix a is generated by an input statement of a shiny dashboard, it is necessary to create a reactive expression. First I tried two statements like above but they doesn't work, as highlighted in yours previous posts
for (h in 1:2) {c1[,,h]← reactive({a[,,h]*b})} c2[]<-reactive({apply(a, 3, function(x) x*b)})
The output matrix is not an array (3,4,2) but a matrix (3,4).
After reading this topic I tried to introduce a local variable in a for loop but it doesn't work again and I use already “apply” .

Any help will be appreciated

Thank you
Walter

@Walshiny, I suggest you read jcheng5's comment from 2014 that explained the use of local inside the loop in order to distinguish the looping variable in each iteration. whc's follow-on comment provided more context with both lapply and for examples. (Side note: you're not likely to get much response from the "main actors" here, since they tend to not pay attention to closed issues.)

Was this page helpful?
0 / 5 - 0 ratings