I am writing a shiny script where a file is uploaded, a dropdown appears with the column names from that file, and then a second dropdown appears with the unique values from the column in the file selected in the first dropdown. I was able to create the first dropdown, but am having trouble with the second dropdown. Here is my code thus far:
ui <- fluidPage(
titlePanel("File Upload Test"),
sidebarLayout(
sidebarPanel(
fileInput("file1", "Choose csv file",
multiple = T,
accept = c(".csv")),
uiOutput("y_input"),
uiOutput("target_input")),
mainPanel(
plotOutput("contents"))))
server <- function(input, output, session) {
inFile <- reactive({
if (is.null(input$file1)) {
return(NULL)
} else {
input$file1}
})
myData <- reactive({
if (is.null(inFile())) {
return(NULL)
} else {
read.csv(inFile()$datapath)}
})
output$y_input <- renderUI({
if (is.null(inFile())) {
return(NULL)
} else {
selectInput("y_output", "Select Y Variable", names(myData()))}
})
output$target_input <- renderUI({
if (is.null(input$y_input)) {
return(NULL)
} else {
selectInput("target_output", "Select Target Group",
myData( [,input$y_output])}
})
}
Any help here is appreciated! This is my first post on stack overflow, so if there are any formatting or clarity things in this post I can/should fix please let me know! Thanks!
First of all some tips to improve your code:
Use req
req(name_of_input) is shorthand for if(is.null(name_of_input)) return NULL. It will save you a few strokes while providing better readability.
Your reactive called inFile is unnecessary
Use input$file1$datapath instead. Again this is more concise while having better performance as well.
myData <- reactive({
req(input$file1)
read.csv(input$file1$datapath)
})
And finally the problem:
Your reference to myData is wrong. The subsetting part of [,input$y_output] should be outside of the parentheses, like this: myData()[,input$y_output]. Keep in mind that even though reactives look like functions they don't take any arguments.
Full server.R:
server <- function(input, output, session) {
myData <- reactive({
req(input$file1)
read.csv(input$file1$datapath)
})
output$y_input <- renderUI({
req(input$file1)
selectInput("y_output", "Select Y Variable", names(myData()))
})
output$target_input <- renderUI({
req(input$file1)
selectInput("target_output", "Select Target Group",
myData()[,input$y_output]
)
})
}
ps: Welcome to stackoverflow! :)
Related
So far I made a Shiny app that has three inputs connected to the database and a final download button. Everything works well except the download button. The actual data downloading part works but I want to add one last logic that hides the download button if myvars$input3 is empty:
observe({
if (is.null(myvars$var3)) {shinyjs::hide("???")}
else {shinyjs::show("???")}
})
server_tab2.R:
Function1 dropdownTab2Server:
Defined the date range logic with id daterange_tab2
Defined the last input dropdown logic with id var_list_tab2
Function2 downloadTab2Server:
Defined the logic for download button
server.R: (This part is not working)
Want to only show the download button if the third input (myvars$input3) is not empty
ui_tab2.R: Defined the three inputs explained in ui.R:
var_lab_tab2: A static dropdown input with only two choices Choice1 and Choice2
daterange_tab2_ui: A date range
subid_dropdown_tab2_ui: The last dropdown input that depends on the first two
##### server_tab2.R
#### Function 1 - A dropdown input dependent on the date range
dropdownTab2Server <- function(id) {
moduleServer(id, function(input, output, session) {
ns <- session$ns
rv <- reactiveValues()
output$daterange_tab2_ui <- renderUI({
req(input$var_lab_tab2)
dateRangeInput(ns("daterange_tab2"), "Date Range:", start = min_max_date_df$min_date, end = min_max_date_df$max_date) # Retrieved from "global.R"
})
unique_lists_tab2 <- reactive({
sql <- glue_sql("
SELECT
DISTINCT list AS unique_list
FROM table1
WHERE date BETWEEN date ({dateid1_tab2*}) AND date ({dateid2_tab2*})
",
dateid1_tab2 = input$daterange_tab2[1],
dateid2_tab2 = input$daterange_tab2[2],
.con = pool
)
dbGetQuery(pool, sql)
})
output$subid_dropdown_tab2_ui <- renderUI({
req(input$daterange_tab2[1], input$daterange_tab2[2])
shinyWidgets::pickerInput(
ns("var_list_tab2"),
"Stations:",
choices = unique_lists_tab2(),
multiple = T
)
})
observe({
rv$var1 <- input$daterange_tab2[1]
rv$var2 <- input$daterange_tab2[2]
rv$var3 <- input$var_list_tab2
})
return(rv)
}
)
}
#### Function 2 - download button
downloadTab2Server <- function(id, df, filename) {
moduleServer(id, function(input, output, session) {
output$downloadbttn_tab2 <- downloadHandler(
filename = function() {
paste0(filename, ".xlsx")
},
content = function(file) {
WriteXLS::WriteXLS(df, file)
}
)
}
)
}
##### server.R => Struggling with this part
function(input, output, session) {
dropdownTab2Server("dropdown_ui_tab2")
myvars <- dropdownTab2Server("dropdown_ui_tab2")
### download button layout => Struggling with this part
observe({
if (is.null(myvars$var3)) {shinyjs::hide("???")}
else {shinyjs::show("???")}
})
downloadTab2Server(
id = "download_ui_tab2",
df = fake_data(), # reactive
filename = "data"
)
}
##### ui_tab2.R
downloadTab2UI <- function(id) {
ns <- NS(id)
tagList(
shinyWidgets::pickerInput(
ns("var_lab_tab2"),
"ID:",
choices = c("Choice1", "Choice2"), multiple = T
),
uiOutput(ns("daterange_tab2_ui")),
uiOutput(ns("subid_dropdown_tab2_ui")),
downloadButton(ns("downloadbttn_tab2"), "Download Data")
)
}
##### ui.R
downloadTab2UI("download_ui_tab2")
You could the following in the main server part (I've changed it to an observeEvent because I think it's easier to reason what exactly it listens to):
observeEvent(myvars$var3, {
if (is.null(myvars$var3)) {shinyjs::hide("download_ui_tab2-downloadbttn_tab2")}
else {shinyjs::show("download_ui_tab2-downloadbttn_tab2")}
}, ignoreNULL = FALSE)
You need to prefix the download button id with the correct namespace, in your case "download_ui_tab2".
However, this is not great style as you need to manually handle the namespace. A cleaner solution would be to pass myvars to the downloadTab2Server module as an argument and then have the observeEvent in the module code. Then you can directly use downloadbttn_tab2 and don't need to manually prefix the namespace.
I want to save the value from username input if it doesn't exist in data frame, and render text if it already exists (for reprex purpose).
Rendering text part works perfectly, but I don't know how to save it and use it later.
I want to save the value permanently, not only on current session
I've got this error:
Warning: Error in <-: invalid (NULL) left side of assignment
library(shiny)
ui <- fluidPage(
textInput("username", "username"),
actionButton("save", "Save!"),
textOutput("confirmation")
)
server <- function(input, output, session) {
df <- reactive(data.frame(column1 = "user1"))
exist <- reactive(input$username %in% df()$column1)
observeEvent(input$save, {
if (exist() == TRUE) {
output$confirmation <- renderText("Username already exists!")
} else {
df() <- rbind(df(), input$username) # <-- THIS dosn't work
}
})
}
shinyApp(ui, server)
EDIT:
Thanks to #I_O answer, I figured out this solution
reactiveVal() keep the changes in current session.
write_csv() and read_csv() part, allows app to use previously saved usernames.
saved_df <- read_csv("C:\\Users\\Przemo\\Documents\\R\\leaRn\\Shiny\\Moodtracker\\testers\\test_safe.csv")
ui <- fluidPage(
textInput("username", "username"),
actionButton("save", "Save!"),
textOutput("confirmation")
)
server <- function(input, output, session) {
df <- reactiveVal(saved_df)
exist <- reactive(input$username %in% df()$column1)
observeEvent(input$save, {
if (exist() == TRUE) {
output$confirmation <- renderText("Username already exists!")
} else {
output$confirmation <- renderText("")
df(rbind(df(), input$username))
write_csv(df(), "C:\\Users\\Przemo\\Documents\\R\\leaRn\\Shiny\\Moodtracker\\testers\\test_safe.csv")
}
})
}
shinyApp(ui, server)
I have written a code to read csv or excel file in shiny app. But what is happening is that, whatever I select first (say Excel file), the output is displayed. But once I switch to other (csv) the excel is still there and csv is not displayed. Not sure what wrong is there in the code. Could anyone please help me?
library(shinydashboard)
library(readxl)
ui <- dashboardPage(
dashboardHeader(title = "Loading data"),
dashboardSidebar(fileInput("datafile","Choose the csv file",multiple = TRUE,
accept = c("text/csv","text/comma-separated-values,text/plain",".csv")),
("Or"),
fileInput("datafile1","Choose the excel file",multiple = TRUE,
accept = c(".xlsx"))),
dashboardBody(
fluidRow(box(uiOutput("filter_70"),width = 5000))
))
server <- function(input,output){
output$contents <- renderTable({
file_to_read <- input$datafile
if(is.null(file_to_read))
return(NULL)
read.csv(file_to_read$datapath)
})
output$contents1 <- renderTable({
file_to_read1 <- input$datafile1
if(is.null(file_to_read1))
return(NULL)
read_excel(file_to_read1$datapath)
})
output$filter_70 <- renderUI(
if (!is.null(input$datafile)) {
tableOutput("contents")
} else if (!is.null(input$datafile1)) {
tableOutput("contents1")
}
)
}
shinyApp(ui, server)
as pointed out by #Rohit, reactive value once gets selected, it won't go back to null.
Keep your main structure unchanged, all you need is to monitor which file last changed. This requires creating a reactiveVal (last_selected) and then use observeEvent to track.
last_selected <- reactiveVal(NA)
observeEvent(input$datafile, {
last_selected("csv")
})
observeEvent(input$datafile1, {
last_selected("excel")
})
output$filter_70 <- renderUI({
req(last_selected())
if (last_selected()=="csv") {
tableOutput("contents")
} else if (last_selected()=="excel") {
tableOutput("contents1")
}
})
I have been trying for a long time to reset fileInput in a Shiny app and read solutions to similar problems, but my problem still persists. Most solutions ultimately lead to using Dean Attali's brilliant shinyjs package and the reset() function therein. Here's what my code looks like after following these instructions:
library(shiny)
library(shinyjs)
library(xlsx)
library(tidyverse)
ui <- fluidPage(
useShinyjs(),
fileInput('inFile', 'Choose file'),
actionButton('reset', 'Reset'),
radioButtons("type","Choose file type",choices = c('csv','xls')),
tableOutput('tbl')
)
server <- function(input, output, session) {
rv <- reactiveValues(data = NULL)
observe({
req(input$inFile)
if(input$type=='csv'){
rv$data <- read.csv(input$inFile$datapath)
}
if(input$type=='xls'){
rv$data <- read_excel(input$inFile$datapath)
}
})
observeEvent(input$reset, {
rv$data <- NULL
reset('inFile')
})
output$tbl <- renderTable({
rv$data
})
}
shinyApp(ui, server)
I initially select the csv option and am able to load a csv file. Now when I press the reset button, it clears the data. As soon as I select the xls option, I get an error:
Listening on http://127.0.0.1:4135
Warning: Error in : Unknown file extension: csv
Which makes me believe that input$inFile$datapath still contains the pathname of the csv file that I selected earlier. I have run out of ideas on how to solve this problem and would greatly appreciate some help please.
Ideally fileInput would properly reset, but you can do this as a workaround. Add an explicit flag variable (rv$clear) to indicate whether you're in cleared state, and toggle that on and off in high-priority observers when reset and upload occur, respectively.
library(shiny)
library(shinyjs)
library(xlsx)
library(tidyverse)
ui <- fluidPage(
useShinyjs(),
fileInput('inFile', 'Choose file'),
actionButton('reset', 'Reset'),
radioButtons("type","Choose file type",choices = c('csv','xls')),
tableOutput('tbl')
)
server <- function(input, output, session) {
rv <- reactiveValues(
data = NULL,
clear = FALSE
)
observe({
req(input$inFile)
req(!rv$clear)
if(input$type=='csv'){
rv$data <- read.csv(input$inFile$datapath)
}
if(input$type=='xls'){
rv$data <- read_excel(input$inFile$datapath)
}
})
observeEvent(input$inFile, {
rv$clear <- FALSE
}, priority = 1000)
observeEvent(input$reset, {
rv$data <- NULL
rv$clear <- TRUE
reset('inFile')
}, priority = 1000)
output$tbl <- renderTable({
rv$data
})
}
shinyApp(ui, server)
I am having trouble with nested selectizeInputs, i.e. a group of select inputs where the selection in the first determines the choices in the second, which control the choices in the third, and so on.
Let's say I have an select1 that lets you choose a dataset, and select2 which lets you pick a variable in the dataset. Obviously the choices in select2 depend on the selection in select1. I find that if a user selects a variable from select2, and then changes select1, it doesn't immediately wipe out the value from select2, but instead it goes through a reactive sequence with the new value in select1, and the old value from select2, which is suddenly referencing a variable in a different dataset, which is a problem.
Example:
library(shiny)
ui =fluidPage(
selectizeInput('d',choices=c('mtcars','iris'),
label="Datasets"),
uiOutput("vars"),
htmlOutput("out")
)
server = function(input, output, session) {
output$vars <- renderUI({
req(input$d)
selectizeInput("v",choices=names(get(input$d)), label="Variables",
options=list(onInitialize=I('function() {this.setValue("");}')))
})
output$out <- renderUI({
req(input$d,input$v)
HTML(paste0("The max is ",max(get(input$d)[[input$v]])))
})
}
runApp(list(ui = ui, server = server))
On launch, select mpg, and displays max value.
Now, after selecting mpg, if you switch to iris, you will get a barely noticeable error, then it corrects itself. This is a toy example, so the error is insignificant, but there could easily be cases where the error is much more dire (as is the case with the app I am currently developing).
Is there a way to handle nested selectizeInputs such that changes in an upstream selectizeInput won't evaluate with old values of down stream selectizeInputs when changed?
Thanks
edit: This issue turns out to have to do more with modules than anything else I believe:
library(shiny)
library(DT)
testModUI <- function(id) {
ns <- NS(id)
DT::dataTableOutput(ns("out"))
}
testMod <- function(input, output, session, data) {
output$out <- DT::renderDataTable({
data()
},caption="IN MODULE")
}
ui =fluidPage(
selectizeInput('d',choices=c('mtcars','iris'),
label="Datasets"),
uiOutput("vars"),
testModUI("test"),
DT::dataTableOutput("test2")
)
server = function(input, output, session) {
output$vars <- renderUI({
req(input$d)
selectizeInput("v",choices=names(get(input$d)), label="Variables",
options=list(onInitialize=I('function() {this.setValue("");}')))
})
observe({
req(input$d,input$v)#,get(input$d)[[input$v]])
validate(
need(input$v %in% names(get(input$d)), 'Wait.')
)
callModule(testMod,"test",reactive(data.frame(v1=max(get(input$d)[[input$v]]))))
})
output$test2 <- DT::renderDataTable({
req(input$d,input$v)#,get(input$d)[[input$v]])
validate(
need(input$v %in% names(get(input$d)), 'Wait.')
)
data.frame(v1=max(get(input$d)[[input$v]]))
},caption="OUTSIDE MODULE")
}
runApp(list(ui = ui, server = server))
Hello you can put condition to check if your code is going to run, here you just need that input$v to be a valid variable from input$d, so do :
output$out <- renderUI({
req(input$d,input$v)
if (input$v %in% names(get(input$d))) {
HTML(paste0("The max is ",max(get(input$d)[[input$v]])))
}
})
# or
output$out <- renderUI({
req(input$d,input$v)
validate(
need(input$v %in% names(get(input$d)), 'Wait.')
)
HTML(paste0("The max is ",max(get(input$d)[[input$v]])))
})
EDIT with module, you can define your module with an expression to validate like this :
testMod <- function(input, output, session, data, validExpr) {
output$out <- DT::renderDataTable({
validate(need(validExpr(), FALSE))
data()
},caption="IN MODULE")
}
And call the module in the server with the expression in a function :
observe({
req(input$d,input$v)
callModule(
module = testMod,
id = "test",
data = reactive({ data.frame(v1=max(get(input$d)[[input$v]])) }),
validExpr = function() input$v %in% names(get(input$d))
)
})