I am trying to build a shiny app that reads a data frame and at every press of an actionButton returns the value of a specific column, one row at a time until all rows of the data frame have been read. The structure of the dataset is 3 columns, one for IDs, one for a characteristic and an indicator variable for the already read IDs (0/1). Every time the user presses the actionButton, the app reads one row of the dataset, starting from row 1, returns the value of a second variable in that row and changes the value of the third variable to 1. The tricky part for me is that I want to save the "used" ids, in order to continue the reading from the last used id after restarting the app. I have tried loading and saving the data at every step and using reactiveValues, but this does not seem to work well for me - probably I don't know how to use them correctly. I am very new in shiny, so any feedback is more than welcome.
An example of the code I have been using:
project.path <- getwd()
dat <- data.frame(id = c(1:20), gender = sample(c("male", "female"), 20, replace = TRUE), used = 0)
write.csv2(dat, file.path(project.path, "data.csv"), row.names = F)
saveData <- function(x) {
write.csv2(x, file = file.path(project.path, "data.csv"),
row.names = FALSE)
}
loadData <- function() {
dat <- read.csv2(file.path(project.path, "data.csv"))
return(dat)
}
# UI
ui <- fluidPage(
# Application title
titlePanel("Tittle"),
sidebarLayout(
sidebarPanel(
actionButton("counter", "Start")
),
mainPanel(
textOutput("result")
)
)
)
# SERVER
server <- function(input, output, session) {
last <- reactive({
d <- loadData()
last <- min(d$id[d$used == 0]) -1
})
cur <- reactive({
if(last() == 0){
cur <- input$counter
}else{
cur <- last() + input$counter
}
})
# Calculate Result
ResultText <- reactive({
d <-loadData()
if (input$counter == 0 & last() == 0){
paste0("No IDs have been used. Press button to start.")
}else{
if(input$counter > 0 & last() == 0){
d$used[cur()] <- 1
paste0("ID ", cur(), " is a ", as.character(d$gender[cur()]))
}else{
if(input$counter == 0 & last() > 0){
paste0("The last ID was ", d$id[last()], " (", as.character(d$gender[last()]), ") Press the button to continue reading the list.")
}else{
d$used[cur()] <- 1
paste0("ID ", cur(), " is a ", as.character(d$gender[cur()]))
}
}
}
})
output$result <- renderText({
ResultText()
})
observe({
d <- loadData()
if (input$counter > 0 & last() == 0){
cur <- input$counter
d$used[cur] <- 1
saveData(d)
}else{
if(input$counter > 0 & last() > 0){
d$used[cur()] <- 1
saveData(d)
}
}
})
}
# Run
shinyApp(ui = ui, server = server)
Related
I have a simple shiny app that holds a dataset as a reactive value.
Once a button is pressed, a function should be applied to each row and the result is added as another variable to that dataset.
The dataset is also shown as a DT.
The result variable should be rendered as soon as the computation for that row is finished.
At the moment, the loop/apply that applies the function to each row finishes and only afterwards the results are displayed.
As the function can run for a long time, I want the DT to be updated as soon as a run is finished, not when all runs finish.
I understand that this means I need to use promises/future so that the main shiny code block spawns new processes which do not block in this case the main thread from updating the values. Correct?
However, I am not able to get it to work.
Here is a small MWE using a simple for loop
library(shiny)
library(DT)
ui <- fluidPage(
actionButton("run", "RUN"),
hr(),
DT::dataTableOutput("table")
)
calc_fun <- function(val) {
Sys.sleep(0.5)
val * 10
}
server <- function(input, output, session) {
set.seed(123)
data_res <- reactiveVal(data.frame(id = 1:10, val = rnorm(10), val10 = NA))
observe({
for (i in seq(nrow(data_res()))) {
print(paste("Looking at row", i))
d <- data_res()
d[i, "val10"] <- calc_fun(val = d[i, "val"])
data_res(d)
}
}) %>% bindEvent(input$run)
# This should be rendered whenever a round in the for-loop has finished
# at the moment it is only run once the loop is finished
output$table <- DT::renderDataTable(data_res())
}
shinyApp(ui, server)
Thanks to #ismirsehregal, I came up with the following solution which uses futures to start the calculation in the background, which in turn write the current status to a file.
Shiny then reactively reads the file and updates the values.
The full MWE looks like this:
library(shiny)
library(DT)
library(future)
library(promises)
library(qs) # for fast file read/write, replace with csv if needed
plan(multisession)
ui <- fluidPage(
actionButton("run", "RUN"),
hr(),
textOutput("prog"),
uiOutput("status"),
hr(),
fluidRow(
column(6,
h2("Current Status"),
DT::dataTableOutput("table")
),
column(6,
h2("Data in File"),
tableOutput("file_data")
)
)
)
calc_fun <- function(val) {
Sys.sleep(runif(1, 0, 2))
val * 10
}
# main function that goes through the rows and starts the calculation
# note that the output is saved to a .qs file to be read in by another reactive
do_something_per_row <- function(df, outfile) {
out <- tibble(id = numeric(0), res = numeric(0))
for (i in seq(nrow(df))) {
v <- df$val[i]
out <- out %>% add_row(id = i, res = calc_fun(v))
qsave(out, outfile)
}
return(out)
}
# create a data frame of tasks
set.seed(123)
N <- 13
tasks_init <- tibble(id = seq(N), val = round(rnorm(N), 2), status = "Open", res = NA)
server <- function(input, output, session) {
# the temporary file to communicate over
outfile <- "temp_progress_watch.qs"
unlink(outfile)
data <- reactiveVal(tasks_init) # holds the current status of the tasks
data_final <- reactiveVal() # holds the results once all tasks are finished
output$prog <- renderText(sprintf("Progress: 0 of %i (0.00%%)", nrow(data())))
output$status <- renderUI(div(style = "color: black;", h3("Not yet started")))
# on the button, start the do_something_per_row function as a future
observeEvent(input$run, {
# if a file exists => the code runs already
if (file.exists(outfile)) return()
print("Starting to Run the code")
output$status <- renderUI(div(style = "color: orange;", h3("Working ...")))
d <- data()
future({do_something_per_row(d, outfile)}, seed = TRUE) %...>% data_final()
print("Done starting the code, runs now in the background! freeing the session for interaction")
# return(NULL) # hide future
})
observe({
req(data_final())
output$status <- renderUI(div(style = "color: green;", h3("Done")))
print("All Done - Results came back from the future!")
})
output$file_data <- renderTable(req(df_done()))
output$table <- DT::renderDataTable({
# no need to fire on every refresh, this is handled automatically later
DT::datatable(isolate(data())) %>%
formatStyle("status", color = styleEqual(c("Open", "Done"), c("white", "black")),
backgroundColor = styleEqual(c("Open", "Done"), c("red", "green")))
})
dt_proxy <- DT::dataTableProxy("table")
# look for changes in the file and load it
df_done <- reactiveFileReader(300, session, outfile, function(f) {
r <- try(qread(f), silent = TRUE)
if (inherits(r, "try-error")) return(NULL)
r
})
observe({
req(df_done())
open_ids <- data() %>% filter(status == "Open") %>% pull(id)
if (!any(df_done()$id %in% open_ids)) return()
print(paste("- new entry found:", paste(intersect(df_done()$id, open_ids), collapse = ", ")))
rr <- data() %>% select(-res) %>% left_join(df_done(), by = "id") %>%
mutate(status = ifelse(is.na(res), "Open", "Done"))
data(rr)
DT::replaceData(dt_proxy, rr)
# replace the progress text
txt <- sprintf("Progress: % 4i of % 4i (%05.2f%%)",
nrow(df_done()), nrow(data()), 100 * (nrow(df_done()) / nrow(data())))
output$prog <- renderText(txt)
})
}
shinyApp(ui, server)
or as a picture:
Suppose I have the following blocks of codes in Shiny:
library(shiny)
rv <- reactiveValues()
observe({
# Event A
# Code Block A
# The code below signals the end of Code Block A
rv$event_a <- F
rv$event_a <- T
})
observe({
# Event B
# Code Block B
# The code below signals the end of Code Block B
rv$event_b <- F
rv$event_b <- T
})
observe({
rv$event_a
rv$event_b
if(rv$event_a & rv$event_b) {
# Do something only after both Code Blocks A and B finish running.
# Code Block C
}
})
As you can see, I'm toggling the reactive values in Blocks A and B from FALSE to TRUE to trigger Block C to run.
I want to write the code so that the cycle can repeat itself:
Some trigger -> Block A & B -> C ->
Some trigger -> Block A & B -> C ...
My questions are the following:
How can I make Code Block C run only once, when both Code Block A and B finished running?
How else can I achieve triggering Code Block C without the weird toggling of reactive values (between FALSE and TRUE) that I am currently relying on?
I have accomplished this before by eventObserving or eventReacting to the reactive objects or reactiveValues generated by 'code-block-a' or 'code-block-b'. I have attached 3 small shiny app examples to give insight into this approach using different methods (hopefully these will help answer the original question - or at least give some ideas).
This app will create a table in 'code-block-a' with as many rows as the sliderInput has selected. Once this 'event_a()' reactive is updated 'code-block-b' subsets one row. Once 'code-block-b' updates its object 'event_b()' a modal is displayed showing the selected row in a table.
library(shiny)
library(tidyverse)
ui <- fluidPage(
sliderInput("slide", "slide", value = 5, min = 1, max = 10),
actionButton("go", "go"),
)
server <- function(input, output, session) {
rv <- reactiveValues(tr1 = 0, el = 0)
final <- reactiveValues()
#CODE BLOCK A#
#takes slider input and makes a table with that many rows
event_a <- eventReactive(input$go,{
nums <- seq(1, input$slide, by = 1)
l <- 1:length(nums)
tibble(Letter = letters[l],
Value = nums)
})
#trigger next series of events in response to event_a()
#observeEvent(event_a(),{
# rv$el <- rv$el + 1
# })
##CODE BLOCK B##
# this will subset a row of data based on the value of the reactive
event_b <- eventReactive(event_a(), {
row <- sample(1:nrow(event_a()), 1)
event_a()[row,]
})
#look for changes in event_b() to trigger event C
#the loading of event_b will trigger the modal via rv$tr1
# observeEvent(event_b(), {
# rv$tr1 <- rv$tr1 + 1
# })
#side effect make a table from event_b() to be shown in modal
output$modal_plot <- renderTable({
event_b()
})
##CODE BLOCK C##
#launch modal showing table
observeEvent(event_b(), {
showModal(modalDialog(title = "Table",
"This is a table",
tableOutput("modal_plot"),
inline = T))
})
}
shinyApp(ui, server)
Or if all your 'code-block' are observers you can use reactive values that are updated inside of an observer. I have found this flexible if multiple things need to happen to trigger something downstream:
library(shiny)
library(tidyverse)
ui <- fluidPage(
sliderInput("slide", "slide", value = 5, min = 1, max = 10),
actionButton("go", "go"),
)
server <- function(input, output, session) {
rv <- reactiveValues(tr1 = 0, el = 0)
final <- reactiveValues()
#CODE BLOCK A#
#takes slider input and makes a table with that many rows
event_a <- eventReactive(input$go,{
nums <- seq(1, input$slide, by = 1)
l <- 1:length(nums)
tibble(Letter = letters[l],
Value = nums)
})
#trigger next series of events in response to event_a()
observeEvent(event_a(),{
rv$el <- rv$el + 1
})
##CODE BLOCK B##
# this will subset a row of data based on the value of the reactive
event_b <- eventReactive(rv$el, ignoreInit = T, {
row <- sample(1:nrow(event_a()), 1)
event_a()[row,]
})
#look for changes in event_b() to trigger event C
#the loading of event_b will trigger the modal via rv$tr1
observeEvent(event_b(), {
rv$tr1 <- rv$tr1 + 1
})
#side effect make a table from event_b() to be shown in modal
output$modal_plot <- renderTable({
event_b()
})
##CODE BLOCK C##
#launch modal showing table
observeEvent(rv$tr1, ignoreInit = T, {
showModal(modalDialog(title = "Table",
"This is a table",
tableOutput("modal_plot"),
inline = T))
})
}
shinyApp(ui, server)
Furthermore, if you are wanting something that iterates like a loop here is an example that describes the above process, but plots each row of data in a modal one row at a time and asking for user input each time:
library(shiny)
library(tidyverse)
ui <- fluidPage(
sliderInput("slide", "slide", value = 5, min = 1, max = 10),
actionButton("go", "go"),
tableOutput("df"),
tableOutput("user_choices_table")
)
server <- function(input, output, session) {
rv <- reactiveValues(tr1 = 0, el = 0)
final <- reactiveValues()
#STEP 1
#some function/series of events that gives us a some data
data1 <- eventReactive(input$go,{
c <- seq(1, input$slide, by = 1)
l <- 1:length(c)
out_table <- tibble(Letter = letters[l],
Value = c)
return(out_table)
})
#side effect - return data1 to UI
output$df <- renderTable({
data1()
})
#number of max iterations we will go though (dependent number of rows in data1)
num_iterations <- reactive({
nrow(data1())
})
#trigger next series of events in response to data1()
#this will get us from 0 to 1 and another observer will be used to add
#all the way up to the max_iterations
observeEvent(data1(),{
rv$el <- rv$el + 1
})
#this ^ observer is like entering the loop on the first iteration
##STEP 2##
##start/continue the "disjointed-loop".
#Subset data1 into smaller piece we want based on rv$el
#this will be our 'i' equivalent in for(i in ...)
#data subset
data2 <- eventReactive(rv$el, ignoreInit = TRUE, {
data2 <- data1()[rv$el,]
return(data2)
})
#side effect make a plot based on data2 to be shown in modal
output$modal_plot <- renderPlot({
d <- data2()
ggplot()+
geom_col(data = d, aes(x = Letter, y = Value, fill = Letter))+
theme_linedraw()
})
#once we get our data2 subset ask the user via modal if this is what they want
#the loading of data2 will trigger the modal via rv$tr1
observeEvent(data2(), {
rv$tr1 <- rv$tr1 + 1
})
##STEP 3##
#launch modal showing plot and ask for user input
observeEvent(rv$tr1, ignoreInit = TRUE, {
showModal(modalDialog(title = "Make a Choice!",
"Is this a good selection?",
plotOutput("modal_plot"),
checkboxGroupInput("check", "Choose:",
choices = c("Yes" = "yes",
"No" = "no"),
inline = T),
footer = actionButton("modal_submit", "Submit")))
})
#when user closes modal the response is saveed to final[[character representing number of iteration]]
observeEvent(input$modal_submit, {
final[[as.character(rv$el)]] <- input$check
if(rv$el < num_iterations()){
rv$el <- rv$el + 1 #this retriggers step2 to go again
} else {
rv$done <- rv$done + 1
} #breaks the disjointed loop and trigger start of next reactions
})
#and the modal is closed
observeEvent(input$modal_submit, {
removeModal()
})
final_choice <- eventReactive(rv$done, ignoreInit = TRUE,{
enframe(isolate(reactiveValuesToList(final))) %>%
mutate(name = as.numeric(name),
value = unlist(value)) %>%
arrange(name)
})
output$user_choices_table <- renderTable({
final_choice()
})
}
shinyApp(ui, server)
I would like to modify the input and display the output of some R code in a more interactive way. I think that this would be an ideal task for a Shiny app, but I am not very familiar with writing them. I have some R code that takes a string of text and iteratively changes it by adding letters or words at random locations:
library(tidyverse)
evolve_sentence <- function(sentence, arg2) {
chars <- str_split(sentence, "") %>% pluck(1)
if (runif(1) > 0.5) {
chars[sample(1:length(chars), 1)] <- sample(chars, 1)
}
sentence <- str_c(chars, collapse = "")
words <- str_split(sentence, " ") %>% pluck(1)
if (runif(1) > 0.9) {
words[sample(1:length(words), 1)] <- sample(words, 1)
}
sentence <- str_c(words, collapse = " ")
sentence
}
tbl_evolve <- tibble(iteration = 1:500, text = "I met a traveller from an antique land")
for (i in 2:500) {
tbl_evolve$text[i] <- evolve_sentence(tbl_evolve$text[i - 1])
}
tbl_evolve %>%
distinct(text, .keep_all = TRUE)
The output look like this:
1 I met a traveller from an antique land
2 I met a tIaveller from an antique land
4 I met a tIaveller from an antique lanr
5 I met a tIaveller from an fntique lanr
6 I met a tIaveller fromnan met lanr
I would love to present this in as a Shiny app where the input text and the probability of different types of changes can be specified by the user. For the latter that would be making the values in (runif(1) > 0.5) and (runif(1) > 0.9) specifiable by the user. I know this is possible in Shiny using insert UI and actionButton.
I am less sure if there is a way to dynamically show the output, so that the user can visually see each iteration of the code (with a defined time delay between each iteration?) rather than seeing all iterations output at once as once does with the existing code. I am open to different ways of dynamically visualising the output but I think ideally the user would see each iteration replaced by the next with a time delay.
I would also like a tab with the current output, each iteration being a row, so the user can go back and review each iteration.
Any advice on whether this is possible in Shiny or if I need a different tool would be much appreciated.
library(shiny)
library(tidyverse)
# Define UI for application that draws a histogram
ui <- fluidPage(
# Application title
titlePanel("Simple Testcase"),
# Sidebar with a slider input for number of bins
sidebarLayout(
sidebarPanel(
textInput("textinput", "Type text here"),
numericInput("p1", "Probability1", value = 0.5),
numericInput("p2", "Probability2", value = 0.9),
sliderInput("iteration", "Iterations", min = 20, max = 1000, step = 10, value = 100),
actionButton("calc", "Run Calculation!")
),
# Show a plot of the generated distribution
mainPanel(
tableOutput("ui")
)
)
)
# Define server logic required to draw a histogram
server <- function(session ,input, output) {
vals <- reactiveValues(counter = 0)
result <- eventReactive(input$calc, {
evolve_sentence <- function(sentence, arg2) {
chars <- str_split(sentence, "") %>% pluck(1)
if (runif(1) > input$p1) { # Value from numericinput p2
chars[sample(1:length(chars), 1)] <- sample(chars, 1)
}
sentence <- str_c(chars, collapse = "")
words <- str_split(sentence, " ") %>% pluck(1)
if (runif(1) > input$p2) { # Value from numericinput p2
words[sample(1:length(words), 1)] <- sample(words, 1)
}
sentence <- str_c(words, collapse = " ")
sentence
}
tbl_evolve <- tibble(iteration = 1:500, text = input$textinput)
for (i in 2:500) {
tbl_evolve$text[i] <- evolve_sentence(tbl_evolve$text[i - 1])
}
output <-tbl_evolve %>%
distinct(text, .keep_all = TRUE)
print(output)
output
})
output$ui <- renderTable({
df <- result()
invalidateLater(millis = 300, session)
vals$counter <- isolate(vals$counter) + 1
while(nrow(df) < vals$counter) {
vals$counter <- isolate(vals$counter) + 1
} #Prevent to add infinite empty columns.
for(i in 1:nrow(df)) {
newdf <- df[1:vals$counter,]
}
newdf
})
}
# Run the application
shinyApp(ui = ui, server = server)
How about this? To render the table we can set a reactiveValue, which gets updated after the invalidateLater function triggers. Take the value of the counter to subset your final dataset.
In the example below, I have 3 DT::datatables. I want the user to be able to select no more than one row from all these tables. I thence use dataTableProxy and selectRow, as per the section "Manipulate An Existing DataTables Instance" in the documentation. It works fine.
However, in my application, I have 24 (call that value N) tables. If I try to adapt the code below to my 24 tables page, I get an horrendous number of lines of code.
What is a smarter way of doing this?
In particular, how can I:
declare the observers dynamically? (answered by user5029763)
know which table (not row) has been clicked upon last? (ie. how to re write reactiveText()?)
EDIT : I copied in user5029763's answer (see below) in the code below.
DTWrapper <- function(data, pl = 5, preselec = c()){
datatable(data,
options = list(pageLength = pl, dom='t',ordering=F),
selection = list(mode = 'single', selected= preselec),
rownames = FALSE)
}
resetRows <- function(proxies, self){
for (i in 1:length(proxies)){
if (self != i){
proxies[[i]] %>% selectRows(NULL)
}
}
}
lapply(1:3, function(id) {
observe({
rownum <- input[[paste0("tab",id,"_rows_selected")]]
if (length(rownum) > 0) { resetRows(proxyList(), id) }
})
})
server = function(input, output) {
output$tab1 <- DT::renderDataTable(DTWrapper(head(mtcars[,1:3]), input$selectTop))
output$tab2 <- DT::renderDataTable(DTWrapper(head(mtcars[,1:3]), input$selectTop))
output$tab3 <- DT::renderDataTable(DTWrapper(head(mtcars[,1:3]), input$selectTop))
proxyList <- reactive({
proxies = list()
for (i in 1:3){
tableID <- paste("tab", i, sep="")
proxies[[i]] = dataTableProxy(tableID)
}
return(proxies)
})
reactiveText <- reactive({
rownum1 <- input$tab1_rows_selected
rownum2 <- input$tab2_rows_selected
rownum3 <- input$tab3_rows_selected
if (length(rownum1) > 0){return(c(rownum1, 1))}
if (length(rownum2) > 0){return(c(rownum2, 2))}
if (length(rownum3) > 0){return(c(rownum3, 3))}
})
output$txt1 <- renderText({
paste("You selected row ", reactiveText()[1]
, " from table ", reactiveText()[2], ".", sep="")
})
}
shinyApp(
ui = fluidPage(
fluidRow(column(4,DT::dataTableOutput("tab1"))
, column(4,DT::dataTableOutput("tab2"))
, column(4, DT::dataTableOutput("tab3")))
,fluidRow(column(4,textOutput("txt1")))
),
server = server
)
The textOutput is: "You selected the Xth row from the Yth table".
After edit:
You could try modules. Another way would be a lapply.
lapply(1:3, function(id) {
observe({
rownum <- input[[paste0("tab",id,"_rows_selected")]]
if (length(rownum) > 0) {
resetRows(proxyList(), id)
msg <- paste0("You selected row ", rownum, ", from table ", id, ".")
output$txt1 <- renderText(msg)
}
})
})
Edited to add reproducible code
I am building a Shiny app and encountered a problem with a while loop inside an observe function. The program does not display the reactive values after while loop. Here is the relevant code snippets from ui.r and server.r
ui.r
library(shiny)
shinyUI(fluidPage(
sidebarLayout(
sidebarPanel(
textAreaInput("inText", label = "Enter text", rows = 3),
submitButton("Predict")
),
mainPanel(
textOutput("outText"),
textOutput("outCount"),
textOutput("outPred")
)
)
))
Server.r
library(shiny)
library(stringr)
predict <- function(term)
{
if(term == 3)
table <- list()
else
if(term == 0)
table <- c("input","text","empty")
else
table <- c("words","were","found")
return(table)
}
shinyServer(
function(input, output) {
state <- reactiveValues()
observe({
state$inText <- input$inText
state$wcount <- sapply(gregexpr("[[:alpha:]]+", state$inText), function(x) sum(x > 0))
if( state$wcount > 2)
term.c <- 3
else
if( state$wcount == 2)
term.c <- 2
else
if( state$wcount == 1)
term.c <- 1
else
term.c <- 0
cont <- TRUE
while(cont == TRUE) {
if(term.c == 3) {
state$predList <- predict(term.c)
if(length(state$predList) > 0) break
else term.c <- 2
}
if(term.c == 2) {
state$predList <- predict(term.c)
if(length(state$predList) > 0) break
else term.c <- 1
}
if(term.c == 1) {
state$predList <- predict(term.c)
if(length(state$predList) > 0) break
else term.c <- 0
}
if(term.c == 0) {
state$predList <- c("Did", "not", "find", "term")
break
}
}
})
output$outText <- renderPrint({ input$inText })
output$outCount <- renderPrint({ sapply(gregexpr("[[:alpha:]]+", input$inText), function(x) sum(x > 0)) })
output$outPred <- renderPrint({ state$predList })
}
)
Enter two words and the values are displayed properly. Enter three words and an empty list is returned to state$predList which will give it a 0 length. the program should then execute the second if statement and populate state$predList with a non-zero length list. this happens but the display is never refreshed and the program goes into an infinite loop. It jumps to the first line of the observe function and continues in an infinite loop. I see this when I add browser().
Am I not using the reactive values correctly? Thanks for any assistance.