Colleagues,
I'm creating a Shiny app that can generate a data set with user-defined properties. The intended data-generation function can take some time, so I've substituted a very simple one.
My problem is that the app seems to just hang, or nothing happens at all, when I hit the GO button.
DEBUG in Rstudio shows nothing, and reactlog also gives no information.
Similar questions on this stackoverflow forum are more than 8 years old, and suggestions don't seem to work either.
I'm sure the solution is head-slapping simple but, right now, I'm lost.
Any suggestions from those more knowledgeable than this Shiny newbie?
## generate data set with user-defined parameters
## load libraries
library(shiny)
library(ggplot2)
library(DT)
##
options(shiny.reactlog = TRUE)
# Define UI
ui <- fluidPage(
# Application title
titlePanel("Synthesise data"),
# Sidebar
sidebarLayout(
sidebarPanel(
## Sample size
numericInput("sample_n", "Sample size", 30,
min = 10, max = 300, step = 1
),
h4("Desired scale moments"),
numericInput("target_mean", "Target mean", 4),
numericInput("target_sd", "Target st dev", 1),
hr(style = "border-top: 1px solid #000000;"),
actionButton("goButton", "Go!"),
actionButton("goChart", "show chart"),
),
mainPanel(
h4("generated data"),
# table of generated data
DT::dataTableOutput("mytable"),
# Show a plot of the generated distribution
plotOutput("resultPlot")
)
)
)
# Define server logic
server <- function(input, output) {
mytable <- reactive(input$goButton, {
## substituting data-gen function that can take some time
mydata <- rnorm(sample_n, target_mean, target_sd) |>
data.frame()
colnames(mydata) <- "scale"
# saveRDS(mydata, file = "generatedData.RDS")
output$mytable <- DT::renderDataTable(DT::datatable({
mydata
}))
})
myplot <- eventReactive(input$goChart, {
output$resultPlot <- renderPlot({
ggplot(mydata, aes(x = scale)) +
geom_density()
})
})
}
# Run the application
shinyApp(ui = ui, server = server)
Few code errors here :
forgot input$ when using sample_n, target_mean and target_sd in server
put some output definition inside eventReactive or reactive is a terrible habit
reactive is not used like you did. EventReactive is what you needed here.
Here is a corrected version of you code
library(shiny)
library(ggplot2)
library(DT)
ui <- fluidPage(
titlePanel("Synthesise data"),
sidebarLayout(
sidebarPanel(
numericInput("sample_n", "Sample size", 30,
min = 10, max = 300, step = 1
),
h4("Desired scale moments"),
numericInput("target_mean", "Target mean", 4),
numericInput("target_sd", "Target st dev", 1),
hr(style = "border-top: 1px solid #000000;"),
actionButton("goButton", "Go!"),
actionButton("goChart", "show chart"),
),
mainPanel(
h4("generated data"),
DT::dataTableOutput("mytable"),
plotOutput("resultPlot")
)
)
)
server <- function(input, output) {
mydata <- eventReactive(input$goButton, {
mydata <- data.frame(scale = rnorm(input$sample_n, input$target_mean, input$target_sd))
return(mydata)
})
output$mytable <- DT::renderDataTable(DT::datatable(
mydata()
))
output$resultPlot <- renderPlot({
input$goChart
isolate(ggplot(mydata(), aes(x = scale)) +
geom_density())
})
}
shinyApp(ui = ui, server = server)
Related
My shiny app generates a number of useful graphs. I would like to allow the user to download the graphs in various formats.
I have done this before for a single graph using How to save plots that are made in a shiny app as a guide. However, I am ending up creating more repeated code for each additional plot. I am not a programmer, but it really seems like I should be able to write one function to do this since I am just passing parameters to downloadHandler and ggsave, but I can't figure it out.
The MRE below represents a page with, say, ten different graphs. Is there a way to write a single function that receives the plot ID from a button (like a tag or something?) and the format from the selectInput to pass those parameters to downloadHandler and ggsave to save each of those graphs in the selected format? The function at the bottom shows my thinking, but I don't know where to go from here or if that is even the right direction.
Thanks!
library(shiny)
library(ggplot2)
# Define UI for application that draws a histogram
ui <- fluidPage(
# Application title
titlePanel("Old Faithful Geyser Data"),
# Sidebar with a slider input for number of bins
sidebarLayout(
sidebarPanel(
sliderInput("bins",
"Number of bins:",
min = 1,
max = 50,
value = 30)
),
# Show plots and download buttons
mainPanel(
plotOutput("distPlot"),
fluidRow(
column(3,
downloadButton("dl_plot1")
),
column(3,
selectInput("plot1_format",label = "Format",choices = c("SVG","PDF","JPEG","PNG"),width = "75px")
)
),
plotOutput("scat_plot"),
column(3,
downloadButton("dl_plot2")
),
column(3,
selectInput("plot2_format",label = "Format",choices = c("SVG","PDF","JPEG","PNG"),width = "75px")
)
)
)
)
# Define server logic required to draw a histogram and scatterplot
server <- function(input, output) {
output$distPlot <- renderPlot({
x <- faithful$waiting
binwidth<-(max(x)-min(x))/input$bins
p<-ggplot(faithful,aes(waiting))+
geom_histogram(binwidth = binwidth)
p
})
output$scat_plot<-renderPlot({
p<-ggplot(faithful,aes(x=waiting,y=eruptions))+
geom_point()
p
})
downloadPlot <- function(plot_name,file_name,file_format){#concept code
downloadHandler(
filename=function() { paste0(file_name,".",file_format)},
content=function(file){
ggsave(file,plot=plot_name,device=file_format)
}
)
}
}
# Run the application
shinyApp(ui = ui, server = server)
To achieve your desired result without duplicating code you could (or have to) use a Shiny module. Basically a module is a pair of an UI function and a server function. For more on modules I would suggest to have a look at e.g. Mastering shiny, ch. 19.
In the code below I use a module to take care of the download part. The job of downloadButtonUI and downloadSelectUI is to add a download button and a selectInput for the file format. The downloadServer does the hard work and saves the plot in the desired format.
Note: Besides the download module I moved the code for the plots to reactives so that the plots could be passed to the downloadHandler or the download module.
EDIT: Added a fix. We have to pass the reactive (e.g. dist_plot without parentheses) to the download server and use plot() inside the downloadServer instead to export the updated plots.
library(shiny)
library(ggplot2)
# Download Module
downloaButtondUI <- function(id) {
downloadButton(NS(id, "dl_plot"))
}
downloadSelectUI <- function(id) {
selectInput(NS(id, "format"), label = "Format", choices = c("SVG", "PDF", "JPEG", "PNG"), width = "75px")
}
downloadServer <- function(id, plot) {
moduleServer(id, function(input, output, session) {
output$dl_plot <- downloadHandler(
filename = function() {
file_format <- tolower(input$format)
paste0(id, ".", file_format)
},
content = function(file) {
ggsave(file, plot = plot())
}
)
})
}
# Define UI for application that draws a histogram
ui <- fluidPage(
# Application title
titlePanel("Old Faithful Geyser Data"),
# Sidebar with a slider input for number of bins
sidebarLayout(
sidebarPanel(
sliderInput("bins",
"Number of bins:",
min = 1,
max = 50,
value = 30
)
),
# Show plots and download buttons
mainPanel(
plotOutput("distPlot"),
fluidRow(
column(3, downloaButtondUI("distPlot")),
column(3, downloadSelectUI("distPlot"))
),
plotOutput("scat_plot"),
fluidRow(
column(3, downloaButtondUI("scatPlot")),
column(3, downloadSelectUI("scatPlot"))
),
)
)
)
server <- function(input, output) {
dist_plot <- reactive({
p <- ggplot(faithful, aes(waiting)) +
geom_histogram(bins = input$bins)
p
})
scat_plot <- reactive({
p <- ggplot(faithful, aes(x = waiting, y = eruptions)) +
geom_point()
p
})
output$distPlot <- renderPlot({
dist_plot()
})
output$scat_plot <- renderPlot({
scat_plot()
})
downloadServer("distPlot", dist_plot)
downloadServer("scatPlot", scat_plot)
}
shinyApp(ui = ui, server = server)
#>
#> Listening on http://127.0.0.1:4092
Here's the minimal reproducible example:
# This is a Shiny web application.
library(shiny)
# UI for application
ui <- fluidPage(
# Application title
titlePanel("A Problematic App - Part 2"),
# Sidebar with two slider inputs
sidebarLayout(
sidebarPanel(
sliderInput(
"NoOfSamples",
label = "Sample Size",
value = 100,
min = 10,
max = 150,
step = 10,
width = "40%"
),
sliderInput(
"KeepSamples",
label = "Samples to Keep",
value = 50,
min = 10,
max = 150,
step = 10,
width = "40%"
)
),
# Shows the resulting table
mainPanel(
tableOutput("table1"),
tableOutput("table2")
)
)
)
# Server logic
server <- function(input, output) {
# Using the iris dataset
datExpr <- as.data.frame(iris)
n = reactive({
input$NoOfSamples
})
datExpr0 <- reactive({
datExpr[1:n(), ]
})
output$table1 <- renderTable({
datExpr0()
})
# Displays the first table correctly if the rest is commented out
keepSamples = reactive({
input$KeepSamples
})
datExpr <- reactive({
datExpr0()[keepSamples(),]
})
output$table2 <- renderTable({
datExpr()
})
}
# Run the application
shinyApp(ui = ui, server = server)
I have created live examples for demonstration.
With the second part of the program commented out.
The complete program. [Shinyapps.io] is supressing the error details, so attached is a screenshot of a local run.
The error is object of type 'closure' is not subsettable. While many questions (and answers) regarding this error exist, I am yet to find any explaining the behaviour demonstrated above.
Why does this happen?
The normal (script-equivalent) works as expected.
datExpr <- as.data.frame(iris)
n = 50
datExpr0 <- datExpr[1:n, ]
datExpr0
keepSamples = 10
datExpr <- datExpr0[keepSamples,]
datExpr
Is there a way to achieve what the normal script does in the shiny app?
The issue is that you have both a dataframe and a reactive in your app called datExpr. Simply rename one of both (I decided for the reactive).
EDIT There is of course nothing special about that in shiny.
A simple example to illustrate the issue:
datExpr <- iris
datExpr <- function() {}
datExpr[1:2]
#> Error in datExpr[1:2]: object of type 'closure' is not subsettable
And you see that we get the famous object of type 'closure' is not subsettable error too. The general issue or lesson is that in R you can't have two different objects with the same name at the same time.
# This is a Shiny web application.
library(shiny)
# UI for application
ui <- fluidPage(
# Application title
titlePanel("A Problematic App - Part 2"),
# Sidebar with two slider inputs
sidebarLayout(
sidebarPanel(
sliderInput(
"NoOfSamples",
label = "Sample Size",
value = 100,
min = 10,
max = 150,
step = 10,
width = "40%"
),
sliderInput(
"KeepSamples",
label = "Samples to Keep",
value = 50,
min = 10,
max = 150,
step = 10,
width = "40%"
)
),
# Shows the resulting table
mainPanel(
tableOutput("table1"),
tableOutput("table2")
)
)
)
# Server logic
server <- function(input, output) {
# Using the iris dataset
datExpr <- as.data.frame(iris)
n = reactive({
input$NoOfSamples
})
datExpr0 <- reactive({
datExpr[1:n(), ]
})
output$table1 <- renderTable({
datExpr0()
})
# Displays the first table correctly if the rest is commented out
keepSamples = reactive({
input$KeepSamples
})
datExpr1 <- reactive({
datExpr0()[keepSamples(),]
})
output$table2 <- renderTable({
datExpr1()
})
}
# Run the application
shinyApp(ui = ui, server = server)
#>
#> Listening on http://127.0.0.1:3648
I am clearly missing something here, but I am pretty new to Shiny apps (I have only every made a couple of them before), and I'm still learning the ropes of them.
This app (which will run on its own) works for the input side (a slider and a text input), but the output (which is supposed to be a table) will not display.
Here is the code:
# This is a Shiny web application. You can run the application by clicking
# the 'Run App' button above.
#
# Find out more about building applications with Shiny here:
#
# http://shiny.rstudio.com/
#
library(shiny)
ui <- fluidPage(
# Application title
titlePanel("CHD Risk Calculator"),
sidebarLayout(
sidebarPanel(
sliderInput("BMI",
"Your BMI (kg/m^2 OR (703*lbs)/in^2):",
min = 10,
max = 70,
value = 24),
textInput("Age",
"Your Age:")
),
mainPanel(
tableOutput("")
)
)
)
server <- function(input, output) {
inputdata <- reactive({
data <- data.frame(
MyBMI = as.integer(input$BMI),
MyAge = as.integer(input$age))
data
})
output$result <- renderTable({
data = inputdata()
chdrisk = -6.293 + (0.0292*data$BMI) + (0.07409*data$age)
resultTable = data.frame(
Result = "Your risk of Coronary Heart Disease (CHD) is",
Risk = chdrisk)
resultTable
})
}
# Run the application
shinyApp(ui = ui, server = server)
What am I missing here?
Thank you!
You have a few things going on here
Your tableOutput() has been given outputID=""; change this to "result"
Your inputs for the slider and the text are called BMI and Age, but in the reactive, you refer to them as BMI and age
The data frame in the reactive has two columns, MyBMI and MyAge, but later, you refer to them like this: data$BMI and data$age
Here is a corrected version
# This is a Shiny web application. You can run the application by clicking
# the 'Run App' button above.
#
# Find out more about building applications with Shiny here:
#
# http://shiny.rstudio.com/
#
library(shiny)
ui <- fluidPage(
# Application title
titlePanel("CHD Risk Calculator"),
sidebarLayout(
sidebarPanel(
sliderInput("BMI",
"Your BMI (kg/m^2 OR (703*lbs)/in^2):",
min = 10,
max = 70,
value = 24),
textInput("Age",
"Your Age:")
),
mainPanel(
tableOutput("result")
)
)
)
server <- function(input, output) {
inputdata <- reactive({
data <- data.frame(
MyBMI = as.integer(input$BMI),
MyAge = as.integer(input$Age))
data
})
output$result <- renderTable({
data = inputdata()
chdrisk = -6.293 + (0.0292*data$MyBMI) + (0.07409*data$MyAge)
resultTable = data.frame(
Result = "Your risk of Coronary Heart Disease (CHD) is",
Risk = chdrisk)
resultTable
})
}
# Run the application
shinyApp(ui = ui, server = server)
I have an interactive visualization that connects to a city government's police data API.
When certain combinations of inputs are selected, my API call comes back empty and I get a nasty red error message (as my plot inputs are unavailable).
Can someone tell me how to display a more informative error message along the lines of, "there are no incidents matching your selection, please try again"? I would like this error message to appear as a showNotification and my ggplot not to render.
Below is an extremely stripped down version of what I am doing. Note how when a combination like "AVONDALE" and "CHEMICAL IRRITANT" is selected, the chart renders, whereas when a combination like "ENGLISH WOODS" and "TASER-BEANBAG-PEPPERBALL-40MM FOAM" is selected, an error message is returned. This error message is what I would like to address with a showNotification alert.
Note that this uses the Socrata API, so the package RSocrata must be installed and loaded.
install.packages("RSocrata")
library(shiny)
library(reshape2)
library(dplyr)
library(plotly)
library(shinythemes)
library(tibble)
library(RSocrata)
# Define UI for application that draws a histogram
ui <- fluidPage(
navbarPage("Example",
theme = shinytheme("united"),
tabPanel("Plot",
sidebarLayout(
sidebarPanel(
# neighborhood selector
selectizeInput("neighbSelect",
"Neighborhoods:",
choices = c("AVONDALE", "CLIFTON", "ENGLISH WOODS"),
multiple = FALSE)),
# incident description selector
selectizeInput("incSelect",
"Incident Type:",
choices = c("CHEMICAL IRRITANT", "TASER-BEANBAG-PEPPERBALL-40MM FOAM"),
multiple = FALSE))
),
# Output plot
mainPanel(
plotlyOutput("plot")
)
)
)
# Define server logic
server <- function(input, output) {
forceInput <- reactive({
forceInput <- read.socrata(paste0("https://data.cincinnati-oh.gov/resource/e2va-wsic.json?$where=sna_neighborhood= '", input$neighbSelect, "' AND incident_description= '", input$incSelect, "'"))
})
# Render plot
output$plot <- renderPlotly({
ggplot(data = forceInput(), aes(x = sna_neighborhood)) +
geom_histogram(stat = "count")
})
}
# Run the application
shinyApp(ui = ui, server = server)
Thank you so much for any help anyone can offer!
Im going to give an example with the shinyalert library to have the popup. Here I added the sample choice TEST to indicate no data:
#install.packages("RSocrata")
library(shiny)
library(reshape2)
library(dplyr)
library(plotly)
library(shinythemes)
library(tibble)
library(RSocrata)
library(shinyalert)
# Define UI for application that draws a histogram
ui <- fluidPage(
useShinyalert(),
navbarPage("Example",
theme = shinytheme("united"),
tabPanel("Plot",
sidebarLayout(
sidebarPanel(
# neighborhood selector
selectizeInput("neighbSelect",
"Neighborhoods:",
choices = c("AVONDALE", "CLIFTON", "ENGLISH WOODS","TEST"),
multiple = FALSE)),
# incident description selector
selectizeInput("incSelect",
"Incident Type:",
choices = c("CHEMICAL IRRITANT", "TASER-BEANBAG-PEPPERBALL-40MM FOAM"),
multiple = FALSE))
),
# Output plot
mainPanel(
plotlyOutput("plot")
)
)
)
# Define server logic
server <- function(input, output,session) {
forceInput <- reactive({
forceInput <- read.socrata(paste0("https://data.cincinnati-oh.gov/resource/e2va-wsic.json?$where=sna_neighborhood= '", input$neighbSelect, "' AND incident_description= '", input$incSelect, "'"))
if(nrow(forceInput)==0){
shinyalert("Oops!", "No data returned", type = "error")
forceInput <- NULL
}
forceInput
})
# Render plot
output$plot <- renderPlotly({
req(forceInput())
ggplot(data = forceInput(), aes(x = sna_neighborhood)) +
geom_histogram(stat = "count")
})
}
# Run the application
shinyApp(ui = ui, server = server)
I'm having difficult using reactive expression in Shiny Apps. I'm creating a pie chart from slider input. This all works fine, however, the labels overlap. To avoid this I would like the label "" when the input is zero. The difficulty is that I'm unable to embed a reactive expression within an if-then statement.
Here is an MWE...
File "ui.R"...
library(shiny)
shinyUI(fluidPage(
titlePanel("Exposure to English calculator"),
sidebarLayout(
sidebarPanel(
h3("Sleeping"),
sliderInput("sleep", "How long does your child sleep every day?",
min=0, max=15, value=0, step = 0.5),
h3("School"),
sliderInput("school", "How long does your child spend in school?",
min = 0, max = 50, value=0, step = 0.5)
),
mainPanel(
plotOutput("plot")
)
)
))
File server.R...
library(shiny)
values <- c(1,2)
# Define server logic for slider examples
shinyServer(function(input, output) {
total_sleep <- reactive({7*input$sleep})
pieLabels<-c("sleep", "school", "other")
if(total_sleep() == 0) {
pieLabels[1] <- ""
}
output$plot <- renderPlot({
pie(c(total_sleep(), input$school, 50),
labels = pieLabels,
col = c("deepskyblue", "orange"),
height = 1500
)
})
})
The compilation crashes due to the "if" expression even though I've put the brackets after it, to show that it is derived from a reactive expression.
Thanks in advance.
You can just put all your total_sleep code and conditional statement inside the renderPlot function:
library(shiny)
app <- shinyApp(
ui = shinyUI(fluidPage(
titlePanel("Exposure to English calculator"),
sidebarLayout(
sidebarPanel(
h3("Sleeping"),
sliderInput("sleep", "How long does your child sleep every day?",
min=0, max=15, value=0, step = 0.5),
h3("School"),
sliderInput("school", "How long does your child spend in school?",
min = 0, max = 50, value=0, step = 0.5)
),
mainPanel(
plotOutput("plot")
)
)
)),
server = shinyServer(function(input, output) {
output$plot <- renderPlot({
total_sleep <- 7*input$sleep
pieLabels<-c("sleep", "school", "other")
if(total_sleep == 0) {
pieLabels[1] <- ""
}
pie(c(total_sleep, input$school, 50),
labels = pieLabels,
col = c("deepskyblue", "orange")
)
})
}))
runApp(app)
Note that I removed the height from pie because that argument doesn't exist and that gives out a warning.