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.
Related
I've created a dynamic fileInput in shiny using lapply. When I want to read the file, I've also used lapply in an observer.
The problem of using lapply here is, it is triggered every time I upload a new file and thus, reads all files again and again if a new file is uploaded.
Here I provide a Hello World app. The lapply function depends on an input paramter which I abtracted from for simplicity.
library(shiny)
ui <- fluidPage(
titlePanel("Hello World"),
sidebarLayout(
sidebarPanel(),
mainPanel(
lapply(1:2, function(i) {
fileInput(
paste0("file", i),
label = NULL,
multiple = F,
accept = c(
"text/csv",
"text/comma-separated-values,text/plain",
".csv"
),
buttonLabel = paste("File", i)
)
}),
verbatimTextOutput("list")
)
)
)
server <- function(input, output) {
r <- reactiveValues()
observe({
lapply(1:2, function(i) {
file <- input[[paste0("file",i)]]
if(is.null(file)) return()
isolate({
r$file[[paste(i)]] <- readr::read_csv2(file = file$datapath)
})
})
})
output$list <- renderPrint(reactiveValuesToList(r))
}
shinyApp(ui = ui, server = server)
How to replace the loop or add a requirement to lapply?
While I started down the road of cache-invalidation in the comments, I think something else may work better for you since you have a fixed number of fileInput fields: swap the lapply and observe lines in your code (plus a couple of other tweaks).
server <- function(input, output) {
lapply(paste0("file", 1:2), function(nm) {
observeEvent(input[[ nm ]], {
req(input[[nm]], file.exists(input[[nm]]$datapath))
readr::read_csv2(file = input[[nm]]$datapath)
})
})
}
Explanation:
I'm creating a list of reactive blocks instead of a reactive block operating on a list. This means "file1" won't react to "file2".
I short-cutted the definition of the input names by putting paste0(...) in the data of the lapply instead of in the function, though it'd be just as easy to do
lapply(1:2, function(i) {
nm <- paste0("file", i)
# ...
})
It's important to have nm defined outside of the observeEvent, and it has to do with delayed evaluation and namespace search order. I fell prey to this a few years ago and was straightened out by Joe Cheng: you can't use a for loop, it must be some environment-preserving operation like this.
N.B.: this is a stub of code, and it is far from complete: having an observe or observeEvent read the data and then discard it is wrong ... it's missing something. Ideally, this should really be a reactive or eventReactive block, or the processed data should be stored in a reactiveValues or reactiveVal. For example:
server <- function(input, output) {
mydata <- lapply(paste0("file", 1:2), function(nm) {
observeEvent(input[[ nm ]], {
req(input[[nm]], file.exists(input[[nm]]$datapath))
readr::read_csv2(file = input[[nm]]$datapath)
})
})
observe({
# the following are identical, the latter more declarative
mydata[[1]]
mydata[["file1"]]
})
}
(And another note about defensive programming: you cannot control perfectly how readr::read_csv2 reacts to that file ... it may error out for some reason. One further step would be to wrap it in tryCatch(..., error = function(e) { errfun(e); NULL; }) where errfun(e) does something meaningful with the error message (logs it and/or gives it to the user in a modal popup) and then returns NULL so that reactive blocks downstream can use req(mydata[[1]]) and will not try to process the NULL.
server <- function(input, output) {
mydata <- lapply(paste0("file", 1:2), function(nm) {
observeEvent(input[[ nm ]], {
req(input[[nm]])
file <- input[[nm]]
tryCatch(
readr::read_csv2(file = input[[nm]]$datapath),
error = function(e) { errfun(e); NULL; })
})
})
observe({
# the following are identical, the latter more declarative
mydata[[1]]
mydata[["file1"]]
})
}
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.
So, I've been on google for hours with no answer.
I want to create a user-defined function inside the server side that takes inputs that I already know to wrap reactive({input$feature)} but the issue is how to incorporate reactive values as inputs too.
The reason why I want to do this is because I have a navbarPage with multiple tabs that shares elements such as same plots. So I want a user defined function that creates all the similar filtering and not have to create multiple of the same reactive expression with different input and reactive variable names which take up 2000+ lines of code.
server <- function(input, output) {
filtered_JointKSA <- reactiveVal(0)
create_filtered_data <- function(df, input_specialtya, filtered_JointKSA) {
if (input_specialtya == 'manual') {
data <- filter(data, SPECIALTY %in% input_specialtyb)
}
if (filtered_JointKSA != 0) {
data <- filter(data, SPECIALTY %in% filtered_JointKSA)
}
reactive({return(data)})
}
filtered_data <- create_filtered_data(df,
reactive({input$specialty1}),
filtered_JointKSA())
observeEvent(
eventExpr = input$clickJointKSA,
handlerExpr = {
A <- filtered_JointKSA(levels(fct_drop(filtered_data()$`Joint KSA Grouping`))[round(input$clickJointKSA$y)])
A
}
)
This gets me an error:
"Error in match(x, table, nomatch = 0L) :
'match' requires vector arguments"
The error is gone if I comment out where I try to create filtered_data but none of my plots are created because filtered_data() is not found.
What is the correct approach for this?
Ideally, I would like my observeEvents to be inside user defined functions as well if that has a different method.
This example may provide some help, but it's hard to tell without a working example. The change is to wrap the call to your function in reactive({}) rather than the inputs to that function, so that the inputs are all responsive to user input and the function will update.
library(shiny)
ui <- fluidPage(
numericInput("num", "Number", value = NULL),
verbatimTextOutput("out")
)
server <- function(input, output){
## User-defined function, taking a reactive input
rvals <- function(x){
req(input$num)
if(x > 5){x * 10} else {x*1}
}
# Call to the function, wrapped in a reactive
n <- reactive({ rvals(input$num) })
# Using output of the function, which is reactive and needs to be resolved with '()'
output$out <- renderText({ n() })
}
shinyApp(ui, server)
I think I'm missing something with respect to reactives in my Shiny app. Here's a MRE that shows my problem printing y.
library(shiny)
ui <- fluidPage(
titlePanel("Test"),
textAreaInput("text", "Text", "", width = "400px"),
verbatimTextOutput("text"),
actionButton("do", "Run"),
textOutput("result")
)
server <- function(input, output) {
observeEvent(input$do, {
y <- reactive({
x <- ""
t <- 1
while (t < 5) {
x <- paste(input$text, x, sep=",")
t <- t + 1
}
})
})
output$result <- renderPrint({y})
}
shinyApp(ui = ui, server = server)
You shouldn't put a reactive value inside of an observeEvent or observe call. In fact, it has been advised by Joe Cheng to never nest either observe or reactive functions within either themselves or the other. They are separate things used for different purposes. Since you want the reactive y to be created based on when input$do is clicked, you should use eventReactive:
server <- function(input, output) {
y <- eventReactive(input$do, {
x <- ""
t <- 1
while (t < 5) {
x <- paste(input$text, x, sep=",")
t <- t + 1
}
return(x)
})
output$result <- renderText({y()})
}
I changed renderPrint() to renderText() so that it displays your desired output. Also, since y is a reactive, you need to add y() after it in the renderText() call. I also added return(x) to the eventReactive call, otherwise t would be the value returned.
I the problem is that your call to reactive() does not return anything. Wrapping an expression inside reactive assigns the return value of the expression to a variable each time a reactive value inside the expression is changed. Reactive values are usually all input$... variables, and those that you store in reactiveValues() objects.
If I get you right you want to change and print y every time the "run" button is hit. Save this to a reactiveValue() collection (similarly accessible like a list), and then put this inside your renderPrint function.
From your code I reckon that you want y to be the value of x after the while loop.
library(shiny)
ui <- fluidPage(
titlePanel("Test"),
textAreaInput("text", "Text", "", width = "400px"),
verbatimTextOutput("text"),
actionButton("do", "Run"),
textOutput("result")
)
server <- function(input, output) {
values <- reactiveValues()
observeEvent(input$do, {
x <- ""
t <- 1
while (t < 5) {
x <- paste(input$text, x, sep=",")
t <- t + 1
}
values$y <- x
})
output$result <- renderPrint({values$y})
}
shinyApp(ui = ui, server = server)
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