Accessing data in different parts of server() in shiny - r

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.

Related

Using Shiny to update dataframe values and access it in the local environment after Shiny session ends

I have been going through most of the Q&As related to dataframe manipulation within Shiny and I still don't understand how to do something which, in my mind, should be very simple. I don't have experience writing Shiny apps and I'm still struggling with concepts like reactive events.
I have a dataframe A, loaded into R. I want to be able to see a specific value in a specific column in the dataframe in the UI and then edit it. After I edit the dataframe, I want to close the Shiny app and then see the edited dataframe in the Environment tab of RStudio. How do I go about doing this?
I think this might be a workable example.
Assume df is your data frame (I used iris to test, commented out below). Create a reactiveVal to hold your data, and use for editing with datatable. After editing, you can store the data back into your global environment dataframe df with <<-. An alternative is to do this when exiting the shiny app (such as through the onStop or session$onSessionEnded method).
library(shiny)
library(DT)
#df <- iris
ui <- fluidPage(
DT::dataTableOutput('data'),
)
server <- function(input, output) {
rv <- reactiveVal(df)
output$data <- DT::renderDataTable ({
DT::datatable(rv(), editable = TRUE)
})
observeEvent(input$data_cell_edit, {
info <- input$data_cell_edit
newdf <- rv()
newdf[info$row, info$col] <- info$value
rv(newdf)
df <<- rv()
})
}
shinyApp(ui = ui, server = server)
Alternative with replacing global df on exiting (requires session):
server <- function(input, output, session) {
rv <- reactiveVal(df)
output$data <- DT::renderDataTable ({
DT::datatable(rv(), editable = TRUE)
})
observeEvent(input$data_cell_edit, {
info <- input$data_cell_edit
newdf <- rv()
newdf[info$row, info$col] <- info$value
rv(newdf)
})
session$onSessionEnded(function() {
df <<- isolate(rv())
})
}
If you don't want to use reactive values, I suppose you could try the following. This can update your data.frame in the global environment as edits are made. Note that server = FALSE is added to handle changes in pages:
server <- function(input, output) {
output$data <- DT::renderDT (df, editable = TRUE, server = FALSE)
observeEvent(input$data_cell_edit, {
info <- input$data_cell_edit
df[info$row, info$col] <<- info$value
})
}

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.)

Using reactiveFileReader function in Shiny

I would like your help to access the result elements that the reactiveFileReader function offers me, in which case the result is fileData ()
The server code is this:
server <- function(input, output,session) {
fileData <- reactiveFileReader(1000,session,filePath = 'ddeLink.xlsm', readFunc = read_excel)
output$data <- renderTable({
fileData()
})
}
The excel spreadsheet linkdde.xslm every five minutes updates. It is composed of 2 columns where only column b is updated. The excel file bellow:
The code works fine. That is, whenever the excel spreadsheet updates my app Shiny also updates the mmatrix above, which is the fileData ()result.
The fileData()is always updating. The fileData() is the matrix above.
But my question is: How do I access the values ​​of this mtrix, represented by the fileData () to create a plot that would be updated because the fileData () is updating. In other words I want to have a plot updating every 5 minutes using the 5 minutes fileData () matrix?
I did this:
output$data <- renderPlot({
df<-as.data.frame(fileData())
plot(df[,1])
})
But it didnt work.
Any help guys
Many thanks
The below example seems to work, where the plot is updated when the Excel file is updated. Is this what you are looking for? If not, please describe further what you need.
library(shiny)
library(readxl)
ui <- fluidPage(
mainPanel(
uiOutput("data"),
plotOutput("plot")
)
)
server <- function(input, output,session) {
fileData <- reactiveFileReader(1000,
session,
filePath = 'ddeLink.xlsx',
readFunc = read_excel)
output$data <- renderTable({
fileData()
})
output$plot <- renderPlot({
df<-as.data.frame(fileData())
plot(df[,2])
})
}
shinyApp(ui, server)

How to clean up CSV data after uploading to Shiny App

Please help!
I'm trying to build a Shiny App with the intent to classify data loaded from a CSV file. How do I successfully create a DataFrame from a CSV file (that is uploaded) so that I can move forward and clean/analyze it.
Please see code:
library(shiny)
library(lubridate)
library(utils)
library(dplyr)
library(tidytext)
ui <- (pageWithSidebar(
headerPanel("CSV File Upload Demo"),
sidebarPanel(
#Selector for file upload
fileInput('datafile', 'Choose CSV file',
accept=c('text/csv', 'text/comma-separated-values,text/plain')),
#These column selectors are dynamically created when the file is loaded
uiOutput("fromCol"),
uiOutput("toCol"),
uiOutput("amountflag"),
#The conditional panel is triggered by the preceding checkbox
conditionalPanel(
condition="input.amountflag==true",
uiOutput("amountCol")
)
),
mainPanel(
tableOutput("filetable")
)
))
Please advise whether to use Reactive
server <- (function(input, output) {
#This function is repsonsible for loading in the selected file
filedata <- reactive({
infile <- input$datafile
if (is.null(infile)) {
# User has not uploaded a file yet
return(NULL)
}
dataframe <- reactive({
readr::read_csv(infile()$datapath)
})
# Clean data by whole-case removal of missing cells (either NAs or "nan")
# Remove the rows which have NAs
myDataClean2 = dataframe[complete.cases(dataframe),]
# In order to turn it into a tidy text dataset, we first put the data into a data frame:
text_df <- data_frame(myDataClean2$text,myDataClean2$title,myDataClean2$author,myDataClean2$id,myDataClean2$label)
names(text_df) <- c("text","title","author","id","label")
# Within the tidy text framework, we break both the text into individual tokens and transform
# it to a tidy data structure. To do this, we use tidytextâs unnest_tokens() function.
tidy_text_df <- text_df %>%
unnest_tokens(word, text)
#This previews the CSV data file
output$filetable <- renderText({
tidy_text_df()
})
})
})
# Run the application
shinyApp(ui = ui, server = server)
You are mixing reactive blocks. Your filedata should end with something that outputs your data, likely the output from unnest_tokens(word, text). (It should put out all data you are interested in, I think that that line does.) From there, your output$filetable needs to be outside of filedata's reactive block, on its own. And it should be using filedata(), not tidy_text_df (which isn't available outside of the first reactive block).
Try this:
server <- (function(input, output) {
#This function is repsonsible for loading in the selected file
filedata <- reactive({
infile <- input$datafile
if (is.null(infile)) {
# User has not uploaded a file yet
return(NULL)
}
dataframe <- reactive({
readr::read_csv(infile()$datapath)
})
# Clean data by whole-case removal of missing cells (either NAs or "nan")
# Remove the rows which have NAs
myDataClean2 = dataframe[complete.cases(dataframe),]
# In order to turn it into a tidy text dataset, we first put the data into a data frame:
text_df <- data_frame(myDataClean2$text,myDataClean2$title,myDataClean2$author,myDataClean2$id,myDataClean2$label)
names(text_df) <- c("text","title","author","id","label")
# Within the tidy text framework, we break both the text into individual tokens and transform
# it to a tidy data structure. To do this, we use tidytextâs unnest_tokens() function.
text_df %>%
unnest_tokens(word, text)
})
#This previews the CSV data file
output$filetable <- renderText({
filedata()
})
})

reactively fetch new data from external source on action button

My goal is to retrieve data from a googlesheet and map it on a leaflet map.
Everything is working fine, only if the code to retrieve data from googlesheet is placed in the global.R and it is only valid for that session of the running shiny app. However, if meanwhile the sheet is updated, these updates are not reflected in the running session. So I need to wire up a ui.R button to fetch new data each time the button is fired and pass the data onto the relevant codes in server.R . (I hope this is clear).
In my current setup, the data gets downloaded from googlesheet (via global.R) and passed on to the environment and used for that running app session.
Here is my working shiny app setup:
ui.R
...
leafletOutput("map"),
actionButton("button", "Get New Data")
...
#added the below lines to update the question:
selectInput("Country",
"Country:",
c("All",
unique(as.character(draw$Country))))
server.R
shinyServer(function(input, output, session) {
#...
output$map <- renderLeaflet({
#... some options here
})
draw <- mydata
drawvalue <- reactive({
if (input$year == year1){return(mydata)} else {
filtered <- filter(mydata, Type == input$type1)
return(filtered)
}
})
observe({
#... some other variable definitions
colorBy <- input$color ##added to update the question
sizeBy <- input$size ##added to update the question
draw <- drawvalue()
colorData <- draw[[colorBy]] ##added to update the question
#... code related to the leaflet
})
#...
}
global.R
mydata <- gs_read(gs_key("0123456abcdabcd123123123"))
After some reading and exploring, I am told that I have to use reactive and observeEvent. But my faulty setup results in error, saying that 'object "mydata" not found'.
I tried in the server.R: (I know the code below is all faulty)
observeEvent(input$button,{
mydata <- gs_read(gs_key("0123456abcdabcd123123123"))
})
mydata <- eventReactive(input$button,{
mydata()
})
update:
in my ui.R, I also refer to "draw", this also bugs. How should I change this one? I updated the lines in the ui.R above in the question. this line is part of the ui.R line which call the DT package to show some tables.
by the way, this app is based on the superzip shiny app.
NB: I will give 100 points bounty for the accepted answer.
In general observe and observeEvent do not return any value and they are used for side effects. So this part of the code below doesn't return any value and even if you used <<- to override the variable mydata shiny wouldn't know that its value has changed.
observeEvent(input$button,{
mydata <- gs_read(gs_key("0123456abcdabcd123123123"))
})
So if you want shiny to know when the data is updated you should read it within reactive environment. So, instead of reading the data via global.R I would advice to do following within server.R:
mydata <- eventReactive(input$button, {
gs_read(gs_key("0123456abcdabcd123123123"))
})
After clicking the button shiny would read (new) data which could be then accessed with mydata() and passed to the render* functions and other reactive parts of the code. For example:
drawvalue <- reactive({
if (input$year == year1){return(mydata() )} else { # added ()
filtered <- filter(mydata(), Type == input$type1) # added () to mydata
return(filtered)
}
})
You had to change this part of code
draw <- drawvalue()
to
draw <- reactive({ drawvalue() })
and then access it with draw()
UPDATE:
If you want make choices of the widget selectInput from UI.R dependent on draw you can do following:
1) Add the parameter session to the server function for updateSelectInput
shinyServer(function(input, output, session) {...} # added session
2) Set choices of the widget in UI.R to ""
selectInput("Country", "Country:", choices = "")
3) Update the choices on the server side with:
observe({
req(draw()) # require that draw is available
updateSelectInput(session, "Country", "Country:",
c("All", unique(as.character(draw()$Country)))) # added ()
})

Resources