Two Reactives Error Invalid Argument to Unary Operator - r

I am trying to build an app that displays a .csv file in table format. The user can choose with radio buttons one of two ways to display the table. I have defined those two ways with the filedata() and data_ranked_words() reactives.
To reproduce this error, please first run this code chunk to get a small subset of my data:
test = rbind(
c(0.00000009, 0.00000009, 0.00046605, 0.00015541, 0.00215630),
c(0.00000016, 0.00137076, 0.00000016, 0.00000016, 0.00000016),
c(0.00012633, 0.00000014, 0.00000014, 0.00000014, 0.00075729),
c(0.00000013, 0.00000013, 0.00000013, 0.00000013, 0.00062728)
)
colnames(test) = c('church', 'appearance', 'restrain', 'parity', 'favor')
rownames(test) = NULL
test = as.data.frame(test)
write.csv(test, 'test.csv', row.names = FALSE)
You will see that you get an Error invalid argument to binary operator as soon as the program launches. Then choose test.csv off your filesystem in your working directory and you will see that the error persists while 'Word View' is selected, but the table correctly displays while 'Probability View' is selected.
This app is very simple. The problem occurs in line 66 temp = matrix(row.names(data)[apply(-data, 2, order)], nrow(data)). It doesn't like the -data within the apply. However, try as I might, I have not been able to reproduce this error just working in the R console, outside of shiny. In regular R, this line runs just fine.
What I am trying to do is display two different tables when the user selects the radio buttons. 'Probability View' is the raw table as is, and 'Word View' is the table with some operations on it (lines 61-71). I can't figure this one out!
Here is my app:
library(shiny)
library(markdown)
library(DT)
library(D3TableFilter)
options(shiny.maxRequestSize=50*1024^2)
# ui.R
#-------------------------------------------------------------------------------------
ui <- shinyUI(
navbarPage("Topic Model App v1.0",
tabPanel("From CSV",
sidebarLayout(
sidebarPanel(
# Define what's in the sidebar
fileInput("file",
"Choose CSV files from directory",
multiple = TRUE,
accept=c('text/csv',
'text/comma-separated-values,text/plain',
'.csv')),
radioButtons('toggle', 'Choose one:',
list('Word View', 'Probability View'))
),
# Define what's in the main panel
mainPanel(
title = 'Topic Model Viewer',
# How wide the main table will be
fluidRow(
column(width = 12, d3tfOutput('data'))
)
)
)
)
)
)
# server.R
#-------------------------------------------------------------------------------------
server <- shinyServer(function(input, output, session) {
# Set up the dataframe for display in the table
# Define 'filedata()' as the .csv file that is uploaded
filedata <- reactive({
infile <- input$file
if (is.null(infile)) {
# User has not uploaded a file yet
return(NULL)
}
# Read in .csv file and clean up
data = read.csv(infile$datapath)
data = t(data)
data = as.data.frame(data)
colnames(data) = paste0(rep('topic', ncol(data)), 1:ncol(data))
data = format(data, scientific = FALSE)
data
})
#PROBLEM
# The ranked and ordered csv file
data_ranked_words <- reactive({
# Sort each column by probability, and substitute the correct word into that column
# This will essentially rank each word for each topic
# This is done by indexing the row names by the order of each column
data = filedata()
temp = matrix(row.names(data)[apply(-data, 2, order)], nrow(data))
temp = as.data.frame(temp)
colnames(temp) = paste0(rep('topic', ncol(data)), 1:ncol(data))
temp
})
# Create table
output$data <- renderD3tf({
tableProps <- list(
rows_counter = TRUE,
rows_counter_text = "Rows: ",
alternate_rows = TRUE
);
# Radio buttons
# The reason why the extensions are in this if() is so that sorting can be
# activated on Probability View, but not Word View
if(input$toggle=='Word View'){
df = data_ranked_words()
extensions <- list(
list( name = "colsVisibility",
text = 'Hide columns: ',
enable_tick_all = TRUE
),
list( name = "filtersVisibility",
visible_at_start = FALSE)
)
} else if(input$toggle=='Probability View'){
df = filedata()
extensions <- list(
list(name = "sort"), #this enables/disables sorting
list( name = "colsVisibility",
text = 'Hide columns: ',
enable_tick_all = TRUE
),
list( name = "filtersVisibility",
visible_at_start = FALSE)
)
}
if(is.null(filedata())){
} else{
d3tf(df,
tableProps = tableProps,
extensions = extensions,
showRowNames = TRUE,
tableStyle = "table table-bordered")
}
})
# This line will end the R session when the Shiny app is closed
session$onSessionEnded(stopApp)
})
# Run app in browser
runApp(list(ui=ui,server=server), launch.browser = TRUE)

So a couple of problems are interacting here to make things difficult to diagnose:
It is running through and trying to execute before the data is defined. The "modern" way to avoid that is to use a req(input$file) - which is now inserted in the filedata reactive. Note that will break the entire chain from executing until input$file is defined in the shiny ui.
The data = format(data, scientific = FALSE) is converting your columns to vectors of type "AsIs", which the unitary minus command does not know how to operate on. It is commented out of filedata() now.
To get that functionality of suppressing the scientific notation back, the was moved to right after where df is created by filedata() before it is displayed in d3tf.
Note: I found it interesting that options with scipen did not work here. Not sure why that is the case, but this AsIs class does the trick.
Here is the adjusted code:
library(shiny)
library(markdown)
library(DT)
library(D3TableFilter)
options(shiny.maxRequestSize=50*1024^2)
# ui.R
#-------------------------------------------------------------------------------------
ui <- shinyUI(
navbarPage("Topic Model App v1.0",
tabPanel("From CSV",
sidebarLayout(
sidebarPanel(
# Define what's in the sidebar
fileInput("file",
"Choose CSV files from directory",
multiple = TRUE,
accept=c('text/csv',
'text/comma-separated-values,text/plain',
'.csv')),
radioButtons('toggle', 'Choose one:',
list('Word View', 'Probability View'))
),
# Define what's in the main panel
mainPanel(
title = 'Topic Model Viewer',
# How wide the main table will be
fluidRow(
column(width = 12, d3tfOutput('data'))
)
)
)
)
)
)
# server.R
#-------------------------------------------------------------------------------------
server <- shinyServer(function(input, output, session) {
# Set up the dataframe for display in the table
# Define 'filedata()' as the .csv file that is uploaded
filedata <- reactive({
req(input$file)
infile <- input$file
if (is.null(infile)) {
# User has not uploaded a file yet
return(NULL)
}
# Read in .csv file and clean up
data = read.csv(infile$datapath)
data = t(data)
data = as.data.frame(data)
colnames(data) = paste0(rep('topic', ncol(data)), 1:ncol(data))
# data = format(data, scientific = FALSE)
data
})
#PROBLEM
# The ranked and ordered csv file
data_ranked_words <- reactive({
# Sort each column by probability, and substitute the correct word into that column
# This will essentially rank each word for each topic
# This is done by indexing the row names by the order of each column
data = filedata()
temp = matrix(row.names(data)[apply(-data, 2, order)], nrow(data))
temp = as.data.frame(temp)
colnames(temp) = paste0(rep('topic', ncol(data)), 1:ncol(data))
temp
})
# Create table
output$data <- renderD3tf({
tableProps <- list(
rows_counter = TRUE,
rows_counter_text = "Rows: ",
alternate_rows = TRUE
);
# Radio buttons
# The reason why the extensions are in this if() is so that sorting can be
# activated on Probability View, but not Word View
if(input$toggle=='Word View'){
df = data_ranked_words()
extensions <- list(
list( name = "colsVisibility",
text = 'Hide columns: ',
enable_tick_all = TRUE
),
list( name = "filtersVisibility",
visible_at_start = FALSE)
)
} else if(input$toggle=='Probability View'){
df = filedata()
df = format(df, scientific = FALSE)
extensions <- list(
list(name = "sort"), #this enables/disables sorting
list( name = "colsVisibility",
text = 'Hide columns: ',
enable_tick_all = TRUE
),
list( name = "filtersVisibility",
visible_at_start = FALSE)
)
}
if(is.null(filedata())){
} else{
d3tf(df,
tableProps = tableProps,
extensions = extensions,
showRowNames = TRUE,
tableStyle = "table table-bordered")
}
})
# This line will end the R session when the Shiny app is closed
session$onSessionEnded(stopApp)
})
# Run app in browser
runApp(list(ui=ui,server=server), launch.browser = TRUE)
And here is a screen shot of it running:

Related

How to upload a table with a R shiny app in two different excluding processes using the same variable name

I have developed a quite complex Shiny app that helps the user filter an uploaded table of genetics variants. So that, the user can upload a table, apply different filters and see the variants remaining. Now I would like the user to be able to upload the table in two different and excluding ways:
First: the table is straightaway uploaded by the user with fileInput.
Second: the user presses a button that makes the table being applied a series of changes with a python program that works outside shiny, then a processed table is created for the session and uploaded for filtering with another button.
Both options result in an uploaded table that can be filtered with my program, so I would like to conserve same the variable name in both cases. Both processes work perfectly when the other is commented, however I would like to have both uploading options available for the user. Due to the complexity of the program I cannot show a totally reproducible example here, but I can show you the part of the code I want to work in.
library(shiny)
library(DT)
library(shinyWidgets)
library(shinyBS)
library(shinyFiles)
ui = fluidPage(
# Uploading variant table straight away with a file input (way 1):
fileInput("file1", "Upload your SNV File",
multiple = FALSE,
accept = c("text/csv", "text/comma-separated-values,text/plain", ".tsv")),
# User presses a button if changes previous to the upload want to be applied (way 2):
actionBttn(
inputId = "WGS",
label = "Analysis of WGS",
),
# User needs to fill a survey before the python program is launched:
bsModal("survey", "Select WGS data information","WGS",
prettyCheckbox(inputId="canonical_filters", label = "Canonical", value = TRUE),
shinyFilesButton("Btn_GetFile", "Process WGS variant file", title = "WGS variant file:", multiple = FALSE),
actionButton("EnterWGS", "Read file")),
# Table is rendered
DTOutput("contents")
)
server <- function(input, output, session) {
# Datatable is uploaded straight away (WAY 1)
df <- reactive({
req(input$file1)
df <- read.table(input$file1$datapath, fill = TRUE, quote = "", header = TRUE,
sep = '\t', na.strings=c("",".","NA"), colClasses = NA)
})
# The path of the file where the changes are going to be applied can be selected and the python program (process_file.py) is launched with the system function. A processed variant table is created for the session.
observeEvent(input$Btn_GetFile, {
volumes = getVolumes()
shinyFileChoose(input, "Btn_GetFile", roots=volumes, session = session, filetypes = c('', 'txt', "tsv", "csv"))
file_selected<-parseFilePaths(volumes, input$Btn_GetFile)
if (length(file_selected$datapath)!=0){
system('process_file.py', file_selected$datapath )
}
})
# The processed variant table is uploaded when the button is pressed (WAY 2)
df <- eventReactive(input$EnterWGS, {
df <- read.table('temp_file', fill = TRUE, quote = "", header = TRUE,
sep = '\t', na.strings=c("",".","NA"), check.names = FALSE, colClasses = NA)
}, ignoreNULL = T)
# Rest of the functions...
# Table renderization.
output$contents <- renderDT({
req(df())
datatable(
df(),
filter = "top",
class = "display nowrap compact",
escape = FALSE)},
server = FALSE)
}
shinyApp(ui, server)
I really hope this is understandable. Any help would be appreciated.
Thanks a lot,
Rachael
You can define a reactiveValues object to display which is set to table 1 or table 2. Try this
library(shiny)
library(DT)
library(shinyWidgets)
library(shinyBS)
library(shinyFiles)
ui = fluidPage(
# Uploading variant table straight away with a file input (way 1):
fileInput("file1", "Upload your SNV File",
multiple = FALSE,
accept = c("text/csv", "text/comma-separated-values,text/plain", ".tsv")),
# User presses a button if changes previous to the upload want to be applied (way 2):
actionBttn(
inputId = "WGS",
label = "Analysis of WGS",
),
# User needs to fill a survey before the python program is launched:
bsModal("survey", "Select WGS data information","WGS",
prettyCheckbox(inputId="canonical_filters", label = "Canonical", value = TRUE),
shinyFilesButton("Btn_GetFile", "Process WGS variant file", title = "WGS variant file:", multiple = FALSE),
actionButton("EnterWGS", "Read file")),
# Table is rendered
DTOutput("contents")
)
server <- function(input, output, session) {
rv <- reactiveValues(df=NULL)
# Datatable is uploaded straight away (WAY 1)
df1 <- reactive({
req(input$file1)
df <- read.table(input$file1$datapath, fill = TRUE, quote = "", header = TRUE,
sep = '\t', na.strings=c("",".","NA"), colClasses = NA)
})
# The path of the file where the changes are going to be applied can be selected and the python program (process_file.py) is launched with the system function. A processed variant table is created for the session.
observeEvent(input$Btn_GetFile, {
volumes = getVolumes()
shinyFileChoose(input, "Btn_GetFile", roots=volumes, session = session, filetypes = c('', 'txt', "tsv", "csv"))
file_selected<-parseFilePaths(volumes, input$Btn_GetFile)
if (length(file_selected$datapath)!=0){
system('process_file.py', file_selected$datapath )
}
})
# The processed variant table is uploaded when the button is pressed (WAY 2)
df2 <- eventReactive(input$EnterWGS, {
df <- read.table('temp_file', fill = TRUE, quote = "", header = TRUE,
sep = '\t', na.strings=c("",".","NA"), check.names = FALSE, colClasses = NA)
}, ignoreNULL = T)
### condition this observer to display df1()
observeEvent(df1(), {
rv$df <- df1()
})
### condition this observer to display df2()
observeEvent(input$WGS, {
rv$df <- df2()
})
# Rest of the functions...
# Table renderization.
output$contents <- renderDT({
datatable(
rv$df,
filter = "top",
class = "display nowrap compact",
escape = FALSE)},
server = FALSE)
}
shinyApp(ui, server)

How to validate csv file for upload via fileInput function in shiny App?

In running the below "abbreviated code", I'm trying to create a file upload validation that the csv to be uploaded must have "Scenario 1" and "Scenario 1" in cells A1 and B1 of the csv. Otherwise the file isn't uploaded and it is flagged "invalid". Any ideas of how to do this?
If you run the below, click the single action button and save the matrix inputs by clicking the button in the bottom of the modal dialog, look at the downloaded csv, and see how cells A1 and B1 show "Scenario 1" and "Scenario 1" from the downloaded matrix. This is good. If you delete these, and save the csv, you'll see that this modified csv can still be uploaded when running the App. I'd like those 2 csv fields to serve as a validation flag.
I really like the try() function as a catch-all test in this App.
Abbreviated code:
library(dplyr)
library(shiny)
library(shinyFeedback)
library(shinyMatrix)
sumMat <- function(x){return(rep(sum(x,na.rm = TRUE), 10))}
ui <- fluidPage(
useShinyFeedback(),
sidebarLayout(
sidebarPanel(
actionButton("matrix3show","Click for matrix input"),
),
mainPanel(plotOutput("plot"))
)
)
server <- function(input, output, session) {
uploadMat3Data <- reactive({
req(input$uploadMat3)
validate(need(identical(tools::file_ext(input$uploadMat3$datapath),"csv"),"Invalid"))
try(read.csv(input$uploadMat3$datapath, header = TRUE))
})
observeEvent(uploadMat3Data(), {
if(is.data.frame(uploadMat3Data())){
updateMatrixInput(session,"matrix3",as.matrix(uploadMat3Data()))
hideFeedback("file")
}
else {showFeedbackWarning("file", "Invalid")}
})
observeEvent(input$matrix3show,{
showModal(
modalDialog(
fileInput(inputId = "uploadMat3",label = NULL,accept = ".csv"),
matrixInput(
inputId = "matrix3",
value = if(is.null(input$matrix3)){matrix(c(1,5),ncol=2,dimnames=list(NULL,rep("Scenario 1",2)))}
else {input$matrix3},
rows = list(extend = TRUE, delete = TRUE),
cols = list(extend = TRUE, delta = 2, delete = TRUE, multiheader = TRUE),
class = "numeric"),
output$verbMat3 <- renderPrint(class(uploadMat3Data())),
footer =
tagList(
downloadButton("saveMat3","Save",style = "width:80px;"),
modalButton("Exit box")
) # close tag list
))
})
observeEvent(input$matrix3, {
tmpMat3 <- input$matrix3
colnames(tmpMat3) <- paste("Scenario",rep(1:ncol(tmpMat3),each=2,length.out=ncol(tmpMat3)))
rownames(tmpMat3) <- paste("Row", seq_len(nrow(input$matrix3)))
updateMatrixInput(session,inputId="matrix3",value=tmpMat3)
})
data <- function(){tibble(X = seq_len(10),Y = sumMat(input$matrix3))}
output$plot<-renderPlot({plot(data(),type="l")})
output$saveMat3 <- downloadHandler(
filename = function(){paste("Inputs","csv",sep=".")},
content = function(file){write.csv(input$matrix3, file,row.names=FALSE)}
)
}
shinyApp(ui, server)
To resolve I changed the observeEvent for uploadMat3Data() to the following:
observeEvent(uploadMat3Data(), {
if(is.data.frame(uploadMat3Data())
&& colnames(uploadMat3Data()[1]) == "Scenario.1"
&& colnames(uploadMat3Data()[2]) == "Scenario.1.1"
){
updateMatrixInput(session,"matrix3",as.matrix(uploadMat3Data()))
hideFeedback("uploadMat3")
}
else {showFeedbackWarning("uploadMat3", "Invalid")}
})
Note the additions of && colnames(... where the code peeks into the headers for the uploaded data frame and checks for the required headers. Also note that in the original code the id references in hideFeedback() and showFeedbackWarning() were incorrect; they are now corrected to "uploadMat3"

R Shiny: Creating unique datatables for different datasets

UPDATED: An example of the problem is shown below the code for the app
I'm building an dynamic ML app where the user can upload a dataset to get a prediction of the first column in the dataset (the response variable should be located in column 1 of the uploaded dataset). The user can select a value for the variables in the uploaded dataset and get a prediction of the response variable.
I'm currently trying to create a datatable that stores all the selected values, timestamp and the prediction.
The table is suppose to store the previous saved values, but only for that perticular dataset. By this I mean that if I save values from the iris dataset, the table uses the variables from the iris dataset as columns. This causes problems when uploading another dataset and saving those values, since the columns from the iris dataset would still be there and not the variables/columns from the new dataset.
My question is: How do I create a unique datatable for each dataset uploaded to the app?
If this sound confusion, try to run the app, calculate a prediction and save the data. Do this for two different datasets and look at the datatable under the "log" tab.
If you don't have two datasets, you can use these two datasets, they are build into R as default and already have the response variable positioned in column 1.
write_csv(attitude, "attitude.csv")
write_csv(ToothGrowth, "ToothGrowth.csv")
You will find the code regarding the datatable under the 'Create the log' section in the server function.
This is the code for the app:
library(shiny)
library(tidyverse)
library(shinythemes)
library(data.table)
library(RCurl)
library(randomForest)
library(mlbench)
library(janitor)
library(caret)
library(recipes)
library(rsconnect)
# UI -------------------------------------------------------------------------
ui <- fluidPage(
navbarPage(title = "Dynamic ML Application",
tabPanel("Calculator",
sidebarPanel(
h3("Values Selected"),
br(),
tableOutput('show_inputs'),
hr(),
actionButton("submitbutton", label = "calculate", class = "btn btn-primary", icon("calculator")),
actionButton("savebutton", label = "Save", icon("save")),
hr(),
tableOutput("tabledata")
), # End sidebarPanel
mainPanel(
h3("Variables"),
uiOutput("select")
) # End mainPanel
), # End tabPanel Calculator
tabPanel("Log",
br(),
DT::dataTableOutput("datatable18", width = 300),
), # End tabPanel "Log"
tabPanel("Upload file",
br(),
sidebarPanel(
fileInput(inputId = "file1", label="Upload file"),
checkboxInput(inputId ="header", label="header", value = TRUE),
checkboxInput(inputId ="stringAsFactors", label="stringAsFactors", value = TRUE),
radioButtons(inputId = "sep", label = "Seperator", choices = c(Comma=",",Semicolon=";",Tab="\t",Space=" "), selected = ","),
radioButtons(inputId = "disp", "Display", choices = c(Head = "head", All = "all"), selected = "head"),
), # End sidebarPanel
mainPanel(
tableOutput("contents")
)# End mainPanel
) # EndtabPanel "upload file"
) # End tabsetPanel
) # End UI bracket
# Server -------------------------------------------------------------------------
server <- function(input, output, session) {
# Upload file content table
get_file_or_default <- reactive({
if (is.null(input$file1)) {
paste("No file is uploaded yet")
} else {
df <- read.csv(input$file1$datapath,
header = input$header,
sep = input$sep,
quote = input$quote)
if(input$disp == "head") {
return(head(df))
}
else {
return(df)
}
}
})
output$contents <- renderTable(get_file_or_default())
# Create input widgets from dataset
output$select <- renderUI({
req(input$file1)
if (is.null(input$file1)) {
"No dataset is uploaded yet"
} else {
df <- read.csv(input$file1$datapath,
header = input$header,
sep = input$sep,
quote = input$quote)
tagList(map(
names(df[-1]),
~ ifelse(is.numeric(df[[.]]),
yes = tagList(sliderInput(
inputId = paste0(.),
label = .,
value = mean(df[[.]], na.rm = TRUE),
min = round(min(df[[.]], na.rm = TRUE),2),
max = round(max(df[[.]], na.rm = TRUE),2)
)),
no = tagList(selectInput(
inputId = paste0(.),
label = .,
choices = sort(unique(df[[.]])),
selected = sort(unique(df[[.]]))[1],
))
) # End ifelse
)) # End tagList
}
})
# creating dataframe of selected values to be displayed
AllInputs <- reactive({
req(input$file1)
if (is.null(input$file1)) {
} else {
DATA <- read.csv(input$file1$datapath,
header = input$header,
sep = input$sep,
quote = input$quote)
}
id_exclude <- c("savebutton","submitbutton","file1","header","stringAsFactors","input_file","sep","contents","head","disp")
id_include <- setdiff(names(input), id_exclude)
if (length(id_include) > 0) {
myvalues <- NULL
for(i in id_include) {
if(!is.null(input[[i]]) & length(input[[i]] == 1)){
myvalues <- as.data.frame(rbind(myvalues, cbind(i, input[[i]])))
}
}
names(myvalues) <- c("Variable", "Selected Value")
myvalues %>%
slice(match(names(DATA[,-1]), Variable))
}
})
# render table of selected values to be displayed
output$show_inputs <- renderTable({
if (is.null(input$file1)) {
paste("No dataset is uploaded yet.")
} else {
AllInputs()
}
})
# Creating a dataframe for calculating a prediction
datasetInput <- reactive({
req(input$file1)
DATA <- read.csv(input$file1$datapath,
header = input$header,
sep = input$sep,
quote = input$quote)
DATA <- as.data.frame(unclass(DATA), stringsAsFactors = TRUE)
response <- names(DATA[1])
model <- randomForest(eval(parse(text = paste(names(DATA)[1], "~ ."))),
data = DATA, ntree = 500, mtry = 3, importance = TRUE)
df1 <- data.frame(AllInputs(), stringsAsFactors = FALSE)
input <- transpose(rbind(df1, names(DATA[1])))
write.table(input,"input.csv", sep=",", quote = FALSE, row.names = FALSE, col.names = FALSE)
test <- read.csv(paste("input.csv", sep=""), header = TRUE)
# Defining factor levels for factor variables
cnames <- colnames(DATA[sapply(DATA,class)=="factor"])
if (length(cnames)>0){
lapply(cnames, function(par) {
test[par] <<- factor(test[par], levels = unique(DATA[,par]))
})
}
# Making the actual prediction and store it in a data.frame
Prediction <- predict(model,test)
Output <- data.frame("Prediction"=Prediction)
print(format(Output, nsmall=2, big.mark=","))
})
# display the prediction when the submit button is pressed
output$tabledata <- renderTable({
if (input$submitbutton>0) {
isolate(datasetInput())
}
})
# -------------------------------------------------------------------------
# Create the Log
saveData <- function(data) {
data <- as.data.frame(t(data))
if (exists("datatable18")) {
datatable18 <<- rbind(datatable18, data)
} else {
datatable18 <<- data
}
}
loadData <- function() {
if (exists("datatable18")) {
datatable18
}
}
# Whenever a field is filled, aggregate all form data
formData <- reactive({
DATA <- read.csv(input$file1$datapath,
header = input$header,
sep = input$sep,
quote = input$quote)
fields <- c(colnames(DATA[,-1]), "Timestamp", "Prediction")
data <- sapply(fields, function(x) input[[x]])
data$Timestamp <- as.character(Sys.time())
data$Prediction <- as.character(datasetInput())
data
})
# When the Submit button is clicked, save the form data
observeEvent(input$savebutton, {
saveData(formData())
})
# Show the previous responses
# (update with current response when Submit is clicked)
output$datatable18 <- DT::renderDataTable({
input$savebutton
loadData()
})
} # End server bracket
# ShinyApp -------------------------------------------------------------------------
shinyApp(ui, server)
UPDATED HERE
To get an idea about how the problem occurs take a look at this:
I upload the iris dataset to the application.
I then make some predictions and save them.
The predictions, as well as the selected inputs and a timestamp of when the save-button was pressed can now be seen under the "Log" tab.
I upload a new dataset (attitude), which of course have different variables included (attitude dataset has 7 variables total, iris dataset has 5).
I calculate a prediction, hit the save button and the app crashes. This happens because the number of columns in the dataset now has changed, so I get this errormessage:
Error in rbind: numbers of columns of arguments do not match
This can be fixed by renaming the datatable object in the server, since this creates a new datatable without any specified columns yet. But as soon as the Save button is pressed for the first time, the datatable locks-in the columns so they can't be changed again.
I can still access the old datatables if I switch the name of the datatable in the server function back the original name. So I'm thinking that if the name of the datatable object can be dynamic dependend on the dataset uploaded to the app, then the correct datatable can be shown.
So I think a better question could be: How do I create a dynamic/reactive datatable output object
Here's a simple shiny app that demonstrates a technique of storing a list of data (and properties). I'll store it in alldata (a reactive-value), and each dataset has the following properties:
name, just the name, redundant with the name of the list itself
depvar, stored dependent-variable, allowing the user to select which of the variables is used; in the displayed table, this is shown as the first column, though the original data is in its original column-order
data, the raw data (data.frame)
created and modified, timestamps; you said timestamps, but I didn't know if you meant on a particular dataset/prediction/model or something else, so I did this instead
Note that the same data can be uploaded multiple times: while I don't know if this is needed, it is allowed since all referencing is done on the integer index within the alldata list, not the names therein.
library(shiny)
NA_POSIXt_ <- Sys.time()[NA] # for class-correct NA
defdata <- list(
mtcars = list(
name = "mtcars",
depvar = "mpg",
data = head(mtcars, 10),
created = Sys.time(),
modified = NA_POSIXt_
),
CO2 = list(
name = "CO2",
depvar = "uptake",
data = head(CO2, 20),
created = Sys.time(),
modified = NA_POSIXt_
)
)
makelabels <- function(x) {
out <- mapply(function(ind, y) {
cre <- format(y$created, "%H:%M:%S")
mod <- format(y$modified, "%H:%M:%S")
if (is.na(mod)) mod <- "never"
sprintf("[%d] %s (cre: %s ; mod: %s)", ind, y$name, cre, mod)
}, seq_along(x), x)
setNames(seq_along(out), out)
}
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
selectInput("seldata", label = "Selected dataset", choices = makelabels(defdata)),
selectInput("depvar", label = "Dependent variable", choices = names(defdata[[1]]$data)),
hr(),
fileInput("file1", label = "Upload data"),
textInput("filename1", label = "Data name", placeholder = "Derive from filename"),
checkboxInput("header", label = "Header", value = TRUE),
checkboxInput("stringsAsFactors", label = "stringsAsFactors", value = TRUE),
radioButtons("sep", label = "Separator",
choices = c(Comma = ",", Semicolon = ";", Tab = "\t", Space = " "),
select = ","),
radioButtons("quote", label = "Quote",
choices = c(None = "", "Double quote" = '"', "Single quote" = "'"),
selected = '"')
),
mainPanel(
tableOutput("contents")
)
)
)
server <- function(input, output, session) {
alldata <- reactiveVal(defdata)
observeEvent(input$seldata, {
dat <- alldata()[[ as.integer(input$seldata) ]]
choices <- names(dat$data)
selected <-
if (!is.null(dat$depvar) && dat$depvar %in% names(dat$data)) {
dat$depvar
} else names(dat$data)[1]
updateSelectInput(session, "depvar", choices = choices, selected = selected)
# ...
# other things you might want to update when the user changes dataset
})
observeEvent(input$depvar, {
ind <- as.integer(input$seldata)
alldat <- alldata()
if (alldat[[ ind ]]$depvar != input$depvar) {
# only update alldata() when depvar changes
alldat[[ ind ]]$depvar <- input$depvar
alldat[[ ind ]]$modified <- Sys.time()
lbls <- makelabels(alldat)
sel <- as.integer(input$seldata)
updateSelectInput(session, "seldata", choices = lbls, selected = lbls[sel])
alldata(alldat)
}
})
observeEvent(input$file1, {
req(input$file1)
df <- tryCatch({
read.csv(input$file1$datapath,
header = input$header, sep = input$sep,
stringsAsFactors = input$stringsAsFactors,
quote = input$quote)
}, error = function(e) e)
if (!inherits(df, "error")) {
if (!NROW(df) > 0 || !NCOL(df) > 0) {
df <- structure(list(message = "No data found"), class = c("simpleError", "error", "condition"))
}
}
if (inherits(df, "error")) {
showModal(modalDialog(title = "Error loading data", "No data was found in the file"))
} else {
nm <-
if (nzchar(input$filename1)) {
input$filename1
} else tools:::file_path_sans_ext(basename(input$file1$name))
depvar <- names(df)[1]
newdat <- setNames(list(list(name = nm, depvar = depvar, data = df,
created = Sys.time(), modified = NA_POSIXt_)),
nm)
alldat <- alldata()
alldata( c(alldat, newdat) )
# update the selectInput to add this new dataset
lbls <- makelabels(alldata())
sel <- length(lbls)
updateSelectInput(session, "seldata", choices = lbls, selected = lbls[sel])
}
})
output$contents <- renderTable({
req(input$seldata)
seldata <- alldata()[[ as.integer(input$seldata) ]]
# character
depvar <- seldata$depvar
othervars <- setdiff(names(seldata$data), seldata$depvar)
cbind(seldata$data[, depvar, drop = FALSE], seldata$data[, othervars, drop = FALSE])
})
}
shinyApp(ui, server)
There is no ML, no modeling, nothing else in this shiny app, it just shows one possible method for switching between multiple datasets.
For your functionality, you'll need to react to input$seldata to find when the user changes dataset. Note that (1) I'm returning the integer of the list index, and (2) selectInput always returns a string. From this, if the user selects the second dataset in the pull-down, you will get "2", which will obviously not index by itself. Your data must be referenced as alldata()[[ as.integer(input$seldata) ]].
To support repeated-data with less ambiguity, I added the timestamps to the selectInput text, so you can see the "when" of some data. Perhaps overkill, easily removed.

R Shiny, fileInput displays "Upload Completed" a few seconds before it is actually done

I typically need to upload large (~700MB) csv files into my shiny app. The problem is, it shows "Upload Completed" in less than 3 seconds or so, while it actually takes around 20 seconds (have also confirmed it by printing some rows of the data).
Is there a workaround for this?
ui <- fluidPage(
titlePanel("Predictive Models"),
# Sidebar layout with input and output definitions ----
sidebarLayout(
# Sidebar panel for inputs ----
sidebarPanel(
# Input: Select a file ----
fileInput("file1", "Choose CSV File",
multiple = FALSE,
accept = c("text/csv",
"text/comma-separated-values,text/plain",
".csv"),
width = "80%")
...
server <- function(input, output) {
values <- reactiveValues(df_data = NULL, station_id= NULL, station_name= NULL, station_data=NULL, processed_data=NULL,df=NULL)
observeEvent(input$file1, {
values$df_data <- read.csv(input$file1$datapath);
output$sum <- renderPrint({
print(head(values$df_data, 10))
})
})
There are 2 steps to uploading a file.
The file is placed into a temp folder defined by tempdir()
The file is read into memory using read.csv()
The upload bar we see with fileInput only measures the time to upload the file to the server and into the temp directory. Not the time to read it into memory.
Since read.csv() blocks the server till the operation is completed, the only way to measure the time to read the file into memory would be to read the file in batches. In each step, we log the progress using Progress.
Here is a sample, it is not the most efficient code.
library(shiny)
ui <- fluidPage(
titlePanel("Predictive Models"),
# Sidebar layout with input and output definitions ----
sidebarLayout(
# Sidebar panel for inputs ----
sidebarPanel(
# Input: Select a file ----
fileInput("file1", "Choose CSV File",
multiple = FALSE,
accept = c("text/csv",
"text/comma-separated-values,text/plain",
".csv"),
width = "80%")
),
mainPanel(verbatimTextOutput("sum"))
)
)
server <- function(input, output,session) {
options(shiny.maxRequestSize=800*1024^2)
read_batch_with_progress = function(file_path,nrows,no_batches){
progress = Progress$new(session, min = 1,max = no_batches)
progress$set(message = "Reading ...")
seq_length = ceiling(seq.int(from = 2, to = nrows-2,length.out = no_batches+1))
seq_length = seq_length[-length(seq_length)]
#read the first line
df = read.csv(file_path,skip = 0,nrows = 1)
col_names = colnames(df)
for(i in seq_along(seq_length)){
progress$set(value = i)
if(i == no_batches) chunk_size = -1 else chunk_size = seq_length[i+1] - seq_length[i]
df_temp = read.csv(file_path, skip = seq_length[i], nrows = chunk_size,header = FALSE,stringsAsFactors = FALSE)
colnames(df_temp) = col_names
df = rbind(df,df_temp)
}
progress$close()
return(df)
}
df = reactive({
req(input$file1)
n_rows = length(count.fields(input$file1$datapath))
df_out = read_batch_with_progress(input$file1$datapath,n_rows,10)
return(df_out)
})
observe({
output$sum <- renderPrint({
print(head(df(), 10))
})
})
}
shinyApp(ui,server)
This code splits the file into 10 chunks and reads each chunk into memory, appending it to the previous chunk. In each step, it logs the progress using progress$set(value = i)

Two Reactives R Shiny

I need to render a table based on user input which will be one of two possible tables. I have defined the first table filedata by the user selecting a .csv file to upload. The second table, data_ranked_words, has the same dimensions.
What I want is for the output to switch between the two tables. I defined each table in a reactive(). However, I know that the data_ranked_words reactive is never being triggered. How do I trigger both of these reactives when the user uploads a file? In my code the issue is with the two reactive() statements at the beginning of server.R.
library(shiny)
library(markdown)
library(DT)
library(D3TableFilter)
options(shiny.maxRequestSize=50*1024^2)
setwd('~/Desktop/DSI/Topic Model App Interface')
# ui.R
#-------------------------------------------------------------------------------------
ui <- shinyUI(
navbarPage("Start",
tabPanel("From Data",
sidebarLayout(
sidebarPanel(
radioButtons("plotType", "Plot type",
c("Scatter"="p", "Line"="l")
)
),
mainPanel(
plotOutput("plot")
)
)
),
tabPanel("From CSV",
sidebarLayout(
sidebarPanel(
# Define what's in the sidebar
fileInput("file",
"Choose CSV files from directory",
multiple = TRUE,
accept=c('text/csv',
'text/comma-separated-values,text/plain',
'.csv')),
h5(div(HTML('Use the radio butons to toggle between the <em>Word View</em> and
<em>Probability View</em>.'))),
radioButtons('toggle', 'Choose one:', list('Word View', 'Probability View')),
p(div(HTML('<strong>Note:</strong> The <em>Probability View</em> will
<u>not</u> yield the top X number of words. It will instead
return the first X columns. You can then sort each column in
ascending or descending order. Keep in mind that it will only
sort from the rows that are displayed, <u>not</u> all rows.'))),
br(),
sliderInput('slider', div(HTML('How many rows to display?')), 1, 100, 20),
br(),
h5('Use the buttons below to quickly show large numbers of rows.'),
radioButtons('rowIdentifier', 'Show more rows:',
list('[ Clear ]', '200', '500', '1000', '5000', '10000', 'All Rows')),
p(div(HTML('<strong>Warning:</strong> Printing all rows to the screen may
take a while.'))),
h3('Tips:'),
p("You can copy and paste the table into Excel. If you only want to
copy one column, use the 'Show/Hide' function at the top-right of the table
to hide all the undesired columns."),
p(div(HTML('Sorting by column is available in <em>Probability View</em> but
not <em>Word View</em>.')))
),
# Define what's in the main panel
mainPanel(
title = 'Topic Model Viewer',
# How wide the main table will be
fluidRow(
column(width = 12, d3tfOutput('data'))
)
)
)
),
navbarMenu("More",
tabPanel("temp"
),
tabPanel("About",
fluidRow(
column(6
),
column(3
)
)
)
)
)
)
# server.R
#-------------------------------------------------------------------------------------
server <- shinyServer(function(input, output, session) {
# Set up the dataframe for display in the table
# Define 'filedata' as the .csv file that is uploaded
filedata <- reactive({
infile <- input$file
if (is.null(infile)) {
# User has not uploaded a file yet
return(NULL)
}
temp = read.csv(infile$datapath)
# Save data as RDS file, which is much faster than csv
saveRDS(temp, file = 'data.rds')
# Read in data file
data = readRDS('data.rds')
# Transpose data for more intuitive viewing. Words as rows, topics as cols
data = t(data)
# Convert to data frame
data = as.data.frame(data)
# Return this
data
})
# The ranked and ordered csv file
data_ranked_words <- reactive({
data = filedata()
# Sort each column by probability, and substitute the correct word into that column
# This will essentially rank each word for each topic
# This is done by indexing the row names by the order of each column
temp = matrix(row.names(data)[apply(-data, 2, order)], nrow(data))
temp = as.data.frame(temp)
# Define column names (same as before) for the new data frame
colnames(temp) = paste0(rep('topic', ncol(data)), 1:ncol(data))
# Return this
temp
print('Success')
})
output$data <- renderD3tf({
# Define table properties. See http://tablefilter.free.fr/doc.php
# for a complete reference
tableProps <- list(
rows_counter = TRUE,
rows_counter_text = "Rows: ",
alternate_rows = TRUE
);
# Radio buttons
# The reason why the extensions are in this if() is so that sorting can be
# activated on Probability View, but not Word View
if(input$toggle=='Word View'){
df = data_ranked_words
extensions <- list(
list( name = "colsVisibility",
text = 'Hide columns: ',
enable_tick_all = TRUE
),
list( name = "filtersVisibility",
visible_at_start = FALSE)
)
} else if(input$toggle=='Probability View'){
df = filedata()
extensions <- list(
list(name = "sort"), #this enables/disables sorting
list( name = "colsVisibility",
text = 'Hide columns: ',
enable_tick_all = TRUE
),
list( name = "filtersVisibility",
visible_at_start = FALSE)
)
}
# Radio button options for more row viewing options
if(input$rowIdentifier=='Clear'){
num_rows = input$slider
} else if(input$rowIdentifier==200){
num_rows = 200
} else if(input$rowIdentifier==500){
num_rows = 500
} else if(input$rowIdentifier==1000){
num_rows = 1000
} else if(input$rowIdentifier==5000){
num_rows = 5000
} else if(input$rowIdentifier==10000){
num_rows = 10000
} else if(input$rowIdentifier=='All Rows'){
num_rows = nrow(df)
} else{
num_rows = input$slider
}
# Create table
if(is.null(filedata())){
} else{
d3tf(df,
tableProps = tableProps,
extensions = extensions,
showRowNames = TRUE,
tableStyle = "table table-bordered")
}
})
# This line will end the R session when the Shiny app is closed
session$onSessionEnded(stopApp)
})
# Run app in browser
runApp(list(ui=ui,server=server), launch.browser = TRUE)

Resources