Specifying data range from excel in Shiny - r

I would like to know if its possible to create a shiny app which allows you to upload an excel file and which allows you to select a data range based on sheet name and cell range.
I would like to build upon it in order to showcase some regression analysis but haven't been able to find a starting point.

John, it is always a good idea to take a look at the Shiny gallery and take a look at past answers on Stack Overflow for code examples when faced with issues like these.
Here is a example tutorial for data upload. This can be CSV and not just xls.
https://shiny.rstudio.com/gallery/file-upload.html. But code layout may be useful for you to set up your inputs.
Keep it simple? You might be able to save the data range you want out as a csv file so your users do not have specify data range and sheet. I do this so users just simply need to look at what data sets they want in a select box and not go hunt for the data. See example below. (This may save you lots of error trapping code).
Do not forget to transform your data. Note this example where you might need to factor some of your variables.
As outlined above by Parth see https://www.r-bloggers.com/read-excel-files-from-r/ for more detail on packages Xl_Connect and xlsx. You can specify sheets.
WORKING WITH FILES
Some code snippets that may help you. I have the data blocks already available as csv files. Setting up an selectInput with a list of these files
# in ui.R
selectInput(("d1"), "Data:", choices = data.choices)
I fill data.choices in global.R with this code.
# filter on .csv
data.files <- list.files(path = "data", pattern = ".csv")
# dataset choices (later perhaps break by date)
# sort by date most recent so selectInput takes first one
data.choices <- sort(data.files, decreasing = TRUE)
I have a reactive around the selectInput that then loads the data. (I use data.tables package fread so you will need to install this package and use library(data.tables) if you use this code).
dataset1 <- reactive({
validate(
need(input$d1 != "", "Please select a data set")
)
if (!is.null(input$d1)) {
k.filename <- input$d1 # e.g. 'screendata20160405.csv'
isolate({
## part of code this reactive should NOT take dependency on
# LOAD CSV
s.dt <- fread(file.path("data", k.filename),
na.strings = c("NA", "#N/A")) %>%
rename(ticker = Ticker)
# You might choose to rather dot.the.column.names to save DT issues
#setnames(DT, make.names(colnames(DT)))
# SET KEYS IF RELEVANT
k.id.cols <- c("ticker")
if ("date" %in% names(s.dt)) {
k.id.cols <- c(k.id.cols, "date")
}
setkeyv(s.dt, k.id.cols)
# NAME CHANGES rename columns if necessary
setnames(s.dt, "Short Name", "name")
})
} else {
s.dt <- NULL #input$d1 is null
}
s.dt
})
Note the validates as my data is plotted and I want to avoid error messages. Please appreciate the key setting and renaming columns code above is not necessary but specific to my example, but shows you what you can do to get your data "ready" for user.
GET SHEET NAMES OUT
John this is very useful. Take a look at this long thread on google groups https://groups.google.com/forum/#!topic/shiny-discuss/Mj2KFfECBhU
Huidong Tian had this very useful code at 3/17/14 (but also see Stephane Laurent's code about closing XLConnect too to manage memory):
library(XLConnect)
shinyServer(function(input, output) {
Dat <- reactiveValues()
observe({
if (!is.null(input$iFile)) {
inFile <- input$iFile
wb <- loadWorkbook(inFile$datapath)
sheets <- getSheets(wb)
Dat$wb <- wb
Dat$sheets <- sheets
}
})
output$ui <- renderUI({
if (!is.null(Dat$sheets)) {
selectInput(inputId = "sheet", label = "Select a sheet:", choices = Dat$sheets)
}
})
observe({
if (!is.null(Dat$wb)) {
if (!is.null(input$sheet)){
dat <- readWorksheet(Dat$wb, input$sheet)
print(names(dat))
output$columns <- renderUI({
checkboxGroupInput("columns", "Choose columns",
choices = names(dat))
})
}
}
})
})
shinyUI(pageWithSidebar(
# Include css file;
tagList(
tags$head(
tags$title("Upload Data"),
tags$h1("Test")
)
),
# Control panel;
sidebarPanel(
fileInput(inputId = "iFile", label = "Escolha um arquivo:", accept="application/vnd.ms-excel"),
radioButtons("model", "Escolha do Modelo:",
list("CRS" = "crs",
"VRS" = "vrs")),
br(),
tags$hr(),
uiOutput(outputId = "ui"),
uiOutput(outputId = "columns")
),
# Output panel;
mainPanel()
))

You could include inputs for the file path and cell range, and use a shiny action button to send the input variables to read_excel()
https://shiny.rstudio.com/articles/action-buttons.html
http://readxl.tidyverse.org

Related

Shiny App: How to collect all text inputs into a data frame without listing them individually (how to index reactive values?)

I have a tab of my app where I display a bunch of text inputs based on a three-column data frame that contains: variable_name, text_prompt, and example_data. The code below seems to work fine since it displays how I want it to. Eventually, I will be feeding it different data frames, depending on the circumstances, so I need to be able to do everything programmatically.
library(shiny)
library(tidyverse)
library(DT)
additional.data.fields <- tibble (var.name = c("project.id", "director.name"),
prompt.text = c("Enter Project ID", "Enter Director's name"),
var.value = c("e.g. 09-111", "e.g. Paul Smith"))
ui <- fluidPage(
tabsetPanel(
#Generate Input fields from dataframe
tabPanel("Input", #value = "input.2",
# Generate input fields with pmap
actionButton("submit", "Submit"),
pmap(additional.data.fields, ~textInput(..1, ..2, value = ..3)),
),
#Output data to tell if it updates with button click
tabPanel("Output", value = "output",
DT::dataTableOutput("data")
)
)
)
server <- function(input, output, session) {
# Create a reactive values object to store the input data
values <- reactiveValues()
# Set the reactive values object when the submit button is clicked
observeEvent(input$submit, {
var.names <- pull(additional.data.fields, var.name)
#THIS IS THE PART I DON'T KNOW HOW TO DO
#input.data <- ???
#I'll add dummy data so that the program loads
input.data <- tibble(var.names,
temp = 1:length(var.names))
values$data <- input.data
})
# Render the input data table
output$data <- DT::renderDataTable({
values$data
})
}
shinyApp(ui, server)
But what I want - and really have no idea how to do - is to get it back into a data frame after the user hits "submit" (I only need two columns in the subsequent data frame; I don't need the text_prompt data again.)
I know that the user input creates a list of read-only ReactiveValues called "input". But I can't figure out how to do anything with this list besides access using known names (i.e. I know that there is a variable named "project_id" which I can access using input$project_id). But what I want is not to have to write them all out, so that I can change the data used to create the input fields. So I need a way to collect them in a data frame without knowing all the individual names of the variables or even how many there are.
I figured this out on my own. You can't index reactive values with []. However, for some reason you can using [[]].
I would love to know why this is, if anyone has an answer that can help me understand why it works this way.
Here's the key bit of code that I was missing before:
input.data <- tibble (names = var.names,
values = map_chr(var.names, ~input[[.x]]))
The full code that works as I want it is pasted below. I'd still appreciate any feedback or recommendations for improvement.
library(shiny)
library(tidyverse)
library(DT)
additional.data.fields <- tibble (var.name = c("project.id", "director.name"),
prompt.text = c("Enter Project ID", "Enter Director's name"),
var.value = c("e.g. 09-111", "e.g. Paul Smith"))
ui <- fluidPage(
tabsetPanel(
#Generate Input fields from dataframe
tabPanel("Input", #value = "input.2",
# Generate input fields with pmap
actionButton("submit", "Submit"),
pmap(additional.data.fields, ~textInput(..1, ..2, value = ..3)),
),
#Output data to tell if it updates with button click
tabPanel("Output", value = "output",
DT::dataTableOutput("data")
)
)
)
server <- function(input, output, session) {
# Create a reactive values object to store the input data
values <- reactiveValues()
# Set the reactive values object when the submit button is clicked
observeEvent(input$submit, {
var.names <- pull(additional.data.fields, var.name)
input.data <- tibble (names = var.names,
values = map_chr(var.names, ~input[[.x]]))
values$data <- input.data
})
# Render the input data table
output$data <- DT::renderDataTable({
values$data
})
}
shinyApp(ui, server)

Read zip file containing multiple .csv tables in R shiny app

I'm working on a shiny app to manipulate data.
I'd like to read a zip file selectioned in a fileInput. This zip is composed by multiple csv files, and I'd like to save as reactive values all .csv dataframes.
For example, if test.zip contains file ONE.csv, TWO.csv, THREE.csv , i'd like to obtain 3 reactives values (as dataframes) called ONE , TWO, THREE .
I'm abble to do it if I know the name and number of csv files.
But if I don't know the number and names of .csv dataframes, how can I achieve it ?
## Only run examples in interactive R sessions
if (interactive()) {
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
fileInput("ZIP", "Choose ZIP File",
accept = ".zip"
)
),
mainPanel(
DT::dataTableOutput("ONEtab")
)
)
)
server <- function(input, output) {
ONE <- reactive({
inFile <-req(input$ZIP)
read_csv(unzip(inFile$datapath,"ONE.CSV"))
})
TWO <- reactive({
inFile <-req(input$ZIP)
read_csv(unzip(inFile$datapath,"TWO.CSV"))
})
THREE <- reactive({
inFile <-req(input$ZIP)
read_csv(unzip(inFile$datapath,"THREE.CSV"))
})
output$ONEtab <- DT::renderDataTable({ DT::datatable(ONE(), option=list(scrollX=T),filter = 'top')})
}
shinyApp(ui, server)
}
Thanks for your help !
One option is to read all the dataframes into a single variable and then use a number to select the one of interest. Here's some code that does this. It uses lapply to read the contents of the zip file to create a reactive variable called all. To reference different dataframes, the code required is all()[[index]] and I have added something that shows this.
library(DT)
library(readr)
ui <- fluidPage(sidebarLayout(sidebarPanel(
fileInput("ZIP", "Choose ZIP File", accept = ".zip"),
selectInput("choice", 'Choose', choices = c(1,2,3), selected = 1)
),
mainPanel(DT::dataTableOutput("selectone"))))
server <- function(input, output) {
all <- reactive({
inFile <- req(input$ZIP)
filelist <- unzip(inFile$datapath, list = T)
lapply(filelist$Name, read_csv)
})
output$selectone <-
DT::renderDataTable({
choice = as.integer(input$choice)
DT::datatable(all()[[choice]], option = list(scrollX = T), filter = 'top')
})
}
shinyApp(ui, server)
Without the rest of your code that processes this, it's difficult to know if this will be what you need but perhaps it's a start.

Subsetting a reactive object in Shiny

I am in the process of learning Shiny and developing a simple app. The start of the program will allow a user to import a CSV file and then apply a filter variable(s) if needed. They will only be able to use factors as filter variables at this stage. I apply the filters on an iterative basis. So, one can apply a filter based on a factor level and then apply another factor level and so on until completed.
The best application I could find of being able to subset a reactive data frame was to apply the data frame as a reactive value. This seems to work, but I am having a couple issues that I can't figure out how to resolve.
1) Given the filtering is an iterative process, I would like to keep track and print out each variable and level applied during the filtering process. The best way I could figure out was creating a global variable (<<-) and using renderText to print out the contents after hitting the apply filter button. The issue is renderText just flashes on the screen and quickly disappears. I included a print to console statement that verifies the text is being saved correctly. I believe this is happening from the filter being applied to the reactive data frame and the updating process, but I can't figure out how to stop the text from disappearing on the screen?
2) When I try to save out the reactive data frame at the end of the shiny code, I get the following error "Warning: Error in $: $ operator is invalid for atomic vectors". I tried a couple things, but don't really understand what is going on here because the object "file$dfSource" is not like a normal reactive data frame dfSource()?
The shiny app below uses iris data so its easier to use/test. I don't know if applying the data frame to a reactive value is the best way to program this or if there is an easier way to do all this - just trying to learn best approach here.
library(shiny)
allfilters <- c()
ui <- (fluidPage(
# Application title
titlePanel("Filter Data"),
# Input Forms
sidebarLayout(
sidebarPanel(
h3("Data"),
checkboxInput("selectFilter", label = "Apply Filter Variable", value = FALSE),
uiOutput("selectFilterVar"),
uiOutput("selectFilterGroup"),
helpText("Apply filter to data"),
uiOutput("selectFilterButton"),
helpText("Reset data to total"),
uiOutput("selectResetButton"),
h3("Download Data"),
helpText("Download Data"),
downloadButton("downloadData", "Download File")
),
# Output Forms
mainPanel(
tabsetPanel(
tabPanel("Summary",
h2("Output Summary"),
textOutput("ncases"),
textOutput("selectedfilters")))
)
)
))
server <- (function(input, output, session) {
data <- iris
file <- reactiveValues(dfSource = data)
## Select Filter Variable
output$selectFilterVar <- renderUI({
req(file$dfSource)
if (input$selectFilter){
selectInput("filterVar", "Select Filter Variable", multiple = FALSE, choices = sort(names(file$dfSource[, sapply(file$dfSource, is.factor), drop = FALSE])))
}
})
# Select Filter Group(s)
output$selectFilterGroup <- renderUI({
req(file$dfSource)
req(input$filterVar)
if (input$selectFilter){
selectInput("filterGroup", "Select Filter Group", multiple = TRUE, choices = sort(unique(file$dfSource[,input$filterVar])))
}
})
# Apply Filter Button
output$selectFilterButton <- renderUI({
req(file$dfSource)
if (input$selectFilter) {
actionButton("filterButton", "Apply Filter")
}
})
# Apply filter group to data
observeEvent(input$filterButton, {
temp <- file$dfSource[(file$dfSource[,input$filterVar] %in% c(input$filterGroup)),]
file$dfSource <- temp
})
# Reset Total Sample Button
output$selectResetButton <- renderUI({
req(file$dfSource)
if (input$selectFilter) {
actionButton("resetButton", "Reset Total")
}
})
# Reset data to total sample
observeEvent(input$resetButton, {
file$dfSource <- data
updateCheckboxInput(session, "selectFilter", value = FALSE)
allfilters <- NULL
})
## Summary number of cases
output$ncases <- renderText({
req(file$dfSource)
mainTitle <- paste("Number of cases =" , nrow(file$dfSource))
return(mainTitle)
})
## Capture selected filter variables in global object
testfilter <- eventReactive(input$filterButton, {
appliedfilter <- paste0(input$filterVar, "(", input$filterGroup,")")
if (is.null(allfilters)) {
allfilters <<- paste("Selected Filters:", appliedfilter)
} else {
allfilters <<- paste(allfilters, "&", appliedfilter)
}
return(allfilters)
})
# Print out filter variables in global object
output$selectedfilters <- renderText({
filteroutput <- testfilter()
print(filteroutput)
return(filteroutput)
})
## Save out case data file
output$downloadData <- downloadHandler(
filename = function() {
paste("data-", Sys.Date(), ".csv", sep="")
},
content = function(file) {
write.csv(file$dfSource, file)
}
)
})
shinyApp(ui, server)
1) Storing it a global variable is probably not a good idea (scope in shiny is already complicated enough!). You already have a reactiveValues object, why not use that?
This alone, however, is not enough; the problem seems to be the eventReactive - I'm not quite sure why.
This works:
# this replaces the testfilter eventReactive
observeEvent(input$filterButton, {
appliedfilter <- paste0(input$filterVar, "(", input$filterGroup,")")
if (is.null(file$allfilters)) {
file$allfilters <- paste("Selected Filters:", appliedfilter)
} else {
file$allfilters <- paste(file$allfilters, "&", appliedfilter)
}
})
# Print out filter variables in global object
output$selectedfilters <- renderText({
filteroutput <- file$allfilters
print(filteroutput)
return(filteroutput)
})
2) The error is in the content function you pass to downloadHandler. The parameter is called file, which shadows the file reactiveValues. This works:
## Save out case data file
output$downloadData <- downloadHandler(
filename = function() {
paste("data-", Sys.Date(), ".csv", sep="")
},
content = function(filetarget) {
write.csv(file$dfSource, filetarget)
}
)
PS ad 1: It might be better to store the filters, instead of storing the filtered data frame and a string listing the filters. If your users change their mind, they have to start over from the beginning, but if you store the filters you can have a table or similar that allows deleting/editing individual filters. You could just store a list of two-element vectors, then iterate though the list to filter the data.

How do you get dates to show up in a date format when working with a Shiny Table?

I'm stuck trying to get dates to show up in a Shiny table. I have done some research and see that in the past xtable does not work nicely with Shiny. There are a couple of questions on SO that dealt with this issue. The one routinely reference can be found here R: xtable and dates.
My problem is that 1)I'm extremely new at programming in Shiny and using xtable. 2) I am unfamiliar with using POSIXct. 3) I don't understand the solution provided in the link above.
Please provide a helping hand for the basic code below. The idea is that somebody would use this app to enter data daily. These data would be stored on a .csv. When stored on the .csv only the numeric value of the R date is stored. This is what shows up on the Shiny table as well. Please teach me how to format correctly in both the table and the .csv file.
Before examining the code below, know that there would be a .csv file stored that would have the Headers Date, A, B. Let's call this file "log" and it would be stored locally. Here is the code:
library(shiny)
log <- read.table("V:\\My\\Path\\log.csv",sep=",",header=T)
ui <- fluidPage(
sidebarLayout(
sidebarPanel(width=2,
#Enter Date
dateInput("date","Date",min="2016-07-04", max = "2017-07-04"),
#Enter Combo
selectInput(inputId = "a", "A", c("Choose one" = "","A1", "A2", "A3"), multiple = FALSE, selectize = TRUE, width = NULL, size = NULL),
#Enter Number
numericInput(inputId = "b", "Favorite Number", NULL, min = 0, max = NA),
#Enter Submit to write info to file
actionButton(inputId = "submit", "Submit", icon = NULL, width = NULL)
),
mainPanel(
# Application title
titlePanel("Read Date"),
tableOutput("summary"))
)
)
server <- function(input, output) {
#Create vector of current trial results
data <- eventReactive(input$submit, {
cbind(input$date,input$a, input$b)
})
#Append current trial results to master list
observeEvent(input$submit, {
write.table(data(), file="V:\\My\\Path\\log.csv", sep=",", col.names= FALSE, row.names=F, append = T)
})
#Create datatable variable reading in latest log
datatable <- eventReactive(c(input$agent,input$submit), { #Putting both reactive variables allow to see dataset without running and see updated dataset after running.
data.frame(read.table("V:\\My\\Path\\log.csv",sep=",",header=T))
})
#Create Table
output$summary <- renderTable({
datatable() }, digits=2,align = "cccc" )
}
shinyApp(ui = ui, server = server)
It seems the answer is to write as character to the log file and read it back in as a character. I can't figure out to do this. Am I on the right track? Because I'm learning I'll take any other suggestions on how to improve my code.
I finally figured out the simple solution.
I just changed the code when I build the dataframe from
data <- eventReactive(input$submit, {
cbind(input$date,input$a, input$b)
to
data <- eventReactive(input$submit, {
cbind(as.character(input$date),input$a, input$b))
Adding the as.character() seems to have done the trick. I don't know if this will have consequences later, but the displayed table now looks nice.

Shiny: dynamic UI - a loop around downloadHandler?

I have a simple example Siny code that works fine (below). It generates a list of several tiny data frames (reactive object 'myout' in server.R). The number of the data frames is based on user's input.('NumRuns').
My current code allows the user to download the first of these data frames - via downloadButton in ui.R and downloadHandler in server.r.
I am wondering if it's possible to create a loop around both downloadButton and downloadHandler so that - after the analysis is run (actionButton) - the user gets as many buttons to download as there were data frames generated in 'myout'. Is it even possible?
Or maybe it's possible to create one downloadButton that allows the user to 'pick' which data frame s/he wants to download?
Thanks a lot for any hints - because I am not sure how to approach it!
My code (I run it using: runApp(launch.browser = T)
### My ui.R code:
shinyUI(pageWithSidebar(
headerPanel("My App"),
sidebarPanel(
numericInput('NumRuns','Number of runs',value=3,min=3,max=10,step=1),
br(),
actionButton(inputId="goButton","Run!"),
br(),
br(),
textInput("downloadData","Save My Data Frame:",value="Data Frame 1"),
downloadButton('downloadData','Save my file!')
),
mainPanel(
tabsetPanel(
tabPanel("Shows the 1st data frame",tableOutput("mydf"))
)
)
))
### My 'server.R' code:
shinyServer(function(input,output){
### Creating files for displaying and downloading
myout = reactive({
if(input$goButton==0) return(NULL)
nrruns=input$NumRuns
mylist=NULL
for(i in 1:nrruns){
mylist[[i]]<-data.frame(a=rnorm(10),b=runif(10))
names(mylist)[i]<-paste("dataframe",i,sep="")
}
return(mylist)
})
# Grabbing only the 1st data frame:
output$mydf <- renderTable({
if(input$goButton==0) return(NULL)
myout()$dataframe1
})
# Allowing to download only the 1st data frame:
output$downloadData <- downloadHandler(
filename = function() { paste(input$downloadData, " ",Sys.Date(),".csv",sep="") },
content = function(file) {
write.csv(myout()$dataframe1,file,row.names=F)
}
)
})
This post might gives you the answer.
https://groups.google.com/forum/#!msg/shiny-discuss/qGN3jeCbFRY/xOW5qoVrr94J
Yes, this is possible. You'll need to use uiOutput/renderUI to render the buttons, each with a different ID (e.g. downloadData1 through downloadDataN). For defining the download handlers dynamically, use a pattern like:
observe({
lapply(1:N, function(i) {
output[[paste0("downloadData", i)]] <- downloadHandler(...)
})
})
the important thing there being that you can assign to output[[id]] if your output names are dynamic.

Resources