How to store an input in a dynamic vector?

862 views
Skip to first unread message

Martin Law

unread,
Dec 10, 2013, 6:05:55 AM12/10/13
to shiny-...@googlegroups.com
Hi there,

As the title states, I'm having trouble storing an input in a dynamic vector. It's an almost identical problem to this question, but I could not make the workaround work for me. I would like to do the following: Input the result of a dice roll, add the result to a vector of previous results and display a histogram of previous results. The full app has slightly more to it, but if I could achieve this, I could get the rest done myself. It is necessary to hide the histogram until the new input has been made -- the app is for a science festival, and we want children to guess the roll of a die, then we record both their guess and the actual result, showing them both histograms.

I have looked all over the forum and tried a lot of different things, but I couldn't get any of them to do what I wanted. I would really appreciate any advice that would get me over this final hurdle.

Below is my code so far. I have changed the code a little to show that I can get the histogram no problem for a single roll, but not for a dynamic vector.

server.R:

library(shiny)
allguesses <- c()
allrolls <- c()

shinyServer(function(input, output) {
 
  formulaText <- reactive({
    paste("You guessed ", input$myguess, ", and you rolled a ", input$roll, sep="")
  })
 
  formulaText2 <- reactive({
    paste("You are guesser number ", input$goButton, sep="")
  })
 
  guessInput <- reactive(function(){
    allguesses <<- c(allguesses, as.numeric(input$myguess))
    return(allguesses)  
  })
   
     output$caption <- renderText({
     formulaText()
  })
    output$outk <- renderText({
      formulaText2()
    })
 
  output$guesshist <- renderPlot({
    hist(as.numeric(allguesses), main="Everyone's guesses so far", xlab="", cex.main=2,col=4,
         breaks=c(seq(0.5, 6.5, by=1)))
  })
 
  output$rollhist <- renderPlot({
    hist(as.numeric(input$roll), main="Everyone's rolls so far (just last roll at the moment)", xlab="", cex.main=2,col=3,
         breaks=c(seq(0.5, 6.5, by=1)))
  })
})



ui.R:

library(shiny)

shinyUI(pageWithSidebar(
  headerPanel("Dice game"),
  sidebarPanel(
    selectInput("myguess", "My guess:",
                list("1" = 1,
                     "2" = 2,
                     "3" = 3,
                     "4" = 4,
                     "5" = 5,
                     "6" = 6),
                "1"),
   
    selectInput("roll", "Actual roll:",
                list("1" = 1,
                     "2" = 2,
                     "3" = 3,
                     "4" = 4,
                     "5" = 5,
                     "6" = 6),
                "1"),
   
    actionButton("goButton", "Click here if you're sure"),
#   submitButton("Let's go!"),
    wellPanel(
      p(strong("Show me the money!")),
      checkboxInput(inputId = "plots_tick", label = "aka click here", value = F)
    )
  ),
  mainPanel(
    conditionalPanel(condition = "input.plots_tick",
                     br(),
                     h3(textOutput(outputId = "caption"))),
    conditionalPanel(condition = "input.plots_tick",
                     br(),
                     h3(textOutput(outputId = "outk"))),
    conditionalPanel(condition = "input.plots_tick",
                     br(),
                     div(plotOutput(outputId = "guesshist"))),
    conditionalPanel(condition = "input.plots_tick",
                     br(),
                     div(plotOutput(outputId = "rollhist")))
  )
))


Regards,
Martin Law

Martin Law

unread,
Dec 10, 2013, 6:14:47 AM12/10/13
to shiny-...@googlegroups.com
Apologies, I made a typo in my server.R code. Here is the corrected version:

library(shiny)
allguesses <- c()
allrolls <- c()

shinyServer(function(input, output) {
 
  formulaText <- reactive({
    paste("You guessed ", input$myguess, ", and you rolled a ", input$roll, sep="")
  })
 
  formulaText2 <- reactive({
    paste("You are guesser number ", input$goButton, sep="")
  })
 
  guessInput <- reactive(function(){
    allguesses <<- c(allguesses, as.numeric(input$myguess))
    return(allguesses)  
  })
   
     output$caption <- renderText({
     formulaText()
  })
    output$outk <- renderText({
      formulaText2()
    })
 
  output$guesshist <- renderPlot({
    hist(as.numeric(guessInput), main="Everyone's guesses so far", xlab="", cex.main=2,col=4,    

ZJ

unread,
Dec 10, 2013, 9:38:23 AM12/10/13
to shiny-...@googlegroups.com
The new server.R I've made bold the changes. Ask any questions you want about the code.


library(shiny)

shinyServer(function(input, output) {
  guessesRolls <- reactiveValues(
    allguesses = c(),
    allrolls = c()    
  )
  
  formulaText <- reactive({
    paste("You guessed ", input$myguess, ", and you rolled a ", input$roll, sep="")
  })
  
  formulaText2 <- reactive({
    paste("You are guesser number ", input$goButton, sep="")
  })
  
  guessInput <- reactive({
    input$myguess
    input$plots_tick
    if(input$goButton == 0) return()
    isolate({
      guessesRolls$allguesses <- c(guessesRolls$allguesses, as.numeric(input$myguess))
      return(guessesRolls$allguesses)   
    })
  })
  
  output$caption <- renderText({
    formulaText()
  })
  output$outk <- renderText({
    formulaText2()
  })
  
  output$guesshist <- renderPlot({
    #browser()
    hist(as.numeric(guessInput()), main="Everyone's guesses so far", xlab="", cex.main=2,col=4,     
         breaks=c(seq(0.5, 6.5, by=1))) 
  })
  
  output$rollhist <- renderPlot({
    input$roll
    hist(as.numeric(input$roll), main="Everyone's rolls so far (just last roll at the moment)", xlab="", cex.main=2,col=3,
         breaks=c(seq(0.5, 6.5, by=1))) 
  })
})

Martin Law

unread,
Dec 10, 2013, 12:23:39 PM12/10/13
to shiny-...@googlegroups.com
Hello ZJ,

Thank you so much for help; that is extremely helpful. I had tried to use reactiveValues and reactive without success. Now I think I see where I'm going wrong. I have just a couple of questions for my own satisfaction:

guessInput is reactive:


  guessInput <- reactive({
    input$myguess
    input$plots_tick
    if(input$goButton == 0) return()
    isolate({
      guessesRolls$allguesses <- c(guessesRolls$allguesses, as.numeric(input$myguess))
      return(guessesRolls$allguesses)   
    })
  })

It looks like every time input$myguess or input$plots_tick changes, guessesRolls$allguesses is updated. Is that correct? guessesRolls$allguesses is also updated when input$goButton is clicked, but I'm not sure why. I'm also confused by isolate(), and why using it to "block" an invalidation is useful, but I'm happy to keep reading the tutorial, and more help questions, and more code examples so that I'll understand it eventually.

Just for completeness, for anyone who reads this in future: I only wanted my histograms to update when input$myguess is changed, and commenting out these lines

  #  input$plots_tick
  #  if(input$goButton == 0) return()


gives me exactly what I wanted.

Thank you again ZJ, your help has been invaluable.


Regards,
Martin

Stéphane Laurent

unread,
Dec 10, 2013, 1:57:41 PM12/10/13
to shiny-...@googlegroups.com

ZJ

unread,
Dec 10, 2013, 9:04:54 PM12/10/13
to shiny-...@googlegroups.com
If you don't isolate allguesses then it will invalidate guessInput which in turn updates allguesses which will invalidate guessInput... This will be an infinite loop.

Any input$anything or reactives you put inside a reactive will invalidate the reactive and causes it to recalculate (unless the input$anything or reactives are put into an isolate). So

 input$myguess
    input$plots_tick
    input$goButton

will invalidate guessInput

 guessInput <- reactive({
    input$myguess
    input$plots_tick
    if(input$goButton == 0) return()
    isolate({
      guessesRolls$allguesses <- c(guessesRolls$allguesses, as.numeric(input$myguess))
      return(guessesRolls$allguesses)   
    })
  })


Joe Cheng

unread,
Dec 11, 2013, 1:18:00 AM12/11/13
to ZJ, shiny-...@googlegroups.com
I almost agree with ZJ's solution except for one thing. Any time you assign to the reactiveValues, you are performing a side-effect; so this operation should not go into a reactive expression (i.e. reactive({ ... })), but in an observer. The code inside reactive expressions should not assign any variables that live outside of that expression. This is because reactive expressions execute lazily, and therefore, unpredictably. For example, if in a later version of the app, the guessInput plot is put on a tab, then whenever the tab is not shown then that reactive will not execute and those guesses will not be added to the list! (That's because Shiny notices when outputs aren't shown, and by default, doesn't execute them; and so guessInput() will never be called; and so guessInput will never execute.) This isn't a problem with an observer; observers execute eagerly when they notice that their reactive dependencies changed.

I'm too tired right now to rewrite the code in accordance with this principle and with Martin's latest clarification, hopefully you can make sense of this.


--
You received this message because you are subscribed to the Google Groups "Shiny - Web Framework for R" group.
To unsubscribe from this group and stop receiving emails from it, send an email to shiny-discus...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.

ZJ

unread,
Dec 11, 2013, 1:25:16 AM12/11/13
to shiny-...@googlegroups.com, ZJ
I think Joe means something like the below. Come to think of it guessInput just returns allguesses so why not make the other parts react to allguess directly


  observe({
    input$myguess
    isolate({
      guessesRolls$allguesses <- c(guessesRolls$allguesses, as.numeric(input$myguess))
      return(guessesRolls$allguesses)   
    })
  })
  
  ....
  output$guesshist <- renderPlot({
    hist(as.numeric(guessesRolls$allguesses ), main="Everyone's guesses so far", xlab="", cex.main=2,col=4,     
         breaks=c(seq(0.5, 6.5, by=1))) 
  })
  

Martin Law

unread,
Dec 11, 2013, 11:32:06 AM12/11/13
to shiny-...@googlegroups.com

On Tuesday, 10 December 2013 18:57:41 UTC, Stéphane Laurent wrote:


Hi Stephane,

Thank you. I've played around with it, and also another version of the app without using isolate(). I can see that when isolate() is not used, output$result is updated automatically. When isolate() is used, A+B only updates when the action button is pressed.

Martin Law

unread,
Dec 11, 2013, 11:43:32 AM12/11/13
to shiny-...@googlegroups.com, ZJ
I've made those suggested updates, and the code now looks like this:

server.R

library(shiny)

shinyServer(function(input, output) {
  guessesRolls <- reactiveValues(
    allguesses = c(),
    allrolls = c()   
  )
 
  formulaText <- reactive({
    paste("You guessed ", input$myguess, ", and you rolled a ", input$roll, sep="")
  })
 
  formulaText2 <- reactive({
    paste("You are guesser number ", input$goButton+1, sep="")
  })

 
  observe({
    input$myguess
    isolate({
      guessesRolls$allguesses <- c(guessesRolls$allguesses, as.numeric(input$myguess))
      return(guessesRolls$allguesses)  
    })
  })
 
  rollInput <- reactive({
    input$roll
    isolate({
      guessesRolls$allrolls <- c(guessesRolls$allrolls, as.numeric(input$roll))
      return(guessesRolls$allrolls)
    })
  })
 
  output$caption <- renderText({
    formulaText()
  })
  output$outk <- renderText({
    formulaText2()
  })
 
  output$guesshist <- renderPlot({
    hist(as.numeric(guessesRolls$allguesses), main="Everyone's guesses so far", xlab="",col=4,    
         breaks=c(seq(0.5, 6.5, by=1)),axes=F,ylab="Frequency", cex.main=2, cex.lab=1.25)
    axis(side=1, at=c(1:6), cex.axis=2)
    axis(side=2)

  })
 
  output$rollhist <- renderPlot({
    input$roll
    hist(as.numeric(rollInput()), main="Everyone's rolls so far", xlab="", cex.main=2,col=3,
         breaks=c(seq(0.5, 6.5, by=1)),axes=F,ylab="Frequency", cex.main=2, cex.lab=1.25)
    axis(side=1, at=c(1:6), cex.axis=2)
    axis(side=2)
  })
})


Of course I can make the same change to the rolls as I did to the guesses. I will also try to figure out a way to add to each vector when the same choice is made twice in a row, e.g. two guesses of "1" in a row.
Reply all
Reply to author
Forward
0 new messages