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())
Related
I have a shiny app, where I test the server component using testthat and testServer. The server part of the app has a couple of reactive values but also renders some elements in an observe() element (I group multiple renders together for performance reasons).
A simple server + testServer example function looks like this
library(shiny)
library(dplyr)
library(testthat)
server <- function(input, output, session) {
data <- reactive(mtcars)
observe({
print("Observe is active")
# do some further analysis on the data here
data2 <- data() |> filter(mpg > 20)
output$nrows <- renderUI(nrow(data2)) # 14
output$unique_mpgs <- renderUI(length(unique(data2$mpg))) # 10
})
}
testServer(server, {
expect_equal(data(), mtcars) # works because data() is a reactive() not observe()...
# how can I test here that nrows == 14 and unique_mpgs == 10
})
As mentioned earlier, I don't want to have multiple reactive values (eg one for nrows, then another for unique_mpgs) if possible.
Is there a way to check the value of an observe element in testServer?
I am creating a package that contains a few interactive shiny apps. The purpose of these apps is to facilitate GUI exploration of in-memory objects. For example, I have an object consisting of discretized variables I would like to pass to the shiny app and then make adjustments via the GUI interface.
However, I am running into trouble when trying to access this in-memory object from the Shiny App.
Here is the relevant code:
First, I am wrapping the shinyServer function in another function. My thought here is to give the shiny server access to the passed object.
#' #export
appServer <- function(bins) {
su <- summary(bins)
shinyServer(function(input, output) {
## values that should trigger updates when changed
values <- reactiveValues(summary=su, i=1, bins=bins)
# excluded rest of body for brevity ...
}
In this function, I create a shinyApp object and pass in the ui (in another file) and the result of the appServer function defined above.
makeApp <- function(bins) {
shiny::shinyApp(
ui = appUI,
server = appServer(bins))
}
The preceding functions are called in this function that wraps the call to runApp and takes an argument from the user.
#' #export
adjust <- function(bins) {
## access data from the app?
app <- makeApp(bins)
shiny::runApp(app)
}
How can I pass an in-memory object to a shinyApp that is imported from another package?
When I execute the above code, I receive the following error:
ERROR: path[1]="C:\Users\myusername\AppData\Local\Temp\RtmpWMpvHT\widgetbinding23e8333e5298": The system cannot find the path specified
In the example below I demonstrate how you can pass an object x from the global environment or from any other environment to the shiny app and change its value. I'm not sure if this answers your question. It maybe prove useful anyway :)
library(shiny)
x <- 5
x
deparse(substitute(x)) # is going to do the trick
fun <- function(obj) {
# get the name of the passed object
object_to_change <- deparse(substitute(obj))
# get the object from a given environment
val <- get(object_to_change, envir = .GlobalEnv)
# ?environment
# Save the object as a reactive value
values <- reactiveValues(x = val)
# Now define the app that is going to change the value of x
ui <- shinyUI(fluidPage(
br(),
actionButton("quit", "Apply changes and quit"),
textInput("new", "", value = NULL, placeholder = "Set new value of x"),
textOutput("out")
))
server <- function(input, output) {
output$out <- renderPrint({
values$x
})
# change the value of x
observe({
req(input$new)
values$x <- as.numeric(input$new)
})
# Apply changes and quit
observe({
if (input$quit == 1) {
assign(x = object_to_change, value = values$x, envir = .GlobalEnv)
stopApp()
}
})
}
# Run the app
shiny::shinyApp(ui, server)
}
fun(x)
# Check the new value of x in the .GlobalEnv
x
I'm a bit new at coding, but I try to teach myself new stuff all the time. Recently I started using shiny in R because I needed to have user interaction somewhere along my script. However, now that I've build my shiny app (a very basic one, but it works), my problem is that after reading dozens of webpages, tutorials etc, I still don't understand how to add code that will store the results of the user input in the shiny app into a vector / value that I can use in the other R script.
my ui.R scipt:
library(shiny)
ui = fluidPage(
radioButtons("Question","Do you want to keep cluster?",
c("YES" = 1, "NO" = 0)),
actionButton(inputId= "submit", label="OK")
)
my server script
shinyServer(function(input, output) {
observe({
if(input$submit > 0){
stopApp(input$Question)
}
})
})
This app will be run inside a loop in the other R script. It will present the user with the yes/no question for each cluster created by a script that autoclusters large amounts of data.
What I need is to have the 0 and 1 output values to be combined into a vector like answer <-c(answer,"ShinyOutputValue") after each run of the loop.
Its driving me nuts that I can't get it to work. Please help :)
Mark
You can create a variable answer in your global environment.
assign("answer", NULL, envir = .GlobalEnv) # answer <- NULL
Then, using functions get (or mget) you can 'get' the variable answer from the global environment to shiny (shiny runs in some random environment) and then using assign overwrite it with new values in global environment.
assign("answer", NULL, envir = .GlobalEnv) # answer <- NULL
library(shiny)
ui <- fluidPage(
radioButtons("Question","Do you want to keep cluster?",
c("YES" = 1, "NO" = 0)),
actionButton(inputId= "submit", label="OK")
)
server <- shinyServer(function(input, output) {
observe({
if(input$submit > 0){
val <- as.numeric(input$Question)
old_val <- get("answer", envir = .GlobalEnv)
assign("answer", c(old_val, val), envir = .GlobalEnv)
stopApp(input$Question)
}
})
})
shinyApp(ui, server)
answer
You can also create your own environment and save objects in:
new_env <- environment()
new_env$number <- 5
new_env$number
get("number", envir = new_env)
assign("n", 1:10, envir = new_env)
new_env$n
I am building a Shiny app that takes a user's text input, compares the last two words to a data frame of trigrams to predict the most likely next word. In server.R below the output of the triPred function which I am trying to ouptut is a single word. When I load this app I get the following error after I type some text into the app - 'argument 1 (type 'closure') cannot be handled by 'cat' - which appears to be related to the final line in server.R As this is just a single word, I am unclear what is failing with 'cat' ie concatenate.
server.R
library(stringr)
shinyServer(function(input, output) {
triSplit <- function(input) {
el <- unlist(str_split(input," "))
bigram <- paste(el[length(el)-1],el[length(el)])
return(bigram)
}
triPred <- function(input) {
## pulls out end words that match the input bigram
temp_wf_T <- wf_T[wf_T$start == triSplit(input),]
##Picks one of the best options at random based on count
ans <- sample(temp_wf_T$end[temp_wf_T$count == max(temp_wf_T$count)],1)
return(ans) }
##Read in a dataframe of bigrams, their possible completions, and counts of occurence
wf_T<-readRDS("C:/Users/LTM/DataScienceCertificateCapstone/ShinyTest/data/tdm.rds")
##Runs the triPred function to guess the next most likely word
ans <- reactive(triPred(input$sent))
##generates an output variable to display
output$out <- renderText({ans})
})
ui.R
library(shiny)
shinyUI(fluidPage(
titlePanel(h1("My Shiny App", align = "center")),
sidebarLayout(
sidebarPanel(helpText("Please enter a sentence you would like me to complete"),
textInput("sent", label = "sentence")),
##########
mainPanel(h1("Best Guess"),
br(),
textOutput("out")
)
)
))
It's hard to tell since I can't reproduce your app, but you should try with:
output$out <- renderText({ans()}) or just output$out <- renderText(ans()).
If you omit the (), you access the reactive itself, and not the value of it. A bit like when you type foo instead of foo() for a function.
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)...