reactiveValues vs reactive; lazy loading from tabPanel [R Shiny] - r

I have a min reprex below. I have two tabs and I want the data to only load in the second tab when an user clicks on the second tab. The actual data in the second tab comes from an API so I only want it to load upon clicking (and not everytime the dashboard is loaded).
I want the data to load and give users a choice to add to it, by appending a row to the dataset.
For this reprex I have used iris dataset. I have used reactiveValues, and this seems to work fine except for one problem. It doesn't lazy load, the iris datasets get loaded when the dashboard is loaded (without having to navigate to the second tab).
library(shiny)
library(dplyr)
ui <- fluidPage(
navlistPanel(
tabPanel(
title = "Main Page" # Empty
)
,tabPanel(
title = "Iris"
,fluidRow(
column(
width = 6
,uiOutput(outputId = "choose_species")
)
,column(
width = 6
,uiOutput(outputId = "add_species")
,uiOutput(outputId = "add_measure")
,uiOutput(outputId = "ok")
)
)
,fluidRow(
column(
width = 6
,verbatimTextOutput(outputId = "print_df")
)
)
)
)
)
server <- function(input, output) {
df <- reactiveValues(iris_df = NULL)
observe({
print(is.null(df$iris_df))
})
df$iris_df <- iris %>%
mutate(Species = as.character(Species))
observe({
print(is.null(df$iris_df))
})
output$choose_species <- renderUI({
selectInput(
inputId = "input_choose_species"
,label = "Choose Species"
,choices = df$iris_df %>% distinct(Species)
)
})
output$add_species <- renderUI({
textInput(
inputId = "input_add_species"
,label = "Add Species"
,value = ""
)
})
output$add_measure <- renderUI({
numericInput(
inputId = "input_add_measure"
,label = "Add Measurements"
,value = ""
)
})
output$ok <- renderUI({
actionButton(
inputId = "input_ok"
,label = "Add New Species"
)
})
observeEvent(input$input_ok, {
req(
input$input_add_species
,input$input_add_measure
)
new_row <- c(rep(input$input_add_measure, 4), input$input_add_species)
df$iris_df <- df$iris_df %>% rbind(new_row)
})
output$print_df <- renderPrint({
req(input$input_choose_species)
df$iris_df %>%
filter(Species == input$input_choose_species)
})
}
shinyApp(ui = ui, server = server)
I have tried to solve this issue by using reactive() call instead, but now I am getting this error instead:
server <- function(input, output) {
df <- reactive({
iris %>%
mutate(Species = as.character(Species))
})
output$choose_species <- renderUI({
selectInput(
inputId = "input_choose_species"
,label = "Choose Species"
,choices = df() %>% distinct(Species)
)
})
output$add_species <- renderUI({
textInput(
inputId = "input_add_species"
,label = "Add Species"
,value = ""
)
})
output$add_measure <- renderUI({
numericInput(
inputId = "input_add_measure"
,label = "Add Measurements"
,value = ""
)
})
output$ok <- renderUI({
actionButton(
inputId = "input_ok"
,label = "Add New Species"
)
})
df <- eventReactive(input$input_ok, {
req(
input$input_add_species
,input$input_add_measure
)
new_row <- c(rep(input$input_add_measure, 4), input$input_add_species)
df() %>% rbind(new_row)
})
output$print_df <- renderPrint({
req(input$input_choose_species)
df() %>%
filter(Species == input$input_choose_species)
})
}
shinyApp(ui = ui, server = server)
Warning: Error in : evaluation nested too deeply: infinite recursion / options(expressions=)?
[No stack trace available]
I think I am close and probably missing something really obvious. TIA

I think it should be possible to make it work with reactive(), but it is easy to create an infinite loop, when modifying a reactive expression based on its own value.
An other approach is to use observeEvent() to delay creating a reactiveValue.
library(shiny)
library(dplyr)
ui <- fluidPage(
navlistPanel(id = 'tabs', # set id to allow the server to react to tab change
tabPanel(title = "Main Page" # Empty
)
,tabPanel(title = "Iris" # Title is value if no value is set
,fluidRow(
column(
width = 6
,uiOutput(outputId = "choose_species")
)
,column(
width = 6
,uiOutput(outputId = "add_species")
,uiOutput(outputId = "add_measure")
,uiOutput(outputId = "ok")
)
)
,fluidRow(
column(
width = 6
,verbatimTextOutput(outputId = "print_df")
)
)
)
)
)
server <- function(input, output) {
df = reactiveVal()
observeEvent(input$tabs, {
req(is.null(df()))
if (input$tabs == 'Iris') df(mutate(iris, Species = as.character(Species)))
})
output$choose_species <- renderUI({
req(df())
selectInput(
inputId = "input_choose_species"
,label = "Choose Species"
,choices = df() %>% distinct(Species)
)
})
output$add_species <- renderUI({
textInput(
inputId = "input_add_species"
,label = "Add Species"
,value = ""
)
})
output$add_measure <- renderUI({
numericInput(
inputId = "input_add_measure"
,label = "Add Measurements"
,value = ""
)
})
output$ok <- renderUI({
actionButton(
inputId = "input_ok"
,label = "Add New Species"
)
})
observeEvent(input$input_ok, {
req(
input$input_add_species
,input$input_add_measure
)
new_row <- c(rep(input$input_add_measure, 4), input$input_add_species)
df(df() %>% rbind(new_row))
})
output$print_df <- renderPrint({
req(input$input_choose_species)
df() %>%
filter(Species == input$input_choose_species)
})
}
shinyApp(ui = ui, server = server)

An alternative solution would be to replace your
df$iris_df <- iris %>%
mutate(Species = as.character(Species))
with the below.
observeEvent(input$tabs == "Iris",
{
df$iris_df <- iris %>%
mutate(Species = as.character(Species))
print("Loaded Iris")
},
ignoreInit = TRUE,
once = TRUE
)
As you can see in the console, this causes the dataset to be loaded on tab change, and only once.

Related

In R/Shiny how to make inputs have the same name of actionButton

I have a code that once I click on the option of my selectInput widget the input value is the names that are showed on the options.
I would like to make the same thing with my actionLink button but the input in this case is the sum of clicks. Is it possible to change the inputs values?
This is my code:
library(shiny)
library(dplyr)
library(purrr)
ui <- fluidPage(
tags$div(
id = "sidebar",
class = "sidebar",
selectInput(
inputId = "custom_select",
label = "Clubs",
choices = names(mtcars),
selectize = F,
size = 5,
width = "300px"
),
div(
names(mtcars) %>% map(~.x %>% actionLink(inputId = .x)))
),
h1(htmlOutput(outputId = 'title')),
h1(htmlOutput(outputId = 'title2')))
server <- function(input, output, session) {
output$title <- renderUI({
input$custom_select
})
output$title2 <- renderUI({
input[[names(mtcars)[1]]]
})
}
shinyApp(ui, server)
As you can see the output is the number of clicks.
For the selectInput widget it works fine.
Any help?
Not sure whether I got you right but using an observeEvent you could do:
library(shiny)
library(dplyr)
library(purrr)
ui <- fluidPage(
tags$div(
id = "sidebar",
class = "sidebar",
selectInput(
inputId = "custom_select",
label = "Clubs",
choices = names(mtcars),
selectize = F,
size = 5,
width = "300px"
),
div(
names(mtcars) %>% map(~ .x %>% actionLink(inputId = .x))
)
),
h1(htmlOutput(outputId = "title")),
h1(htmlOutput(outputId = "title2"))
)
server <- function(input, output, session) {
output$title <- renderUI({
input$custom_select
})
lapply(names(mtcars), function(x) {
observeEvent(input[[x]], {
output$title2 <- renderUI({
paste(x, input[[x]], sep = ": ")
})
})
})
}
shinyApp(ui, server)

How to set up actionButton() or actionBttn() to clear all selections in pickerInput()

When I click on the Action Button, I would like to clear everything: both the output and the selections in the picketInput() (input$engine and input$cylinder in the code below). For consistency if I can do it with shinyWidget's actionBttn, that will be great as well.
library(shiny)
library(shinyWidgets)
df <- mtcars
ui <- fluidPage(
sidebarPanel(
pickerInput("engine", "Select engine:", choices = unique(df$vs),
options = list(
`actions-box` = TRUE),
multiple = TRUE
),
pickerInput("cylinder", "Select cylinder:", choices = unique(df$cyl),
options = list(
`actions-box` = TRUE),
multiple = TRUE
),
actionButton("reset", "Clear Selection"),
),
mainPanel(
textOutput("results")
)
)
server <- function(input, output, session) {
data <- reactiveValues()
observeEvent(input$cylinder, {
tmp <- df
tmp1 <- tmp[tmp$vs %in% input$engine, ]
tmp2 <- tmp1[tmp1$cyl %in% input$cylinder, ]
data$tmp2 <- tmp2
})
output$results <- renderText({
if(is.null(data$tmp2)) return()
print(row.names(data$tmp2))
})
observeEvent(input$reset, {
updatePickerInput(session, "engine", NULL)
updatePickerInput(session, "cylinder", NULL)
data$tmp2 <- NULL
})
}
shinyApp(ui = ui, server = server)
You'll have to respect the order of updatePickerInput's parameters or name them. Your above approach would have updated the label.
Please see ?updatePickerInput and check the following:
library(shiny)
library(shinyWidgets)
library(datasets)
DF <- mtcars
ui <- fluidPage(
sidebarPanel(
pickerInput("engine", "Select engine:", choices = unique(DF$vs),
options = list(
`actions-box` = TRUE),
multiple = TRUE
),
pickerInput("cylinder", "Select cylinder:", choices = unique(DF$cyl),
options = list(
`actions-box` = TRUE),
multiple = TRUE
),
actionBttn("reset", "Clear Selection"),
),
mainPanel(
textOutput("results")
)
)
server <- function(input, output, session) {
data <- reactiveValues()
observeEvent(input$cylinder, {
tmp <- DF
tmp1 <- tmp[tmp$vs %in% input$engine, ]
tmp2 <- tmp1[tmp1$cyl %in% input$cylinder, ]
data$tmp2 <- tmp2
})
output$results <- renderText({
req(data$tmp2)
row.names(data$tmp2)
})
observeEvent(input$reset, {
updatePickerInput(session, inputId = "engine", selected = "")
updatePickerInput(session, inputId = "cylinder", selected = "")
data$tmp2 <- NULL
})
}
shinyApp(ui = ui, server = server)

Update Select Input Reactive Triggers Twice in R Shiny

I would like to update the options for the select input when someone chooses to filter for the cylinders. However, whenever I update the options in the select input by filtering for cylinders, the reactive fires two times. How can I avoid that?
library(tidyverse)
library(shiny)
library(DT)
data("mtcars")
mtcars <- mtcars %>% tibble::rownames_to_column(var = "cars")
ui <- fluidPage(
shiny::selectInput(
inputId = "cars",
label = "Cars",
choices = mtcars$cars,
selected = mtcars$cars,
multiple = TRUE
),
shiny::checkboxGroupInput(
inputId = "cyl",
label = "Cyl",
choices = unique(mtcars$cyl),
selected = unique(mtcars$cyl)
),
DT::dataTableOutput(outputId = "table")
)
server <- function(session, input, output) {
temp <- shiny::reactive({
temp <- mtcars %>%
dplyr::filter(cars %in% input$cars, cyl %in% input$cyl)
print("Reactive fires twice")
return(temp)
})
shiny::observeEvent(input$cyl, {
shiny::updateSelectInput(
session,
inputId = "cars",
choices = temp()$cars,
selected = temp()$cars
)
})
output$table <- DT::renderDataTable({
temp()
})
}
This solution uses reactive values and I believe avoids the double trigger as it separates trigger events.
library(tidyverse)
library(shiny)
library(DT)
data("mtcars")
mtcars <- mtcars %>% rownames_to_column(var = "cars")
ui <- fluidPage(
selectInput(
inputId = "cars",
label = "Cars",
choices = mtcars$cars,
selected = mtcars$cars,
multiple = TRUE
),
checkboxGroupInput(
inputId = "cyl",
label = "Cyl",
choices = unique(mtcars$cyl),
selected = unique(mtcars$cyl)
),
dataTableOutput(outputId = "table")
)
server <- function(session, input, output) {
r <- reactiveValues(
temp = mtcars
)
observeEvent(input$cyl, ignoreNULL = FALSE, {
r$temp <- mtcars %>%
filter(cyl %in% input$cyl)
updateSelectInput(session,"cars",choices = r$temp$cars, selected = r$temp$cars)
print(input$cyl)
})
observeEvent(input$cars, ignoreNULL = FALSE, {
r$temp <- mtcars %>%
filter(cars %in% input$cars)
})
output$table <- DT::renderDataTable({
r$temp
})
}
shinyApp(ui,server)
Here is a solution using a reactive value instead of a reactive conductor, a priority level for the observers, and freezeReactiveValue:
library(shiny)
library(DT)
data("mtcars")
mtcars <- mtcars %>% tibble::rownames_to_column(var = "cars")
ui <- fluidPage(
selectInput(
inputId = "cars",
label = "Cars",
choices = mtcars[["cars"]],
selected = mtcars[["cars"]],
multiple = TRUE
),
checkboxGroupInput(
inputId = "cyl",
label = "Cyl",
choices = unique(mtcars[["cyl"]]),
selected = unique(mtcars[["cyl"]])
),
DTOutput(outputId = "table")
)
server <- function(session, input, output) {
Temp <- reactiveVal()
observeEvent(list(input[["cars"]], input[["cyl"]]), {
temp <- mtcars %>%
dplyr::filter(cars %in% input[["cars"]], cyl %in% input[["cyl"]])
Temp(temp)
}, priority = 2) # higher priority than the other observer
observeEvent(input[["cyl"]], {
freezeReactiveValue(input, "cars") # prevents the above observer to trigger
updateSelectInput(
session,
inputId = "cars",
choices = mtcars[["cars"]], # don't use Temp() here, otherwise you can't select the removed items
selected = Temp()[["cars"]]
)
}, priority = 1)
output[["table"]] <- renderDT({
Temp()
})
}
shinyApp(ui, server)

Upload a csv file with actionbutton and display a corrplot

I tried to make a web application with R::shiny but I met a problem with a piece of code. Indeed, I would like to upload a csv file and display a correlogram.
I tried to set up the correlogram with the actionbutton() followed by the updateSelectizeInput()
However an error has been occured :
Error: Unsupported index type: NULL
Anybody have a solution ? thanks
NB - I don't want to use the fileInput widget to upload the csv file ! Only by the actionbutton !
library(shiny)
library(readr)
library(corrplot)
library(DT)
# File used for the example
data(iris)
write.csv(x = iris, file = "iris.csv")
#UI
ui <- shinyUI(
fluidPage(
navbarPage(
id = "navbar",
tabPanel(
title = "UPLOAD",
br(),
actionButton(inputId = "file", label = "ADD A FILE")
)
)
)
)
#SERVER
server <- function(input, output, session) {
path <- reactiveValues(pth = NULL)
file.choose2 <- function(...) {
pathname <- NULL;
tryCatch({
pathname <- file.choose();
}, error = function(ex) {
})
pathname;
}
observeEvent(input$file,{
path$pth <- file.choose2()
})
observeEvent(input$file, {
newvalue <- "B"
updateNavbarPage(session, "navbar", newvalue)
})
data <- reactive({
df <- readr::read_csv(file = path$pth)
return(df)
})
observeEvent(input$file, {
appendTab(
inputId = "navbar",
tabPanel(
value = "B",
title = "Corr",
sidebarLayout(
sidebarPanel(
selectizeInput(
inputId = "select04",
label = "Select features",
choices = NULL,
multiple = TRUE)
),
mainPanel(
plotOutput(
outputId = "corrplot01", height = "650px")
)
)
)
)
}, once = TRUE)
# I suppose there is a problem with this line
observeEvent(input$select04, {
col <- names(data())
col.num <- which(sapply(data(), class) == "numeric")
col <- col[col.num]
updateSelectizeInput(session = session, inputId = "select04", choices = col)
})
output$corrplot01 <- renderPlot({
df <- data()
df1 <- df[,input$select04]
corr <- cor(x = df1, use = "pairwise.complete.obs")
corrplot(corr = corr,
title = "")
})
}
shinyApp(ui, server)
I changed your ui and server a bit, but I think that might solve your problem.
I deleted the observeEvent(input$file, ...{}) from the server and added the ui part in the Ui directly.
I also added 3 req() calls in the data reactive, in the second observeEvent(input$select04, ...{}) which I changed to a normal observe and in the renderPlot call.
library(shiny)
library(readr)
library(corrplot)
library(DT)
# File used for the example
data(iris)
write.csv(x = iris, file = "iris.csv", row.names = F)
#UI
ui <- shinyUI(
fluidPage(
navbarPage(
id = "navbar",
tabPanel(
title = "UPLOAD",
br(),
actionButton(inputId = "file", label = "ADD A FILE"),
tabPanel(
value = "B",
title = "Corr",
sidebarLayout(
sidebarPanel(
selectizeInput(width = "300px",
inputId = "select04",
label = "Select features",
choices = NULL,
multiple = TRUE)
),
mainPanel(
plotOutput(
outputId = "corrplot01", height = "650px")
)
)
)
)
)
)
)
#SERVER
server <- function(input, output, session) {
path <- reactiveValues(pth = NULL)
file.choose2 <- function(...) {
pathname <- NULL;
tryCatch({
pathname <- file.choose();
}, error = function(ex) {
})
pathname;
}
observeEvent(input$file,{
path$pth <- file.choose2()
})
observeEvent(input$file, {
newvalue <- "B"
updateNavbarPage(session, "navbar", newvalue)
})
data <- reactive({
req(path$pth)
df <- readr::read_csv(file = path$pth)
return(df)
})
# I suppose there is a problem with this line
observe({
req(names(data()))
col <- names(data())
col.num <- which(sapply(data(), class) == "numeric")
col <- col[col.num]
updateSelectizeInput(session = session, inputId = "select04", choices = col)
})
output$corrplot01 <- renderPlot({
req(input$select04)
df <- data()
df1 <- df[,input$select04]
corr <- cor(x = df1, use = "pairwise.complete.obs")
corrplot(corr = corr,
title = "")
})
}
shinyApp(ui, server)

Select columns from expression in shiny app

I am trying to build a shiny dashboard app, where I process the input data and produce summary statistics based on the user supplied grouping variables. The last step, where I am stuck is to
implement a working function , which enables the user to select to display only a subset of the columns after the calculation of the summary statistics.
My attempt is in the lines after output$select_col in global.R . Right now each time I try to use the selector shiny crashes with the error "incorrect number of dimensions".
global.R
# Shiny
library(shiny)
library(shinydashboard)
library(shinyjs)
# Data tools
library(dplyr)
library(tidyr)
library(tibble)
library(data.table)
server.R
server <- function(input, output) {
raw_tables<-reactive({
mtcars
})
output$cyl <- renderUI({
selectInput(inputId = "cyl",
label = "Which number of cyl to consider",
choices = c(4,6,8),
selected = NULL,
multiple=TRUE)
})
filtered_tables<-
reactive({
if(is.null(input$cyl)){
data_filtered <- raw_tables()
}
else{
data_filtered <- raw_tables() %>% filter(cyl %in% input$cyl)
}
})
new_statistics <- reactive({
if(is.null(filtered_tables())){
return(NULL)
}
if(length(input$grouping_variables) == 0){
op <- filtered_tables() %>%
ungroup()
} else {
op <- filtered_tables() %>%
group_by_(.dots = input$grouping_variables)
}
op %>% #
summarise(nr_cars = n(),
mean_mpg = mean(mpg,na.rm=T),
sd_mpg = sd(mpg,na.rm=T))
})
nice_table <-reactive({
if(is.null(new_statistics())){
return(NULL)
}
DT::datatable(new_statistics(),
colnames = c(
"nbr cars"="nr_cars" ,
"mean mpg"="mean_mpg",
"sd mpg"="sd_mpg"
), selection = list(target = 'column') , extensions = c('ColReorder'), options = list(colReorder = TRUE)
) %>%
DT::formatRound(columns=c(
"nbr cars" ,
"mean mpg",
"sd mpg"),
digits=2)
})
output$select_col <- renderUI({
if(is.null(nice_table())){
return(NULL)
}
selectInput("col", "Select columns:", choices = colnames(nice_table()), selected=NULL, multiple=TRUE)
})
output$statistics = DT::renderDataTable({
if(length(input$col)>0)
{
return(DT::datatable(nice_table()[, colnames(nice_table()) %in% input$col]))
}
else
{
return(NULL)
}
})
}
ui.R
dbHeader <- dashboardHeader(title = "test",
titleWidth = 250)
sidebar <- dashboardSidebar(
width = sidebarWidth,
br(),
sidebarMenu(
menuItem(text = "Data View",
tabName = "dat_view",
icon = icon("cloud-download")
)
)
)
body <- dashboardBody(
# Add shinyJS mini-sidebar
shinyjs::useShinyjs(),
tabItems(
tabItem(tabName = "dat_view",
fluidPage(
sidebarLayout(
sidebarPanel(width=2,
selectInput(inputId = 'grouping_variables',
label = 'Which grouping var?',
choices = c("cyl","gear","carb"),
selected = NULL,
multiple=TRUE,
selectize=TRUE),
uiOutput("cyl"),
uiOutput("select_col")
)
,
mainPanel(
tabsetPanel(id="dat_view_tabs",
tabPanel(
'statistics',
DT::dataTableOutput(outputId='statistics')
)
)
)
)))))
ui <- dashboardPage(skin = "blue",
header = dbHeader,
sidebar = sidebar,
body = body)

Resources