I'm trying to access an object(a<-get(obj1,envir=parent.environment())) residing in the calling environment from the called environment myf and I can't get it working. Error I'm getting is Object obj1 not found. I tried parent.frame()also. Any ideas?
library(shiny)
shinyApp(
ui = textOutput("test1"),
server = function(input, output) {
myf <- function(x) {
a <- get(obj1, envir = parent.environment())
return(paste0(x,a))
}
output$test1 <- renderText({
obj1 <- "testing"
a <- lapply(c("a","b","c"), myf)
return(paste(unlist(a), collapse = ","))
})
}
)
NOTE : I do NOT want to create obj1 using obj1<<- as it creates in Global Environment and is available for all sessions
The correct solution is that you have three problems: First of all, you need to quote "obj1" like this
get("obj1", envir = ...)
Secondly, parent.environment() is not a function. It doesn't exist.
Thirdly, you need to understand environment and calling frames a little bit to know how this works (it has nothing to do with Shiny). What you want to use is parent.frame(2) (being inside an lapply adds a layer)
So to modify your original code, this is the solution:
library(shiny)
shinyApp(
ui = textOutput("test1"),
server = function(input, output) {
myf <- function(x) {
a <- get("obj1", envir = parent.frame(2))
return(paste0(x,a))
}
output$test1 <- renderText({
obj1 <- "testing"
a <- lapply(c("a","b","c"), myf)
return(paste(unlist(a), collapse = ","))
})
}
)
I'm not sure why it doesn't work, but there' a simple workaround: explicitly pass obj1 to myf:
library(shiny)
shinyApp(
ui = textOutput("test1"),
server = function(input, output) {
## myf now takes two arguments, x and a:
myf <- function(x, a) {
return(paste0(x, a))
}
output$test1 <- renderText({
obj1 <- "testing"
## Now you can just pass obj1 as a second argument to myf
## without worrying about scoping:
a <- lapply(c("a","b","c"), myf, obj1)
return(paste(unlist(a), collapse = ","))
})
}
)
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 looking for some help please to print a reactive value in a Shiny session into a docx with WordR. A very stripped down version of my app is presented below.
The code for the docx template is `r reactive({declared_user()})` (which is bookended with MS Word’s formatting symbols). I don’t know how to show the format symbols or provide the docx template here on SO but that’s the only applicable code.
I have tried numerous ways of wrapping the declared_user() in a reactive context in both the r file and docx but still can’t seem to see either value/user in ‘slt_input’ printing out in rprt_out.docx.
All that prints out is…
function () { .dependents$register() if (.invalidated || .running) { ..stacktraceoff..(self$.updateValue()) } if (.error) { stop(.value) } if (.visible) .value else invisible(.value) }
library(shiny)
library(WordR)
library(officer)
library(dplyr)
ui <- fluidPage(
selectInput('slt_input', 'name', choices = c("god", 'devil')),
actionButton("btn_inline", 'inline')
)
server <- function(input, output, session) {
declared_user <- reactive({
input$slt_input
})
observeEvent(input$btn_inline,{
renderInlineCode("rprt_tmplt.docx", "rprt_out.docx")
})
}
shinyApp(ui, server)
Here is a solution. I think 2 things complicated the issue:
renderInlineCode extracts the R code from the .docx template and uses eval to evaluate the code. Somehow, it couldn't use the correct environment for the evaluation. Therefore I slightly changed the code so that you can pass the environment as an argument to the function.
it still doesn't work to evaluate shiny code. Therefore, I generated a normal variable out of the reactive directly before the docx rendering and use this in the template
library(shiny)
library(WordR)
library(officer)
library(dplyr)
renderInlineCode_2 <- function (docxIn, docxOut, eval_envir = parent.frame(), debug = F)
{
if (debug) {
browser()
}
doc <- officer::read_docx(docxIn)
smm <- officer::docx_summary(doc)
styles <- officer::styles_info(doc)
regx <- "^[ ]*`r[ ](.*)`$"
smm$expr <- ifelse(grepl(regx, smm$text), sub(regx, "\\1",
smm$text), NA)
smm$values <- sapply(smm$expr, FUN = function(x) {
eval(parse(text = x), envir = eval_envir)
})
smm <- smm[!is.na(smm$expr), , drop = F]
i <- 3
for (i in seq_len(nrow(smm))) {
stylei <- switch(ifelse(is.na(smm$style_name[i]), "a",
"b"), a = NULL, b = styles$style_name[styles$style_id ==
paste0(styles$style_id[styles$style_name == smm$style_name[i] &
styles$style_type == "paragraph"], "Char")])
doc <- officer::cursor_reach(doc, keyword = paste0("\\Q",
smm$text[i], "\\E")) %>% officer::body_remove() %>%
officer::cursor_backward() %>% officer::slip_in_text(smm$values[i],
pos = "after", style = stylei)
}
print(doc, target = docxOut)
return(docxOut)
}
ui <- fluidPage(
selectInput('slt_input', 'name', choices = c("god", 'devil')),
actionButton("btn_inline", 'inline')
)
server <- function(input, output, session) {
declared_user <- reactive({
input$slt_input
})
observeEvent(input$btn_inline,{
eval_user <- declared_user()
renderInlineCode_2("rprt_tmplt.docx", "rprt_out.docx")
})
}
shinyApp(ui, server)
In the template, use:
`r eval_user`
Edit
When thinking a bit more about it, I think in the original renderInlineCode function the parent.frame() of eval is renderInlineCode. Obviously, there the required objects are not included but in its parent.frame(). So you have to relay on R's scoping which doesn't work correctly here with shiny. I'm happy to get some more thorough explanations.
Having
library(DTedit)
library(shiny)
ui <- fluidPage(
fluidRow(
uiOutput("listing")
)
)
server <- function(input, output){
update.callback <- function(data, olddata, row){
print("Update callback")
return(data)
}
delete.callback <- function(data, row){
print("Delete callback")
return(data)
}
insert.callback <- function(data, row){
print("Insert callback")
return(data)
}
# Dummy data
new_df <- data.frame(
list(1:2, c("First", "Second"))
)
# Force addition of bad names to df
names(new_df) <- c("ID", "VALUE WITH : SPACE AND ARBITARY CHARACTERS")
dtedit_form <-DTedit::dtedit(
input,
output,
name = "listing",
thedata = new_df,
callback.delete = delete.callback,
callback.insert = insert.callback,
callback.update = update.callback
)
}
shinyApp(ui=ui, server=server)
Causes error
Warning: Error in <Anonymous>: No handler registered for type listing_edit_VALUE WITH : SPACE AND ARBITARY CHARACTERS
[No stack trace available]
Error in (function (name, val, shinysession) :
No handler registered for type listing_edit_VALUE WITH : SPACE AND ARBITARY CHARACTERS
When trying to edit anything inside the server.
Is the only solution to use make.names when generating columns from some source? The variable new_df's columns are generated from a database call and these values return strings which sometimes contain spaces, dots and other "carbage" characters. Down the line having column names in callback functions to be equal of those received from the database would make implementation more elegant. There is also the consideration for the user to see matching values for the database insertions versus the generated names.
Incredibly hacky, but here is a solution using my fork of DTedit
yes, it uses make.names to create 'sane' column names
At 'edit' time, the original crazy names are used, with the edit.label.cols argument
At 'display' table time, a customization to the datatable.call function defines the colnames with the original crazy names
I am afraid that still leaves you to convert the 'sane' names to 'crazy' names at the time of modifying the original database in your callback functions.
p.s. note that the delete.callback is defined a little differently to your original example.
David
library(DTedit)
library(shiny)
ui <- fluidPage(
fluidRow(
uiOutput("listing")
)
)
server <- function(input, output){
update.callback <- function(data, olddata, row){
print("Update callback")
return(data)
}
delete.callback <- function(data, row){
print("Delete callback")
return(data[-row, ])
}
insert.callback <- function(data, row){
print("Insert callback")
return(data)
}
# Dummy data
new_df <- data.frame(
list(1:2, c("First", "Second"))
)
# Force addition of bad names to df
newnames <- c("ID", "VALUE WITH : SPACE AND ARBITARY CHARACTERS")
names(new_df) <- make.names(newnames)
dtedit_form <-DTedit::dtedit(
input,
output,
name = "listing",
thedata = new_df,
edit.cols = names(new_df),
edit.label.cols = newnames,
callback.delete = delete.callback,
callback.insert = insert.callback,
callback.update = update.callback,
datatable.call = function(...) {DT::datatable(colnames = newnames, ...)}
)
}
shinyApp(ui=ui, server=server)
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'd like to output several tables as a one uiOutput. If I put them together in a list using a loop then all outputs are equal to the last one.
Example:
library(shiny)
ui <- fluidPage(
mainPanel(
uiOutput("tables")
)
)
server <- function(input, output) {
output$tables <- renderUI({
data=array(rnorm(150),c(10,5,3))
tfc = function(m){
# x = m[1,1]
renderTable({m})
}
result=list()
for(i in 1:3)
result[[i]] = tfc(data[,,i])
return(result)
})
}
shinyApp(ui = ui, server = server)
If I remove the commented line (x = m[1,1]) I get the desired result.
I can live with this workaround but is there a reason why shiny behaves like that or is there a different way to do it?
I usually use lapply for such usecases. This way, you don't run into issues with lazy evaluation.
library(shiny)
ui <- fluidPage(
mainPanel(
uiOutput("tables")
)
)
server <- function(input, output) {
output$tables <- renderUI({
data=array(rnorm(150),c(10,5,3))
tfc = function(m){renderTable({m})}
lapply(1:3, function(i){tfc(data[,,i])})
})
}
shinyApp(ui = ui, server = server)
If you want to use a reacive table, you can use something like
tfc = function(m, output, id){
output[[id]] <- renderTable({m()})
tableOutput(id)
}
instead.
To get around this, you can force evaluation of function arguments:
tfc = function(m) {
force(m)
renderTable(m)
}
or
create a local scope for each loop iteration:
for (i in 1:3) {
local({
i <- i
result[[i]] <<- tfc(data[,,i])
})
}
lapply works as well, but only for R versions 3.2 and above: https://cran.r-project.org/bin/windows/base/old/3.2.0/NEWS.R-3.2.0.html