I am currently creating a Shiny app which should build plots according to user input. The user can choose between histograms, density and scatter plots, which variables to depict and what titles/labels to display. Since this is Shiny, I tried to follow the instructions given in https://mastering-shiny.org/action-tidy.html which explain how to use tidy evaluation in this context.
I create a basic plot object with ggplot(..., aes(rlang::.data[[]]))
and add the geoms later on. Unfortunately, I then encounter the following error message on trying to display the plot:
Error: stat_bin() requires an x or y aesthetic.
The error replicates without the Shiny environment, so I seem to have gotten something fundamentally wrong. This error not only replicates no matter what geom I use, but also no matter what input variables I use. I am quite confused at this point because the rlang::.data[[]]
strategy has worked for me in different contexts before and I thought I had copied the example 12.2.2 in the book.
Looking at the resulting gplot
, the required aesthetics are present at the highest level, but they aren't used for the layers. So I somehow broke the data flow?
Thus, my question is a rather basic one:
I have searched online for an answer and read the documentation, but I don't get it. Also, all of the questions I have found so far deal with the plot not printing or errors because of not considering the data-masking problem, but that somehow seems to be the root of my problem.
Adding the geoms at the same time as I create the plot, does not work either. Nor does adding the mapping information to the geoms themselves. The gplot
object still does not contain it in the layers. I haven't tried replicating the problem with base R plots yet because I would prefer using ggplot in the app and I have a feeling that I couldn't replicate it because I introduced the problem by somehow getting the tidy evaluation wrong.
I provided a code example without the Shiny context below.
Thank you very much!
A. S.
library(ggplot2)
library(shiny)
input <- list(
fill = "gear",
x = "wt",
y = "mpg",
geom = "Histogram"
)
# aesthetics: x, y, fill
if (isTruthy(input$fill)) {
if (isTruthy(input$y) & input$geom == "Scatter plot") {
gplot <- ggplot2::ggplot(
mtcars,
ggplot2::aes(x = rlang::.data[[input$x]], y = rlang::.data[[input$y]],
fill = rlang::.data[[input$fill]])
)
} else if (input$geom %in% c("Histogram", "Density plot")) {
gplot <- ggplot2::ggplot(
mtcars,
ggplot2::aes(x = rlang::.data[[input$x]],
fill = rlang::.data[[input$fill]])
)
} else {
showNotification("Variable on y-axis of scatter plot still missing.",
type = "message")
}
} else {
if (isTruthy(input$y) & input$geom == "Scatter plot") {
gplot <- ggplot2::ggplot(
mtcars,
ggplot2::aes(x = rlang::.data[[input$x]], y = rlang::.data[[input$y]])
)
} else if (input$geom %in% c("Histogram", "Density plot")) {
gplot <- ggplot2::ggplot(
mtcars,
ggplot2::aes(x = rlang::.data[[input$x]])
)
} else {
showNotification("Variable on y-axis of scatter plot still missing.",
type = "message")
}
}
# geom
plot_geom <- switch(input$geom,
"Histogram" = ggplot2::geom_histogram(),
"Density plot" = ggplot2::geom_density(),
"Scatter plot" = ggplot2::geom_point()
)
# build plot
gplot <- gplot +
plot_geom
gplot
You appear to be a victim of tidyverse's non-standard evaluation (NSE).
Roughly speaking, tidyverse functions expect tibble columns as arguments. You're passing characters from the input
list.
Using, for example,
gplot <- ggplot(mtcars, aes_string(x = input$x, y = input$y, fill = input$fill))
produces a plot of the expected form.
There's probably a more general solution involving the use of enquo
and !!
, but I don't have time to investigate right now.
EDIT
I was close. You need to use get()
rather than enquo()
and !!
. Here's a proof of concept Shiny app.
library(shiny)
library(tidyverse)
ui <- fluidPage(
selectInput("x", "X variable:", choices=c("mpg", "cyl", "disp", "hp", "drat", "wt", "qsec", "vs", "am", "gear", "carb")),
selectInput("y", "Y variable:", choices=c("mpg", "cyl", "disp", "hp", "drat", "wt", "qsec", "vs", "am", "gear", "carb")),
selectInput("type", "Plot type:", choices=c("Histogram", "Scatter", "Density")),
plotOutput("plot")
)
server <- function(input, output) {
output$plot <- renderPlot({
req(input$x, input$y)
if (input$type == "Histogram") {
mtcars %>% ggplot() + geom_histogram(aes(x=get(input$x)))
} else if (input$type == "Scatter") {
mtcars %>% ggplot() + geom_point(aes(x=get(input$x), y=get(input$y)))
} else if (input$type == "Density"){
mtcars %>% ggplot() + geom_density(aes(x=get(input$x)))
}
})
}
shinyApp(ui = ui, server = server)