R Shiny: Modify output values in a data frame - r

The following simple shiny app displays a word and its sentiment as stored in the R data frame named sent.
library(shiny)
sent <- data.frame(word=c('happy', 'sad', 'joy', 'upset'),
sentiment=c('positive', 'negative', 'positive', 'negative'),
stringsAsFactors = FALSE)
ui <- fluidPage(
numericInput(inputId = 'num', label='', value=1, min=1, max=nrow(sent)),
br(),
h4("Word:"),
textOutput('word'),
br(),
h4("Sentiment:"),
textOutput('sentiment')
)
server <- function(input, output){
output$word <- renderText({ sent$word[input$num] })
output$sentiment <- renderText({ sent$sentiment[input$num] })
}
shinyApp(ui=ui, server=server)
I would like to modify this in 2 ways:
(1) I would like the user to be able to scroll through the words in the column sent$word, rather than using numericInput()
(2) More importantly, I would like the user to be able to modify the sentiment value associated with each word. Ideally, this would be a drop down menu (with 'positive' and 'negative' as options), which would display the current sentiment value stored in sent for that word, but which could be changed by the user and overridden in the dataframe.
Any suggestions?

This should do the trick
library(shiny)
sent <- data.frame(word=c('happy', 'sad', 'joy', 'upset'),
sentiment=c('positive', 'negative', 'positive', 'negative'),
stringsAsFactors = FALSE)
sent2 <- reactiveVal(sent)
i <- 1
i2 <- reactiveVal(i)
ui <- fluidPage(
uiOutput("wordSelect"),
br(),
h4("Word:"),
textOutput('word'),
br(),
h4("Sentiment:"),
textOutput('sentiment'),
br(),
uiOutput("change"),
actionButton("go","Change")
)
server <- function(input, output){
output$wordSelect <- renderUI({
selectizeInput(inputId = 'wrd', label='select word', choices=sent$word, selected=sent$word[i2()])
})
output$word <- renderText({ input$wrd })
output$sentiment <- renderText({ sent$sentiment[which(sent2()$word==input$wrd)] })
observeEvent(input$go, {
out <- sent
out$sentiment[which(sent$word==input$wrd)] <- input$newLabel
sent <<- out
sent2(out)
i <<- which(sent$word==input$wrd)+1
if(i > length(sent$word)) {
i <<- i - 1
}
i2(i)
})
output$change <- renderUI({
radioButtons("newLabel", label="Change value", choices=c('positive','negative'), sent$sentiment[which(sent2()$word==input$wrd)])
})
}
shinyApp(ui=ui, server=server)
The adjusted output is first stored in a reactiveVal named sent2(). This is required for you see the adjure values while running the Shiny App.
A selectizeInput() is used to scroll through the words (Q1).
radioButtons() are used to select positive and negative values. The default value is whatever value is currently applied to the corresponding word.
An actionButton() is used to make the change when wanted.
UPDATE: I added sent <<- out so that your sent dataframe actually gets updated. Be aware that this will overwrite the values you had stored in sent before.
UPDATE: Each time the action button is clicked, the index of the currently selected word is determined using which(). Then it is incremented and stored in i and i2(). The new index is used to determine the default value of selectizeInput(). This way, when no manual selection of words is done, you will scroll through all options. When a word is selected manually, you will continue incrementing from that word onwards. When the last word is reached, the value does not increment further

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)

Collect current user inputs and ignore/remove the previous inputs using R Shiny

I want to collect user inputs, and output them as a table/data frame within Shiny. Inspired by the post here (Collect All user inputs throughout the Shiny App), I was able to do this but with some issues below. Here I have attached the example code, where I intend to collect the user input using the reactive textInput box. The number of textInput boxes is according to the user input for "Number of box". The output table looks fine when I kept increasing the "Number of box", such as I increased the number of boxes from 1 to 2 (see attached screenshot 1 and 2). However, when I changed the number of boxes from 2 back to 1, the output table still showed the previous results "textBox2 the second input" (see screenshot 3). My question is how to remove this previous input ("textBox2 the second input") and only print out the current user input. Thanks!
Example code
library(shiny)
ui <- basicPage(
fluidRow(column(6,
numericInput("nbox", "Number of box", value = 1,
min = 1))),
fluidRow(column(6,style='padding:0px;',
wellPanel(
uiOutput("textInputBox"))),
column(6, style='padding:0px;',
tableOutput('show_inputs')))
)
server <- shinyServer(function(input, output, session){
output$textInputBox <- renderUI({
num <- as.integer(input$nbox)
lapply(1:num, function(i) {
textInput(paste0("textBox", i),
label = paste0("textBox", i))
})
})
AllInputs <- reactive({
myvalues <- NULL
for(i in 1:length(names(input))){
myvalues <- as.data.frame(rbind(myvalues,
(cbind(names(input)[i],input[[names(input)[i]]]))))
}
names(myvalues) <- c("User input variable","User input value")
myvalues
})
output$show_inputs <- renderTable({
AllInputs()
})
})
shinyApp(ui = ui, server = server)
Screen shot (1,2,3 in order)
Created inputs are not deleted when a new renderUI is called, so there is no solution this way.
A workaround is to use a counter (like the one previously used to create the input) and use it to rebuild the names:
AllInputs <- reactive({
num <- as.integer( input$nbox )
my_names <- paste0("textBox", 1:num)
all_values <- stack( reactiveValuesToList(input) )
myvalues <- all_values[ all_values$ind %in% c("nbox", my_names),
c(2, 1)]
names(myvalues) <- c("User input variable","User input value")
myvalues
})
Note the use of reactiveValuesToList to replace the loop.

accessing reactiveValues in a reactiveValuesToList

Instead of specifying separate fileInput variables, I'd like to use reactiveValues to store uploaded CSV dataframes, manipulate them in some way, and then store them for accession later. My design is to name each dataframe by its filename and append to the reactiveValue rvTL. My questions are,
How can I access individual dataframes under the list I created using reactiveValuesToList(rvTL)?
Next step, how to create a selectInput menu to access the individual dataframes uploaded by fileInput
To learn this concept, I am piggybacking off the answer from Dean Attali and made rvTL the same as his values variable.
R shiny: How to get an reactive data frame updated each time pressing an actionButton without creating a new reactive data frame?
I've gone over many example codes on reactiveValues, yet still at an incomplete understanding. Most examples are using some sort variation on reactiveValuesToList(input) R Shiny: Keep/retain values of reactive inputs after modifying selection, I'm really not seeing the logic here. Any help/suggestions would be appreciated!
library(shiny)
runApp(shinyApp(
ui=(fluidPage(
titlePanel("amend data frame"),
mainPanel(
fileInput("file", "Upload file", multiple=T),
tabsetPanel(type="tabs",
tabPanel("tab1",
numericInput("Delete", "Delete row:", 1, step = 1),
actionButton("Go", "Delete!"),
verbatimTextOutput("df_data_files"),
verbatimTextOutput("values"),
verbatimTextOutput("rvTL"),
tableOutput("rvTL_out")
),
tabPanel("tab2",
tableOutput("df_data_out")
)
)))),
server = (function(input, output) {
values <- reactiveValues(df_data = NULL) ##reactiveValues
rvTL <- reactiveValues(rvTL = NULL)
observeEvent(input$file, {
values$df_data <- read.csv(input$file$datapath)
rvTL[[input$file$name]] <- c(isolate(rvTL), read.csv(input$file$datapath))
})
observeEvent(input$Go, {
temp <- values$df_data[-input$Delete, ]
values$df_data <- temp
})
output$df_data_files <- renderPrint(input$file$name)
output$values <- renderPrint(names(values))
output$rvTL <- renderPrint(names(reactiveValuesToList(rvTL))[1] )
output$rvTL_out <- renderTable(reactiveValuesToList(rvTL)[[1]])
output$df_data_out <- renderTable(values$df_data)
})
))
It really is as straightforward as you thought. You were close too, just fell into some syntax traps. I made the following changes:
that c(isolate(.. call was messing things up, I got rid of it. It was leading to those "Warning: Error in as.data.frame.default: cannot coerce class "c("ReactiveValues", "R6")" to a data.frame" errors.
Also you were reusing the rvTL name too often which is confusing and can lead to conflicts, so I renamed a couple of them.
I also added a loaded file name list (lfnamelist) to keep track of what was loaded. I could have used names(rvTL$dflist) for this but it didn't occur to me at the time - and I also this is a useful example of how to organize related reactive values into one declaration.
And then I added rendered selectInput so you can inspect what is saved in the reactiveValue list.
So here is the adjusted code:
library(shiny)
runApp(shinyApp(
ui=(fluidPage(
titlePanel("amend data frame"),
mainPanel(
fileInput("file", "Upload file", multiple=T),
tabsetPanel(type="tabs",
tabPanel("rvTL tab",
numericInput("Delete", "Delete row:", 1, step = 1),
uiOutput("filesloaded"),
actionButton("Go", "Delete!"),
verbatimTextOutput("df_data_files"),
verbatimTextOutput("values"),
verbatimTextOutput("rvTL_names"),
tableOutput("rvTL_out")
),
tabPanel("values tab",
tableOutput("df_data_out")
)
)))),
server = (function(input, output) {
values <- reactiveValues(df_data = NULL) ##reactiveValues
rvTL <- reactiveValues(dflist=NULL,lfnamelist=NULL)
observeEvent(input$file, {
req(input$file)
values$df_data <- read.csv(input$file$datapath)
rvTL$dflist[[input$file$name]] <-read.csv(input$file$datapath)
rvTL$lfnamelist <- c( rvTL$lfnamelist, input$file$name )
})
observeEvent(input$Go, {
temp <- values$df_data[-input$Delete, ]
values$df_data <- temp
})
output$df_data_files <- renderPrint(input$file$name)
output$values <- renderPrint(names(values))
output$rvTL_names <- renderPrint(names(rvTL$dflist))
output$rvTL_out <- renderTable(rvTL$dflist[[input$lftoshow]])
output$df_data_out <- renderTable(values$df_data)
output$filesloaded <- renderUI(selectInput("lftoshow","File to show",choices=rvTL$lfnamelist))
})
))
And here is a screen shot:

Update two sets of radiobuttons reactively - shiny

I have read this (How do I make the choices in radioButtons reactive in Shiny?) which shows me how to update radioButtons in a reactive way. However, when I try and update two sets of buttons from the same data, only one set renders. Example:
Server:
# Create example data
Wafer <- rep(c(1:3), each=3)
Length <- c(1,1,2,1,1,1,3,5,1)
Width <- c(3,1,6,1,1,1,1,1,6)
dd <- data.frame(Wafer, Length, Width)
shinyServer(function(input, output, session){
# Create reactive dataframe to store data
values <- reactiveValues()
values$df <- data.frame()
# Get Lengths and Widths of wafer from user input
a <- eventReactive(input$do, {
subset(dd, Wafer %in% input$wafer, select = Length:Width)
})
# Update reactive data frame will all Lengths and Widths that have been selected by the user input
observe({
if(!is.null(a())) {
values$df <- rbind(isolate(values$df), a())
}
})
output$wl <- renderDataTable({ a() })
# Update radio buttons with unique Length and Widths stored in values$df
# Which ever "observe" I put first in the code, is the one which updates
# the radio buttons. Cut and paste the other way round and "width"
# updates but not "length" radio buttons
observe({
z <- values$df
updateRadioButtons(session, "length", choices = unique(z$Length), inline=TRUE)
})
observe({
z <- values$df
updateRadioButtons(session, "width", choices = unique(z$Width), inline=TRUE)
})
})
ui:
library(markdown)
shinyUI(fluidPage(
titlePanel("Generic grapher"),
sidebarLayout(
sidebarPanel(
numericInput("wafer", label = h3("Input wafer ID:"), value = NULL),
actionButton("do", "Search wafer"),
radioButtons("length", label="Length", choices=""),
radioButtons("width", label="Width", choices = "")
),
mainPanel(
dataTableOutput(outputId="wl")
)
)
)
)
In the above, radiobuttons do update but only for the first set of buttons in order of code i.e. above "length" updates but "width" doesn't. If I write them in reverse, "width" updates but "length" doesn't. Do I need to define a new session maybe?
It turns out that:
"it's because a JS error occurs if the choices argument isn't a
character vector."
I have posted an issue on Shiny's Github:
https://github.com/rstudio/shiny/issues/1093
This can be resolved by:
To fix your problem, you can either convert your choices to characters
using as.character or set selected to a random string such as "".
See:
Update two sets of radiobuttons - shiny

Update two sets of radiobuttons - shiny

I asked this question (Update two sets of radiobuttons reactively - shiny) yesterday but perhaps it was too messy to get a response. I have stripped the question down: why can't I get two sets of radiobuttons to update reactively:
server.R:
# Create example data
Wafer <- rep(c(1:3), each=3)
Length <- c(1,1,2,1,1,1,3,5,1)
Width <- c(3,1,6,1,1,1,1,1,6)
dd <- data.frame(Wafer, Length, Width)
shinyServer(function(input, output, session){
# Get Lengths from user input
a <- eventReactive(input$do, {
subset(dd, Wafer %in% input$wafer, select = Length)
})
# Get Widths from user input
b <- eventReactive(input$do, {
subset(dd, Wafer %in% input$wafer, select = Width)
})
#Observe and update first set of radiobuttons based on a(). Does
#render
observe({
z <- a()
updateRadioButtons(session, "length", choices = unique(z$Length), inline=TRUE)
})
#Observe and update second set of radiobuttons based on b(). Does
#not render
observe({
z <- b()
updateRadioButtons(session, "width", choices = unique(z$Width), inline=TRUE)
})
output$l <- renderDataTable({ a() })
output$w <- renderDataTable({ b() })
})
ui.R:
library(markdown)
shinyUI(fluidPage(
titlePanel("Generic grapher"),
sidebarLayout(
sidebarPanel(
numericInput("wafer", label = h3("Input wafer ID:"), value = NULL),
actionButton("do", "Search wafer"),
radioButtons("length", label="Length", choices=""),
radioButtons("width", label="Width", choices = "")
),
mainPanel(
dataTableOutput(outputId="l"),
dataTableOutput(outputId="w")
)))
)
In the above, I can only get one set of radiobuttons to reactive ("Length"). However, if I comment out the Length observe, the Width one works so my code must be OK in isolation. Maybe I'm missing something simple?
This might be a bug of the updateRadioButtons function. When selected is not set, it is replaced by the first choice. I guess this causes an error if the choices list is numeric.
To fix your problem, you can either convert your choices to characters using as.character or set selected to a random string such as "".
Using as.character is probably better as you then get your first selection automatically selected.

Resources