I'm trying to populate a data.frame/matrix based on some user-defined rules. I managed to create a function in R, but am stuck trying to replicate this as a Shiny app [it's my first time using Shiny, and I'm an idiot to start with this one]
This is the crux of the code in regular r-script -
user-inputs are: size (1~3), changes (1~2) and iterations (10~1000)
school_choice_function<- function(changes, size, iterations )
{
######## 1509
##### List of schools
p<-1
j<-1
k<-1
l<-1
s_list<- rep(0,80)
for (i in 1:80) {
if (i <= 26) {
schl<- paste(LETTERS[p],LETTERS[i],sep = "")
s_list[i]<- schl }
if (i>26 & i<=52) {p<- 2
schl<- paste(LETTERS[p],LETTERS[j],sep = "")
s_list[i]<- schl
j=j+1}
if (i>52 & i<=78) {p<- 3
schl<- paste(LETTERS[p],LETTERS[k],sep = "")
s_list[i]<- schl
k=k+1}
if (i>78 ) {p<- 4
schl<- paste(LETTERS[p],LETTERS[l],sep = "")
s_list[i]<- schl
l=l+1}
}
rm(p,i,j,k,l)
########## Applicant Data
a<- c(2011:2015)
c<- 1:size
d<- 1:changes
y<-0
v<-1
w<-10
mat <- matrix(ncol=5, nrow=(iterations*10))
for(pop in 1:iterations){
for (z in v:w)
{
b<- s_list[(1+y):(8+y)]
e<- rep(0,5)
e[1]<- b[1]
g<- sample(d,1)
h<- sample(2:5,g, replace = FALSE)
f1<- rep(0,length(h))
for(j in 1:g){
for(i in 1:length(h))
{
f<- sample(c, 1)
f1[i]<- paste(sample(b,f,replace = FALSE),collapse = ",")
e[h[i]]<- f1[i]
}
}
for(i in c(which(e %in% 0))){
e[i]<- e[i-1]
}
mat[z,]<- e
y<-y+8
}
v<- w+1
w<- w+10
y<-0
}
df<- data.frame(mat,stringsAsFactors = FALSE)
colnames(df)<- c("2011","2012","2013","2014","2015")
return(df)
}
Ignoring the use of worst-practices in coding (I've just learnt to think in terms of loops), I'm using this is a shiny app like this. "s_list/schools" is a character matrix with 80 elements, created before this code.
Just so you get an intuition of what on earth is this - basically it is applicant data over 5 years, who may or many not get assigned to alternatives over time, (based on the rules which comes through in the loops).
The code works in the current form - except the output table is full of NAs.... Any kind of help would be a step up from where I'm at!
ui<- fluidPage(
numericInput(inputId="Changes", label="Changes", value=1, min = 1, max = 3, step = 1),
numericInput(inputId="Size", label="Size", value=2, min = 1, max = 3, step = 1),
numericInput(inputId="Iterations", label="Iterations", value=10, min = 10, max = 1000, step = 10),
tableOutput("dframe")
)
server<- function(input,output) {
Changes<- reactive({input$Changes})
Size<- reactive({input$Size})
Iterations<- reactive({input$Iterations})
schools<- s_list
########## Applicant Data
a<- c(2011:2015)
cc<- reactive(1:(Size()))
d<- reactive(1:(Changes()))
y<-0
v<-1
w<-10
mat <- reactive(matrix(ncol=5, nrow=((input$Iterations)*10)))
pop<- 0
z<- 0
i<- 0
j<- 0
this<- reactive({
for(pop in 1:(Iterations())){
for (z in v:w)
{
b<- schools[(1+y):(8+y)]
e<- rep(0,5)
e[1]<- b[1]
g<- reactive(sample(d(),1))
h<- reactive(sample(2:5,g(), replace = FALSE))
f1<- reactive(rep(0,length(h())))
for(j in 1:g()){
for(i in 1:length(h()))
{
f<- reactive(sample(cc(), 1))
f1()[i]<- reactive(paste(sample(b,f(),replace = FALSE),collapse = ","))
e[h()[i]]<- f1()[i]
}
}
for(i in cc()(which(e %in% 0))){
e[i]<- e[i-1]
}
mat()[z,]<- e
y<-y+8
}
v<- w+1
w<- w+10
y<-0
}
})
df<- reactive(data.frame(mat(),stringsAsFactors = FALSE))
output$dframe <- renderTable({ df() })
}
shinyApp(ui= ui, server = server)
Instead of writing the whole code of the function school_choice_function inside your server why don't you define the function outside your server and just call it from inside your server. Something like this:
server<- function(input,output) {
Changes<- reactive({input$Changes})
Size<- reactive({input$Size})
Iterations<- reactive({input$Iterations})
df<- reactive({
df <- school_choice_function(Changes(), Size(), Iterations())
return(data.frame(df, stringsAsFactors = FALSE))
})
output$dframe <- renderTable({ df() })
}
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:
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)
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.
I've been stuck on this problem for hours. I tried to create a simpler version of my actual use case to see if I can debug the problem but still having trouble.
My shiny app is below. It takes an input from user, runs it through some calculations until it meets a threshold, then collects the value in a list. It does this for several iterations (10 here in the example), then plots a histogram of these values.
The specific error I'm getting says
Warning: Error in : evaluation nested too deeply: infinite recursion / options(expressions=)?
I think that my error is related to the way I'm using x.new, but I can't figure out how to code it differently.
# install.packages('shiny')
library(shiny)
ui <- fluidPage(
headerPanel('Title Here'),
sidebarPanel(
numericInput('x.qty', 'Pick a number', 3, 1, 10)),
mainPanel(plotOutput('valueplot'))
)
server <- function(input, output, session) {
results <- reactive({
x.list <- list()
for (i in 1:10){
x.new <- reactive({input$x.qty * sample(1:10, 1)})
repeat{
if (x.new() > 20){
x.list <- c(x.list, x.new())
results <- x.list
break
} # end if loop
x.new <- reactive({x.new() * sample(1:10, 1)})
} # end repeat
} # end for loop
results
})
output$valueplot <- renderPlot({hist(as.numeric(results()))})
}
shinyApp(ui = ui, server = server)
I would suggest to modify your server function in the following way:
server <- function(input, output, session) {
results <- reactive({
x.list <- list()
for (i in 1:10){
x.new <- input$x.qty * sample(1:10, 1)
repeat{
if (x.new > 20){
x.list <- c(x.list, x.new)
results <- x.list
break
} # end if loop
x.new <- x.new * sample(1:10, 1)
} # end repeat
} # end for loop
results
})
output$valueplot <- renderPlot({hist(as.numeric(results()))})
}
This way you avoid the infinite recursion provoked by
x.new <- reactive({x.new() * sample(1:10, 1)})
Does this work as you want? Each for loop iteration, x.new is calculated using x.qty then in the repeat loop x.new recursively multiplies itself by a random number until it is greater than 20.
library(shiny)
ui <- fluidPage(headerPanel('Title Here'),
sidebarPanel(numericInput('x.qty', 'Pick a number', 3, 1, 10)),
mainPanel(plotOutput('valueplot')))
server <- function(input, output, session) {
x.new <- function(x) {
x * sample(1:10, 1)
}
results <- reactive({
x.list <- list()
for (i in 1:10) {
x.new_draw <- x.new(input$x.qty)
repeat {
if (x.new_draw > 20) {
x.list <- c(x.list, x.new_draw)
break
} # end if loop
x.new_draw <- x.new(x.new_draw)
} # end repeat
} # end for loop
x.list
})
output$valueplot <- renderPlot({
hist(as.numeric(results()))
})
}
shinyApp(ui = ui, server = server)
the code below produces a table of inputs with 2 rows and 2 columns (to simplify the situation - see picture below). Based on these inputs, I'd like to produce four histograms, and they should be aligned with the input boxes. It's not the case at the moment. Ideally, there should be a blank space below the rownames of the table, then the first(second) column of histograms should be right below the first(second) column of the table.
One option I've thought about is to add the plots directly in the table in the form of "matrix_input[i,j] <- hist(rnorm(n),breaks=10)", where "n <- input[[paste0("C", j,"_A",i)]]". This way, they would definitely be aligned. But I didn't manage it because I didn't succeed in making the table accept plots as elements. A second option is to code the current framework with a table and the plots underneath, but making sure that they are properly aligned.
I've tried many things without success. Can anyone help please ? Any inputs would be greatly appreciated. Many thanks.
library(shiny)
library(grid)
library(gridBase)
u <- shinyUI(navbarPage(
"My Application",
tabPanel("Component 1",
fluidPage(fluidRow(column(6,tableOutput('decision_Matrix'),plotOutput('decision_Matrix_plots')),column(6))
)),
tabPanel("Component 2")
))
s <- shinyServer(
function(input,output) {
output$decision_Matrix <- renderTable({
matrix_input <- matrix(data = NA,nrow = 2,ncol = 2)
for (j in 1:2) {
for (i in 1:2) {
matrix_input[i,j] <- paste0("<input id='C",j,"_A",i,"' type='number' class='form-control shiny-bound-input' value='",input[[paste0("C",j,"_A",i)]],"'>")
}
}
rownames(matrix_input) <- c("alternative1","alternative2")
colnames(matrix_input) <- c("crit1","crit2")
matrix_input
},include.rownames = TRUE,sanitize.text.function = function(x) x)
output$decision_Matrix_plots <- renderPlot({
layout(matrix(c(1,2,3,4),nrow = 2,ncol = 2))
for (j in 1:2) {
for (i in 1:2) {
set.seed(123)
n <- input[[paste0("C",j,"_A",i)]]
if (is.null(n) || is.na(n) || n < 1) n <- 1
hist(rnorm(n),breaks = 10,main=sprintf("histogram of rnorm( %d )",n))
}
}
recordPlot()
})
})
shinyApp(ui = u,server = s)