I am challenged trying to implement a feature into my Shiny app. The problem is two-fold:
Is it possible to have 2 inputs from the same variable? I have one variable that is a list of indicators. I want the user to be able to select 2 indicators with selectInput, and then draw a scatter plot. There has to be 2 selectInputs because other parts of the app will rely on only the first selectInput. My data is long. I don't think it will work if I make it wide because my data includes latitude and longitude information so it wouldn't make sense to create a selectInput with names(data), for example.
If I can have 2 selectInputs from the same variable, how would I call the values in my plot, since the value is called 'value' for both the inputs?
EDIT: Following Gregor's suggestion to reference the inputs with aes_string, I would expect the following example of mtcars gathered into long format to work, but I instead get an aesthetics or object not found error. I think I probably need to filter the data, but I don't understand how I can do that since my variable indicators now refers to both 'indicators' and 'indicators2'. I.e., I can't have
filtered <-
cars %>%
filter(indicators == input$indicators,
indicators == input$indicators2)
Maybe I need to create a reactive expression that creates a new data frame instead? This is my non-working reproducible code with long-form mtcars:
library(ggplot2)
library(shiny)
cars <- mtcars %>%
gather(indicators, value, mpg:carb)
ui <- fluidPage(
# Application title
titlePanel("mtcars"),
sidebarLayout(
sidebarPanel(
selectInput("indicators",
label = "select indicator:",
choices = c("mpg", "cyl", "disp", "hp", "drat", "wt", "qsec", "vs", "am", "gear", "carb")
),
selectInput("indicators2",
label = "select indicator:",
choices = c("mpg", "cyl", "disp", "hp", "drat", "wt", "qsec", "vs", "am", "gear", "carb")
)
),
mainPanel(
plotOutput("carsPlot")
)
)
)
server <- function(input, output) {
output$carsPlot <- renderPlot({
ggplot(cars, aes_string(x = input$indicators, y = input$indicators2)) +
geom_point(shape = 1)
})
}
# Run the application
shinyApp(ui = ui, server = server)
You can use aes_string to pass input$indicators and input$indicators2 to ggplot like this. There is no need to cast your data into wide format since ggplot can actually handle long data better.
library(ggplot2)
library(shiny)
ui <- fluidPage(
# Application title
titlePanel("mtcars"),
sidebarLayout(
sidebarPanel(
selectInput("indicators",
label = "select indicator:",
choices = names(mtcars)),
selectInput("indicators2",
label = "select indicator:",
choices = names(mtcars))
),
mainPanel(
plotOutput("carsPlot")
)
)
)
server <- function(input, output) {
output$carsPlot <- renderPlot({
ggplot(mtcars, aes_string(x = input$indicators, y = input$indicators2)) +
geom_point(shape = 1)
})
}
# Run the application
shinyApp(ui = ui, server = server)
Here another solution with a selectInput in multiple mode.
library(dplyr)
library(tidyr)
library(ggplot2)
library(shiny)
cars <- mtcars %>%
gather(indicators, value, mpg:carb)
ui <- fluidPage(
titlePanel("mtcars"),
sidebarLayout(
sidebarPanel(
uiOutput("ui_indicators")
),
mainPanel(
plotOutput("carsPlot")
)
)
)
server <- function(input, output) {
output$ui_indicators <- renderUI({
choices <- unique(cars$indicators)
selectInput("indicators",
label = "select indicators :",
choices = choices,
multiple = TRUE)
})
output$carsPlot <- renderPlot({
filtered <- cars %>% filter(indicators %in% input$indicators)
ggplot(filtered, aes(x = indicators, y = value)) +
geom_point(shape=1)
})
}
# Run the application
shinyApp(ui = ui, server = server)
Building on the tip from Gregor on aes_string, I managed to fix this. I ended up using the wide data and adding a proper reactive statement that creates a new data frame out of the selected indicators.
My server function now looks like this:
server <- function(input, output) {
selectedVars <- reactive({
cars[, c(input$indicators, input$indicators2)]
})
output$carsPlot <- renderPlot({
ggplot(selectedVars(), aes_string(x = input$indicators, y = input$indicators2)) +
geom_point(shape = 1)
})
}
All works beautifully, and I am beginning to learn more about the utility of reactive functions in Shiny :)
Gregor de Cillia provided the answer I was looking for.
The two inputs (which are not a problem) can be referenced using aes_string.
Related
I am trying to make a shiny app that will allow to color the points of the scatterplot based on the selected categorical variables.
library(shiny)
data<-data.frame(iris)
ui <- navbarPage("Summary",
tabsetPanel(
tabPanel("Graph", fluid=T,
fluidPage(
sidebarPanel(selectInput(inputId = "varColor",
label = "Color",
choices = c("Species", "Other_Category"),
selected = "Species")),
mainPanel(plotOutput(outputId = "plot"))
)
)
)
)
server <- function(input, output) {
p<-reactive({ggplot(data,
aes(y = Sepal.Length, x = Petal.Length))+
# This Part needs help
geom_point(aes( input$varColor)) })
output$plot <- renderPlot({
p()
})
}
shinyApp(ui, server)
I think that now, the program reads the color selection from the input as a string, instead of taking it as category.
Thank you for help.
The issue is that input$colorVar is simply a character. Hence, ggplot2 will treat this character value as the one and only category. Hence, you end up with one color.
To tell ggplot2 that it should color the plot according to the data column whose named is stored in input$colorVar you could make use of the so called .data pronoun provided by the rlang package, i.e. do aes(color = .data[[input$varColor]]):
library(shiny)
library(ggplot2)
data <- data.frame(iris)
ui <- navbarPage(
"Summary",
tabsetPanel(
tabPanel("Graph",
fluid = T,
fluidPage(
sidebarPanel(selectInput(
inputId = "varColor",
label = "Color",
choices = c("Species", "Other_Category"),
selected = "Species"
)),
mainPanel(plotOutput(outputId = "plot"))
)
)
)
)
#> Warning: Navigation containers expect a collection of `bslib::nav()`/
#> `shiny::tabPanel()`s and/or `bslib::nav_menu()`/`shiny::navbarMenu()`s. Consider
#> using `header` or `footer` if you wish to place content above (or below) every
#> panel's contents.
server <- function(input, output) {
p <- reactive({
ggplot(
data,
aes(y = Sepal.Length, x = Petal.Length)
) +
# This Part needs help
geom_point(aes(color = .data[[input$varColor]]))
})
output$plot <- renderPlot({
p()
})
}
shinyApp(ui, server)
#>
#> Listening on http://127.0.0.1:3070
There are several questions on this issue, including here, but I am still not sure what I need to change to get this right.
The selectInput choices are working as expected, other than when I change the second selectInput, it temporarily changes to the desired selection but then automatically goes back to the first filtered selection.
For example, if "gear" is chosen for Variable 1, then the Variable 1 choices correctly display "3, 4, 5" for possible gear choices. If I select "5" for gear, it briefly shows up and then goes back to gear "3" as a choice. I am not sure how to prevent that reactive behavior.
Here is a simple reproducible example using the mtcars built-in data set:
library(tidyverse)
library(shiny)
# Variables interested in selecting
my_vars <- c("cyl", "gear", "carb")
# UI
ui <- fluidPage(
# Title
titlePanel("Reprex"),
# Sidebar
sidebarLayout(
sidebarPanel(
selectInput("sel_1",
"Variable 1",
choices = my_vars,
selected = my_vars[[1]],
multiple = FALSE
),
selectInput("sel_2",
"Variable 1 choices",
choices = unique(mtcars[[ my_vars[[1]] ]]),
multiple = FALSE
)
), # sidebarPanel close
# Plot
mainPanel(
plotOutput("plot_out")
) # mainPanel close
) # sidebarLayout close
) # UI close
# Server
server <- function(input, output, session) {
output$plot_out <- renderPlot({
# Assign inputs
sel_1 <- input$sel_1
sel_2 <- input$sel_2
# Make drop-down choice of sel_2 dependent upon user input of sel_1
# *** Must put "shiny::observe" instead of "observe" since "observe" is masked by the Tidy infer package ***
shiny::observe({
updateSelectInput(session,
"sel_2",
choices = sort(unique(mtcars[[sel_1]]))
)
})
# Data to plot
my_data <- mtcars %>%
filter(.data[[sel_1]] == sel_2)
# Plot
p <- ggplot(my_data, aes(x = factor(.data[[sel_1]]), y = hp)) + geom_point()
p
})
}
# Run the application
shinyApp(ui = ui, server = server)
That's because your observer is inside the renderPlot. It has nothing to do here.
server <- function(input, output, session) {
# Make drop-down choice of sel_2 dependent upon user input of sel_1
observeEvent(input$sel_1, {
updateSelectInput(session,
"sel_2",
choices = sort(unique(mtcars[[input$sel_1]]))
)
})
output$plot_out <- renderPlot({
# Assign inputs
sel_1 <- input$sel_1
sel_2 <- input$sel_2
# Data to plot
my_data <- mtcars %>%
filter(.data[[sel_1]] == sel_2)
# Plot
ggplot(my_data, aes(x = factor(.data[[sel_1]]), y = hp)) + geom_point()
})
}
Here the observeEvent instead of observe is not necessary, since input$sel_1 is the only reactive value inside the observer, but I find that observeEvent is more readable.
Also, avoid to load tidyverse. That loads a ton of packages you don't need. Here dplyr and ggplot2 are enough
I have an application which utilizes an actionButton to apply a filter selection to a plot. The application also contains a reset actionButton which resets the drop-down selector to its original value, in this instance mpg.
I would like to know whether it is possible to have the reset button not only update the selector itself, but then trigger the apply button so that the plot is reverts back to showing mpg as the y-axis value as it did at initialization.
Please note that the application must utilize the reactiveValues construct shown below as that is present in the actual business use case.
library(shiny)
library(plotly)
ui <- fluidPage(
## input and output ui elements and apply/reset buttons
selectInput("var", "Select Y-Axis Variable", c("mpg", "hp", "wt", "am")),
actionButton("apply", "Apply"),
actionButton("reset", "Reset"),
plotlyOutput("plot")
)
server <- function(input, output, session) {
## stored default values
plot_vals <- reactiveValues(y = "mpg")
observeEvent(input$apply, {
plot_vals$y <- input$var
})
## render plot
output$plot <- renderPlotly(
mtcars %>%
plot_ly(x = ~disp,
y = ~get(plot_vals$y),
type = "scatter",
mode = "markers")
)
## update selectors (how can I have this segment not only update the drop down, but also trigger the apply button?)
observeEvent(input$reset, {
updateSelectInput(session = session, "var", selected = "mpg")
})
}
shinyApp(ui, server)
Just update the reactiveVal on reset:
library(shiny)
library(plotly)
ui <- fluidPage(
## input and output ui elements and apply/reset buttons
selectInput("var", "Select Y-Axis Variable", c("mpg", "hp", "wt", "am")),
actionButton("apply", "Apply"),
actionButton("reset", "Reset"),
plotlyOutput("plot")
)
server <- function(input, output, session) {
## stored default values
plot_vals <- reactiveValues(y = "mpg")
observeEvent(input$apply, {
plot_vals$y <- input$var
})
## render plot
output$plot <- renderPlotly({
mtcars %>%
plot_ly(x = ~disp,
y = ~get(plot_vals$y),
type = "scatter",
mode = "markers")
})
## update selectors (how can I have this segment not only update the drop down, but also trigger the apply button?)
observeEvent(input$reset, {
updateSelectInput(session = session, "var", selected = "mpg")
plot_vals$y <- "mpg"
})
}
shinyApp(ui, server)
I'm new to R. I have a large dataset that I want the user to be able to select the x values plotted on a graph. To make it easier, I've done the same thing using the mpg dataset:
library(shiny)
ui <- fluidPage(
selectInput(
inputId= "manuf",
label= "Manufacturer",
choices= mpg$manufacturer,
multiple= TRUE
),
plotOutput("graph1")
)
server <- function(input, output) {
output$graph1 <- renderPlot({
ggplot() +
geom_point (
mapping = aes (
x= input$manuf,
y= ???
)
)
})
}
shinyApp(ui = ui, server = server)
I can't for the life of me figure out what the correct syntax is for the 'y' input. I have been googling my heart out and can't figure it out, and I'm sure it's relatively simple. I want it to only output the data for whatever you've selected in the drop down.
putting in y= mpg$hwy shows ALL hwy datapoints when one manufacturer is selected and throws an error ("Aesthetics must be either length 1 or the same as the data") with more. I think the errors are self-explanatory, but that doesn't help me figure out the correct code for 'y'. Ideas? Thanks in advance.
The aesthetic mappings for ggplot (like aes(x = ...)) should be column names, but you aren't giving the user a choice of column names, you give the user the choice of manufacturer values---which correspond to rows. If you want the user to select certain rows to plot based on the manufacturer, you should subset/filter the data that you give to ggplot, perhaps like this:
library(shiny)
library(ggplot2)
ui <- fluidPage(
selectInput(
inputId = "manuf",
label = "Manufacturer",
choices = mpg$manufacturer,
multiple = TRUE
),
plotOutput("graph1")
)
server <- function(input, output) {
output$graph1 <- renderPlot({
ggplot(data = mpg[mpg$manufacturer %in% input$manuf, ]) +
geom_point (
mapping = aes (
x = manufacturer,
y = hwy
)
)
})
}
shinyApp(ui = ui, server = server)
Let's forget about Shiny for a moment and focus on how you would filter a dataset for plotting with ggplot(). The tidyverse approach is to use dplyr::filter:
library(dplyr)
library(ggplot2)
mpg %>%
filter(manufacturer == "audi") %>%
ggplot(aes(manufacturer, hwy)) +
geom_point()
So your server function would look something like this (untested):
server <- function(input, output) {
output$graph1 <- renderPlot({
mpg %>%
filter(manufacturer == input$manuf) %>%
ggplot(aes(manufacturer, hwy)) +
geom_point()
)}
}
I have tried to plot the graph separately using ggplot (outside the shiny app) and it plots well so I know the problem is not with my ggplot code but with how the inputs in the shiny app are entered into the renderplot({}) section. The inputs are the axes.
Code:
library(ggplot2)
library(shiny)
data1 <- mtcars
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
selectInput(
inputId = "xaxis",
label = "Choose a Variable for the X-axis of the First Graph",
choices = colnames(data1)
),
selectInput(
inputId = "yaxis",
label = "Choose a Variable for the Y-axis of the First Graph",
choices = colnames(data1)
)
),
mainPanel(
plotOutput(outputId = "scatterplot"))
)
)
server <- function(input, output) {
output$scatterplot <- renderPlot({
req(input$xaxis)
req(input$yaxis)
ggplot(data1, aes(x = input$xaxis, y = input$yaxis))+geom_point()
})}
shinyApp(ui = ui, server = server)
Solution
You are passing a string to your aes, which does not work. You should try
server <- function(input, output) {
output$scatterplot <- renderPlot({
req(input$xaxis)
req(input$yaxis)
gggplot(data1, aes_string(x = paste0("`", input$xaxis, "`"),
y = paste0("`", input$yaxis, "`"))) + geom_point()
})
}
Explanation
aes expects the bare column name like in ggplot(mtcars, aes(am, vs)) + geom_point(). Note that we do not use quotatation marks " for am or vs (i.e. we are passing variable names and not strings). On the other hand input$xaxis returns a string. Thus, you have to use aes_string which is meant for working with strings rather than column names.
Update
Added backtricks to deal with non standard names.