Programming a reactive counter with if conditional loop statement in Shiny - r

I have been trying to create a dice/gambling simulator with Shiny, but I am having two problems (though both may be linked to the one error, I am not sure). I basically create a reactiveValue, set at 10000 (dollars) initially within the server, along with a sample( ) function that only draws from between 1-6 (for a six-faced die) and allows the user to choose the number of dice/tosses. I then set up an observeEvent where an if-loop determines that if the random numbers generated within the sample( ) function are equal to or greater than the number of dice that the user bet would show up, and so long as the reactiveValue remains about 1, then the user can double their "money." If their bet is less than what was generated by sample, then they lose half. If they lose so much that they are below one, then they aren't allowed to bet anymore or at least told that they are broke, but this isn't such an important part to the code.
Right now, I seem to be unable to get the if loop to work, as it throws the error:
Warning: Error in if: missing value where TRUE/FALSE needed
library(shiny)
ui <- fluidPage(
pageWithSidebar(
titlePanel("Feelin' Lucky?"),
sidebarPanel(
p("In this simulation, you can select the number of dice you would like to roll,
and then make a 'bet' on the numbers that will show up with every roll. If there are more faces
than the number you guessed, then you still win. If there are less faces, however,
you will lose 'money,' displayed at the top of the main panel. Give it a try and
good luck!"),
numericInput("dicenum", "Enter the number of dice to roll:", 1),
numericInput("face", "What face do you think will be rolled?", 1),
numericInput("bet", "How many of this face do you think will be rolled?", 1),
actionButton("go1", "GO!")
),
mainPanel(
htmlOutput("the.dough"),
htmlOutput("the.bet"),
textOutput("results"),
htmlOutput("results2"),
htmlOutput("warning")
)
)
)
server <- function(input, output, session) {
money <- 10000
output$the.dough <- renderText(
paste(money)
)
buttonValue <- reactiveValues(go1=FALSE)
rv <- reactiveValues(money.tot = 10000)
observeEvent(input$go1,{
isolate({buttonValue$go1=TRUE})
the.roll <- sample(1:6, size = input$dicenum, replace=TRUE)
the.face <- as.numeric(input$face) #Dice Face Bet
amount <- as.numeric(input$bet)
output$warning <- renderText({
goop <- sum(the.roll==the.face)
if ((goop >= amount) & (rv$money.tot>1)) {
rv$money.tot <- sum(rv$money.tot, rv$money.tot*2-rv$money.tot)
paste("Well-done! You won the bid! You now have: $", rv$money.tot)
} else if ((goop < amount) & (rv$money.tot>1)) {
rv$money.tot <- rv$money.tot-(rv$money.tot/2)
paste("Darn! It looks like you lost the bid! You now have: $", rv$money.tot)
} else if (rv$money.tot<1) {
paste("You are broke! You cannot bid anymore money!")
} else {
NULL }
})
})
}
shinyApp(ui, server)
I have looked up what this means, but I cannot find a case that would exactly explain why it is doing it in my script, especially since the if loop works when written as a base R if loop, i.e. no reactivity or Shiny involved. Example of the working loop:
rvv <- 10000
face.guess <- 4
throw <- sample(1:6, size = 10, replace = TRUE)
guess.howmany <- 2
if (sum(throw==face.guess)>=guess.howmany & rvv>1) {
rvv <- sum(rvv, rvv*2-rvv)
print("Woohoo!")
print(rvv)
} else if (sum(throw==face.guess)<guess.howmany & rvv>1){
print("Darn")
rvv <- rvv-(rvv/2)
print(rvv)
} else if (rvv<1) {
print("You are broke! You can't bid anymore money!") }
Other times, the if loop does seem to partially work, but only to jump to the last option, somehow reducing the starting reactiveValue to below 1 and declaring them broke on the first bid, which should not be possible.
My computer is wonky right now, too, so I apologise if there is syntax issues in the above - it could be partly from not being able to copy/paste properly at the moment.
Any help is much appreciated!
EDIT: Fixed code. I also figured out that when the user gets the bid right, there is a delay, and then it throws the above error. When they guess too high/wrong, it displays the last option (print("You are broke!")), which seems to suggest there is a problem explicitly with how I am invoking the other condition ("goop"). I just do not get what.

I figured out a fix for it: I basically had to take the if loop outside of the renderText( ), and instead imbed multiple renders within each step of the if loop. I copied the corrected server portion below. Probably not the most elegant fix, but it works as I wanted:
server <- function(input, output, session) {
money <- 10000
output$the.dough <- renderText(
paste(money)
)
buttonValue <- reactiveValues(go1=FALSE)
rv <- reactiveValues(money.tot = 10000)
observeEvent(input$go1,{
isolate({buttonValue$go1=TRUE})
the.roll <- sample(1:6, size = input$dicenum, replace=TRUE)
the.face <- as.numeric(input$face) #Dice Face Bet
amount <- as.numeric(input$bet)
goop <- sum(the.roll==the.face)
if (isTRUE(goop >= amount) & isTRUE(rv$money.tot>1) & buttonValue$go1) {
rv$money.tot <- sum(rv$money.tot, rv$money.tot*2-rv$money.tot)
output$warning <- renderText({
paste("Well-done! You won the bid! You now have: $", rv$money.tot)
})
} else if (isTRUE(goop < amount) & isTRUE(rv$money.tot>1) & buttonValue$go1) {
rv$money.tot <- rv$money.tot-(rv$money.tot/2)
output$warning <- renderText({
paste("Darn! It looks like you lost the bid! You now have: $", rv$money.tot)
})
} else if (isTRUE(rv$money.tot<1) & buttonValue$go1) {
output$warning <- renderText({
paste("You are broke! You cannot bid anymore money!")
})
} else {
NULL }
output$results <- renderText(
paste("You bid that there would be:", amount, "of the", the.face, "face.")
)
output$results2 <- renderText(
paste("Actual Result:", as.numeric(sum(the.roll==the.face)), "of the", the.face)
)
})
}
Hope this helps anyone who is trying to do anything similar!

Related

How to force evaluation in shiny render when generating dynamic number of elements?

I generate a dynamic number of valueBox in my shiny, and this number can change depending of the user input.
I managed to handle this with a renderUI where I put the wanted number of valueBoxOutput, and I have an observe that will feed them with the content using renderValueBox.
My problem is: the code in the renderValueBox, for some reason, is actually executed after the observe is finished, so because the renderValueBox is in a loop (to have a dynamic number of them) but the code is executed for all the output after the loop, all my output will get the last value of the loop.
Here is a min reprex:
library(shiny)
library(shinydashboard)
library(shinyWidgets)
# Function
compute <- function(id)
{
print(paste("Compute ", id))
return(id)
}
# UI
ui = shinyUI(fluidPage(
titlePanel("Compare"),
useShinydashboard(),
sidebarLayout(
sidebarPanel(
numericInput("numitems", label = "Number of items", min = 1, max = 10, value = 2)
),
mainPanel(
uiOutput("boxes")
)
)
))
# Server
server = shinyServer(function(input, output, session) {
data <- reactiveValues(
ids = list()
)
output$boxes <- renderUI({
print("boxes")
box_list <- list()
id_list <- list()
for(id in 1:(input$numitems)) {
id_box <- paste0("box_", id)
print(paste("boxes - ", id_box))
id_list <- append(id_list, id_box)
box_list <- append(
box_list,
tagList(
shinydashboard::valueBoxOutput(id_box)
)
)
data$ids <- id_list
}
print("boxes end")
fluidRow(box_list)
})
observe({
print("observe")
for(id_box in data$ids) {
print(paste("observe - ", id_box))
output[[id_box]] <- shinydashboard::renderValueBox(valueBox(id_box, compute(id_box), icon = icon("circle-info"), color = "teal"))
}
print("end observe")
})
})
# Run
shinyApp(ui = ui , server = server)
Here is the result:
And the console output:
As you can see the compute (and the render in general) is done after the end of the observe function, and both output will use the last id_box that were set (so the last loop, box_2), instead of correctly using box_1 and box_2.
I tried using force, computing valueBox outside the render, using reactive lists, nothing worked, because whatever I do the render is evaluated after the observe so only the last loop values will be used no matter what.
Do anyone know a way to force execution during the loop ? Or see another way of achieving the same result ?
Why it's always after spending hald a day on a problem, looking for dozens of posts and forum, don't find anything, finally decide to ask a question... that a few minutes later I finally find an answer.
Anyway, one way to correct this (found here) is to encapsulate the render inside the local function, like this:
observe({
print("observe")
for(id_box in data$ids) {
print(paste("observe - ", id_box))
local({
tmp <- id_box
output[[tmp]] <- shinydashboard::renderValueBox(valueBox(tmp, compute(tmp), icon = icon("circle-info"), color = "teal"))
})
}
print("end observe")
})
Now the compute is still called after the end of the observe, but the tmp variable has the correct value:
The result is what I wanted:
For the record, I had already tried to use the local function, but if you don't copy the id_box inside another variable just for the local bloc, it won't work.

Debounce timer not restarting when input is changed

I am trying to create a date input that automatically updates the inputted value to the end of the selected month. The problem I was having was when I ran this first bit of code (below) was that if the user tries to manually change the date by typing in a date, they are unable to because the input is updated instantly to the end of the month before the user can finish typing.
library(shinydashboard)
library(lubridate)
ui <- dashboardPage(
dashboardHeader(),
dashboardSidebar(),
dashboardBody(
dateInput(
inputId = "date",
label = "End of Month Date",
value = ceiling_date(x = Sys.Date() + 365, unit = "month") - 1,
startview = "year"
)
)
)
server <- function(input, output, session) {
observe({
if(is.Date(input$date) & length(input$date) > 0){
if(input$date != ceiling_date(input$date, unit = "month") - 1) {
updateDateInput(
session,
inputId = "date",
value = ceiling_date(x = input$date, unit = "month") - 1
)
}
}
})
}
shinyApp(ui, server)
So, I tried implementing debounce (as shown below - server code change only) so that it would delay the input from updating until the user was finished typing; however, I am running into an issue. The timer does not reset on each key stroke as I understand it should. Instead, the timer operates as if I were using the throttle function and starts when the input is first changed and doesn't reset when the input changes.
server <- function(input, output, session) {
observe({
dateinputdelay <- debounce(r = reactive(input$date), millis = 2000)
if(is.Date(dateinputdelay()) & length(dateinputdelay()) > 0){
if(dateinputdelay() != ceiling_date(dateinputdelay(), unit = "month") - 1) {
updateDateInput(
session,
inputId = "date",
value = ceiling_date(x = input$date, unit = "month") - 1
)
}
}
})
}
Edit: the best I can get is with the code below, but because the "fancy date input thingy" has its own internal updating mechanism separate from shiny's reactive concept, this debouncing is only addressing one source of problem.
The first trick is that the debounce needs to happen before the block of code is even started. That is, it "de-bounces" the start of the dependent code blocks; once they are started, it does not help.
The second is knowing that observe blocks attempt to run their code eagerly (see shiny docs and read "Details"), whereas reactive blocks are relatively lazy -- they only run dependent code as needed. It's the "eager" part that may be hurting.
Additionally, you use side-effect in the observe block to update the input field, but you never store the value elsewhere. I suggest that it might be better to calculate the new value in one place (in a functional manner, like a reactive block that should not operate in side-effect) and then use that later.
server <- function(input, output, session) {
dateinputdelay <- debounce(reactive(input$date), 2000)
end_of_month <- reactive({
# print("react!")
x <- dateinputdelay()
if (is.Date(x) & length(x) > 0) {
if (x != ceiling_date(x, unit = "month") - 1) {
x <- ceiling_date(x = input$date, unit = "month") - 1
}
}
x
})
observe({
# print("observe!")
updateDateInput(
session,
inputId = "date",
value = end_of_month()
)
})
}
(I've kept a couple of print statements in there, not because they help much here, but because they can be a good tool to see when/how-frequently reactitivity is causing code-blocks to run.)
As I said up top, I suspect this behavior is indicative of something within dateInput, not within the subsequent reactive or observe blocks.

shiny interacitvity between two output variables

I struggle at the moment and would require some help.
I have two rhandondables therefore two outputs. Within the render function I make some changes of a dataframe and the user is able to change the dataframe in rhandsonable.
first table:
output$out <- renderRHandsontable({
if (is.null(input$out)) {
hot <- rhandsontable(df())
} else {
hot <- hot_to_r(input$out)
hot <- rhandsontable(hot)
}
})
second table:
output$out2 <- renderRHandsontable({
if (is.null(input$out)) {
hot <- rhandsontable(df())
} else {
hot <- hot_to_r(input$out2)
hot <- rhandsontable(hot)
}
})
To make it a little more clear, lets assume the first table (output$out) shows a table in absolute numbers and the second (output$out2) in percentage. What I would like to point at is that if one updates one table the other table needs to be updated as well. i.e. percentage numbers need to be calculated in absolute numbers and "go back" to a big flat datatable.
Now how do I make this two interactive so that if I update table one the changes will be submitted to table two and the other way around so that always the "most recent" changes are reflected.
Appreciate any help
If both your tables share the same underlying data, and you update the underlying data whenever change happens, then it should work. Below is an simple example where both tables will show the same dataset. Update in one will be reflected in the other.
library(shiny)
ui <- shinyUI(fluidPage(
titlePanel("Syncing two RHandontables"),
sidebarLayout(
sidebarPanel(
),
mainPanel(
rHandsontableOutput("out1"),
tags$hr(),
rHandsontableOutput("out2")
)
)
))
server <- shinyServer(function(input, output) {
data <- reactiveValues(data=head(iris))
output$out1 <- renderRHandsontable({
rhandsontable(data$data)
})
output$out2 <- renderRHandsontable({
rhandsontable(data$data)
})
observeEvent(input$out1, {
data$data <- hot_to_r(input$out1)
})
observeEvent(input$out2, {
data$data <- hot_to_r(input$out2)
})
})
shinyApp(ui = ui, server = server)

In R Shiny, use the value from an SelectInput that was populated by a previous choice

I have a very simple Shiny app.
I want to select an ID and a Date, and get the result.
The data is simple
DF_custs <- data.frame(ID=c(1,2,3,3), Date=c('1/1/2010', '1/1/2011', '1/1/2012', '1/1/2013'), result=c(100, 200, 300, 400))
And the UI is set up and currently looks like this:
Say, I choose ID number 3. Then, the Select date menu is populated. Next I select a date. So far so good.
My question is, what kind of object to I need to implement, in order to display the correct result which depends on the date I've just selected? As it stands this functionality is missing and the app just burps out both results, no matter what.
In looking to solve this, I'm not sure if SelectInput will be of much use. I see that the SelectInput that is used alongside the renderUI does what it is supposed to do, which is to render the dropdown in the UI. How would I send the value in the SelectInput back over to the server?
I could try some sort of modification of output$result, such as
output$result <- renderText({
ans <- get_cust()$result
ans <- ans[which(ans$Date==output$dates), Date]
paste("Result: ", ans)})
but now I'm in some Dr Who paradox where the ans can't exist if the ans didn't already exist.
For me, working with reactivity in Shiny gets uncomfortable when there are sequences of actions that need to be performed, before a final result. This inevitably means that things need to be evaluated at different times. Shiny is easy when things fire all at once and all the reactive stuff happily executes together. But even in simple toy situations like this, I realize I need another tool in my toolbox. Any help is greatly appreciated.
app.R
DF_custs <- data.frame(ID=c(1,2,3,3), Date=c('1/1/2010', '1/1/2011', '1/1/2012', '1/1/2013'), result=c(100, 200, 300, 400))
DF_custs$Date <- as.character(DF_custs$Date)
## app.R ##
server <- function(input, output, session) {
get_cust <- reactive({
cust <- DF_custs[which(DF_custs$ID == input$ID), ]
return(cust)})
output$result <- renderText({
ans <- get_cust()$result
paste("Result: ", ans)})
output$dates<-renderUI({
selectInput('dates', 'Select date:', choices=get_cust()$Date, selected=get_cust()$Date)})
}
ui <- fluidPage(
numericInput(inputId="ID", label="Pick an ID: ", value=1),
uiOutput("dates"),
mainPanel(textOutput("result"))
)
shinyApp(ui = ui, server = server)
PS thanks to #Victorpfor his help here.
Me again, just do :
output$result <- renderText({
ans <- get_cust()$result
ans <- ans[DF_custs$Date[which(DF_custs$ID == input$ID)] == input$dates]
paste("Result: ", ans)
})
You can probably do something more efficient but it will work, if you want to check that input$dates is defined you can add a if with !is.null(input$dates) for preventing the code to execute.
Here's a simple solution using observeEvent and reactive in the server and selectInput for the date in the ui:
DF_custs <- data.frame(ID=c(1,2,3,3),
Date=c('1/1/2010', '1/1/2011',
'1/1/2012', '1/1/2013'),
result=c(100, 200, 300, 400))
DF_custs$Date <- as.character(DF_custs$Date)
server <- function(input, output, session) {
result <- reactive({paste("Result: ", input$ID, " # ", input$Date) })
output$result <- renderText({ result()})
observeEvent(input$ID, {updateSelectInput(session, "Date",
choices=DF_custs[input$ID == DF_custs$ID, "Date"])})
}
ui <- fluidPage(
numericInput(inputId="ID", label="Pick an ID: ", value=1,
min=1, max=3, step=1),
selectInput(inputId="Date",label="Pick a date",
choices=c("Choose") , selectize = FALSE ),
mainPanel(textOutput("result"))
)
shinyApp(ui = ui, server = server)
Hope this helps! There are more examples # RStudio

for Loop - Replacement has length zero

I have a little problem with a loop. Here is the code of the loop:
for (i in 1:length(input$count))
{
id<-paste("text",i)
titles[i]<-input$id
}
This returns the following error
Error in titles[i] <- input$id : replacement has length zero
ui.R
library(shiny)
ui <- fluidPage(
numericInput("count", "Number of textboxes", 3),
hr(),
uiOutput("textboxes")
)
server.R
server <- function(input, output, session) {
output$textboxes <- renderUI({
if (input$count == 0)
return(NULL)
lapply(1:input$count, function(i) {
id <- paste0("text", i)
print(id) // its prints the text1, text2,text3
numericInput(id, NULL, value = abc)
print(input$text1) //it should print value abc , but it is not, why??
})
})
}
This error indicates that your input id is either NULL or a length 0 vector: make sure the indices are correct.
Additionally, in R it is usually best to avoid for loops as they tend to be pretty slow: see Why are loops slow in R?. There's almost always a way to avoid using a for loop and using a vectorised function instead, although the example as it stands doesn't provide enough detail to actually suggest a function.

Resources