RShiny: modify numericInput based on data - r

Here's a sample code where I generate random vector and plot its histogram. In addition, there's a numericInput field that I use to clip data, i.e. to assign values lower than a threshold to that threshold. The initial value of the numericInput field is assigned based on data.
The problem is that when I press the button to generate data, the plot is evaluated twice, which I want to avoid. I emphasise this by adding sleep routine in the plotting function.
It seems to me that I'm updating the numericInput incorrectly. When I simply hard-code initial field value of that field, the issue is gone and the plot is evaluated once.
library(shiny)
ui <- shinyUI(fluidPage(
titlePanel("test data clipping"),
sidebarLayout(
sidebarPanel(
actionButton('inDataGen', 'Generate dataset'),
br(),
br(),
uiOutput('resetable_input_clip'),
actionButton('inDataClipReset', 'Reset data clipping')
),
mainPanel(plotOutput("plotHist", width = "100%"))
)
))
server <- shinyServer(function(input, output) {
rValues <- reactiveValues(dataIn = NULL,
dataMin = -10e10)
# generate random dataset
userDataGen <- observeEvent(input$inDataGen, {
cat(file = stderr(), '\nuserDataGen\n')
# assign result to shared 'dataIn' variable
x <- rnorm(1000)
rValues$dataIn = x
rValues$dataMin = min(x)
})
# modify data
userDataProc <- reactive({
cat(file = stderr(), 'userDataProc\n')
dm = rValues$dataIn
if (is.null(rValues$dataIn))
return(NULL)
else {
# Data clipping
dm[dm < input$inDataClipMin] <-
input$inDataClipMin
return(dm)
}
})
output$resetable_input_clip <- renderUI({
cat(file = stderr(), 'output$resetable_input_clip\n')
times <- input$inDataClipReset
div(
id = letters[(times %% length(letters)) + 1],
numericInput(
'inDataClipMin',
'Clip data below threshold:',
value = rValues$dataMin,
width = 200,
step = 100
)
)
})
output$plotHist <- renderPlot({
cat(file = stderr(), 'plotHist \n')
if (is.null(rValues$dataIn))
return(NULL)
else {
plot(hist(userDataProc()))
Sys.sleep(2)
}
})
})
shinyApp(ui = ui, server = server)
The flow after pressing the button to generate data involves two evaluations of plotHist:
output$resetable_input_clip
plotHist
userDataGen
plotHist
userDataProc
output$resetable_input_clip
plotHist
userDataProc
SOLVED ELSWHERE
This issue has been solved on Shiny Google group. The final solution is available here and is a combination of changing observeEvent + reactiveValues to reactive(), and using freezeReactiveValue.

I believe your issue is occurring in
# modify data
userDataProc <- reactive({
cat(file = stderr(), 'userDataProc\n')
dm = rValues$dataIn
if (is.null(df))
return(NULL)
else {
# Data clipping
dm[dm < input$inDataClipMin] <-
input$inDataClipMin
return(dm)
}
})
Since input$inDataClipMin is dependent on the reactive value rValues$dataMin, you end up rendering this for the initial value of rValues$dataMin, the rValues$dataMin is being reevaluated, which triggers a reevaluation of input$inDataClipMin.
If you replace this snippet with what I have below, you should get your desired behavior.
# modify data
userDataProc <- reactive({
cat(file = stderr(), 'userDataProc\n')
dm = rValues$dataIn
if (is.null(df))
return(NULL)
else {
# Data clipping
dm[dm < rValues$dataMin] <-
rValues$dataMin
return(dm)
}
})
As an alternative, you could put the following in your ui
numericInput(
'inDataClipMin',
'Clip data below threshold:',
value = rValues$dataMin,
width = 200,
step = 100
)
And then use updateNumericInput to replace it's value. This would require a lot more tinkering in your current code, however, and depending on what else is happening in your app, may not be the ideal solution anyway.

Here's what I came up with. The key difference is introduction of a shared reactive variable rValues$dataClip that stores clipped data. Previously, data modification was achieved by a reactive function userDataProc. The output of that function was used for plotting which, as suggested by #Benjamin, was the culprit of double evaluation of plotting. In this version, the userDataProc is turned into observeEvent that monitors input$inDataClipMin numeric input field.
library(shiny)
ui <- shinyUI(fluidPage(
titlePanel("test data clipping"),
sidebarLayout(
sidebarPanel(
actionButton('inDataGen', 'Generate dataset'),
br(),
br(),
uiOutput('resetable_input_clip'),
actionButton('inDataClipReset', 'Reset data clipping')
),
mainPanel(plotOutput("plotHist", width = "100%"))
)
))
server <- shinyServer(function(input, output, session) {
rValues <- reactiveValues(dataIn = NULL,
dataClip = NULL,
dataMin = -10e10)
# generate random dataset
userDataGen <- observeEvent(input$inDataGen, {
cat(file = stderr(), '\nuserDataGen\n')
# assign result to shared 'dataIn' variable
x <- rnorm(1000)
rValues$dataIn = x
rValues$dataMin = min(x)
})
# modify data
userDataProc <- observeEvent(input$inDataClipMin, {
cat(file = stderr(), 'userDataProc\n')
dm = rValues$dataIn
if (is.null(rValues$dataIn))
rValues$dataClip = NULL
else {
dm[dm < input$inDataClipMin] <-
input$inDataClipMin
rValues$dataClip <- dm
}
})
output$resetable_input_clip <- renderUI({
cat(file = stderr(), 'output$resetable_input_clip\n')
times <- input$inDataClipReset
div(
id = letters[(times %% length(letters)) + 1],
numericInput(
'inDataClipMin',
'Clip data below threshold:',
value = rValues$dataMin,
width = 200,
step = 100
)
)
})
output$plotHist <- renderPlot({
cat(file = stderr(), 'plotHist \n')
if (is.null(rValues$dataClip))
return(NULL)
else {
plot(hist(rValues$dataClip))
Sys.sleep(2)
}
})
})
shinyApp(ui = ui, server = server)
Now, there's only one evaluation of plotHist after pressing the button to generate data:
output$resetable_input_clip
plotHist
userDataProc
userDataGen
output$resetable_input_clip
userDataProc
plotHist

Related

In R Shiny, how to dynamically expand the use of a function as user inputs expand?

The following MWE code interpolates user inputs (Y values in 2-column matrix input grid in sidebar panel, id = input1) over X periods (per slider input in sidebar, id = periods). The custom interpolation function interpol() is triggered in server section by results <- function(){interpol(...)}. User has the option to add or modify scenarios by clicking on the single action button, which triggers a modal housing a 2nd expandable matrix input (id = input2). Interpolation results are presented in the plot in the main panel. So far so good, works as intended.
As drafted, the results function only processes the first matrix input including any modifications to it executed in the 2nd matrix input.
My question: how do I expand the results function to include scenarios > 1 added in the 2nd expandable matrix input, and automatically include them in the output plot? I've been wrestling with a for-loop to do this but don't quite know how. I've avoided for-loops, instead relying on lapply and related. But in practice a user will not input more than 20-30 scenarios max and perhaps a for-loop is a harmless option. But I'm open to any solution and am certainly not wedded to a for-loop!
MWE code:
library(shiny)
library(shinyMatrix)
interpol <- function(a,b){ # a = periods, b = matrix inputs
c <- rep(NA,a)
c[1] <- b[1]
c[a] <- b[2]
c <- approx(seq_along(c)[!is.na(c)],c[!is.na(c)],seq_along(c))$y # this interpolates
return(c)
}
ui <- fluidPage(
sidebarLayout(
sidebarPanel(uiOutput("panel"),actionButton("showInput2","Modify/add interpolation")),
mainPanel(plotOutput("plot1"))
)
)
server <- function(input, output, session){
results <- function(){interpol(req(input$periods),req(input$input1))}
output$panel <- renderUI({
tagList(
sliderInput('periods','Interpolate over periods (X):',min=2,max=12,value=6),
uiOutput("input1"))
})
output$input1 <- renderUI({
matrixInput("input1",
label = "Interpolation 1 (Y values):",
value = matrix(if(isTruthy(input$input2)){c(input$input2[1],input$input2[2])}
else {c(1,5)}, # matrix values
1, 2, # matrix row/column count
dimnames = list(NULL,c("Start","End")) # matrix column header
),
rows = list(names = FALSE),
class = "numeric")
})
observeEvent(input$showInput2,{
showModal(
modalDialog(
matrixInput("input2",
label = "Automatically numbered scenarios (input into blank cells to add):",
value = if(isTruthy(input$input2)){input$input2}
else if(isTruthy(input$input1)){input$input1},
rows = list(names = FALSE),
cols = list(extend = TRUE,
delta = 2,
delete = TRUE,
multiheader=TRUE),
class = "numeric"),
footer = modalButton("Close")
))
})
observe({
req(input$input2)
mm <- input$input2
colnames(mm) <- paste(trunc(1:ncol(mm)/2)+1, " (start|end)")
isolate(updateMatrixInput(session, "input2", mm))
})
output$plot1 <-renderPlot({
req(results())
plot(results(),type="l", xlab = "Periods (X)", ylab = "Interpolated Y values")
})
}
shinyApp(ui, server)
As a user can (presumably) add only one scenario at a time, I don't think a for loop is going to help. The way I handle situations like this is to bind additional data to the appropriate reactive in an observeEvent. This will then trigger updates in the necessary outputs automatically. Here's a MWE to illustrate the technique.
library(shiny)
library(tidyverse)
ui <- fluidPage(
actionButton("add", "Add scenario"),
plotOutput("plot"),
)
server <- function(input, output, session) {
v <- reactiveValues(results=tibble(Scenario=1, X=1:10, Y=runif(10)))
observeEvent(input$add, {
newData <- tibble(Scenario=max(v$results$Scenario) + 1, X=1:10, Y=runif(10))
v$results <- v$results %>% bind_rows(newData)
})
output$plot <- renderPlot({
v$results %>% ggplot() + geom_point(aes(x=X, y=Y, colour=as.factor(Scenario)))
})
}
shinyApp(ui, server)

R Shiny: Switching between tabPanels causes errors

I have created an App that will use an randomforest model to predict the type of Species in the Iris dataset. The idea is that a user can select a value for the other varaibles using input widgets, which the model then use to give a prediction. This all works fine.
I recently decided to implement a log containing the different inputs, a timestamp and the estimation. I've placed this log in another tabPanel to give a better overview. Everything apperes to work fine, when I hit the save button, the inputs, timestamp and estimation are saved in the log, however, when I go back to the original tabPanel ("Calculator"), errors appear saying that the number of columns doesn't match (or something like that, I have translated it from danish).
Does anyone know why this problem occours and how to fix it?
Im also having trouble running the app by using the "Run App" button in R. It works fine when I select everything with ctrl+A and hit ctrl+enter to run the code.
Here is my code:
require(shiny)
require(tidyverse)
require(shinythemes)
require(data.table)
require(RCurl)
require(randomForest)
require(mlbench)
require(janitor)
require(caret)
require(recipes)
require(rsconnect)
# Read data
DATA <- datasets::iris
# Rearrange data so the response variable is located in column 1
DATA <- DATA[,c(names(DATA)[5],names(DATA)[-5])]
# Creating a model
model <- randomForest(DATA$Species ~ ., data = DATA, ntree = 500, mtry = 3, importance = TRUE)
.# UI -------------------------------------------------------------------------
ui <- fluidPage(
navbarPage(title = "Dynamic Calculator",
tabPanel("Calculator",
sidebarPanel(
h3("Values Selected"),
br(),
tableOutput('show_inputs'),
hr(),
actionButton("submitbutton", label = "calculate", class = "btn btn-primary", icon("calculator")),
actionButton("savebutton", label = "Save", icon("save")),
hr(),
tableOutput("tabledata")
), # End sidebarPanel
mainPanel(
h3("Variables"),
uiOutput("select")
) # End mainPanel
), # End tabPanel Calculator
tabPanel("Log",
br(),
DT::dataTableOutput("datatable15", width = 300),
) # End tabPanel "Log"
) # End tabsetPanel
) # End UI bracket
# Server -------------------------------------------------------------------------
server <- function(input, output, session) {
# Create input widgets from dataset
output$select <- renderUI({
df <- req(DATA)
tagList(map(
names(df[-1]),
~ ifelse(is.numeric(df[[.]]),
yes = tagList(sliderInput(
inputId = paste0(.),
label = .,
value = mean(df[[.]], na.rm = TRUE),
min = round(min(df[[.]], na.rm = TRUE),2),
max = round(max(df[[.]], na.rm = TRUE),2)
)),
no = tagList(selectInput(
inputId = paste0(.),
label = .,
choices = sort(unique(df[[.]])),
selected = sort(unique(df[[.]]))[1],
))
) # End ifelse
)) # End tagList
})
# creating dataframe of selected values to be displayed
AllInputs <- reactive({
id_exclude <- c("savebutton","submitbutton")
id_include <- setdiff(names(input), id_exclude)
if (length(id_include) > 0) {
myvalues <- NULL
for(i in id_include) {
myvalues <- as.data.frame(rbind(myvalues, cbind(i, input[[i]])))
}
names(myvalues) <- c("Variable", "Selected Value")
myvalues %>%
slice(match(names(DATA[,-1]), Variable))
}
})
# render table of selected values to be displayed
output$show_inputs <- renderTable({
AllInputs()
})
# Creating a dataframe for calculating a prediction
datasetInput <- reactive({
df1 <- data.frame(AllInputs(), stringsAsFactors = FALSE)
input <- transpose(rbind(df1, names(DATA[1])))
write.table(input,"input.csv", sep=",", quote = FALSE, row.names = FALSE, col.names = FALSE)
test <- read.csv(paste("input.csv", sep=""), header = TRUE)
# Defining factor levels for factor variables
cnames <- colnames(DATA[sapply(DATA,class)=="factor"])
if (length(cnames)>0){
lapply(cnames, function(par) {
test[par] <<- factor(test[par], levels = unique(DATA[,par]))
})
}
# Making the actual prediction and store it in a data.frame
Prediction <- predict(model,test)
Output <- data.frame("Prediction"=Prediction)
print(format(Output, nsmall=2, big.mark=","))
})
# display the prediction when the submit button is pressed
output$tabledata <- renderTable({
if (input$submitbutton>0) {
isolate(datasetInput())
}
})
# -------------------------------------------------------------------------
# Create the Log
saveData <- function(data) {
data <- as.data.frame(t(data))
if (exists("datatable15")) {
datatable15 <<- rbind(datatable15, data)
} else {
datatable15 <<- data
}
}
loadData <- function() {
if (exists("datatable15")) {
datatable15
}
}
# Whenever a field is filled, aggregate all form data
formData <- reactive({
fields <- c(colnames(DATA[,-1]), "Timestamp", "Prediction")
data <- sapply(fields, function(x) input[[x]])
data$Timestamp <- as.character(Sys.time())
data$Prediction <- as.character(datasetInput())
data
})
# When the Submit button is clicked, save the form data
observeEvent(input$savebutton, {
saveData(formData())
})
# Show the previous responses
# (update with current response when Submit is clicked)
output$datatable15 <- DT::renderDataTable({
input$savebutton
loadData()
})
} # End server bracket
# ShinyApp -------------------------------------------------------------------------
shinyApp(ui, server)
When creating your reactive AllInputs, you are making a loop on id_include.
The problem is that all input[[i]] are not length 1 : they can be NULL or length more than one.
You cannot use a cbind on two variables of different lengths, which causes the mistake.
So I added a condition before calculating myvalues, and everything works fine :
# creating dataframe of selected values to be displayed
AllInputs <- reactive({
id_exclude <- c("savebutton","submitbutton")
id_include <- setdiff(names(input), id_exclude)
if (length(id_include) > 0) {
myvalues <- NULL
for(i in id_include) {
if(!is.null(input[[i]]) & length(input[[i]] == 1)){
myvalues <- as.data.frame(rbind(myvalues, cbind(i, input[[i]])))
}
}
names(myvalues) <- c("Variable", "Selected Value")
myvalues %>%
slice(match(names(DATA[,-1]), Variable))
}
})
By the way, for loops are not good practice in R, you may want to have a look at apply family functions.

R Shiny Application Conditional calculations and panel with condition on the output

I am new to Shiny. What I want to do in my application is, running & displaying some part of the code only when a condition on another calculation is met.
The conditionalPanel works fine with the conditions on input values but I could not figure out how to do this with the 'output' values, i.e., conditionally on the output values of the functions. Below is my example code:
library(shiny)
msLocation <- "msLoc"
searchMWText <- "searchMW"
bid <- "2333333"
fulltext <- "fullDisplay"
ui <- fluidPage(
titlePanel("Run server codes conditionally"),
sidebarLayout(
sidebarPanel(
helpText("Evaluate input and run different parts of the code depending on the output functions"),
br(),
sliderInput("rand", "select seed", min = 1, max = 50, step = 1, value = 1)
),
mainPanel(
fluidRow(conditionalPanel("output.rand == 1"),
tags$h4("Here comes the default part"),
br(),
textOutput("defaultCalc")),
fluidRow(conditionalPanel("output.randomint != 1",
tags$h4("I can evaluate if the chosen number is even or odd."),
br(),
textOutput("evenodd")
),
fluidRow(conditionalPanel("output.evenodd == 'Number is even'",
tags$h4("Number even calculation "),
textOutput("msLoc"),
br(),
textOutput("searchMW"),
br(),
textOutput("defaultID"),
br()
),
fluidRow(conditionalPanel("output.evenodd == 'Number is odd'",
tags$h4("Here is some id:", textOutput("id")),
textOutput("displayFull")
)
)
)
)
)))
#
server <- function(input, output) {
rand1 <- reactive({
if(is.null(input$rand)){return(NULL)}
rn <- input$rand
return(rn)
})
randomint <- reactive({
seedn <- rand1()
set.seed(seedn)
rint <- sample(1:50, 1)
return(rint)
})
calc1 <- reactive({
intn <- randomint()
modn <- intn %% 2
return(modn)
})
evenOdd <- reactive({
modn <- calc1()
if(modn == 0){valueText = "Number is even"}
if(modn != 0){valueText = "Number is odd"}
return(valueText)
})
idtext <- reactive({
idint <- sample(1:10000, 3)
idint <- as.character(idint)
idint <- paste(idint, collapse = "")
return(idint)
})
output$defaultCalc <- renderText({
as.character(randomint())
})
output$evenodd <- renderText({
evenOdd()
})
output$searchMW <- renderText({
searchMWText
})
output$defaultID <- renderText({
bid
})
output$id <- renderText({
idtext()
})
output$displayFull <- renderText({
fulltext
})
}
shinyApp(ui = ui, server = server)
The problem is, the parts after default always appear, e..g., 'Here is some id' text always appears and this is not what I want. I want to display 'Here is some id' and run the calculation (idtext) only when the number is odd.The number is not coming from the slider input, the slider input is providing the seed only. The number is also calculated and depends on its value, the other parts should be run and displayed. Until the user selects a slider input value, only the 'default part' should be displayed and nothing else.
I searched a lot and could not find a solution that mentions the conditions on output. What is the best way to solve this?
Do:
randomint <- reactive({
seedn <- rand1()
set.seed(seedn)
rint <- sample(1:50, 1)
return(rint)
})
output$randomint <- reactive(randomint())
outputOptions(output, "randomint", suspendWhenHidden = FALSE)
Then you can use "output.randomint !== 1".

Using dynamic input from tagList to renderTable in Shiny

What I am trying to do is have the user specify the number of groups then, based on the number of groups specified, the UI generates a numericInput for each group. Then I want to use that value to do some other operations (in this example, I'm making a table of means). Using this example, I was able to make it return some text, but not use that label as input for anything else.
When I try to use that information (i.e., as reactive conductor), I get a "replacement has length zero" error. It seems shiny is not recognizing the updated UI. I know it probably has something to do with using reactive, but I can't figure out why it's not working. Here's my code:
library(shiny)
library(purrr)
# functions ---------------------------------------------------------------
## generic function that creates an input from an i
make_list = function(i, idname, labelname){
idname <- paste(idname, i, sep = "")
div(style="display: inline-block;vertical-align:top; width: 45%;",
numericInput(idname, labelname, 0))
}
## make function that can be used within a loop
list_loop = function(i) {
make_list(i, "mean", "Mean of Group ")
}
# UI ----------------------------------------------------------------------
# Define UI for application that draws a histogram
ui <- fluidPage(
# Application title
titlePanel("A Test Page"),
sidebarLayout(
sidebarPanel(width = 8,
#### UI for groups
numericInput("groups", "How many groups?", 4),
hr(),
uiOutput("inputMean")),
# Main panel for displaying outputs ----
mainPanel(width = 4,
h3("Data Preview"),
#textOutput("inputValues"),
tableOutput("table"))
)
)
# Server ------------------------------------------------------------------
# Define server logic required to draw a histogram
server = function(input, output) {
## loop through # of groups for all i and make the UI
## this is passed back to the UI
observeEvent(input$groups,
{
output$inputMean = renderUI(
{
mean_list <- 1:input$groups %>% map(~list_loop(.x))
do.call(tagList, mean_list)
}
)
}
)
## return the inputnames
## This WORKS
output$inputValues <- renderText({
paste(lapply(1:input$groups, function(i) {
inputName <- paste("mean", i, sep = "")
input[[inputName]]
}))
})
make_table = reactive({
### prepopulate a table
d = data.frame(group = 1:input$groups)
d$means = NA
paste(lapply(1:input$groups, function(i) {
inputName <- paste("mean", i, sep = "")
# this fails because input is NULL at this point
d$means[i] = input[[inputName]]
}))
d
})
output$table <- renderTable({
make_table()
})
}
# Run the application
shinyApp(ui = ui, server = server)
If you replace your make_table with the following, it works.
I added a req that checks if all the input is present, so it won't throw errors anymore. Then, I filled d$means using the lapply you created.
make_table = reactive({
req(input$groups, input[[paste("mean", input$groups, sep = "")]])
### prepopulate a table
d = data.frame(group = 1:input$groups)
d$means = lapply(1:input$groups, function(i) {
inputName <- paste("mean", i, sep = "")
# this fails because input is NULL at this point
input[[inputName]]
})
d
})

R Shiny: Use reactiveValues() with data.table assign-by-reference

I have a shiny app in which more than one reactive component uses the same result from a function that is slow to calculate. To avoid calculating the slow function more than once, I can use reactiveValues() to recalculate the function when its inputs change, and make the result available to all reactive components that require it.
But, if the reactiveValues object is a data.table, and I update it using :=, shiny does not detect the change, and the outputs that rely on it do not get updated.
Is there any way to use data.table assign by reference either with reactiveValues or another way that avoids recalculating the function multiple times.
Here is a reproducible example using data.table assign-by-reference in which output$result2 fails to get updated when the input changes:
library(shiny)
library(data.table)
library(plotly)
ui = fluidPage(
sidebarLayout(
sidebarPanel(
sliderInput('x1', 'x1', min=0, max=2, value=1, step=0.1)
),
mainPanel(
plotlyOutput('result1'),
verbatimTextOutput('result2')
)
)
)
server = function(input, output) {
values <- reactiveValues()
values$dt = data.table(v1 = 1:100, v2 = 1)
slow.func = function(my.dt, x) {
Sys.sleep(2)
my.dt[, v2 := v1^x]
}
output$result1 = renderPlotly({
values$dt = slow.func(values$dt, input$x1)
plot_ly(values$dt) %>%
add_lines(x = ~v1, y = ~v2)
})
output$result2 = renderText({
paste('Final value =', values$dt[.N, v2])
})
}
shinyApp(ui = ui, server = server)
For comparison, here is a version of the server function using standard assignment of data.frames, which does perform as expected:
server = function(input, output) {
values <- reactiveValues()
values$dt = data.frame(v1 = 1:100, v2 = 1)
slow.func = function(my.dt, x) {
my.dt$v2 = my.dt$v1^x
Sys.sleep(2)
my.dt
}
output$result1 = renderPlotly({
values$dt = slow.func(values$dt, input$x1)
plot_ly(values$dt) %>%
add_lines(x = ~v1, y = ~v2)
})
output$result2 = renderText({
paste('Final value =', values$dt[100,]$v2)
})
}
say you have defined a reactive variable table_updated so you can increment it by one each time the slow function is done. Other values/plots will only need to observe table_updated.
Actually the actionButton(see description section) does the same thing, every time it gets clicked, its value is incremented by 1.
values <- reactiveValues(table_updated = 0)
slow.func = function(my.dt, x) {
# do stuff
values$table_updated <- values$table_updated + 1
}
output$result2 = renderText({
values$table_updated
paste('Final value =', values$dt[100,]$v2)
})

Resources