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
Related
Sorry if my question is a bit silly but I can't find a way of making to work a custom function that use reactiveValues as options.
I created several functions to do some "heavy processing" that I have put in global.R. These functions are something like this
estimateDEG <- function(variables = NULL, design = NULL, ...){
# do some processing for example
design <- model.matrix(variables$group[,1]
d <- estimateDisp(d, design))
suppressMessages(fit <- glmQLFit(d, design))
suppressMessages(out <- glmQLFTest(fit, coef = 2))
p <- out$table$PValue
p[is.na(p)] <- 1
variables$stat$p.value <<- p
variables$stat$rank <<- rank(p)
variables$stat$q.value <<- p.adjust(p, method = "BH")
variables$stat$logFC <<- out$table$logFC
... # more coding
}
Then I want to use this function in server.R
server.R
shinyServer(function(input, output, session) {
variables <- reactiveValues(
group = NULL,
stat = list()
)
# for example, I have a button that when it is clicked store some
# information in `variables$group` that I want to use in the function `estimateDEG`.
observeEvent(input$buttonList, {
group <- fread(input$groupSelectViaText, header = FALSE) # a TextAreaInput from ui.R
variables$group <- lapply(unique(group$V2), function(x) {
group[group$V2 == x, ]$V1
})
names(variables$group) <- unique(group$V2)
})
# and now I would like to use the estimateDEG function in another observeEvent
observeEvent(input$runButton, {
deg <- reactive(estimateDE(variables = variables,
test.method = input$testMethod, # another input from ui.R
FDR = input$fdr # another input f
))
})
However, when I run this code the reactiveValues are not updated, i.e, after running estimateDEG the variables$stat value is NULL. Is there any way of using a function inside server.r that use reactiveValues as options and update another values inside these reactiveValues? I would expect variables$stat to be populated with p.value, rank, q.value and so on
Many thanks in advance
Any time you're assigning to global in a shiny app, especially with <<- you've likely gone astray.
Reactive values are functions, and any argument passed is the new value of the function. To change the value of a reactive, you cannot use assign aka <-. This will just reassign the object from a reactive object to a static object in memory. You instead use the new value as the argument for the reactive function object. See below:
If you have the reactive value x, then want to assign its value to 3, you would use:
x <- reactiveVal(0)
# then
x(3)
print(x())
## 3
In your case, you need to pass the reactive object as a function to be called inside your custom made function. If x is a reactive value equal to 0, and I have a function that updates it:
f <- function(rval, newval) {
rval(newval)
}
f(x, 3)
x()
##3
To see that in action, the below is a quick demo app.
library(shiny)
# function that takes a reactive element as a function
# and returns that element's value plus some
# you can define this in global, under the /R directory
# or even in server.
add_some <- function(r_val, some) {
x <- r_val()
x <- x + some
r_val(x)
}
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
actionButton("button", "Button"),
numericInput("some", "Some", 1)
),
mainPanel(
verbatimTextOutput("console")
)
)
)
server <- function(input, output, session) {
init <- reactiveVal(0)
observeEvent(input$button, ignoreInit = TRUE, {
add_some(init, input$some)
})
output$console <- renderPrint({
init()
})
}
shiny::runApp(list(ui = ui, server = server))
Hope that helps.
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())
Hi I have this shiny app. You can run it and try it.
This application helps me to read a csv files and render a Table with the information of the csv file.
app.R
library(shiny)
ui <- shinyUI(
fluidPage(
textInput("fname","File name: ",value="data.csv"),
verbatimTextOutput("text"),
actionButton("chck_file", "Check for file"),
actionButton("create_file", "Create file"),
#Data Table
tableOutput("table1")
))
server <- shinyServer(function(input, output, session) {
# Listens for click from element with ID=chck_file
observeEvent(input$chck_file,{
# Check if file exists
if ( file.exists( isolate({input$fname}) ) ){
# Display text
output$text <- renderText({ paste("File exists in: ",getwd(),sep="") })
data <- input$fname
print(data)
df = read.csv(data)
output$table1 <- renderTable(df)
}
else{
output$text <- renderText({ paste("No such file in: ",getwd(),sep="") })
}
})
# Listens for click from element with ID=create_file
observeEvent(input$create_file,{
# Create file
file.create(isolate({input$fname}))
})
})
shinyApp(ui = ui, server = server)
I want to be able to load the dataframe that I am reading in the global environment of RStudio, how can I do this?
I tried with this operator <<-
df <<- read.csv(data)
Warning: Error in observeEventHandler: cannot change value of locked binding for 'df'
Also I tried with this solution, but only works when I stop my shiny app
data <- reactiveValues()
output$contents <- renderText({
if(is.null(input$file1))return()
inFile <- input$file1
data2<-read.csv(inFile$datapath)
assign('data',data2,envir=.GlobalEnv)
print(summary(data))
})
And I don't want to stop my session in order to see my data frame in the global env
df <<- read.csv(data)
Warning: Error in observeEventHandler: cannot change value of locked binding for 'df'
The operator <<- does not mean assignment in the global environment. Rather, it means "non-local assignment". When you do x <<- 2, R searches for x in the current environment, then in the parent environment if it does not find it, etc, until the global environment. If x is never found then R performs the assignment in the global environment.
Here df is found: this is the name of a function in the stats package. And one cannot change its value. So you have to use another name. But I don't know whether this will have an effect before closing the app, I didn't try.
EDIT
Yes this works, but you have to refresh the environment pane while the app is running (click on the circular arrow in the environment pane).
I'm trying to get a reactiveValue that is depending on a reactive. In the real code (this is a very simplified version), I load a dataset interactively. It changes when pushing the buttons (prevBtn/nextBtn). I need to know the number of rows in the dataset, using this to plot the datapoints with different colors.
The question: Why can't I use the reactive ro() in the reactiveValues function?
For understanding: Why is the error saying "You tried to do something that can only be done from inside a reactive expression or observer.", although ro() is used inside a reactive context.
The error is definitely due to vals(), I already checked the rest.
The code :
library(shiny)
datasets <- list(mtcars, iris, PlantGrowth)
ui <- fluidPage(
mainPanel(
titlePanel("Simplified example"),
tableOutput("cars"),
actionButton("prevBtn", icon = icon("arrow-left"), ""),
actionButton("nextBtn", icon = icon("arrow-right"), ""),
verbatimTextOutput("rows")
)
)
server <- function(input, output) {
output$cars <- renderTable({
head(dat())
})
dat <- reactive({
if (is.null(rv$nr)) {
d <- mtcars
}
else{
d <- datasets[[rv$nr]]
}
})
rv <- reactiveValues(nr = 1)
set_nr <- function(direction) {
rv$nr <- rv$nr + direction
}
observeEvent(input$nextBtn, {
set_nr(1)
})
observeEvent(input$prevBtn, {
set_nr(-1)
})
ro <- reactive({
nrow(dat())
})
output$rows <- renderPrint({
print(paste(as.character(ro()), "rows"))
})
vals <- reactiveValues(needThisForLater = 30 * ro())
}
shinyApp(ui = ui, server = server)
Error in .getReactiveEnvironment()$currentContext() :
Operation not allowed without an active reactive context. (You tried to do something that can only be done from inside a reactive expression or observer.)
I think you want
vals <- reactiveValues(needThisForLater = reactive(30 * ro()))
Not everything in a reactiveValues list is assumed to be reactive. It's also a good place to store constant values. So since it's trying to evaluate the parameter you are passing at run time and you are not calling that line in a reactive environment, you get that error. So by just wrapping it in a call to reactive(), you provide a reactive environment for ro() to be called in.
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