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.
Related
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.
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")
}
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.
I'm trying to plot a vertical line on a plot in shiny, where the x intercept is a function of two of the user inputs. The variable is used in other outputs, so I am creating it an storing it in a variable. I can print the variable in an renderText so I know the calculation is correct, however ggplot is returning "object 'xintercept' not found". Below is the server function that isn't working. Note the ggplot call, when the geom_vline is removed, works find. Furthermore if the time_after_formation() is wrapped in a renderText works fine too.
library(shiny)
library(ggplot2)
library(data.table)
ui = fluidPage(
sidebarLayout(
# Inputs
sidebarPanel(
numericInput(inputId = 'lon', label="Longitude:", value=-20),
numericInput(inputId = 'lat', label="Lattitude:", value=20),
numericInput(inputId = 'time_bp', label="Time:", value=65)
),
mainPanel(
plotOutput(outputId="ocean_sub", width="auto", height="200px")
)))
server = function(input, output){
elementwise.all.equal <- Vectorize(function(x, y) {isTRUE(all.equal(x, y))})
seafloor_age_drop = data.table(lon=-20, lat=20, age=150)
setnames(floor_age_drop, c("lon", "lat", "age"))
setkey(floor_age_drop, lon, lat)
# Make model
model_age = seq(0,200, by=0.1)
crosby_depth =
ifelse(model_age <=75, 2652+324*sqrt(model_age),
ifelse(model_age>75 & model_age <=160, 5028+5.26*model_age-250*sin(((model_age-75)/30)),
ifelse(model_age>160, 5750,NA)))
crosby_model = data.frame(age=model_age, depth=crosby_depth)
# Reactives
age_from_grid = reactive({floor_age_drop[.(round(input$lon, digits=1), round(input$lat, digits=1)),'age']})
time_after_floor_formation = reactive({age_from_grid() - input$time_bp})
depth_crosby = reactive({crosby_model$depth[elementwise.all.equal(crosby_model$age, round(time_after_floor_formation(), 1))]
})
output$ocean_sub=renderPlot({
ggplot(crosby_model, aes(x=age, y=depth)) + geom_line() +
scale_y_reverse(lim=c(6000, 2500)) +
geom_vline(xintercept=time_after_floor_formation(), colour='red')})
}
shinyApp(ui=ui, server=server)
First create the plot in a reactive environment and then you use render plot for plotting it something like this:
p = reactive({ggplot(my_model, aes(x=age, y=depth)) + geom_line() +
scale_y_reverse(lim=c(6000, 2500)) +
geom_vline(xintercept=time_after_formation(), colour='red')})
output$ocean_sub = renderPlot({p})
EDITED
I found your problem and it is that the variable is data.table and that's why it doesn't work. just rap it up in as.numeric and it works perfect
ggplot(crosby_model, aes(x=age, y=depth)) + geom_line() +
scale_y_reverse(lim=c(6000, 2500)) +
geom_vline(xintercept=as.numeric(time_after_floor_formation()), colour='red')
I am new to Shiny and I've been struggling with the following. In the ui I define a set of variables that users can choose. However, when I call upon the input variable the correct label for the variable displays on the x-axis but there is no data. When I change the input$variable to any of the actual variable names, the correct results display. What am I doing wrong?
output$analysis1 <- renderPlot({
df <- m1
df <- subset(df, df$rcid %in% unique(Select_voting()$rcid))
ggplot(subset(df, unsc_region == 'Latin America'), aes(x = input$variable, y = reorder(CountryAbb, input$variable),
color = ordvote)) +
geom_point(size=3) + theme_light() + ggtitle("Latin America") + scale_colour_manual(values=vcolors)
})