How to detect a blank input for a date in Shiny - r

I have a series of inputs in my R Shiny app that I am using as arguments to a function to select specific data from a data frame. On of the inputs is dateInput:
dateInput("dateSelect", "Date", format = "yyyy-mm-dd", value = NA)
In my function, I need to specify if dateSelect is blank or not selected, to be able select All dates. See an example that is working correctly, that isn't a date, and a simple selectInput:
selectInput("teamSelect", "Team", choices = c("All", levels(newEffortstable$team)))
In the function, this works to select 'All teams':
if(!missing(teamSelect)){
if(teamSelect!="All"){
selections[["teamEfforts"]] =
newEffortstable$effortNo[which(newEffortstable$team %in% teamSelect)]
}else{
selections[["teamEfforts"]] = newEffortstable$effortNo
}
}
I have tried the following with NA and NULL and " " and keep getting 'Error in if: argument is of length zero'
if(!missing(dateSelect)){
if(!dateSelect== "NA"){
selections[["dateEfforts"]] =
newEffortstable$effortNo[which(newEffortstable$date == dateSelect)]
}else{
selections[["dateEfforts"]] = newEffortstable$effortNo
}
}
Thanks!

There's one option you didn't try out yet: checking the length. When no date is given, dateInput returns an empty Date vector as illustrated in the example below.
So you could check if(length(input$dateSelect) == 0), but this is not the most solid shiny option. In order to avoid that you have to check all possibilities (i.e. NULL, NA, "", numeric(0) etc), you can use the function isTruthy() as in the example below:
shinyApp(
ui = fluidPage(
dateInput("dateSelect","Date"),
verbatimTextOutput("out"),
textOutput("text")
),
server = function(input,output,session){
output$text <- renderText({
if(!isTruthy(input$dateSelect)){
"NO DATE"
} else {
paste("The chosen date is:",input$dateSelect)
}
})
output$out<- renderPrint({str(input$dateSelect)})
}
)

Related

R Shiny: Check condition based on reactive expressions in observeEvent

I would like to build a Shiny App with two tabs:
In one tab, some values are entered as input. In the next tab, the user can find an output that is based on the values entered in the first tab.
However, before proceeding to the output I want to check if summing up three entries will give the fourth entry.
To do so, I want to use reactive expressions that contain the values of the different entries.
Here is an example of what I would like to do:
# clean environment
rm(list = ls(all = TRUE))
library(shiny)
# Create user interface (UI)
u <- tagList(
navbarPage(
# UI for input
title = "",
id = "Example_App",
tabPanel("Model input",
fluidRow(
column(11, offset = 0,
br(),
h4("Model input"),
br(),
sidebarPanel(
div(textInput('str_Input1', 'Input 1\n', "",
placeholder = "5.6, 6.7, 4.1"), class = "subheading"),
div(textInput('str_Input2', 'Input 2\n', "",
placeholder = "5.6, 6.7, 4.1"), class = "subheading"),
div(textInput('str_Input3', 'Input 3\n', "",
placeholder = "5.6, 6.7, 4.1"), class = "subheading"),
div(textInput('str_Input4', 'Input 4\n', "",
placeholder = "5.6, 6.7, 4.1"), class = "subheading"),
actionButton('jumpToModelOutput', 'Run')),
mainPanel(
h4('You entered'),
verbatimTextOutput("oid_Input1"),
verbatimTextOutput("oid_Input2"),
verbatimTextOutput("oid_Input3"),
verbatimTextOutput("oid_Input4"))))),
# UI for output
tabPanel("Model output",
fluidRow(
column(11, offset = 0,
br(),
h4('Your output will be here.'))
))))
# Define server output
s <- shinyServer(function(input, output, session) {
# Define reactive expressions
num_Input1 <- reactive(as.numeric(unlist(strsplit(input$str_Input1,","))))
num_Input2 <- reactive(as.numeric(unlist(strsplit(input$str_Input2,","))))
num_Input3 <- reactive(as.numeric(unlist(strsplit(input$str_Input3,","))))
num_Input4 <- reactive(as.numeric(unlist(strsplit(input$str_Input4,","))))
# Define server output for input check
output$oid_Input1 <- renderPrint({
cat("Input 1:\n")
print(num_Input1())
})
output$oid_Input2 <- renderPrint({
cat("Input 2:\n")
print(num_Input2())
})
output$oid_Input3 <- renderPrint({
cat("Input 3:\n")
print(num_Input3())
})
output$oid_Input4 <- renderPrint({
cat("Input 4:\n")
print(num_Input4())
})
# Check if conditions are fulfilled before switching to Model output
observeEvent(input$jumpToModelOutput, {
if(!all.equal((num_Input1() + num_Input2() + num_Input3()),num_Input4())){
showNotification("Error.", type = "error")
}else{
updateTabsetPanel(session, "Example_App",
selected = "Model output")
}})
})
# Create the Shiny app
shinyApp(u, s)
When I enter "1,2,3" into all tabs and press the button, the App stops and I get the following message:
"Listening on http://127.0.0.1:3925
Warning: Error in !: invalid argument type"
Removing the ! gives the following message:
Warning: Error in if: argument is not interpretable as logical
As far as I get the messages, the reactive expressions are not interpreted as numeric (?) but summing them up and printing them gives correct results.
Could anyone please help me finding the problem?
The issue is that all.equal returns a string containing a report of the difference in the passed values. That's why the docs (see ?all.equal) state:
Do not use all.equal directly in if expressions—either use isTRUE(all.equal(....)) or identical if appropriate.
Hence, to fix your issue wrap inside isTRUE:
observeEvent(input$jumpToModelOutput, {
if (!isTRUE(all.equal(num_Input1() + num_Input2() + num_Input3(), num_Input4()))) {
showNotification("Error.", type = "error")
} else {
updateTabsetPanel(session, "Example_App",
selected = "Model output"
)
}
})
all.equal returns a string if the elements are not equal, and you can't use a ! on a string. You can first check with isTRUE if it's TRUE or not and then negate it (note: you can't use isFALSE because in case it's not TRUE, all.equal returns a string). If you expect the elements to be exactly equal, you could use identical to make things easier.
I've also summed up all element in each input before adding them, is this what you wanted to do?
# Check if conditions are fulfilled before switching to Model output
observeEvent(input$jumpToModelOutput, {
if(!isTRUE(all.equal((sum(num_Input1()) + sum(num_Input2()) + sum(num_Input3())),sum(num_Input4())))){
showNotification("Error.", type = "error")
}else{
updateTabsetPanel(session, "Example_App",
selected = "Model output")
}})

How to use input from selectInput to slice data

I have similar issue to R Shiny selectInput, I would like to use the input to slice my data like this:
selectInput("category", "Choose a Category:",
choices = c('Any', levels(as.factor(unique(BD$DX_01_Cat))))),
uiOutput("secondSelection")
if (input$category != "Any"){
subsetSubsTab <<- subsetSubsTab[subsetSubsTab$DX_01_Cat==input$category];
output$secondSelection <- renderUI({
selectInput("subdiagnosis", "Choose a Subdiagnosis:", choices = c("Any", as.character(subsetSubsTab[subsetSubsTab$DX_01_Cat==input$category, DX_01_Sub])) , selected = "Any")
})
if (input$subdiagnosis != "Any"){
subsetSubsTab <<- subsetSubsTab[subsetSubsTab$DX_01_Sub==input$subdiagnosis];
}
but the last if statement does not work.
I get warning Warning: Error in if: argument is of length zero,
the subsetSubsTab actualize for a second and goes back. Could someone help please?

Select different columns in renderTable

I have a dataframe that is updated each time the user changes the year, let's call it mydata.
In output, I'd like to have a table with the possibility to select columns to show, but I'd like the "choices" argument (in the checkboxGroupInput) to be different according the year.
Short part of my current code :
Ui :
sliderInput("year","Choose the year",min=2013,max=2018,value=2017,step=1,sep=""),
conditionalPanel(condition="input.year==2013",
checkboxGroupInput("show_vars13", "Choose variables to show:",colnames13,
selected=c(var1,var2))
),
conditionalPanel(condition = "input.year==2014",
checkboxGroupInput("show_vars14","Choose variables to show:",colnames14,
selected=c(c(var1,var2))
),
conditionalPanel(condition="input.year==2015",
checkboxGroupInput("show_vars15","Choose variables to show:",colnames15,
selected=c(var1,var2))
),
conditionalPanel(condition="input.year==2016",
checkboxGroupInput("show_vars16","Choose variables to show:",colnames16,
selected=c(var1,var2))
),
conditionalPanel(condition="input.year==2017",
checkboxGroupInput("show_vars17","Choose variables to show:",colnames17,
selected=c(var1,var2))
),
tableOutput("data")
server :
output$data <- renderTable({
year <- as.numeric(substr(input$year,3,4))
for (i in 13:17){
if (year==i){
show_vars <- get(paste("input$show_vars",i,sep=''))
}
}
return(mydata[, show_vars, drop = FALSE])
})
This code doesn't work. R Shiny returns "object 'input$show_vars17' not found"
Try
show_vars <-input[[paste0("show_vars",i)]]
in
show_vars <- get(paste("input$show_vars",i,sep='')) ,
"input$show_vars" is just a string ..
if you want to refer to the variable, remove quotes, and write :
show_vars <- get(paste(input$show_vars,i,sep=''))

How to validate date range input in Shiny

For testing, please upload a csv file with 1+ column that can be converted to Date in the app.
My app generates date range inputs (input$daterange) dynamically depending on the date columns selected. I'd like to validate each input$daterange from 1 to n (the length of dt$datecols) to make sure the user won't select start date earlier than the oldest date, and end date later than the latest date in the corresponding column. I use lapply on observeEvent to do that.
For ease of debugging, I pass the value of input$daterange(i) to reactive values dt$daterange(i) and print dt$daterange1 (the first date range's value) to the console rendered to check whether the it is smaller or bigger than the min and max of the corresponding date column, as I did in the lapply function. Supposedly, when the check result is FALSE, lappy function shall display an error message warning the user the start or end date is not valid, which, however doesn't work. Please find my code below, please check the comments for explanation of problem.
library("shiny")
library("DT") # Datatable
library("rsconnect") # deploy to shinyapps.io
library("shinyjs") # use toggle button from shinyJS pacakage
library("stats")
library("zoo") # to use as.Date() on numeric value
ui <- fluidPage(
fluidRow(
column(4,
# file upload div
fileInput("file", "Choose a file",
accept=c(
"text/csv",
"text/comma-separated-values,text/plain",
".csv"
)),
# show ui for upload file control
uiOutput("ui")
),
column(4,
# no choices before a file uploaded
uiOutput("columnscontrol")
)
),
hr(),
fluidRow(
column(4,
uiOutput("datecolscontrol")),
column(6,
uiOutput("daterangescontrol"))
),
hr(),
dataTableOutput("datatbl"),
# print console for debugging (delete after completion)
verbatimTextOutput("print_con")
) #end of fluidPage (ui)
# server
server <- function(input, output, session) {
#########################################################
# upload & datatable output
#########################################################
# create dataset reactive objects
dt <- reactiveValues()
# reset all uis upon new file upload
observeEvent(input$file, {
# reset reactive values
dt$data = NULL
dt$df = NULL
dt$cols = NULL
dt$rows = NULL
dt$summary = NULL
dt$colchoices = NULL
dt$datecols = NULL
# remove columns div and datecols div when a new file uploaded
removeUI(selector = "div#columns_div")
removeUI(selector = "div#datecols_div")
# remove all <div> elements indside <div>#daterangescontrol:
removeUI(selector = "div#daterangescontrol div")
# generate upload file control ui once file uploaded
output$ui <- renderUI({
actionButton("readF", "Update")
})
})
# when read file button pressed:
observeEvent (input$readF, {
# store data to dt$data
file <- input$file
dt$data <- read.csv(file$datapath, header = TRUE)
# render columnscontrol
output$columnscontrol <- renderUI({
# get the col names of the dataset and assign them to a list
dt$colchoices <- mapply(list, names(dt$data))
# render column group checkbox ui after loading the data
# tags#div has the advantage that you can give it an id to make it easier to reference or remove it later on
tags$div(id = "columns_div",
checkboxGroupInput("columns", "", choices = NULL, selected = NULL))
})
# render div containing #datecols under datecolscontrol
output$datecolscontrol <- renderUI({
tags$div(id = "datecols_div",
selectInput("datecols", "Filter data by dates):", choices = NULL, multiple = TRUE, selected = NULL))
})
})
# update columns choices when dt$choices is ready
observeEvent(dt$colchoices, {
updateCheckboxGroupInput(session, "columns", "Select Columns:", choices = dt$colchoices, selected = dt$colchoices)
})
# the other reactivity on dt$cols is input$file (when new file uploaded, dt$data and dt$cols set to NULL)
# so that the following line set apart the reactivity of input$columns on dt$cols
observeEvent(input$columns, {
dt$cols <- input$columns
dt$df <- dt$data[dt$cols]
}, ignoreNULL = FALSE)
# upon any change of dt$df
observeEvent(dt$df, {
f <- dt$df
# render output$datatbl
output$datatbl <- DT::renderDataTable(
f, rownames = FALSE,
filter = 'top',
options = list(autoWidth = TRUE)
)
# update datecols choices with those columns can be converted to Date only:
dt$date_ok = sapply(f, function(x) !all(is.na(as.Date(as.character(x), format = "%Y-%m-%d"))))
dt$datecolchoices = colnames(f[dt$date_ok])
updateSelectInput(session, "datecols", "Filter data by dates:", choices = dt$datecolchoices, selected = NULL)
}, ignoreNULL = FALSE)
# whenver columns convertable to date updated to choices of input$datecols, convert the columns to Date in the dataset
observeEvent(dt$datecolchoices, {
dt$df[dt$date_ok] = lapply(dt$df[dt$date_ok], function(x) as.Date(as.character(x)))
})
# generate daterange uis per selected input$datecols
observeEvent(input$datecols, {
dt$datecols = input$datecols
dt$datecols_len = length(dt$datecols)
# render daterange ui(s) per selected datecols
output$daterangescontrol <- renderUI({
# when input$datecols is NULL, no daterange ui
if ( is.null(input$datecols) ) { return(NULL) }
# otherwise
else {
D = dt$df[dt$rows, dt$cols]
output = tagList()
for (i in 1:dt$datecols_len) {
output[[i]]= tagList()
output[[i]][[1]] = tags$div(id = paste("dateranges_div", i, sep = "_"),
dateRangeInput(paste0("daterange", i),
paste("Date range of", dt$datecols[[i]]),
start = min(D[[dt$datecols[[i]]]]),
end = max(D[[dt$datecols[[i]]]])))
}
# return output tagList() with ui elements
output
}
}) # end of renderUI
}, ignoreNULL = FALSE)
# loop observeEvent to check whether each input$daterange is valid:
#### why I can't just call lapply() without observe() as suggested in this post:
#### https://stackoverflow.com/questions/40038749/r-shiny-how-to-write-loop-for-observeevent
observe({
lapply( X = 1:dt$datecols_len,
FUN = function(i) {
observeEvent(input[[paste0("daterange", i)]], {
# update reactive values to test whether this loop is working
dt[[paste0("range",i)]] = input[[paste0("daterange", i)]]
range = dt[[paste0("range",i)]]
req(range)
#########################################
## CODE BLOCK WITH PROBLEM!!!
#########################################
# Why the following doesn't work, when I pick a date earlier than the oldest date
# no error message shows!
shiny::validate(
need( range[[1]] >= min(dt$df[[dt$datecols[[i]]]]), "The start date cannot be earlier than the oldest date!"),
need( range[[2]] <= max(dt$df[[dt$datecols[[i]]]]), "The end date cannot be later than the latest date!")
)
})
}
) # end of lapply
})
# rows displayed in input$datatbl (the rendered data table)
observeEvent( input$datatbl_rows_all, {
dt$rows <- input$datatbl_rows_all
})
#########################################################
# print console
#########################################################
output$print_con <- renderPrint({
req(input$daterange1)
list(
# to verify whether the observeEvent loop is working for input validation
# I used dt$range1 to check the first (input$daterange1) against the date range of the corresponding column of the dataset.
# It's supposed that when the check result is FALSE (either by selecting a start date earlier than the oldest date or selecting an end date later than the latest date),
# the code block with problem shall prompt an error message to warn the user
min(dt$range1) >= min(dt$df[[dt$datecols[[1]]]]),
max(dt$range1) <= max(dt$df[[dt$datecols[[1]]]])
)
})
} # end of shiny server function
shinyApp(ui = ui, server = server)
This may not be the exact answer you are looking for but I think it may simplify things. I would simply order your date column which would allow you to select the oldest and newest date. Then set your start and end dates to those two values (see ?dateRangeInput). Lubridate is also a great package for working with dates
I think the problem maybe related to the format of your dates.
please look at this post:
R: Shiny dateRangeInput format
you may need to use
format(range[[1]])

Shiny R: Populate a list from input and return list on output via reactive

I try to populate a list on shiny with the elements of the list passed over from a shiny input. The list should accumulate all the made choices. The list should finally be sent to the shiny output. Actually I already get a list which I can send to output. This list is however always just of length one and this single element gets updated as the input does. Actually I am only interested in the "names" of the list, this is why I assign the value 1 to each name element:
UI.R
shinyUI(
fluidRow(
column(1,
# reactive input
selectInput("Input1",
label = "Select Parameter 1",
choices = c("none",letters[1:16]),
multiple = T),
selectInput("Input2",
label = "Select Parameter 2",
choices = c("none",c(1:24) )
multiple = T),
# printout of list
htmlOutput("printoutList")
) # end of column
) # end of fluid row
) # end of Shiny UI
Shiny.R
# create an empty list
container <- list()
shinyServer(function(input, output) {
# pass over input to reactive
inputInfo <- reactive({
if(input$Input1 == "none" | input$Input2 == "none") {
"-"
} else {
paste(input$Input1 ,input$Input2, sep = "")
}
})
# fill list and pass over list to output
output$printoutList <- renderUI({
container[[inputInfo()]] <- 1
paste("You have chosen: ", names(container), sep = "")
})
)} #end of shinyServer function
Any idea how to solve this? I already tried around a lot... unfortunately I am quite new to R, especially to shiny ! I would really appreciate any kind of help! Thanks !
include the multiple = TRUE argument for selectInput
selectInput("Input1",
label = "Select Parameter 1",
choices = c("none",letters[1:16]),
multiple = TRUE
)
But also it seems like your server and ui files are mixed up and you don't have the shinyServer function in the code.

Resources