So I am using mpg dataset to practice my R-shiny skills, but I encountered a problem.
I want to write a app which I could choose different variables to make graph, if it involves at least one discrete variable, then I draw a geom_boxplot, else, I will just draw a geom_point.
My ui.R looks like this:
library(shiny)
shinyUI(fluidPage(
# Application title
titlePanel("Old Faithful Geyser Data"),
# Sidebar with a slider input for number of bins
sidebarLayout(
sidebarPanel(
selectInput(inputId = "var1",
label = "Choose x variable",
choices =
names(mpg)
),
selectInput(inputId = "var2",
label = "Choose y variable",
choices =
names(mpg))
),
# Show a plot of the generated distribution
mainPanel(
plotOutput("distPlot")
)
)
))
And
My server.R looks like this:
library(shiny)
library(tidyverse)
library(dplyr)
shinyServer(function(input, output) {
output$distPlot <- renderPlot({
if(typeof(mpg[, input$var1]) == "chr" ||
typeof(mpg[, input$var2]) == "chr")
{
ggplot(data = mpg) +
geom_boxplot(mapping =
aes(x = mpg[, input$var1],
y = mpg[, input$var2])) +
xlab(input$var1) +
ylab(input$var2) +
ggtitle(paste("Plot", input$var1, "vs", input$var2))
}
else
{
ggplot(data = mpg) +
geom_point(mapping =
aes(x = mpg[, input$var1],
y = mpg[, input$var2])) +
xlab(input$var1) +
ylab(input$var2) +
ggtitle(paste("Plot", input$var1, "vs", input$var2))
}
})
})
But it simply does not work! When I choose discrete variable, it will return me an error message says "Discrete value applied to continuous scale". However, if I both choose continuous value, it seems to be work fine.
Why it shows such an error message like that?
Please help me!
Thank you very much!
You have a couple of issues here all on the server side:
One: If you check an example of your typeof you'll see that you always get a list:
typeof(mpg[,"model"])
#[1] "list"
typeof(mpg[,"displ"])
#[1] "list"
This is because [ doesn't extract the actual element, but the list containing that element. From ?"[":
Indexing by [ is similar to atomic vectors and selects a list of the specified element(s).
Rather you should use typeof(mpg[[input$var1]]), etc because you want to extract the element of the list (not the list that contains the element).
Two:
There is actually a specific function in ggplot, aes_string which selects the columns to plot based on a string.
Incorporating these two changes should make your shiny app work. I also simplified you server a bit to get rid of the common ggplot code.
server <- function(input,output){
output$distPlot <- renderPlot({
p <- ggplot(mpg) + xlab(input$var1) +
ylab(input$var2) +
ggtitle(paste("Plot", input$var1, "vs", input$var2))
if(typeof(mpg[[input$var1]]) == "character" |
typeof(mpg[[input$var2]]) == "character")
{
p <- p + geom_boxplot(mapping =
aes_string(x = input$var1,
y = input$var2))
}
else
{
p <- p + geom_point(mapping =
aes_string(x = input$var1,
y = input$var2))
}
return(p)
})
}
Related
I have a trivial question after playing with this code
library(gapminder)
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
checkboxInput("fit", "Add line of best fit", FALSE),
selectInput("continents", "Continents",
choices = levels(gapminder$continent),
multiple = TRUE,
selected = 'Europe')
),
mainPanel(
plotOutput("plot")
)
)
)
server <- function(input, output) {
output$plot <- renderPlot({
data <- subset(gapminder,
continent %in% input$continents)
p <- ggplot(data, aes(gdpPercap, lifeExp)) +
geom_point() +
scale_x_log10()
if (input$fit) {
p <- p + geom_smooth(method = "lm")
}
p
})
}
shinyApp(ui = ui, server = server)
If I change part of the code above to this
if (input$fit) {
p + geom_smooth(method = "lm")
}
# p
})
}
The plot won't be displayed. Why do we have to be too verbose, that is, passing p + geom_smooth() to p, then calling p for it to work? Why can't we be more succinct by simply calling p + geom_smooth()? Is it because the code is wrapped inside renderPlot()?
Thankyou.
Based on your if statement, the plot is returned and then rendered in case input$fit is TRUE. If input$fit is FALSE, the if statement returns NULL and therefore no plot is rendered. This is the default behaviour of if() in R.
You can solve your problem by simply add else p to your if statement like
if (input$fit) p + geom_smooth(method = "lm") else p
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'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')
working on integrating plotly into shiny dashboard and ran into an error when using conditional statements. I'm trying to switch through charts using an 'if' statement with selectInput. I've used this with the circlize package and ggplot graphs without issue but when trying to use it with plotly I get the following error:
Error in UseMethod: no applicable method for 'plotly_build' applied to an object of class "NULL"
I found a posting here with similar issues, but not exactly answering my specific question:
Convert ggplot object to plotly in shiny application
Here is a sample using a similar code to the one used in the posting above but with modifications to show what I'm looking to do and the error that keeps popping up:
library(shiny)
library(ggplot2)
library(ggthemes)
library(plotly)
ui = dashboardPage(
dashboardHeader(title = 'sample'),
dashboardSidebar(), ##Body content dashboardBody(
fluidRow(
box(background = "green", selectInput(inputId = "dimension",
label = strong("Choose Metric"),
choices = c('choice' = '1', 'choice2' = '2'),
multiple = FALSE, selectize = TRUE)),
box(plotlyOutput(outputId = 'plot2')))
))
server < - function(input, output) {
output$plot2 < -renderPlotly({
print(
ggplotly(
ggplot(data = mtcars, aes(x = disp, y = cyl)) + geom_smooth(method =
lm, formula = y~x) + geom_point() + theme_gdocs()))
if (input$dimension == '2') {
print(
ggplotly(
ggplot(data = mtcars, aes(x = hp, y = cyl)) + geom_smooth(method =
lm, formula = y~x) + geom_point() + theme_gdocs()))
}
})
}
shinyApp(ui, server)
I'm still learning so I'm sure this a simple error that escapes me but I am unsure what it might be. Appreciate the help!
Shortly, the problem was that if input$dimension was '1' there was no return value. You were printing the plot out but R went then one step further and checked if the condition was met. There are few ways to code it correctly.
You can save the plot in the object, say res, and if the condition is met, overwrite it with a new plot and finally return it at the end of the function - see the example below. You could also use the else statement.
library(shiny)
library(shinydashboard)
library(ggplot2)
library(ggthemes)
library(plotly)
ui = dashboardPage(
dashboardHeader(title = 'sample') ,
dashboardSidebar(),
## Body content
dashboardBody(
fluidRow(
box(background="green", selectInput(inputId = "dimension",
label = strong("Choose Metric"),
choices = c('choice'='1','choice2'='2'),
multiple = FALSE, selectize = TRUE)),
box(plotlyOutput(outputId = 'plot2')))
))
server <- function(input, output) {
output$plot2 <- renderPlotly({
res <- ggplotly(
ggplot(data = mtcars, aes(x = disp, y = cyl)) +
geom_smooth(method = lm, formula = y ~ x) +
geom_point() +
theme_gdocs())
if (input$dimension == '2') {
res <- ggplotly(
ggplot(data = mtcars, aes(x = hp, y = cyl)) +
geom_smooth(method = lm, formula = y ~ x) +
geom_point() +
theme_gdocs())
}
res
})
}
shinyApp(ui, server)
Just discovering shiny apps but this is driving me insane.......I have looked at numerous examples of server.R and ui.R code and cannot figure out what I am doing wrong. Apologies in advance if it's something very basic..........
Taking the iris dataset as an example, I want to plot one column against another, something simple using qplot or preferably ggplot
However, using qplot I get this:
and using ggplot2, I get the error:
I don't think I need the reactive function as I'm not subsetting the dataset, just extracting columns to plot.
server.R code
library(shiny)
library(shinyapps)
library(ggplot2)
shinyServer(function(input, output, session) {
output$text1 <- renderText({input$id1})
output$text2 <- renderText({input$select1})
output$plot1 <- renderPlot({
g <- qplot(Sepal.Length, input$select1, data = iris)
print(g)
})
})
or using ggplot function to replace the qplot call
g <- ggplot(iris, aes(x = Sepal.Length, y = input$select1))
g <- g + geom_line(col = "green", lwd =1) +
labs(x = "Date", y = "Ranking") +
theme_bw() + scale_y_reverse()
ui.R code
library(shiny)
library(shinyapps)
data(iris)
opts <- unique(colnames(iris))
opts <- opts[-1] ## want to keep Sepal.Length as the x values
shinyUI(pageWithSidebar(
headerPanel('test with iris database'),
sidebarPanel(
selectInput(inputId = "select1", label = "select",
choices = opts),
textInput(inputId = "id1", label = "Input Text", "")
),
mainPanel(
p('Output text1'),
textOutput('text1'),
textOutput('text2'),
plotOutput('plot1')
)
))
Change your aes statement to aes_string and make x a string. This should fix the problem.
g <- ggplot(iris, aes_string(x = "Sepal.Length", y = input$select1))
g <- g + geom_line(col = "green", lwd =1) +
labs(x = "Date", y = "Ranking") +
theme_bw() + scale_y_reverse()