Problem when using Shiny app to save the data - r

I have a shiny app which has many text inputs. I could not get the save data part right, for example, to save to a local drive. Any suggestions?
server = function(input, output) {
values <- reactiveValues()
#Initial Dataframe
values$df <- data.frame(matrix(ncol=4,nrow=0, dimnames=list(NULL, c("Name", "date","Traning", "certificate"))))
FinalData =observe({
if(input$submit >0) {
isolate(values$df <- rbind(values$df,data.frame("name" = input$name,"date" = input$date,
"training" = input$training, "certificate" = input$certificate)))
# saveRDS(values$df)
# saveRDS(FinalData)
}})
#display the inputs
output$Combined_table = renderDataTable({values$df})
}
)

Try this demonstration:
library(shiny)
.log <- function(...) message(format(Sys.time(), format = "[ %H:%M:%S ]"), " ", ...)
.read <- function(path) if (file.exists(path)) return(readRDS(path))
shinyApp(
ui = fluidPage(
textInput("txt", "Text: "),
actionButton("btn", "Submit"),
tableOutput("tbl")
),
server = function(input, output, session) {
.log("hello world")
rv <- reactiveValues()
rv$df <- data.frame(row = 0L, word = "a", stringsAsFactors = FALSE)[0,]
observeEvent(req(input$btn), {
.log("submit!")
rv$df <- rbind(rv$df,
data.frame(row = input$btn, word = input$txt,
stringsAsFactors = FALSE))
.log("saveRDS: ", nrow(rv$df))
saveRDS(rv$df, "local.rds")
})
filedata <- reactiveFileReader(1000, session, "local.rds", .read)
output$tbl <- renderTable(filedata())
}
)
The engineering of this app:
I use a reactiveValues like you did, in order to keep the in-memory data. (Note: iteratively adding rows to a frame is bad in the long-run. If this is low-volume adding, then you're probably fine, but it scales badly. Each time a row is added, it copies the entire frame, doubling memory consumption.)
I pre-fill the $df with a zero-row frame, just for formatting. Nothing fancy here.
observe and observeEvent do not return something you are interested in, it should be operating completely by side-effect. It does return something, but it is really only meaningful to shiny internals.
saveRDS as you do, nothing fancy, but it works.
I added a shiny::reactiveFileReader in order to demonstrate that the file was being saved. When the shiny table shows an update, it's because (1) the data was added to the underlying frame; (2) the frame was saved to the "local.rds" file; then (3) reactiveFileReader noticed that the underlying file exists and has changed, causing (4) it to call my .read function to read the contents and return it as reactive data into filedata. This block is completely unnecessary in general, just for demonstration here.
I create a function .read for this reactiveFileReader that is resilient to the file not existing first. If the file does not exist, it invisibly returns NULL. There may be better ways to do this.

Related

Read a file after taking user input(selection) in Shiny

I am trying to read a file after taking inputs from user - first a date and then a selection of file, but I can't get the code read any file and do anything with it. The Error message is following. Also is there a way to make this code more efficient?
ui <- fluidPage(
titlePanel("Select the execution date")
,dateInput("ME_DATE",label=h3("Execution Date Input"), value="2020-05-29")
,hr()
,fluidRow(column(3,verbatimTextOutput("Output_path")))
,hr()
,selectInput('selectfile','Select File in Above Location', choices=NULL)
,textOutput('fileselection_statement')
,tableOutput('selected_table')
)
server <- function(input, output, session) {
# Location for Outputs
Output_DIR <- "K:/Outputs/"
Output_loc <- reactive({
year_N_ME_DATE <- format(input$ME_DATE,"%Y")
month_N_ME_DATE <- format(input$ME_DATE,"%m")
month_T_ME_DATE <- months(input$ME_DATE)
file.path(paste(Output_DIR,month_N_ME_DATE,". ",month_T_ME_DATE, " ",year_N_ME_DATE,"/",sep=""))
})
# Output Path
output$Output_path <- renderPrint({ Output_loc() })
# files list
Updated_Output_files_list <- reactive({ list.files(Output_loc()) })
observeEvent(input$selectfile, {
updateSelectInput(session, "selectfile", choices=Updated_Output_files_list())
output$fileselection_statement <- renderText({paste0('You have selected: ', input$selectfile) })
})
selectfile <- reactive(get(input$selectfile))
output$selected_table <- renderTable({ read.csv(paste0(renderPrint({ Output_loc() }),renderPrint({ selectfile() }),sep="")) })
}
shinyApp(ui, server)
(Since changed) Make the block containing file.path(.) a reactive block and assign it to something so that other reactive components can use it. In your case, you changed it to Output_loc, so other blocks will refer to it as Output_loc().
Similarly, you cannot put output$... <- assignments or render* calls inside an observe or observeEvent block. So we'll move your output$fileselection_statement outside of the observeEvent.
renderPrint is its own rendering function, on the same level as renderTable. You cannot nest them. In this case, I'm just going to remove them from inside the renderTable call, they make no sense there.
This case did not need selectfile <- reactive(get(input$selectfile)), there is no apparent gain in that indirection. Just use input$selectfile. Removed.
After fixing all of the above, it is also the case that you were updating the selectInput every time the selectInput was changed, which is incorrect (and completely disallows any real use of this). Instead, you want to update it when the Updated_Output_files_list() changes.
Also, instead of repeatedly concatenating the path together to create the file to be read, I use list.files(..., full.names=TRUE). This will be a named vector, where the values are the full path and filename, but the names will be just the filename (no leading path). This useful because selectInput displays the name but returns the value (full path). There is rarely a time when I think not specifying full.names=TRUE is the right thing (I cannot think of any right now).
Here's a working copy. It is not perfectly-awesome, there are still areas where some grooming might be in order.
server <- function(input, output, session) {
# Location for Outputs
Output_DIR <- "K:/Outputs/"
Output_loc <- reactive({
year_N_ME_DATE <- format(input$ME_DATE, "%Y")
month_N_ME_DATE <- format(input$ME_DATE, "%m")
month_T_ME_DATE <- months(input$ME_DATE)
file.path(Output_DIR,
paste0(month_N_ME_DATE, ". ", month_T_ME_DATE, " ", year_N_ME_DATE),
"/")
})
# alternative
# Output_loc <- reactive({
# file.path(Output_DIR, format(Sys.Date(), format = "%m. %b %Y"))
# })
# Output Path
output$Output_path <- renderPrint({ req(Output_loc()) })
# files list
Updated_Output_files_list <- reactive({
lf <- list.files(Output_loc(), full.names = TRUE)
names(lf) <- basename(lf)
# in this example, 'lf' is now:
# c(iris.csv = "K:/Outputs/05. May 2020/iris.csv", mtcars.csv = "K:/Outputs/05. May 2020/mtcars.csv")
# ... the *name* will be displayed in the selectInput, but the
# *full path* will be the value of the selection
lf
})
output$fileselection_statement <- renderText({
paste0('You have selected: ', input$selectfile)
})
observeEvent(Updated_Output_files_list(), {
updateSelectInput(session, "selectfile", choices = Updated_Output_files_list())
})
output$selected_table <- renderTable({
req(input$selectfile)
read.csv(input$selectfile)
})
}

reactiveValues issue

I'm trying to merge two uploaded data frames, output it as a table, then being able to download it and reset the inputs, but only get the error: "Error 'by' must match numbers of columns".
I have trouble understanding reactiveValues I guess, since I can't simply call them as data frames in the app...
library(shiny)
library(shinyjs)
library(readxl)
library(DT)
ui <- fluidPage(
useShinyjs(),
fileInput('inFile1', 'Choose file'),
fileInput('inFile2', 'Choose file'),
actionButton('reset', 'Reset'),
tableOutput('overlap')
)
server <- function(input, output, session) {
rv <- reactiveValues()
observe({
req(input$inFile1)
rv$data1 <- readxl::read_xls(input$inFile1$datapath)
})
observe({
req(input$inFile2)
rv$data2 <- readxl::read_xls(input$inFile2$datapath)
})
observeEvent(input$reset, {
rv$data1 <- NULL
rv$data2 <- NULL
reset('inFile1')
reset('inFile2')
})
dataframe<-reactive({
if (!is.null(rv$data1) | !is.null(rv$data2))
return(NULL)
df <- merge(as.data.frame(rv$data1),as.data.frame(rv$data2),by.x = 1,by.y = 1)
colnames(df) <- c("GeneID",paste0(colnames(rv$data1)[2:ncol(rv$data1)],"_file_1"),
paste0(colnames(rv$data2)[2:ncol(rv$data2)],"_file_2"))
df
})
overlap1 <- reactive({
if(!is.null(dataframe()))
dataframe()
})
output$overlap <- renderDataTable({
datatable(overlap1())
})
}
shinyApp(ui, server)
At a first glance your reactive expressions look fine to me. And given that error message the error is caused by merge(). Taking a closer look there, what strikes me are those is.null checks at the top of the dataframe<-reactive(. The condition (!is.null(rv$data1) | !is.null(rv$data2)) means that you are trying to merge two objects that are NULL because only then the code wont't stop with return(NULL). If one or both rv-values are "Truthy" the code won't run and all the reactive is going to return is NULL.
I used isTruthy() below. I think it helps in two ways:
isTruthy() checks if the values contain anything "usable". That way, you do not have to care about how rv is initialised. It could be NA or integer(0) or anything else that is meaningless. isTruthy handles all these cases. Merging now only takes place when there are two values with "meaningful" data (note that this does not necessarily mean that the data can be coerced to data.frame).
It avoids a complicated double negative in the if-statement.
dataframe <- reactive({
if (isTruthy(rv$data1) && isTruthy(rv$data2)) {
df <- merge(rv$data1, rv$data2, by.x = 1,by.y = 1)
colnames(df) <- c("GeneID", paste0(colnames(rv$data1)[2:ncol(rv$data1)], "_file_1"),
paste0(colnames(rv$data2)[2:ncol(rv$data2)], "_file_2"))
} else df <- NULL
df
})
Final tweak: I removed as.data.frame in the merge statement because the first thing merge is trying to do is coerce the arguments to a data frame.

Object not found R Shiny

I am trying to access the data frame created in one render function into another render function.
There are two server outputs, lvi and Category, in lvi I have created Data1 data frame and Category I have created Data2 dataframe. I want to select Data2 where Data1 ID is matching.
I am following the below steps to achieve my objective but I get error "Object Data1 not found".
My UI is
ui <- fluidPage(
# App title ----
titlePanel("Phase1"),
fluidPage(
column(4,
# Input: Select a file ----
fileInput("file1", "Import file1")
)
),
fluidPage(
column(4,
# Input: Select a file ----
fileInput("file2", "Import File2")
)
),
# Main panel for displaying outputs ----
mainPanel(
# Output: Data file ----
dataTableOutput("lvi"),
dataTableOutput("category")
)
)
My server code is
server <- function(input, output) {
output$lvi <- renderDataTable({
req(input$file1)
Data1 <- as.data.frame(read_excel(input$file1$datapath, sheet = "Sheet1"))
})
output$category <- renderDataTable({
req(input$file2)
Data2 <- as.data.frame(read_excel(input$file2$datapath, sheet = "Sheet1"))
Data2 <- Data2[,c(2,8)]
Data2 <- Data2[Data1$ID == "ID001",]
})
}
shinyApp(ui, server)
Once a reactive block is done executing, all elements within it go away, like a function. The only thing that survives is what is "returned" from that block, which is typically either the last expression in the block (or, when in a real function, something in return(...)). If you think of reactive (and observe) blocks as "functions", you may realize that the only thing that something outside of the function knows of what goes on inside the function is if the function explicitly returns it somehow.
With that in mind, the way you get to a frame inside one render/reactive block is to not calculate it inside that reactive block: instead, create that frame in its own data-reactive block and use it in both the render and the other render.
Try this (untested):
server <- function(input, output) {
Data1_rx <- eventReactive(input$file1, {
req(input$file1, file.exists(input$file1$datapath))
as.dataframe(read_excel(input$file1$datapath, sheet = "Sheet1"))
})
output$lvi <- renderDataTable({ req(Data1_rx()) })
output$category <- renderDataTable({
req(input$file2, file.exists(input$file2$datapath),
Data1_rx(), "ID" %in% names(Data1_rx()))
Data2 <- as.data.frame(read_excel(input$file2$datapath, sheet = "Sheet1"))
Data2 <- Data2[,c(2,8)]
Data2 <- Data2[Data1_rx()$ID == "ID001",]
})
}
shinyApp(ui, server)
But since we're already going down the road of "better design" and "best practices", let's break data2 out and the data2-filtered frame as well ... you may not be using it separately now, but it's often better to separate "loading/generate frames" from "rendering into something beautiful". That way, if you need to know something about the data you loaded, you don't have to (a) reload it elsewhere, inefficient; or (b) try to rip into the internals of the shiny DataTable object and get it manually. (Both are really bad ideas.)
So a slightly better solution might start with:
server <- function(input, output) {
Data1_rx <- eventReactive(input$file1, {
req(input$file1, file.exists(input$file1$datapath))
as.dataframe(read_excel(input$file1$datapath, sheet = "Sheet1"))
})
Data2_rx <- eventReactive(input$file2, {
req(input$file2, file.exists(input$file2$datapath))
dat <- as.dataframe(read_excel(input$file2$datapath, sheet = "Sheet1"))
dat[,c(2,8)]
})
Data12_rx <- reactive({
req(Data1_rx(), Data2_rx())
Data2_rx()[ Data1_rx()$ID == "ID001", ]
})
output$lvi <- renderDataTable({ req(Data1_rx()); })
output$category <- renderDataTable({ req(Data12_rx()); })
}
shinyApp(ui, server)
While this code is a little longer, it also groups "data loading/munging" together, and "render data into something beautiful" together. And if you need to look at early data or filtered data, it's all right there.
(Side note: one performance hit you might see from this is that you now have more copies of data floating around. As long you are not dealing with "large" data, this isn't a huge deal.)

Accessing data in different parts of server() in shiny

I am having a problem with accessing data in different parts of my server() function. The basic structure is something like this:
server <- shinyServer(function(input, output) {
# get the data from a file obtained from a textInput in the ui
data <- reactive({
req(input$file)
file <- input$file$datapath
# process the file and return a new dataframe
})
output$head <- renderTable({
mydf <- data()
head(mydf)
})
output$tail <- renderTable({
mydf <- data()
tail(mydf)
})
})
I would like to avoid having to call data() twice but I haven't found a way to do that.
Edit following the comment by #KentJohnson
What I am trying to achieve is for the user to select a file to open, using textInput, and after the file is opened, the app should do some processing and populate the two tables in the ui. After this, the user then chooses some other actions which also require the same data.
I wanted to avoid having to call data() twice but I haven't found a way to do that. I was assuming that each call would mean reading from the file each time. The file is very large so that is my motivation.
As #KentJohnson points out, reactive already achieves your goal. The expression that makes up data...
req(input$file)
file <- input$file$datapath
# process the file and return a new dataframe
...only runs when input$file$datapath changes. It does not rerun each time data() is called.
Putting your two tables into an observe environment makes it possible to call data() only twice, but I don't know if it will fit with what you want to do. Notice that here, I didn't put a textInput or things like that because my point was to show the observe environment. I'll let you adapt it to your situation (since you didn't put the ui part in your post):
library(shiny)
ui <- basicPage(
fileInput("file",
"Import a CSV file",
accept = ".csv"),
tableOutput("head"),
tableOutput("tail")
)
server <- shinyServer(function(input, output) {
# get the data from a file obtained from a textInput in the ui
data <- reactive({
req(input$file)
inFile <- input$file
read.csv(inFile$datapath, header = F, sep = ";")
# process the file and return a new dataframe
})
observe({
mydf <- data()
if (is.null(mydf)){
output$head <- renderTable({})
output$tail <- renderTable({})
}
else {
output$head <- renderTable({
head(mydf)
})
output$tail <- renderTable({
tail(mydf)
})
}
})
})
shinyApp(ui, server)
Edit: I misunderstood the OP's question, see #SmokeyShakers' answer for a more appropriate answer.

Subsetting a reactive object in Shiny

I am in the process of learning Shiny and developing a simple app. The start of the program will allow a user to import a CSV file and then apply a filter variable(s) if needed. They will only be able to use factors as filter variables at this stage. I apply the filters on an iterative basis. So, one can apply a filter based on a factor level and then apply another factor level and so on until completed.
The best application I could find of being able to subset a reactive data frame was to apply the data frame as a reactive value. This seems to work, but I am having a couple issues that I can't figure out how to resolve.
1) Given the filtering is an iterative process, I would like to keep track and print out each variable and level applied during the filtering process. The best way I could figure out was creating a global variable (<<-) and using renderText to print out the contents after hitting the apply filter button. The issue is renderText just flashes on the screen and quickly disappears. I included a print to console statement that verifies the text is being saved correctly. I believe this is happening from the filter being applied to the reactive data frame and the updating process, but I can't figure out how to stop the text from disappearing on the screen?
2) When I try to save out the reactive data frame at the end of the shiny code, I get the following error "Warning: Error in $: $ operator is invalid for atomic vectors". I tried a couple things, but don't really understand what is going on here because the object "file$dfSource" is not like a normal reactive data frame dfSource()?
The shiny app below uses iris data so its easier to use/test. I don't know if applying the data frame to a reactive value is the best way to program this or if there is an easier way to do all this - just trying to learn best approach here.
library(shiny)
allfilters <- c()
ui <- (fluidPage(
# Application title
titlePanel("Filter Data"),
# Input Forms
sidebarLayout(
sidebarPanel(
h3("Data"),
checkboxInput("selectFilter", label = "Apply Filter Variable", value = FALSE),
uiOutput("selectFilterVar"),
uiOutput("selectFilterGroup"),
helpText("Apply filter to data"),
uiOutput("selectFilterButton"),
helpText("Reset data to total"),
uiOutput("selectResetButton"),
h3("Download Data"),
helpText("Download Data"),
downloadButton("downloadData", "Download File")
),
# Output Forms
mainPanel(
tabsetPanel(
tabPanel("Summary",
h2("Output Summary"),
textOutput("ncases"),
textOutput("selectedfilters")))
)
)
))
server <- (function(input, output, session) {
data <- iris
file <- reactiveValues(dfSource = data)
## Select Filter Variable
output$selectFilterVar <- renderUI({
req(file$dfSource)
if (input$selectFilter){
selectInput("filterVar", "Select Filter Variable", multiple = FALSE, choices = sort(names(file$dfSource[, sapply(file$dfSource, is.factor), drop = FALSE])))
}
})
# Select Filter Group(s)
output$selectFilterGroup <- renderUI({
req(file$dfSource)
req(input$filterVar)
if (input$selectFilter){
selectInput("filterGroup", "Select Filter Group", multiple = TRUE, choices = sort(unique(file$dfSource[,input$filterVar])))
}
})
# Apply Filter Button
output$selectFilterButton <- renderUI({
req(file$dfSource)
if (input$selectFilter) {
actionButton("filterButton", "Apply Filter")
}
})
# Apply filter group to data
observeEvent(input$filterButton, {
temp <- file$dfSource[(file$dfSource[,input$filterVar] %in% c(input$filterGroup)),]
file$dfSource <- temp
})
# Reset Total Sample Button
output$selectResetButton <- renderUI({
req(file$dfSource)
if (input$selectFilter) {
actionButton("resetButton", "Reset Total")
}
})
# Reset data to total sample
observeEvent(input$resetButton, {
file$dfSource <- data
updateCheckboxInput(session, "selectFilter", value = FALSE)
allfilters <- NULL
})
## Summary number of cases
output$ncases <- renderText({
req(file$dfSource)
mainTitle <- paste("Number of cases =" , nrow(file$dfSource))
return(mainTitle)
})
## Capture selected filter variables in global object
testfilter <- eventReactive(input$filterButton, {
appliedfilter <- paste0(input$filterVar, "(", input$filterGroup,")")
if (is.null(allfilters)) {
allfilters <<- paste("Selected Filters:", appliedfilter)
} else {
allfilters <<- paste(allfilters, "&", appliedfilter)
}
return(allfilters)
})
# Print out filter variables in global object
output$selectedfilters <- renderText({
filteroutput <- testfilter()
print(filteroutput)
return(filteroutput)
})
## Save out case data file
output$downloadData <- downloadHandler(
filename = function() {
paste("data-", Sys.Date(), ".csv", sep="")
},
content = function(file) {
write.csv(file$dfSource, file)
}
)
})
shinyApp(ui, server)
1) Storing it a global variable is probably not a good idea (scope in shiny is already complicated enough!). You already have a reactiveValues object, why not use that?
This alone, however, is not enough; the problem seems to be the eventReactive - I'm not quite sure why.
This works:
# this replaces the testfilter eventReactive
observeEvent(input$filterButton, {
appliedfilter <- paste0(input$filterVar, "(", input$filterGroup,")")
if (is.null(file$allfilters)) {
file$allfilters <- paste("Selected Filters:", appliedfilter)
} else {
file$allfilters <- paste(file$allfilters, "&", appliedfilter)
}
})
# Print out filter variables in global object
output$selectedfilters <- renderText({
filteroutput <- file$allfilters
print(filteroutput)
return(filteroutput)
})
2) The error is in the content function you pass to downloadHandler. The parameter is called file, which shadows the file reactiveValues. This works:
## Save out case data file
output$downloadData <- downloadHandler(
filename = function() {
paste("data-", Sys.Date(), ".csv", sep="")
},
content = function(filetarget) {
write.csv(file$dfSource, filetarget)
}
)
PS ad 1: It might be better to store the filters, instead of storing the filtered data frame and a string listing the filters. If your users change their mind, they have to start over from the beginning, but if you store the filters you can have a table or similar that allows deleting/editing individual filters. You could just store a list of two-element vectors, then iterate though the list to filter the data.

Resources