How to save input to variable after button click in Shiny? - r

i'm new in shiny, but i try to write a simple app. It will connect to DB, download DF and print it on site. And i got this. Now I want to pick range to save as csv a part of that DF.
So I have to input labels: Start and Stop, and Action Button.
I tried to find information how to implement that functionality, and i didn't. I found some info about observe function, but it's totaly not working in my example. When I do it as in official doc, after button click noting is happend.
My code:
ui <- fluidPage(
titlePanel("Skrypt"),
DT::dataTableOutput("table"),
numericInput("Start", "Start", 0),
verbatimTextOutput("v1"),
numericInput("Stop", "Stop", length(a)),
verbatimTextOutput("v"),
actionButton("button", "Generate For Range")
)
server <- function(input, output) {
# Filter data based on selections
output$table <- DT::renderDataTable(DT::datatable({
data <- myDat}))
}
shinyApp(ui, server)
And only what I tried to do is save Start and Stop as a variables after click button to use it in function to generate_csv(df, start_v, stop_v) as args.
Can someone explain me how to do that in simple way?

One solution uses eventReactive. It creates a calculated value that only updates in response to an event. In this case, the click on your button. That provides a data frame you can use in renderDataTable. Any code to filter data frame moves to the eventReactive named df.
myDat <- data.frame(A = 1:3, B = LETTERS[1:3]) # dummy data for testing
ui <- fluidPage(
titlePanel("Skrypt"),
DT::dataTableOutput("table"),
numericInput("Start", "Start", 1),
verbatimTextOutput("v1"),
numericInput("Stop", "Stop", 2),
verbatimTextOutput("v"),
actionButton("button", "Generate For Range")
)
server <- function(input, output) {
df <- eventReactive(input$button, {
# Test if filter is valid
if (input$Start >= input$Stop) stop("Start cannot be larger or equal than stop")
if (input$Start < min(myDat$A)) stop("Start cannot be less than smallest value")
if (input$Stop > max(myDat$A)) stop("Stop cannot be larger than largest value")
myDat[input$Start:input$Stop,] # use any filter you deem necessary
})
# Filter data based on selections
output$table <- DT::renderDataTable({
d <- DT::datatable(
data <- df()
)
})
}
shinyApp(ui, server)

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)

Unable to reset reactive rhandsontable observeEvent

I have been using R for awhile but R shiny seems like a completely foreign language to me.
I have an RShiny app utilizing the rhandsontable, which takes input from both another column in the rhandsontable and outside the rhandsontable to calculate a new column. Currently, the table does update everything when any entry from within the table is modified, but I'd like to minimize the risk of user error as much as possible, either by making the rhandsontable reactive to both changes inside and outside the table (preferred) or reset when a button is clicked.
The example below is modified from How to reset to default a reactive rhandsontable? but reproduced my issue. As soon as I add an observeEvent for the rhandsontable, even before I call the external input, the reset button no longer works.
To make the observeEvent reactive to both changes in the external input and the table changes, I tried to make a reactive input (e.g., listener <- reactive(c(input$two_by_two$changes$changes, input$reset_input))), put them both in the observeEvent (e.g., input$two_by_two$changes$changes | input$reset_input), which both result in Warning: Error in do.call: second argument must be a list error.
library(shiny)
library(rhandsontable)
server <- shinyServer(function(input, output, session) {
FACTOR <- reactive(input$factor_val)
DF <- data.frame(A = c(1, 2), B = c(2, 4), row.names = c("C", "D"))
DF1 <- reactiveValues(data=DF)
output$two_by_two <- renderRHandsontable({
input$reset_input
rhandsontable(DF1$data)
})
observeEvent(input$two_by_two$changes$changes,
{DF1$data <- hot_to_r(input$two_by_two)
DF1$data[,2] = DF1$data[,1]*FACTOR() }) })
ui <- shinyUI(fluidPage(
actionButton(inputId = "reset_input", label = "Reset"),
br(),
numericInput("factor_val","Multiplier Value",min=0,max=10,value=2),
rHandsontableOutput("two_by_two")
))
shinyApp(ui, server)
Thanks for any help again.
The reason you were not able to reset was that you had changed the reactive dataframe, but were never resetting it back. I added a new observe event to handle that. Please see if this is what you are looking for.
library(shiny)
library(rhandsontable)
server <- shinyServer(function(input, output, session) {
FACTOR <- reactive(input$factor_val)
DF <- data.frame(A = c(1, 2), B = c(2, 4), row.names = c("C", "D"))
DF1 <- reactiveValues(data=DF)
observeEvent(input$reset_input, {
DF1$data <- DF
})
output$two_by_two <- renderRHandsontable({
#input$reset_input
rhandsontable(DF1$data)
})
observeEvent(input$factor_val, {
req(input$factor_val)
DF1$data <- hot_to_r(req({input$two_by_two}))
DF1$data[,2] = DF1$data[,1]*FACTOR()
})
})
ui <- shinyUI(fluidPage(
actionButton(inputId = "reset_input", label = "Reset"),
br(),
numericInput("factor_val","Multiplier Value",min=0,max=10,value=2),
rHandsontableOutput("two_by_two")
))
shinyApp(ui, server)

R Shiny: Switching datasets based on user input

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().

Updating a data frame in real time in RShiny

I am trying to get my head around RShiny by building what I thought would be a pretty simple but useful app. What I would like the app to do is allow the user to input some data made up of dates, numeric, and characters. Then when the user presses a save/submit button this data is appended onto a pre-existing data frame made up of previous recordings and over write the .csv of these recordings. I would also like this data to be presented to the users in the form of a table in the UI which is updated as soon as the user presses the save/submit button.
I have managed to get the majority of the UI features working, however, I am having real difficulty 1) saving the data in the correct format and 2) updating the table displayed on the UI. My current method of saving the data involves creating an isolated list of the input values and rbinding this to the original data frame. However, the formats of the input values appear to all revert to factors which is especially problematic with the date as the output is meaningless as far as I am aware. In terms of updating the UI I have attempted to create a reactive object out of the data frame and use this object as the data displayed in renderDataTable but this approach seems to have no affect.
I have created a dummy minimal example below.
Thank you for all your help in advance.
require(shiny)
require(tidyverse)
require(lubridate)
require(plotly)
#Would import the data in reality using read.csv() but to allow for an easily
#recreated example I made a dummy data frame
DateRecorded <- dmy(c("10/07/2018", "11/07/2018", "13/07/2018"))
Value <- c(1, 2, 3)
Person <- c("Bob", "Amy", "Charlotte")
df <- data.frame(DateRecorded, Value, Person)
ui <- fluidPage(
#UI Inputs
dateInput(inputId = "SessionDate", label = "Date Recorded", format = "dd-mm-yyyy"),
numericInput(inputId = "SessionValue", label = "Value Recorded", value = 0),
textInput(inputId = "SessionPerson", label = "Person Recording"),
actionButton(inputId = "Save", label = "Save"),
#UI Outputs
dataTableOutput("TheData"),
textOutput("TotRecorded")
)
# Define server logic required to draw a histogram
server <- function(input, output) {
#When "Save" is pressed should append data to df and export
observeEvent(input$Save, {
newLine <- isolate(c(input$SessionDate, input$SessionValue, input$SessionPerson))
isolate(df <- rbind(as.matrix(df), unlist(newLine)))
write.csv(df, "ExampleDataFrame.csv") #This export works but the date is saved incorrectly as "17729" not sure why
})
#Create a reactive dataset to allow for easy updating
ReactiveDf <- reactive({
df
})
#Create the table of all the data
output$TheData <- renderDataTable({
ReactiveDf()
})
#Create the totals print outs
output$TotRecorded <- renderPrint({
data <- ReactiveDf()
cat(nrow(data))
})
}
# Run the application
shinyApp(ui = ui, server = server)
I made some small tweaks.
You do not need isolate in the body of the observeEvent; it does not take a reactive dependency to values in its body.
I made ReactiveDf a reactiveVal instead of a reactive. This allows you to write its value from inside an observeEvent.
Instead of rowbinding a matrix and unlisting a list - the issue is that all the new values are parsed to the same class, while they are obviously not - it might be easier to rbind two data.frames, so create the newLine with newLine <- data.frame(DateRecorded = input$SessionDate, Value = input$SessionValue, Person = input$SessionPerson)
So a working example would be as shown below. Hope this helps!
require(shiny)
require(tidyverse)
require(lubridate)
require(plotly)
#Would import the data in reality using read.csv() but to allow for an easily
#recreated example I made a dummy data frame
DateRecorded <- dmy(c("10/07/2018", "11/07/2018", "13/07/2018"))
Value <- c(1, 2, 3)
Person <- c("Bob", "Amy", "Charlotte")
df <- data.frame(DateRecorded, Value, Person)
ui <- fluidPage(
#UI Inputs
dateInput(inputId = "SessionDate", label = "Date Recorded", format = "dd-mm-yyyy"),
numericInput(inputId = "SessionValue", label = "Value Recorded", value = 0),
textInput(inputId = "SessionPerson", label = "Person Recording"),
actionButton(inputId = "Save", label = "Save"),
#UI Outputs
dataTableOutput("TheData"),
textOutput("TotRecorded")
)
# Define server logic required to draw a histogram
server <- function(input, output) {
#When "Save" is pressed should append data to df and export
observeEvent(input$Save, {
newLine <- data.frame(DateRecorded = input$SessionDate, Value = input$SessionValue, Person = input$SessionPerson)
df <- rbind(df, newLine)
ReactiveDf(df) # set reactiveVal's value.
write.csv(df, "ExampleDataFrame.csv") #This export works but the date is saved incorrectly as "17729" not sure why
})
#Create a reactive dataset to allow for easy updating
ReactiveDf <- reactiveVal(df)
#Create the table of all the data
output$TheData <- renderDataTable({
ReactiveDf()
})
#Create the totals print outs
output$TotRecorded <- renderPrint({
data <- ReactiveDf()
cat(nrow(data))
})
}
# Run the application
shinyApp(ui = ui, server = server)

R shiny isolate reactive data.frame

I am struggling to understand how isolate() and reactive() should be used in R Shiny.
I want to achieve the following:
Whenever the "Refresh" action button is clicked:
Perform a subset on a data.frame and,
Feed this into my function to recalculate values.
The subset depends on a group of checkboxes that the user has ticked, of which there are approximately 40. I cannot have these checkboxes "fully reactive" because the function takes about 1.5 sec to execute. Instead, I want to give the user a chance to select multiple boxes and only afterwards click a button to (a) subset and (b) call the function again.
To do so, I load the data.frame in the server.R function:
df1 <- readRDS("D:/././df1.RData")
Then I have my main shinyServer function:
shinyServer(function(input, output) {
data_output <- reactive({
df1 <- df1[,df1$Students %in% input$students_selected]
#Here I want to isolate the "students_selected" so that this is only
#executed once the button is clicked
})
output$SAT <- renderTable({
myFunction(df1)
})
}
How about something like
data_output <- eventReactive(input$button, {
df1[,df1$Students %in% input$students_selected]
})
Here is my minimal example.
library(shiny)
ui <- list(sliderInput("num", "rowUpto", min= 1, max = 10, value = 5),
actionButton("btn", "update"),
tableOutput("tbl"))
server <- function(input, output) {
data_output <- eventReactive(input$btn, {
data.frame(id = 1:10, x = 11:20)[seq(input$num), ]
})
output$tbl <- renderTable({
data_output()})
}
runApp(list(ui = ui, server = server))
Edit
Another implementation, a bit more concise.
renderTable by default inspects the changes in all reactive elements within the function (in this case, input$num and input$button).
But, you want it to react only to the button. Hence you need to put the elements to be ignored within the isolate function.
If you omit the isolate function, then the table is updated as soon as the slider is moved.
library(shiny)
ui <- list(sliderInput("num", "rowUpto", min= 1, max = 10, value = 5),
actionButton("btn", "update"),
tableOutput("tbl"))
server <- function(input, output) {
output$tbl <- renderTable({
input$btn
data.frame(id = 1:10, x = 11:20)[seq(isolate(input$num)), ]
})
}
runApp(list(ui = ui, server = server))
Use eventReactive instead:
data_output <- eventReactive(input$updateButton, {
df1 <- df1[,df1$Students %in% input$students_selected] #I think your comments are messed up here, but I'll leave the filtering formatting to you
})
output$SAT <- renderTable({
data_output()
})
And in your UI you should have something like:
actionButton('updateButton',label = "Filter")
Looking at ?shiny::eventReactive:
Use eventReactive to create a calculated value that only updates in
response to an event. This is just like a normal reactive expression
except it ignores all the usual invalidations that come from its
reactive dependencies; it only invalidates in response to the given
event.

Resources