I am working on building a shiny App. I have used some filters and rendered a data frame and the data frame changes dynamically as per the user input. But I cannot store a particular column value from a data frame into a vector. I need to store the reactive output every time into a vector so that I can use the values later again. Here the values are stored in text_vec and i need to pass that into the API but I cannot access the values from text_vec and i have to pass the updated values every time into the API
library(dplyr)
library(shiny)
shinyApp(ui = fluidPage(
sidebarLayout(
sidebarPanel(
selectInput(inputId = "cyl",
label = "Number cylinders:",
choices = c("all",sort(unique(mtcars$cyl))),
selected = "all"),
actionButton("capture",
"capture value")
), # closes sidebarPanel
mainPanel(
tableOutput("text"),
tableOutput("text2"),
tableOutput("text3"),
tableOutput("table")
) # closes mainPanel
) # closes sidebarLayout
), # closes fluidPage
server = function(input, output) {
# some example reactive data
cars_react <- reactive({
mtcars %>%
filter(cyl == input$cyl | input$cyl == "all")
})
# simply global assignment of a reactive vector
observeEvent(cars_react(), {
# here is a globally assigned vector taken from the reactive data
# reused in a render statement it will not react to change, since it is not reactive
test_vec3 <<- unique(cars_react()$hp)
})
# here a file is written to the working directory of your shiny app
# everytime cars_react() changes write (and overwrite) vector to a file
observeEvent(cars_react(), {
test_vec = unique(cars_react()$hp)
saveRDS(test_vec, file = "test_vec.Rdata")
})
# same as above but the file is gradually growing and not overwritten
# everytime cars_react() changes add vector to a (over several sessions growing) list
observeEvent(cars_react(), {
test_vec2 = unique(cars_react()$hp)
if (file.exists("test_list.Rdata")) {
temp = readRDS("test_list.Rdata")
test_list = c(temp, list(test_vec2))
} else {
test_list = list(test_vec2)
}
saveRDS(test_list, file = "test_list.Rdata")
})
# here we access the reactive data with isolate and make it non-reactive, but can update the values through a button click
text_vec <<- eventReactive(input$capture, {
isolate(unique(cars_react()$hp))
})
# output of our reactive data as table
output$table <- renderTable({
cars_react()
})
# text output of globally assigned non-reactive vector test_vec3 (not changing!)
output$text <- renderText({
test_vec3
})
# you can capture values of reactives with isolate, but then, they don't change anymore
# text output of isolated formely reactive vector unique(cars_react()$hp (not changing!)
output$text2 <- renderText({
isolate(unique(cars_react()$hp))
})
# text output of new reactive vector (changes when input$capture button is clicked)
output$text3 <- renderText({
text_vec()
})
for (i in text_vec)
{
url = "https://oscar.com/prweb/PRRestService/"
parameters<-'{
{
"Reference":"Account"
,"ReferenceValue":""
}'
b<-fromJSON(parameters)
b["ReferenceValue"]=i
r <- POST(url, body = parameters,encode = "json")
r_c<-toJSON(content(r))
print(r_c)
}
}
)
A simple way to get a data frame to persist across all environments used within your Shiny app, is to use the '<<-' assignment instead of the '<-" assignment. This is not a great programming technique, but it may be what you're hoping to find.
# To get a data frame to persist, use
a <<- b
# instead of
a <- b
** Updated answer **
Based on your updated answer, I would wrap you API call into an observeEvent which gets triggered once the action button is pressed. Since you do not provide a working example with some real code, I am not sure whether the example below is of help. I further assume that your for loop is correct and working (on my end, I cannot know without a real API and some real values).
library(dplyr)
library(shiny)
library(httr)
library(jsonlite)
shinyApp(ui = fluidPage(
selectInput(inputId = "cyl",
label = "Number cylinders:",
choices = c("all",sort(unique(mtcars$cyl))),
selected = "all"),
actionButton("capture",
"capture value")
), # closes fluidPage
server = function(input, output) {
# some example reactive data
cars_react <- reactive({
mtcars %>%
filter(cyl == input$cyl | input$cyl == "all")
})
# here we access the reactive data with isolate and make it non-reactive, but can update the values through a button click
observeEvent(input$capture, {
for (i in unique(cars_react()$hp))
{
url = "https://oscar.com/prweb/PRRestService/"
parameters<-'{
"Reference":"Account"
,"ReferenceValue":""
}'
b<-fromJSON(parameters)
b["ReferenceValue"]=i
r <- POST(url, body = parameters,encode = "json")
r_c<-toJSON(content(r))
print(r_c)
}
})
}
)
Old answer
It is not clear from your question how, where and how often you want to use the vector of your reactive data frame. But it is an important question, since the concept of reactivity and how to access it is very hard to grasp when you come from a pure non reactive R environment.
Below is a simple example app which shows how to access vectors in reactive data frames, and how they could be used.
I hope it helps to get a better understanding of reactivity in shiny.
library(dplyr)
library(shiny)
shinyApp(ui = fluidPage(
sidebarLayout(
sidebarPanel(
selectInput(inputId = "cyl",
label = "Number cylinders:",
choices = c("all",sort(unique(mtcars$cyl))),
selected = "all"),
actionButton("capture",
"capture value")
), # closes sidebarPanel
mainPanel(
tableOutput("text"),
tableOutput("text2"),
tableOutput("text3"),
tableOutput("table")
) # closes mainPanel
) # closes sidebarLayout
), # closes fluidPage
server = function(input, output) {
# some example reactive data
cars_react <- reactive({
mtcars %>%
filter(cyl == input$cyl | input$cyl == "all")
})
# simply global assignment of a reactive vector
observeEvent(cars_react(), {
# here is a globally assigned vector taken from the reactive data
# reused in a render statement it will not react to change, since it is not reactive
test_vec3 <<- unique(cars_react()$hp)
})
# here a file is written to the working directory of your shiny app
# everytime cars_react() changes write (and overwrite) vector to a file
observeEvent(cars_react(), {
test_vec = unique(cars_react()$hp)
saveRDS(test_vec, file = "test_vec.Rdata")
})
# same as above but the file is gradually growing and not overwritten
# everytime cars_react() changes add vector to a (over several sessions growing) list
observeEvent(cars_react(), {
test_vec2 = unique(cars_react()$hp)
if (file.exists("test_list.Rdata")) {
temp = readRDS("test_list.Rdata")
test_list = c(temp, list(test_vec2))
} else {
test_list = list(test_vec2)
}
saveRDS(test_list, file = "test_list.Rdata")
})
# here we access the reactive data with isolate and make it non-reactive, but can update the values through a button click
text_vec <- eventReactive(input$capture, {
isolate(unique(cars_react()$hp))
})
# output of our reactive data as table
output$table <- renderTable({
cars_react()
})
# text output of globally assigned non-reactive vector test_vec3 (not changing!)
output$text <- renderText({
test_vec3
})
# you can capture values of reactives with isolate, but then, they don't change anymore
# text output of isolated formely reactive vector unique(cars_react()$hp (not changing!)
output$text2 <- renderText({
isolate(unique(cars_react()$hp))
})
# text output of new reactive vector (changes when input$capture button is clicked)
output$text3 <- renderText({
text_vec()
})
}
)
Related
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)
I would like to use a Shiny app to load a file (tab-separated), dynamically create a checkboxGroupInput, after the loading of the file (using observeEvent) using the column headers, then subset the data frame that comes from the file based on the selected checkboxes. The data is then plotted using code I can't share right now.
All is working fine, apart from the last bit: subsetting the dataframe based on the selected checkboxes in checkboxGroupInput. The checkboxes all start selected, and the plot is created fine. If you un-select one of the checkboxes, the plot re-plots appropriately for a split second (so the subsetting is working fine) then the unselected checkbox re-selects itself and the plot goes back to the old plot.
This is the tiny problem I'm trying to solve, guessing it's one line of code. I'm assuming it's because of some reactivity that I don't understand and the checkbox constantly resetting itself.
Here is an example:
###
## Some functions I can't share
### Shiny app
library(shiny)
# Define UI
ui <- fluidPage(
# Application title
titlePanel("MagicPlotter"),
# Sidebar
sidebarLayout(
sidebarPanel(
fileInput(inputId = "myInputID",
label = "Your .csv file",
placeholder = "File not uploaded"),
uiOutput("mylist"),
uiOutput("submitbutton")
),
# Show a plot
mainPanel(
verticalLayout(
plotOutput("myPlot"))
)
)
)
# Define server
server <- function(input, output) {
output$myPlot <- renderPlot({
inputfile <- input$myInputID
if(is.null(inputfile))
{return()}
mydataframe <- read.table(file=inputfile$datapath, sep="\t", head=T, row.names = 1)
mydataframecolumnnames <- colnames(mydataframe[1:(length(mydataframe)-1)])
# the last column is dropped because it's not relevant as a column name
observeEvent(input$myInputID, {
output$mylist <- renderUI({
checkboxGroupInput(inputId="mylist",
label="List of things to select",
choices=mydataframecolumnnames,
selected=mydataframecolumnnames)
})
})
observeEvent(input$myInputID, {
output$submitbutton <- renderUI({
submitButton("Subset")
})
})
mysubset <- mydataframe[input$mylist]
myPlot(mysubset)
})
}
# Run the application
shinyApp(ui = ui, server = server)
Thanks all
I think there are a few things that might help...
One, you can move your observeEvent methods outside of your renderPlot.
Also, you can create a reactive function to read in the data table.
I hope this helps.
server <- function(input, output) {
myDataFrame <- reactive({
inputfile <- input$myInputID
if(is.null(inputfile))
{return()}
read.table(file=inputfile$datapath, sep="\t", head=T, row.names = 1)
})
output$myPlot <- renderPlot({
req(input$mylist)
mysubset <- myDataFrame()[input$mylist]
plot(mysubset)
})
observeEvent(input$myInputID, {
mydata <- myDataFrame()
mydataframecolumnnames <- colnames(mydata[1:(length(mydata)-1)])
output$mylist <- renderUI({
checkboxGroupInput(inputId="mylist",
label="List of things to select",
choices=mydataframecolumnnames,
selected=mydataframecolumnnames)
})
})
observeEvent(input$myInputID, {
output$submitbutton <- renderUI({
submitButton("Subset")
})
})
}
I am working on a shiny app where users can upload their own data and get some plots and statistics back. However, I also want to include an example dataset that gets used instead if the user presses a specific button. Importantly, the plots should be reactive so that users get updated plots whenever they click on the "use example data instead" button or upload a new file. I tried to recreate my current approach of overwriting the data object as best as I could here, but simply defining the data object twice doesn't overwrite the data in the way I hoped it would. Any suggestions are appreciated.
library(shiny)
# UI
ui <- fluidPage(
# Application title
titlePanel("Reproducible Example"),
# Sidebar with a slider input for number of bins
sidebarLayout(
sidebarPanel(
fileInput("Upload", "Upload your own Data"),
actionButton("Example", "Use Example Data instead")
),
# Show a plot of the generated distribution
mainPanel(
plotOutput("hist")
)
)
)
# Server Logic
server <- function(input, output) {
data <- eventReactive(input$Upload,{input$Upload})
data <- eventReactive(input$Example, {faithful$eruptions})
output$hist <- renderPlot({hist(data())})
}
# Run the application
shinyApp(ui = ui, server = server)
You can use a reactiveVal like this:
server <- function(input, output) {
my_data <- reactiveVal()
observeEvent(input$Upload, {
tmp <- read.csv(input$Upload$datapath)
## do whatever is needed to parse the data
my_data(tmp)
})
observeEvent(input$Example, {
my_data(faithful)
})
output$hist <- renderPlot({
dat <- as.data.frame(req(my_data()))
dat <- dat[, sapply(dat, is.numeric), drop = FALSE]
validate(need(NCOL(dat) > 1, "No numeric columns found in provided data"))
hist(dat[,1])
})
}
Depending on upload or button click, you store your data in my_data which is a reactive value. Whenever this value changes, the renderPlot function fires and uses the correct data.
You can use a reactive value to access whether the user has chosen to use an example dataset or use their own dataset. The user can choose to switch between the active dataset using an input from your UI.
Here's the official explanation on reactive values from RStudio: link
This would go in your ui.R:
radioButtons("sample_or_real",
label = h4("User data or sample data?"),
choices = list(
"Sample Data" = "sample",
"Upload from user data" = "user",
),
selected = "user"
)
This would go in your server.R:
data_active <- reactive({
# if user switches to internal data, switch in-app data
observeEvent(input$sample_or_real_button, {
if(input$sample_or_real == "sample"){
data_internal <- sample_data_object
} else {
data_internal <- uploaded_data_object
}
})
Note, that when using a reactive value in your server.R file, it must have parentheses () at the end of the object name. So, you call the data_internal object as data_internal().
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.
i have a code (copied of another post) that loads a dataframe and can add values to this data frame (with a input box), but this added values doesnt stay on the dataframe when i quit shiny. The question i have is how to save this added values to the original dataframe.
Code:
require(shiny)
setwd("C:/Users/DC7900/Documents/CENAGAS/Presupuesto/Pagos/")
iris<-read.csv("ConcentradoR.csv")
runApp(
list(
ui = fluidPage(
headerPanel('Gasto'),
sidebarPanel(
textInput("mes", label="fecha", value="Ingresar fecha"),
textInput("concepto", label="Concepto", value=""),
numericInput("partida", label="Partida", value=""),
numericInput("actividad", label="Actividad", value=""),
numericInput("monto", label="Monto", value=""),
actionButton("addButton", "UPLOAD!")
),
mainPanel(
tableOutput("table"))
),
server = function(input, output) {
# just a small part of iris for display
iris_sample <- iris[sample(nrow(iris), 10),]
row.names(iris_sample) <- NULL
# The important part of reactiveValues()
values <- reactiveValues()
values$df <- iris_sample
addData <- observe({
# your action button condition
if(input$addButton > 0) {
# create the new line to be added from your inputs
newLine <- isolate(c(input$mes, input$concepto, input$partida, input$actividad, input$monto))
# update your data
# note the unlist of newLine, this prevents a bothersome warning message that the rbind will return regarding rownames because of using isolate.
isolate(values$df <- rbind(as.matrix(values$df), unlist(newLine)))
}
})
output$table <- renderTable({values$df}, include.rownames=F)
}
)
)
Thank you very much
Adding the line below will create df called save_data that you can access in your local environment once you quit the shiny app.
# your action button condition
if(input$addButton > 0) {
# create the new line to be added from your inputs
newLine <- isolate(c(input$mes, input$concepto, input$partida, input$actividad, input$monto))
# update your data
# note the unlist of newLine, this prevents a bothersome warning message that the rbind will return regarding rownames because of using isolate.
isolate(values$df <- rbind(as.matrix(values$df), unlist(newLine)))
save_data <<- values$df
}
You could also have(in place of that line) like
write.csv(values$df, file = "ConcentradoR.csv")
but also see write.table, you may need to specify some parameters to make sure it's formatted correctly