"Missing value where TRUE/FALSE needed" when running R Shiny app - r

I am new to creating R Shiny apps. So far I'm making a part of my app where I am trying to generate different plots depending on which variable selected to analyze. I will use the built-in dataset iris as an example.
library(shiny)
library(tidyverse)
ui <- fluidPage(
titlePanel("Title"),
sidebarLayout(
sidebarPanel("Create plots of mean variables by species. ",
varSelectInput("vars", h5("Choose a variable to display."),
data = iris,
selected = "Sepal.Length"),
sliderInput("toprange", h5("Display Number of Species"),
min = 1, max = 3, value = 3)),
#in my actual dataset there are more than 30 different levels.
mainPanel(plotOutput("bars"))
)
)
server <- function(input, output) {
output$bars <- renderPlot({
species_plot(input$vars, input$toprange)
})
}
shinyApp(ui = ui, server = server)
Here is the function used to create the plots:
species_plot <- function(variable, min) {
iris %>%
group_by(Species) %>%
filter(Species != "") %>%
summarize(avg = mean({{variable}})) %>%
top_n(avg, min) %>%
ggplot(aes(x = reorder(Species, avg), y = avg)) +
geom_col() +
labs(x = "Species", y = paste("Mean", toString(sym(variable)))) +
ggtitle(paste("Mean", toString(sym(variable)), "by Species")) +
coord_flip()
}
When I run the app, the everything on the sidebar shows, but on the main panel an error "missing value where TRUE/FALSE needed" pops up, and I am not sure where this is stemming from. I don't see a conditional anywhere, for example, that would output this error.

The problem is in your plotting function, not your shiny code. The approach you're using to pass a quoted variable name to filter doesn't work. See this Q&A for one that does. In your function, that looks like...
species_plot <- function(variable, min) {
iris %>%
group_by(Species) %>%
filter(Species != "") %>%
summarize(avg = mean(!!sym(variable))) %>%
top_n(avg, min) %>%
ggplot(aes(x = reorder(Species, avg), y = avg)) +
geom_col() +
labs(x = "Species", y = paste("Mean", variable)) +
ggtitle(paste("Mean", variable, "by Species")) +
coord_flip()
}
Note that this also means you don't need all that toString(sym(variable)) stuff later in the function either. It's already a string, so just pass variable.
As a side note, I think top_n isn't doing what you think it's doing in that function, either. After you've run summarize, each group only has one value, so top_n throws an error message. The function works anyway because it just ignores that illogical call and moves on. But whatever it is you're trying to do there, you're going to need to do differently.

Related

R shiny : Color plot using ggplot

I am creating a plot in R shiny app based on usr input. My filtering of data is a bit complicated and I am unsure fow to pass it to "fill" in ggplot. Below is my code:
data <- reactive({
req(input$name)
req(input$type)
fp %>%
dplyr::filter(
name %in% input$name,
if_any(
matches(
str_c('status___', tolower(input$type))), ~
.x ==2),
on_date >= input$Dates[1] &
off_date <= input$Dates[2]
) %>%
group_by(country) %>%
summarize(All = n(), .groups = "drop")
})
##Plot
output$plot <- renderPlot({
g <- ggplot(data(), aes( y = All, x = country)) #this is wehere I want to use fill to color the plot by "type"
g + geom_bar(stat = "sum")
})
When you want to pass reactive input as variables in the ggplot aes() you need to use aes_string().
Try this ggplot code :
geom_bar(data = data(),
aes_string(y = "All",
x = "country",
fill = input$type),
stat = "sum")
Your problem is that your data() does not contain a column called type. You can verify that, by including a dataTableOutput element to your UI and render data to that. summarize will include only the grouping variables (country in your case) and the aggregated column (All).
Maybe you can provide a full reprex then we cna help better.

Tooltip in percent stacked barchart

I am trying to format my ggplotly tooltip in a percent stacked barchart. In order to do this, I am editing a 'text' parameter in 'aes'
library(scales)
library(tidyverse)
library(plotly)
library(ggplot2)
library(shiny)
#Define dataframe
weeknum <- c(1,1,1,1,2,2,2,2,3,3,3,3)
channel <- rep(c("a", "b"), 6)
product <- c(rep("x",6), rep("y",6))
data <- data.frame(weeknum, channel, product)
# Define UI
ui <- fluidPage(theme = shinytheme("flatly"),
mainPanel(
h1("plot"),
plotlyOutput(outputId = "plot_1", height = "800px")
))
# Define server function
server <- function(input, output) {
output$plot_1 <- renderPlotly({
p1 <- data %>%
ggplot(aes(x=weeknum, fill=channel, text = paste('share:', percent(..count..), "<br> channel", fill))) +
geom_bar(position = "fill", stat ='count')+
facet_grid(rows = vars(product))
fig1 <- ggplotly(p1, tooltip = "text")
fig1
})
}
# Create Shiny object
shinyApp(ui = ui, server = server)
so here I got only count * 100% in the tooltip. I know I need to divide it by a dynamic height of a bar, because in this dashboard I'm gonna use some filters. Question is how can I do this? (..count..)/sum(..count..) doesn't work.
Since you did not provide a reproducible example, I created one using mtcars data set. This is how I would approach your task.
library(ggplot2)
library(dplyr)
plot <- mtcars %>%
count(cyl, am, name = "count") %>%
mutate(across(c(cyl, am), as.character)) %>%
ggplot(
aes(x = cyl, fill= am, y = count,
text = paste('share:', scales::percent(count/sum(count)), '<br>AM:', am)
)
) +
geom_col(position = "fill")
plotly::ggplotly(plot, tooltip = "text")

[R Shiny]: How to filter by time range on the x-axis and simultaneously have two different variables on the y axis in R Shiny app

I want to build a shiny app using Covid-19 data (https://data.europa.eu/euodp/de/data/dataset/covid-19-coronavirus-data) and I would like to show barplot with ggplot where you can see the development of worldwide cases or deaths over time. I would furthermore like to have a dateRangeInput in which you can set a time period. At the same time I have on the y axis either the possibility to choose from selectInput either the variable "cases" or "deaths". I can do this separately but I can't figure out how to have this in one final plot.
It works with the time range if I use this code:
ui <- fluidPage(
titlePanel("Covid-19 by Country"),
sidebarLayout(
sidebarPanel(
selectInput(inputId = "y", label = "Y-Axe:",
choices=c("cases", "deaths"),
selected = "cases"),
dateRangeInput("datum", "Zeitraum auswählen", start = min(covid_worldwide$dateRep), end = max(covid_worldwide$dateRep), min = min(covid_worldwide$dateRep), max = max(covid_worldwide$dateRep), format = "dd.mm.yyyy", language = "de")
),
mainPanel(
plotOutput("covidPlot")
)
)
)
server <- function(input, output, session) {
s <- reactive({
covid_worldwide %>%
filter(
as.Date(dateRep) >= as.Date(input$datum[1]),
as.Date(dateRep) <= as.Date(input$datum[2])
)
})
output$covidPlot <- renderPlot({
ggplot(data= s(), aes(x = dateRep, y = cases)) +
geom_bar(stat="identity", fill="red") + theme_classic() + xlab("Zeitraum") + ylab("Anzahl")
}
)}
shinyApp(ui = ui, server = server)
It works also if I do not change the time period but give two different variables for the y-axis, see following code (the UI is the same as above):
server <- function(input, output, session) {
s <- reactive({
covid_worldwide %>%
filter(
as.Date(dateRep) >= as.Date(input$datum[1]),
as.Date(dateRep) <= as.Date(input$datum[2])
)
})
yvar <- reactive({
if ( "cases" %in% input$y) return(covid_worldwide$cases)
if ( "deaths" %in% input$y) return(covid_worldwide$deaths)
})
output$covidPlot <- renderPlot({
ggplot(data= s(), aes(x = dateRep, y = yvar())) +
geom_bar(stat="identity", fill="red") + theme_classic() + xlab("Zeitraum") + ylab("Anzahl")
}
)}
shinyApp(ui = ui, server = server)
But if I then try to change the time period in the shiny app I receive this error: "Aesthetics must be either length 1 or the same as the data (26852): y"
Does anyone have an idea on how to make the two things in one ggplot barplot work? Thank you in advance!
You simply have to map input$y on y in your plotting code. Additionally as the input is a character it's convenient to switch to aes_string instead of aes as it allows to pass the variables as names or strings to ggplot().
The first version does not work as you map cases on y. The second one does not work as your reactive yvar extracts a vector from the original unfiltered df. Therefore the length of yvar is greater than the number of rows of the filtered df or the length of your date variable.
output$covidPlot <- renderPlot({
ggplot(data= s(), aes_string(x = "dateRep", y = input$y)) +
geom_bar(stat="identity", fill="red") + theme_classic() + xlab("Zeitraum") + ylab("Anzahl")
}

Vary Plots By Different Date Ranges In RShiny

I am trying to make a shiny app that plots a custom ggplot plot1 depending on the different dates.
The parameter of plot1 is days which plots by different dates as the days are specified accordingly. However, I just can't figure out how to integrate into the server part of shiny. Here is my best approach:
# create sample data frame with dates
set.seed(1)
date = seq(Sys.Date(), by = "day", length.out = 30)
number = 100 * rnorm(30)
df = data.frame(date = date, number = number)
head(df)
# Plot
library(ggplot2)
library(shiny)
library(dplyr)
plot1 <- function(days) {
df %>% filter(between(date, max(df$date) - days, max(df$date))) %>%
ggplot(aes(x = date, y = number)) +
geom_line() +
theme_classic()
}
# Shiny
ui <- fluidPage(
dateRangeInput(
inputId = "daterange",
label = "Select the date range",
start = min(df$date),
end = max(df$date),
),
plotOutput("plotA")
)
server <- function(input, output, session) {
output$plotA <- renderPlot({
plot1(input$daterange)
})
}
shinyApp(ui, server)
Is filter a masked function for you? You can try using a qualified function call to filter via dplyr::filter().
Other than that, as the other user pointed out, input$daterange is a vector with 2 values - a "beginning" (input$daterange[1]) and an "end" (input$daterange[2]). You mentioned that you tried changing input$daterange[1] or [2], but it doesn't work: I presume that means you changed your call to plot(input$daterange) in server, right? You should be changing and specifying that up in your declaration of that function. Since you define the date from input$daterange based on min(df$date) and max(df$date) already, you can just use days[1] and days[2] to refer to the min and max of the user input. Maybe like this?
plot1 <- function(days) {
df %>% dplyr::filter(between(date, days[1], days[2])) %>%
ggplot(aes(x = date, y = number)) +
geom_line() +
theme_classic()
}
Finally, I have had some issues displaying plots from ggplot in shiny apps myself. In order to show the plot, I store in a variable, and then explicity show the plot with print(). Example:
myPlot <- ggplot(df, aes(x=..., y=...)) + geoms_...
print(myPlot)
If I simply call ggplot without the print() after, it does not always work as intended.

R, shiny: reactive data.frame as a factor, set levels and order

In my shiny app, I have a data.frame that is reactive. The data.frame is then given to ggplot and the barchart is made. However, I would like to set the exact order of the bars in the barchart.
This I can do with
JOIN11$ID_Polymer <- factor(JOIN11$ID_Polymer,
levels=JOIN11$ID_Polymer[order(JOIN11[["Content"]])])
in my R script (a function() that prepares the data outside the shiny server).
I would like to set the order in the shiny server so the user can change the ordering argument (the user can decide if he wants to order the data.frame by "Content" or by some other column that he chooses).
I was trying something like this:
dataforplot <- reactive({
plot_data <- data() %>%
filter(Name %in% input$polymers)
plot_data$ID_Polymer <- factor(plot_data$ID_Polymer,
levels =plot_data$ID_Polymer[ order(plot_data[["Content"]])])
})
which does not work (the ggplot is not displayed), the error says: data must be a data frame, or other object coercible byfortify(), not a factor.
the function for ggplot goes like this:
plotInput <- reactive({
ggplot(data = dataforplot(), aes(x = ID_Polymer, y = value), position = position_dodge(width = 1)) +
geom_bar(aes_string( fill=razeni()), position = position_dodge(width = 1), stat="identity", color="white")+
theme_minimal() +
theme(legend.text=element_text(size=21))+
theme(text = element_text(size=21))+
theme(panel.grid.major = element_blank(), panel.grid.minor = element_blank()) +
ggtitle(input$title_text_box_id) +
labs(x = "", y = input$ylabel_text_box_id) +
geom_text(aes(x = ID_Polymer, y = value,Group=Polymer,label=value),
position = position_dodge(width = 1),vjust=2, size=5,colour = "white", fontface = "bold") +
scale_fill_tableau("Tableau 10")+
scale_x_discrete(labels=c(xpopisky()))#puts a reactive in x labels
})
It works when I am not trying to set the order to the data.fram, when I leave out the
plot_data$ID_Polymer <- factor(plot_data$ID_Polymer,
levels =plot_data$ID_Polymer[ order(plot_data[["Content"]])])
How to solve this?
When you use:
dataforplot <- reactive({
plot_data <- data() %>%
filter(Name %in% input$polymers)
plot_data$ID_Polymer <- factor(plot_data$ID_Polymer,
levels =plot_data$ID_Polymer[ order(plot_data[["Content"]])])
})
The last line of whatever is inside your reactive() is returned as the value of that reactive element. Hence, in your case plot_data$ID_Polymer(which is not a dataframe, but a factor column of the dataframe) is returned as dataforplot(). This is the reason for the error. Change you dataforplot() definition to:
dataforplot <- reactive({
plot_data <- data() %>%
filter(Name %in% input$polymers)
plot_data$ID_Polymer <- factor(plot_data$ID_Polymer,
levels =plot_data$ID_Polymer[ order(plot_data[["Content"]])])
# Add return statement for returning the dataframe
return(plot_data)
})

Resources