I want to update a dataframe with user input in an R Shiny app. The user selects a row of data, and then chooses the value to update the Species column with using selectInput. The updates need to accumulate each time, i.e., the new updates update the previously updated data. I tried using reactiveValues as per this answer, but couldn't get it to work.
library(shiny)
library(reactable)
library(tidyverse)
iris_df = iris %>%
mutate(
id = row_number(),
Species = as.character(Species)
)
ui <- fluidPage(
navbarPage(
"HELP!",
tabPanel(
"Iris",
sidebarLayout(
sidebarPanel(
selectInput("update_species", "Update species", choices = c("Rose", "Daffodil"))
),
mainPanel(fluidRow(reactableOutput("iris")))
)
)
)
)
server <- function(input, output) {
observeEvent(input$update_species, {
iris_df = iris_df %>%
mutate(Species = case_when(id == selected_row() ~ input$update_species, TRUE ~ Species))
})
selected_row = reactive(getReactableState("iris", "selected"))
output$iris = renderReactable({
reactable(
iris_df,
selection = "single",
)
})
}
shinyApp(ui = ui, server = server)
The first issue with your code is that the observeEvent is triggered by input$update_species which results in an error if no row was selected in which case selected_row() is NULL. To prevent that you could add a req(selected_row()) or use selected_row() to trigger the observeEvent as I do in my code below.
Next, as you already realized you need a reactiveVal or a reactiveValues to actually update the dataframe inside the observeEvent based on the user choice.
library(shiny)
library(reactable)
library(tidyverse)
iris_df <- iris %>%
mutate(
id = row_number(),
Species = as.character(Species)
)
ui <- fluidPage(
navbarPage(
"HELP!",
tabPanel(
"Iris",
sidebarLayout(
sidebarPanel(
selectInput("update_species", "Update species", choices = unique(iris_df$Species))
),
mainPanel(fluidRow(reactableOutput("iris")))
)
)
)
)
server <- function(input, output) {
iris_df <- reactiveVal(iris_df)
observeEvent(selected_row(), {
iris_df(iris_df() %>%
mutate(Species = case_when(id == selected_row() ~ input$update_species, TRUE ~ Species)))
})
selected_row <- reactive(getReactableState("iris", "selected"))
output$iris <- renderReactable({
reactable(
iris_df(),
selection = "single",
)
})
}
shinyApp(ui = ui, server = server)
And this is an example output after updating rows 1, 4 and 7:
Related
I'm working on a project where table is filtered based on the Inputs provided by the user. There are three selectInput conditions.
For better understanding lets assume the mtcars data. User can first select the number of cylinders, then the user should see a selectInput list of number of gears filtered for given value of cylinder. (**for instance, if number of cylinder is 4, then number of gears should be either 4,3,5 **)
Similarly, after selecting the Number of Cylinders and Number of gears the user must see the value of Transmission type as either 0,1.
The table should be updated and filtered based on the selected inputs.
I have tried the given code. Please help me.
#loading libraries
library(tidyverse)
library(shiny)
library(DT)
#using mtcars as dataset
df <- read.csv("https://gist.githubusercontent.com/seankross/a412dfbd88b3db70b74b/raw/5f23f993cd87c283ce766e7ac6b329ee7cc2e1d1/mtcars.csv")
# Define UI for application
ui <- fluidPage(
# Application title
titlePanel("Details of Given Cars"),
# Sidebar for input filter
sidebarLayout(
sidebarPanel(
selectInput("cylinder","Number of Cylinders",unique(df$cyl)),
selectizeInput("gears","Number of gears",choices = NULL),
selectizeInput("gearbox","Transmission Type 'AUTO=0'",choices = NULL)
),
# Show a table
mainPanel(
DT::DTOutput("table")
)
)
)
# Define server logic required
server <- function(input, output,session) {
#----reactive calculations
cyl_sel <- reactive({
df %>% filter(cyl == input$cylinder)
})
observeEvent(cyl_sel(),{
updateSelectizeInput(session,"gears", choices = cyl_sel()$gear)
# })
gearbox_sel <- reactive({
cyl_sel() %>% filter(am == input$gears)
})
observeEvent(gearbox_sel,{
updateSelectizeInput(session,"gearbox",choices = gearbox_sel()$am)
output$table <- DT::renderDT({
df %>% filter(cyl == input$cylinder,
gear == input$gears)
# am== input$gearbox) # commented because output is not shown when uncommented
})
})
})
}
# Run the application
shinyApp(ui = ui, server = server)
You could use selectizeGroupUI from library(shinyWidgets) to achive this:
library(datasets)
library(shiny)
library(shinyWidgets)
library(DT)
df <- mtcars
ui <- fluidPage(
titlePanel("Details of Given Cars"),
sidebarLayout(
sidebarPanel(
selectizeGroupUI(
id = "my-filters",
params = list(
cyl = list(inputId = "cyl", title = "Number of Cylinders:"),
gear = list(inputId = "gear", title = "Number of gears:"),
am = list(inputId = "am", title = "Transmission Type 'AUTO=0':")
),
inline = FALSE
)
),
mainPanel(
DT::DTOutput("table")
)
)
)
server <- function(input, output, session) {
res_mod <- callModule(
module = selectizeGroupServer,
id = "my-filters",
data = df,
vars = c("cyl", "gear", "am"),
inline = FALSE
)
output$table <- DT::renderDT(res_mod())
}
shinyApp(ui = ui, server = server)
I have a simple shiny that presents descriptive statistics using reactive. However, I would like to use ifelse within tidyverse pipe (and not writing tons of codes). However, I´m not being able to do that. I checked previous post but it´s not working as well. I imagine this part is close to what I want:
students_results <- reactive({
ds %>%
if (input$all_quest == TRUE) { do nothing here!! } else {
filter(domain == input$domain) %>%
group_by(input$quest)
}
summarise(mean(test))
This code is 100% working,
library(shiny)
library(tidyverse)
library(DT)
ds <- data.frame(quest = c(2,4,6,8), domain = c("language", "motor"), test = rnorm(120, 10,1))
ui <- fluidPage(
sidebarLayout(
tabPanel("student",
sidebarPanel(
selectInput("domain", "domain", selected = "language", choices = c("language", "motor")),
selectInput("quest", "Questionnaire", selected = "2", choices = unique(ds$quest)),
checkboxInput("all_quest",
label = "Show all questionnaires",
value = FALSE)
)
),
mainPanel(
dataTableOutput("table")
)
)
)
server <- function(input, output) {
students_results <- reactive({
if (input$all_quest == TRUE) {
ds %>%
group_by(quest, domain) %>%
summarise(mean(test))
}
else {
ds %>%
filter(domain == input$domain) %>%
group_by(input$quest) %>%
summarise(mean(test))
}
})
output$table <- renderDataTable({
students_results()
}
)
}
shinyApp(ui = ui, server = server)
Please check the akrun response below. Everything is working.
We may need to use {} to block the code between the %>%
students_results <- reactive({
ds %>%
{
if (input$all_quest == TRUE) {
.
} else {
{.} %>%
filter(domain == input$domain) %>%
group_by(input$quest)
}
}%>%
summarise(mean(test))
})
Another option is purrr::when which can help to build case_when like pipes. Note that I changed the example code slightly to better show how its working.
library(shiny)
library(tidyverse)
library(DT)
ds <- data.frame(quest = c(2,4,6,8), domain = c("language", "motor"), test = rnorm(120, 10,1))
ui <- fluidPage(
sidebarLayout(
tabPanel("student",
sidebarPanel(
selectInput("domain", "domain", selected = "language", choices = c("language", "motor")),
selectInput("quest", "Questionnaire", selected = "2", choices = unique(ds$quest)),
checkboxInput("all_quest",
label = "Show all questionnaires",
value = FALSE)
)
),
mainPanel(
dataTableOutput("table")
)
)
)
server <- function(input, output) {
students_results <- reactive({
ds %>%
when(input$all_quest == TRUE ~ .,
~ filter(., domain == input$domain) %>%
filter(quest == input$quest) %>%
summarise(mean(test))
)
})
output$table <- renderDataTable({
students_results()
}
)
}
shinyApp(ui = ui, server = server)
I have an app, which fetches data from an SQL-db, then allows the user to edit it, and this should be saved to the DB. In the repex I have used a CSV-file, but the logic should still be comparable.
However, the data is saved in the session once I edit the column value, but if I switch input or close the app and re-open, it's back to the original. Edits are not reflected in the summary table. What am I doing wrong?
# Load libraries
library(DT)
library(gt)
library(shiny)
library(shinydashboard)
library(dplyr)
# Load data (run once for replication; in real use case will be a DB-connection)
#gtcars_tbl <- gtcars
#write.csv(gtcars_tbl, "gtcars_tbl.csv", row.names = FALSE)
# Simple UI
sidebar <- dashboardSidebar(
sidebarMenu(
menuItem("Summary table", tabName = "summary", icon = icon("project-diagram")),
menuItem("Edit table", tabName = "edit", icon = icon("project-diagram")),
uiOutput("country")
)
)
body <- dashboardBody(
tabItems(
tabItem(tabName = "summary",
h2("Summary of GT Cars"),
gt_output(outputId = "gt_filt_tbl")
),
tabItem(tabName = "edit",
h2("Editer GT Cars"),
DTOutput("edit")
)
)
)
ui <- dashboardPage(
dashboardHeader(title = "GT Cars"),
sidebar,
body)
# Define server functions
server <- function(input, output, session) {
# Load data
gtcars_tbl <- read.csv("gtcars_tbl.csv")
countries <- sort(as.vector(unique(gtcars_tbl$ctry_origin)))
# Create dropdown output
output$country <- renderUI({
selectInput("country", "Country", countries)
})
# Create reactive table
gt_tbl_react <- reactiveVal(NULL)
gt_tbl_react(gtcars_tbl)
# Create filtered table
gt_filt_tbl <- reactive({
req(input$country)
gt_tbl_react() %>%
filter(ctry_origin == input$country)
})
# Render summary table
output$gt_filt_tbl <- render_gt({
gt_filt_tbl() %>%
group_by(ctry_origin, mfr) %>%
summarise(
N = n(),
Avg_HP = mean(hp),
MSRP = mean(msrp)
) %>%
gt(
rowname_col = "ctry_origin",
groupname_col = "mfr")
})
# Render editable table
output$edit <- renderDT(
gt_tbl_react() %>%
filter(ctry_origin == input$country),
selection = 'none', editable = TRUE,
rownames = TRUE,
extensions = 'Buttons'
)
observeEvent(input$edit_cell_edit, {
gtcars_tbl[input$edit_cell_edit$row,input$edit_cell_edit$col] <<- input$edit_cell_edit$value
write.csv(gtcars_tbl, "gtcars_tbl.csv", row.names = FALSE)
})
}
# Run app
shinyApp(ui, server)
The issue is that input$edit_cell_edit$row and input$edit_cell_edit$col are provided according to the subsetted dataframe that is displayed whereas you are changing the values on complete dataframe.
Use this in observeEvent -
observeEvent(input$edit_cell_edit, {
inds <- which(gtcars_tbl$ctry_origin == input$country)
gtcars_tbl[inds[input$edit_cell_edit$row],input$edit_cell_edit$col] <- input$edit_cell_edit$value
write.csv(gtcars_tbl, "gtcars_tbl.csv", row.names = FALSE)
})
The user interface of the Shiny app I'm working on is supposed to work in the following manner:
User finds the desired observation(s) after applying a set of filters.
User clicks "Add" action button, so selected observation(s) are added to a running list/vector/etc of observations to be analyzed.
User modifies filters to find other observations which are to be included as well.
Loop back to step 1 as many times as user desires.
I cannot seem to find a way to save this list of observations to be analyzed. In the example I attached, the "observation ID" is the name of the model of the car (mtcars is used). I also did not include any data analysis, since I do not think that's necessary. In essence, the entire dataset (mtcars) should be filtered using dplyr in a reactive environment to only include the running list of selected observations.
Here's the code:
data("mtcars")
mtcars$model <- rownames(mtcars)
ui <- fluidPage(
titlePanel("sample"),
sidebarLayout(
sidebarPanel(
uiOutput("disp"),
uiOutput("qsec"),
uiOutput("model"),
actionButton("add", "Add"),
uiOutput("selectedModel")
),
mainPanel(
plotOutput("data_analysis")
)
)
)
server <- function(input, output) {
output$disp <- renderUI({
selectInput(
"disp_sel",
"Select disp:",
unique(mtcars$disp),
selected = NULL,
multiple = T,
selectize = T
)
})
output$qsec <- renderUI({
temp = mtcars
if (!is.null(input$disp_sel)){temp = temp %>% filter(disp %in% input$disp_sel)}
selectInput(
"qsec_sel",
"Select qsec:",
unique(temp$qsec),
selected = NULL,
multiple = T,
selectize = T
)
})
output$model <- renderUI({
temp = mtcars
if (!is.null(input$disp_sel)){temp = temp %>% filter(disp %in% input$disp_sel)}
if (!is.null(input$qsec_sel)){temp = temp %>% filter(qsec %in% input$qsec_sel)}
selectInput(
"model_sel",
"Select model:",
unique(temp$model),
selected = NULL,
multiple = T,
selectize = T
)
})
output$selectedModel <- renderUI({
req(input$add)
selectInput(
"list_of_selections",
"Selected models:",
unique(mtcars$model),
selected = NULL, # this should change when "Add" is pressed
multiple = T,
selectize = T
)
})
r_data = eventReactive(input$add,{
mtcars %>% filter(model %in% input$list_of_selections)
})
output$data_analysis <- renderPlot({
# do something with r_data (filtered data)
})
}
# Run the application
shinyApp(ui = ui, server = server)
I've looked into modular code, reactive lists, and other stuff I don't even remember... Any help is greatly appreciated.
Try this
data("mtcars")
mtcars$model <- rownames(mtcars)
df1 <- mtcars
ui <- fluidPage(
titlePanel("sample"),
sidebarLayout(
sidebarPanel(
uiOutput("disp"),
uiOutput("qsec"),
uiOutput("model"),
actionButton("add", "Add"),
uiOutput("selectedModel")
),
mainPanel(
DTOutput("selecteddata"),
plotOutput("data_analysis")
)
)
)
server <- function(input, output) {
output$disp <- renderUI({
selectInput(
"disp_sel",
"Select disp:",
unique(mtcars$disp),
selected = NULL,
multiple = T,
selectize = T
)
})
output$qsec <- renderUI({
temp = mtcars
if (!is.null(input$disp_sel)){temp = temp %>% filter(disp %in% input$disp_sel)}
selectInput(
"qsec_sel",
"Select qsec:",
unique(temp$qsec),
selected = NULL,
multiple = T,
selectize = T
)
})
output$model <- renderUI({
temp = mtcars
if (!is.null(input$disp_sel)){temp = temp %>% filter(disp %in% input$disp_sel)}
if (!is.null(input$qsec_sel)){temp = temp %>% filter(qsec %in% input$qsec_sel)}
selectInput(
"model_sel",
"Select model:",
unique(temp$model),
selected = NULL,
multiple = T,
selectize = T
)
})
selected_data <- eventReactive(input$add,{
df1 %>% filter(model %in% input$model_sel)
})
output$selecteddata <- renderDT(
selected_data(), # reactive data
class = "display nowrap compact", # style
filter = "top", # location of column filters
options = list( # options
scrollX = TRUE # allow user to scroll wide tables horizontally
)
)
output$selectedModel <- renderUI({
req(input$add)
selectInput(
"list_of_selections",
"Selected models:",
choices = unique(selected_data()$model),
selected = unique(selected_data()$model), # this should change when "Add" is pressed
multiple = T,
selectize = T
)
})
r_data = eventReactive(input$add,{
mtcars %>% filter(model %in% input$list_of_selections)
})
output$data_analysis <- renderPlot({
ggplot(data=selected_data(), aes(x=disp, y=qsec)) + geom_point()
# do something with r_data (filtered data)
})
}
# Run the application
shinyApp(ui = ui, server = server)
Found the answer. I included
selected <- reactiveValues(s = NULL)
observeEvent(input$add,{selected$s = c(selected$s, input$model})
into the server part. Then the selected models are stored in selected$s.
When using filtering and the verbatimTextOutput function in R Shiny, rows go seemingly go missing when I select more than one of the input choices in my checkboxGroupInput.
Below is my code. Any advice?
Thanks in advance.
infantmort <- read.csv("infantmort.csv", header = TRUE)
ui <- fluidPage(
checkboxGroupInput("regioninputID",
"Select Region(s)",
choices = unique(infantmort$whoregion)
),
mainPanel(
verbatimTextOutput("regionoutputID"), width = "auto", height = "auto"
)
)
server <- function(input, output) {
dataset <- reactive({
as.data.frame(infantmort %>% select(whoregion, year, deathsinthousands) %>%
filter(whoregion == input$regioninputID) )
})
output$regionoutputID <- renderPrint({ dataset()
})
}
shinyApp(ui = ui, server = server)
You need to change your filter from == to %in%
The following should do the trick
server <- function(input, output) {
dataset <- reactive({
as.data.frame(infantmort %>% select(whoregion, year, deathsinthousands) %>%
filter(whoregion %in% input$regioninputID) )
})