R / R Shiny - global dataframes array (assigned by reference)? - r

I have a dataframe df1, and subset it to df1sub and display it in an R shiny renderPlot() call. Similarly, I have df2, and I subset it to df2sub, and render it in R shiny via a separate renderPlot() call. Btw these subsets are created based on user choices in an R Shiny app.
Now, I have a datatable that I want to change to reflect whatever the current dataset is, so I wanted some kind of global like:
buffers[1] <- df1sub
buffers[2] <- df2sub
How would I go about defining this global var? I tried separately doing buffers = array() to initialize a global var but then the assignments as I wrote them above don't work?
Update: attempts to use the the '<<-' operator as suggested below yields the following:
buffers <- NULL #don't know how else to initialize. array() yields same error as below.
buffers[1] <<- df # Error in buffers[1] <<- df : object 'buffers' not found

You can adopt this approach:
library(shiny)
shinyServer(function(input, output) {
...
some.reactive.expression <- reactive1({
...
buffers[1] <<- df1sub
buffers[2] <<- df2sub
...
})
})
With some updates:
#In global.R
buffers <- list()
buffers[[1]] <- data.frame()
buffers[[2]] <- data.frame()
buffers[[1]] <- df1 #original dataset, as a default, before subsets are created
buffers[[2]] <- df2 #ditto.
Then in server.R:
r1 <- reactive({
... #create subset of df, then return it
buffers[[1]] <<- subset(...)
buffers[[1]] #return it as dynamic data for plots
})
r2 <- reactive({...}) #ditto
then in renderplot:
output$blah <- renderplot(r1()...)
output$foo <- renderplot(r2()...)
Leaving the global buffers[] var separately available to a 3rd UI widget (e.g. a data table)...

Related

R Shiny Object not found - Evaluating a strings as code

I am trying to creat a shiny app which takes strings within a column of a data frame that are pieces of R code, and evaluate those against data frames which have been generated in the app. Below is a working reprex of the code outside of the shiny app:
## create df with eval expressions
code_df <- data.frame(desired_outcome = c("this should be true",
"this should be false",
"this will be true or false"),
code_string = c('nrow(random_df) > 0',
'nrow(random_df) == 0',
'nrow(random_df) >= 100'),
stringsAsFactors = F)
# generate a dataframe with 1-150 rows
random_df <- data.frame(rand_binary = sample(0:1,sample(1:150, 1),rep=TRUE))
## helper function for sapply
eval_parse <- function(x){
eval(parse(text = x))
}
## evaluate code strings
tf_vector <- sapply(code_df$code_string, eval_parse)
## add data to original df
code_df$nrow <- nrow(random_df)
code_df$tf <- tf_vector
code_df
If you run the code above, it will generate a 'random_df' with between 1-150 rows, then evaluate the code strings from code_df. This code works as intended.
The problem arises when I try to implement this in shiny (code below), the implementation returns "Error: object 'random_df' not found" when the action button is clicked.
One other wrinkle: If you run the non-shiny reprex code first, and do not clean the environment before you run the shiny app, the app will return the table, but it evaluates the code strings based on the non-shiny "random_df", not the newly randomly generated one from the shiny app. You can see this based on the fact that the 'nrow' column will change in value, while the 'tf' will not change.
server.R
library(shiny)
code_df <- data.frame(desired_outcome = c("this should be true", "this should be false", "this will be true or false"),
code_string = c('nrow(random_df) > 0', 'nrow(random_df) == 0', 'nrow(random_df) >= 100'),
stringsAsFactors = F)
## helper function for sapply
eval_parse <- function(x){
eval(parse(text = x))
}
# Define server logic required to draw a histogram
shinyServer(function(input, output) {
new_code_df <- eventReactive(input$newDF,{
# create data.frame
random_df <- data.frame(rand_binary = sample(0:1,sample(1:150, 1),rep=TRUE))
##
tf_vector <- sapply(code_df$code_string, eval_parse)
code_df$nrow <- nrow(random_df)
code_df$tf <- tf_vector
code_df
})
output$randomdf <- renderTable({new_code_df()})
})
ui.R
#
# This is the user-interface definition of a Shiny web application. You can
# run the application by clicking 'Run App' above.
#
# Find out more about building applications with Shiny here:
#
# http://shiny.rstudio.com/
#
library(shiny)
# Define UI for application that draws a histogram
shinyUI(fluidPage(
# Application title
titlePanel("Eval Code from Data Frame"),
sidebarLayout(
sidebarPanel(
actionButton("newDF","Generate New Dataframe")
),
mainPanel(
tableOutput('randomdf')
)
)
))
Functions in R (and therefore shiny) are lexically scoped. This mean that functions can only see the variables defined in the environment where they themselves are defined. You are defining eval_parse in the global environment but random_df is defined in the shiny server function. This the former cannot see the latter because random_df is not in the gloabl enviroment like it was in your non-shiny example.
If you want to make all the server variables available to your expression, you can specify an environment to eval(). First change the helper so you can pass an environment
eval_parse <- function(x, env=parent.frame()){
eval(parse(text = x), envir=env)
}
and then change your server code to pass along the function environment
tf_vector <- sapply(code_df$code_string, eval_parse, env=environment())

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.

data tibble empties on shiny

I am having trouble sending a tibble loaded from a user file to a plotting function. It seems that the table is indeed read and modified properly, but when I ask another function to use it for plotting the entries are missing.
on the Shiny server I have the following:
myData <- reactive({
if (is.null(inFile())) {
return(NULL)
} else {
tmp_table = read_csv(inFile()$datapath[1])
# tmp_table = read_csv('Fazael_grain_size.csv')
big_table=tmp_table
# modify the factor columns into factors
big_table$sample_name <- factor(big_table$sample_name)
big_table$site <- factor(big_table$site)
big_table$fraction <- factor(big_table$fraction)
# remove NA rows
big_table=big_table[!is.na(big_table$sample_name),]
# make sure the table was loaded correctly
output$table <- renderTable((big_table))
return(big_table)
}
})
a screenshot showing the proper loading of the table
then I want to use this table for plotting, so I have the following chunk:
myPlot <- function(){
# make sure the data exists
req(myData())
big_table=myData()
# checking the tibble was properly sent to the plotting function
output$table <- renderTable(big_table)
# filter the data based on user selection
big_table = filter(big_table,sample_name %in% input$selected_sample_name)
big_table = filter(big_table,site %in% input$selected_site)
big_table = filter(big_table,fraction %in% input$fraction)
p = big_table %>%
ggplot(aes_string(x=colnames(big_table)[4],y=colnames(big_table)[4])) +
geom_point()
if (!(input$color_variable %in% c("none"))) {
p = p +
geom_point(aes_string(color=input$color_variable))
}
p
}
for some reason the plot remains empty, and after some debugging I found out that the tibble is forwarded empty to it. any answers?
a screenshot demonstrating that the entries in the table are not forwarded into the plotting function
and indeed, the plot doesn't show up:
output$plot1 <- renderPlot({
myPlot()
})
no plot appears on the plotting area
solved. there was a small bug in the filter function:
should have been:
big_table = filter(big_table,fraction %in% input$selected_fraction)

How to grab selected event data from a ggplotly chart?

I'm aware of https://plot.ly/r/shinyapp-plotly-events/ and have been using it as a guide. But the ggplot element I'm converting to plotly is the output from the fviz_dend function of the factoextra package. Here's a minimum shiny app example I'm working with:
library(factoextra)
library(plotly)
library(shiny)
library(DT)
ui <- fluidPage(
plotlyOutput("ggp"),
verbatimTextOutput("selected_points"),
DT::dataTableOutput("filtered_table")
)
server <- function(input, output, session) {
## ggplot output
fviz <- fviz_dend(
x = hclust(dist(mtcars)),
k = 5,
show_labels = TRUE,
type = "phylogenic",
phylo_layout = "layout_as_tree",
color_labels_by_k = TRUE,
palette = "igv"
)
## convert to ggplotly
ggfviz <- ggplotly(fviz)
## add keys
for (i in seq(7, 11)) {
ggfviz[["x"]][["data"]][[i-5]][["key"]] <-
as.character(ggfviz[["x"]][["data"]][[i]][["text"]])
}
output$ggp <- renderPlotly({
ggfviz
})
output$selected_points <- renderPrint({
event_data("plotly_selected")[5]
})
output$filtered_table <- DT::renderDataTable(
mtcars[which(rownames(mtcars) == event_data("plotly_selected")[5]), ],
)
}
shinyApp(ui, server)
So I'm trying to use the key accessed with event_data("plotly_selected")[5] in order to filter the data table, and while event_data("plotly_selected")[5] does show the key per output$selected_points, it is somehow not passed to the datatable filter.
It looks like event_data will return a data frame with multiple rows. Instead of filtering with == you will need %in% instead to see which multiple cars are contained within the multiple possible selections from plotly_selected. In addition, even though you subset by column 5, you still have a data frame, and need to include the column key only for filtering (containing a vector of cars). This should work:
mtcars[which(rownames(mtcars) %in% event_data("plotly_selected")$key), ]
Or
mtcars[which(rownames(mtcars) %in% event_data("plotly_selected")[["key"]]), ]

Call Variable from reactive data() in R Shiny App

I would like to call a certain variable within a reactive expression. Something like this:
server.R
library(raster)
shinyServer(function(input, output) {
data <- reactive({
inFile <- input$test #Some uploaded ASCII file
asc <- raster(inFile$datapath) #Reads in the ASCII as raster layer
#Some calculations with 'asc':
asc_new1 <- 1/asc
asc_new2 <- asc * 100
})
output$Plot <- renderPlot({
inFile <- input$test
if (is.null(inFile)
return (plot(data()$asc_new1)) #here I want to call asc_new1
plot(data()$asc_new2)) #here I want to call asc_new2
})
})
Unfortunately I could't find out how to call asc_new1 and asc_new2 within data(). This one doesn't work:
data()$asc_new1
Reactives are just like other functions in R. You can't do this:
f <- function() {
x <- 1
y <- 2
}
f()$x
So what you're within output$Plot() won't work either. You can do what you want by returning a list from data().
data <- reactive({
inFile <- input$test
asc <- raster(inFile$datapath)
list(asc_new1 = 1/asc, asc_new2 = asc * 100)
})
Now you can do:
data()$asc_new1
"With data()$asc_new1 you wont be able to access the in the reactive context created variables (at least with the current shiny version).
You need data()[1] data()[2] if you put it in a list like MadScone. Calling it with the $ sign would raise
Warning: Unhandled error in observer: $ operator is invalid for atomic vectors
However, the error your getting
Error in data()$fitnew : $ operator not defined for this S4 class
is not only because you access the variable wrong. You named the output of your reactive function data which is reserved name in R. You want to change that to myData or something.

Resources