I have tried all solutions recommended in How to display only integer values on an axis using ggplot2. Unfortunately, I could not solve the issue with any of them.
I have created a Shiny app that produces line graphs of annual data on a variety of variables. This works out nicely for most parameterizations:
No non-integer breaks
However, if I choose certain time spans on the slider, it produces graphs that have non-integer breaks on the x-axis, which makes no sense for a yearly data.
With non-integer breaks
Edit: Here a minimal reproducible version of the application
library(tidyverse)
library(shiny)
options(scipen = 999)
# Data
data1<-data.frame(values = c(15500, 16300, 18200, 28300, 23500, 23700,
31500, 35800, 34700, 36900, 40000, 44700,
53300, 55800, 69800, 89500, 1.13E+5,
1.53E+5, 1.77E+5, 1.83E+5, 1.99E+5),
year = seq(1990, 2010, 1))
#Shiny app
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
sliderInput("period", "Year:", min = 1990, max = 2010, value = c(1990, 2010), sep = "")),
mainPanel(plotOutput("ggplot2"))))
server <- function(input, output) {
data1_subset <- reactive({
filter(data1, year >= input$period[1] & year <= input$period[2])
})
output$ggplot2 <- renderPlot({
ggplot(data = data1_subset(), aes(x = year, y = values)) +
geom_line(aes(color = "red")) +
scale_x_continuous(name = "Year") +
scale_color_discrete(guide=FALSE)+
theme_minimal()
})
}
shinyApp(ui = ui, server = server)
To see the problem, select e.g. time span 2000-2010
Is there any way to suppress non-integer breaks as there are clearly nonsensical with annual data?
Thanks a lot in advance for your help!
Thanks for your help! It seems that the answer was much simpler as I thought. Putting breaks = function(x) unique(floor(pretty(x))) in my scale_x_continuous() function produced integer-only breaks, even without transforming the data into Date format. Removing the unique() does not change the behavior in my case, but it might do in other cases.
You should take care to use the correct class for your date column year (something like Date or POSIXct). As the comment suggests, you need to pick a particular date, like Jan 1st. This will inform ggplot2 about what's probably your intention and it will make it easier for you to steer the display format to something meaningful.
It will work out of the box, you can tune it more with ggplot's date/time scales.
The reprex with the presented idea implemented (and one more glitch regarding the line colour fixed):
library(tidyverse)
library(shiny)
# Data
tibble(values = c(15500,16300,18200,28300,23500,23700,
31500,35800,34700,36900,40000,44700,
53300,55800,69800,89500,1.13E+5,
1.53E+5,1.77E+5,1.83E+5,1.99E+5),
year = seq(1990, 2010, 1)) %>%
mutate(year = lubridate::ymd(year, truncated = 2L)) ->
data1
# Shiny app
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
sliderInput("period", "Year:",
min = 1990, max = 2010,
value = c(1990, 2010), sep = "")),
mainPanel(plotOutput("ggplot2"))))
server <- function(input, output) {
data1_subset <- reactive({
filter(data1,
year >= lubridate::ymd(input$period[1], truncated = 2L),
year <= lubridate::ymd(input$period[2], truncated = 2L))
})
output$ggplot2 <- renderPlot({
ggplot(data = data1_subset(), aes(x = year, y = values)) +
geom_line(color = "red") +
xlab("Year") +
theme_minimal()
})
}
shinyApp(ui = ui, server = server)
Related
I am fairly new to R Shiny and I've been working on an app with an interactive world map which shows each country's performance at the Olympics, using scale_fill_gradient. The app user gets to choose the performance indicator (total medals won, gold only, weighted score) and the year (1996 to 2020).
The problem is there's no more error shown, but the plot doesn't show either! I have tried to run the functions as normal R script and they worked fine there (the plot showed up in the viewer pane). I found a few others who have also run into problems with no plot or error showing, but their cases are different to mine (e.g. mismatch in Output and Render) so the resolutions don't work for me.
It's a massive dataset so I've not included it here, I thought I might check first if the error could be spotted from the code alone. Here's what I've used:
function
world_map1 <- function(WorldMap, year, performance) {
w_plot1 <- WorldMap %>%
filter(Year == year) %>%
select("long", "lat", "group", "region", all_of(performance)) %>%
replace(is.na(.), 0) %>%
rename_at(performance, ~ "Value") %>%
mutate(Value = as.numeric(as.character(Value)))
tooltip_css <- "background-color:#2E2E2E; font-family: Calibri; color:#F2F2F2;"
w_g1 <- ggplot() +
geom_polygon_interactive(data = subset(w_plot1, lat >= -60 & lat <= 90),
aes(x = long,
y = lat,
fill = Value,
group = group,
tooltip = sprintf("%s<br/>%s", region, Value))) +
scale_fill_gradient(name = "Medals /Score",
low = "lightgoldenrodyellow",
high = "goldenrod1",
na.value = "white")
return(
girafe(
ggobj = w_g1,
options = list(
opts_tooltip(
css = tooltip_css
)
))
)
}
ui
ui <- fluidPage(
titlePanel("Title"),
sidebarLayout(
sidebarPanel(
radioButtons(inputId = "performance", label = "Performance measure:",
choices = c("Total medals won" = "Total",
"Gold medals won" = "Gold",
"Weighted points system" = "Weighted"
)),
width = 3
),
mainPanel(
girafeOutput("mapPlot1"),
sliderInput(inputId = "year", label = "Year:",
min = 1996, max = 2020, step = 4, value = 1996, ticks = FALSE, sep = ""
)
)
)
)
server
server <- function(input, output) {
output$mapPlot1 <- renderGirafe({
ggiraph(code = print(world_map1(WorldMap, input$year, input$performance)))
}
)
}
run app
shinyApp(ui = ui, server = server)
Any help or insights appreciated!
I thought it was my theme() block so I removed that, as shown above. Also checked other cases on no plot showing here, couldn't find one with fixes that would work for me because it seems the underlying problem is different?
In the below code, I render two separate plots using the autoplot() function. The first plot (transPlot1) allows the user to transform the data series via slider input, whereas the 2nd plot shows the original untransformed series (transPlot2) and is completely static. I'd like to show the two data series in the same plot, with the first series shown on the primary x-axis (on the left, and its x-axis values vary depending on the slider input) and the 2nd series shown on the secondary x-axis (on the right, and it always remains fixed as this shows the original data before transformation). I'd like to place the two series on the same plot so the user can see the effect on the transformation on the shape of the data. This is the sort of thing that was easy to do in XLS but I have migrated to R.
Any recommendations for how to do this?
Code:
library(feasts)
library(fabletools)
library(ggplot2)
library(shiny)
library(tsibble)
DF <- data.frame(
Month = c(1:12),
StateX = c(59,77,45,42,32,26,27,21,19,22,24,10)
)
DF1 <- DF %>% as_tsibble(index = Month)
library(shiny)
ui <- fluidPage(
sliderInput("lambda","Transformation lambda:",min=-2,max=2,value=0.5,step = 0.1),
plotOutput("transPlot1"),
plotOutput("transPlot2"),
)
server <- function(input, output) {
output$transPlot1 <- renderPlot({
DF1 %>%
autoplot(box_cox(StateX, input$lambda)) +
labs(y = "",
title = latex2exp::TeX(paste0(
"Transformed units reaching StateX",
round(input$lambda,2))))
})
output$transPlot2 <- renderPlot({
autoplot(DF1, StateX) +
labs(y = "")
})
}
shinyApp(ui, server)
One option to achieve your desired result would be to add your untransformed series to the autoplot via a geom_line and to add a secondary scale. As usual when adding a secondary scale to a ggplot this requires some transformation of the data to be displayed on the secondary axis. To this end I added a reactive to do all the transformations, including the Box-Cox transformation and the transformation needed for the secondary scale. For the last part I use scales::rescale.
library(feasts)
#> Loading required package: fabletools
library(fabletools)
library(ggplot2)
library(shiny)
library(tsibble)
library(dplyr)
library(scales)
DF <- data.frame(
Month = c(1:12),
StateX = c(59, 77, 45, 42, 32, 26, 27, 21, 19, 22, 24, 10)
)
DF1 <- DF %>% as_tsibble(index = Month)
library(shiny)
ui <- fluidPage(
sliderInput("lambda", "Transformation lambda:", min = -2, max = 2, value = 0.5, step = 0.1),
plotOutput("transPlot1")
)
server <- function(input, output) {
DF1_trans <- reactive({
DF1 %>%
mutate(
state_x_box = box_cox(StateX, input$lambda),
state_x_raw = scales::rescale(StateX, to = range(state_x_box))
)
})
output$transPlot1 <- renderPlot({
to_range <- range(DF1_trans()$StateX)
DF1_trans() %>%
autoplot(state_x_box) +
geom_line(data = DF1_trans(), aes(Month, state_x_raw, color = "Untransformed Series")) +
scale_y_continuous(
sec.axis = sec_axis(
name = "Untransformed",
trans = ~ scales::rescale(.x, to = to_range))) +
labs(
y = "",
title = latex2exp::TeX(paste0(
"Transformed units reaching StateX",
round(input$lambda, 2)
))
)
})
}
shinyApp(ui, server)
#>
#> Listening on http://127.0.0.1:3492
A new self-learner for Shiny apps, still trying to explore the structure of Shiny apps. I have a piece of code which I want to convert into Shiny apps. I wonder if someone can walk me through how the process goes. My goal is to make the figure title/subtitle dynamic based on inputs (i.e., lnHR0, p_ctl0, tfu) to function. Thanks!!!
R code:
library(tidyverse)
library(ggplot2)
WR_sim_OC <- function(n_trt, n_ctl, lnHR0, p_trt0, med_ctl, tfu, enroll, dur_boost){
#n_trt=60: number of subjects (treatment)
#n_ctl=30: number of subjects (control)
#lnHR0=log(1): Overall Survival Log Hazard Ratio
#p_trt0 = seq(0.46, 0.66, 0.01): Response Rate (Treatment)
#med_ctl=21.8: Median Overall Survival (Control)
#tfu=9: Minimum Follow-up Time
#enroll=19: Enrollment Time
#dur_boost=0: Durability Boost (percentage)
## insert real simulation code here ##
## Fake Results
results <- tibble(ORR_trt = p_trt0, avg_HR = rep(0.774, times = 4), maturity = rep(36.7, times = 4), ORR = c(20.71, 38.87, 60.61, 78.95),
WR = c(46.8, 56.0, 64.3, 72.8), WO = c(46.0, 55.0, 63.7, 71.9), OS = rep(55.0, times = 4))
## Create Operating Characteristic Figure
dat <- results %>% pivot_longer(c(ORR, WR, WO, OS), names_to = 'Method', values_to = 'POS')
out1 <- ggplot(data = dat, aes(x = ORR_trt, y = POS, group = Method)) +
geom_line(aes(color = Method), size = 1) +
geom_point(aes(color = Method), size = 2.5) +
theme(legend.position = 'bottom') +
labs(title = 'HR=1.0 Treatment vs. Control', subtitle = 'ORR in Control Arm=46%, 9mo follow-up', color = 'Method') +
ylab('Probability of Incorrect Go') +
xlab('ORR in Treatment Arm') +
ylim(0, 100)
## Output OC Table + Figure to shiny app
list(results, out1)
}
WR_sim_OC(n_trt=60, n_ctl=30, lnHR0=log(1), p_ctl0=0.46, p_trt0 = seq(0.46, 0.66, 0.06), med_ctl=21.8, tfu=9, enroll=19, dur_boost=0)
I tried writing the ui.R as follows (suppose the suv_plot is the output name), which I know is wrong. The server.R part is too hard for me... Can someone help?
fluidPage(
numericInput("lnHRO",
label = h3("ln(HRO)"),
value = log(1)),
numericInput("pctl",
label = h3("Response Rate (Control)"),
value = 0.46),
numericInput("tfu",
label = h3("Minimum Follow-up Time (Month)"),
value = 9),
hr(),
plotOutput("suv_plot")
)
My first suggestion is just to look at tutorials on shiny, they give a great overview on how to start a project: https://shiny.rstudio.com/tutorial/
I didn't know a thing about programming a few years back, so I understand it can be hard figuring out where to start, so I wanted to give you an idea of how to implement a function, and use shiny inputs to make the resulting table/plot be dynamic.
I switched up your code to be easier to reproduce for myself. I hope this gives you the starting point you need:
library(tidyverse)
library(ggplot2)
library(shiny)
WR_sim_OC <- function(MPG, CYL, DISP){
results <- mtcars%>% #Function to make a table
filter(cyl > CYL,
mpg > MPG,
disp > DISP)
out1 <- ggplot(data = results, aes(x = mpg, y = disp, group = cyl)) +
geom_line(aes(color = hp), size = 1) #Function to make a plot
list(results, out1) #List to create table and function
}
ui <- fluidPage(
numericInput("MilesPerGallon", "mpg", value = 15),
numericInput("Cylinders", "cyl", value = 4),
numericInput("Displacement", "disp", value = 200),
tableOutput("TABLE"),
plotOutput("PLOT")
)
server <- function(input, output, session) {
output$TABLE<-renderTable({
req(input$MilesPerGallon, input$Cylinders, input$Displacement) #Requires all three inputs before it makes the table
WR_sim_OC(input$MilesPerGallon, input$Cylinders, input$Displacement)[1] #Only pulling the table from the function
})
output$PLOT<-renderPlot({
req(input$MilesPerGallon, input$Cylinders, input$Displacement) #Requires all three inputs before it makes the plot
WR_sim_OC(input$MilesPerGallon, input$Cylinders, input$Displacement)[2] #Only pulling the plot from the function
})
}
shinyApp(ui, server)
Essentially on the server side where you render the plot or table, you use those inputs from the ui as the dynamic points in your function. I used req() for both of the renderTable and renderPlot to make sure the inputs are filled out before it makes the table plot. Best of luck!
I am new to R and Shiny and have a problem that I have not been able to solve for hours.
I have a dataset from which I display the daily consumption of coffee on a dashboard, which works very well. The plot is a ggplot geom_line chart.
But now I want to be able to change the time period with two sliders.
The sliders I have also managed to do, but the plot does not change when the slider is moved.
I also suspect that I have an error with the date format.
What am I doing wrong?
Thanks for the help
RawData Dataset
library(shiny)
library(dplyr)
library(data.table)
library(ggplot2)
ui <- shinyUI(fluidPage(
# Application title
titlePanel("Coffee consumption"),
# Sidebar with a slider input for the number of bins
sidebarLayout(
sidebarPanel(
sliderInput("DatesMerge",
"Dates:",
min = as.Date("2018-01-22","%Y-%m-%d"),
max = as.Date("2020-04-04","%Y-%m-%d"),
value= c(as.Date("2018-01-22","%Y-%m-%d"),as.Date("2020-04-04","%Y-%m-%d")),
timeFormat="%Y-%m-%d")
),
mainPanel(
plotOutput("plot_daycount"),
tableOutput("structure"),
tableOutput("rawdata"),
tableOutput("dayconsumption"))
)
)
)
# RawData import
coffeedata = fread("C:/temp/ProductList.csv")
setDF(coffeedata)
coffeedata$Date = as.Date(coffeedata$Date, "%d.%m.%Y")
# Products a day counter
countcoffee <- function(timeStamps) {
Dates <- as.Date(strftime(coffeedata$Date, "%Y-%m-%d"))
allDates <- seq(from = min(Dates), to = max(Dates), by = "day")
coffee.count <- sapply(allDates, FUN = function(X) sum(Dates == X))
data.frame(day = allDates, coffee.count = coffee.count)}
# Making a DF with day consumption
daylicounter = countcoffee(df$coffee.date)
server <- shinyServer(function(input, output) {
output$structure = renderPrint({
str(coffeedata)
})
# Raw Data
output$rawdata = renderTable({
head(coffeedata)
})
output$dayconsumption = renderTable({
head(daylicounter)
})
# GGPLOT2
output$plot_daycount = renderPlot({
DatesMerge = input$DatesMerge
ggplot(daylicounter[daylicounter == DatesMerge], aes(daylicounter$day, daylicounter$coffee.count)) +
geom_line(color = "orange", size = 1)
scale_x_date(breaks = "3 month",
date_labels = "%d-%m-%Y")
# Try outs
# ggplot(daylicounter[month(day) == month(DatesMerge)], mapping = aes(day = day)) +
# geom_line(color = "orange", size = 1)
# scale_x_date(breaks = "3 month",
# date_labels = "%d-%m-%Y")
})
})
shinyApp(ui, server)
I appreciate your help
As noted by #Kevin, you need to use input$DatesMerge[1] and input$DatesMerge[2] when subsetting your data. For clarity, this can be done in a separate step. Try something like this in your server:
output$plot_daycount = renderPlot({
DatesMerge <- as.Date(input$DatesMerge, format = "%Y-%m-%d")
sub_data <- subset(daylicounter, day >= DatesMerge[1] & day <= DatesMerge[2])
ggplot(sub_data, aes(x = day, y = coffee.count)) +
geom_line(color = "orange", size = 1) +
scale_x_date(breaks = "3 month", date_labels = "%d-%m-%Y")
})
Edit Additional question from OP was asked:
Why does my date format look normal with str(coffeedata) but with
head(coffeedata) the date is just a number?
renderTable uses xtable which may have trouble with dates. You can get your dates to display correctly by converting to character first (one option):
output$rawdata = renderTable({
coffeedata$Date <- as.character(coffeedata$Date)
head(coffeedata)
})
output$dayconsumption = renderTable({
daylicounter$day <- as.character(daylicounter$day)
head(daylicounter)
})
See other questions on this topic:
as.Date returns number when working with Shiny
R shiny different output between renderTable and renderDataTable
Welcome to R and Shiny, you are off to a great start. Few things:
I don't recommend you using = in shiny, majority of the cases you want to use <-
To simplify code, no reason to add a variable like DatesMerge. It adds no value (at least in the code above)
For a dual slider, you need to tell shiny which end to pick. input$DatesMerge doesn't mean anything but input$DatesMerge[1] does.
When asking for help, it is always better to add a subset of data within the code itself. You tend to get more help and it is easier to run for the person trying to help (like me I was too lazy to download the file and place it in a folder so I didn't run it)
You need to account for a range of dates in your slider when subsetting the data; you also for the , when subsetting the data.
ggplot(daylicounter[daylicounter %in% input$DatesMerge[1]:input$DatesMerge[2],], aes(daylicounter$day, daylicounter$coffee.count)) +
geom_line(color = "orange", size = 1) +
scale_x_date(breaks = "3 month",
date_labels = "%d-%m-%Y")
I get a warning (Warning: Removed 2 rows containing missing values (geom_path).), which I don't want to have for the following code:
library(shiny)
library(ggplot2)
library(scales)
ui <- navbarPage("Test",
tabPanel("Test_2",
fluidPage(
fluidRow(
column(width = 12, plotOutput("plot", width = 1200, height = 600))
),
fluidRow(
column(width = 12, sliderInput("slider",
label = "Range [h]",
min = as.POSIXct("2019-11-01 00:00"),
max = as.POSIXct("2019-11-01 07:00"),
value = c(as.POSIXct("2019-11-01 00:00"),as.POSIXct("2019-11-01 07:00"))))
))))
server <- function(input, output, session) {
df <- data.frame("x" = c(as.POSIXct("2019-11-01 00:00"),as.POSIXct("2019-11-01 01:00"),
as.POSIXct("2019-11-01 02:00"),as.POSIXct("2019-11-01 03:00"),
as.POSIXct("2019-11-01 04:00"),as.POSIXct("2019-11-01 05:00"),
as.POSIXct("2019-11-01 06:00"),as.POSIXct("2019-11-01 07:00")),
"y" = c(0,1,2,3,4,5,6,7))
observe({
len_date_list <- length(df$x)
min_merge_datetime <- df$x[1]
max_merge_datetime <- df$x[len_date_list]
updateSliderInput(session, "slider",
min = as.POSIXct(min_merge_datetime),
max = as.POSIXct(max_merge_datetime),
timeFormat = "%Y-%m-%d %H:%M")
})
output$plot <- renderPlot({
in_slider_1 <- input$slider[1]
in_slider_2 <- input$slider[2]
ggplot(data=df, aes(x, y, group = 1)) +
theme_bw() +
geom_line(color="black", stat="identity") +
# geom_point() +
scale_x_datetime(labels = date_format("%m-%d %H:%M"),
limits = c(
as.POSIXct(in_slider_1),
as.POSIXct(in_slider_2)))
})
}
shinyApp(server = server, ui = ui)
It seems to be an general problem with the "missing values", because I have found a lot of similar questions. In this question it is explained that it must be the range of the axis. So in my case I'm sure that it is because of the limits in scale_x_datetime.
scale_x_datetime(labels = date_format("%m-%d %H:%M"),
limits = c(
as.POSIXct(in_slider_1),
as.POSIXct(in_slider_2)))
But I didn't found an answered question when scale_x_datetime, as.POSIXct and a slider is used.
BTW: If I comment out "geom_point" I get a further similar warning.
I think it is because you haven't filtered df so when the limits of scale_x_datetime come along they remove the rows in df that don't fit between the slider parameters. I added this:
df %>% filter(between(x, in_slider_1, in_slider_2))
which seems to remove the issue for me. Please test. Just to mention that I did have some time zone problems.
Full code below:
library(shiny)
library(ggplot2)
library(scales)
ui <- navbarPage("Test",
tabPanel("Test_2",
fluidPage(
fluidRow(
column(width = 12, plotOutput("plot", width = 1200, height = 600))
),
fluidRow(
column(width = 12, sliderInput("slider",
label = "Range [h]",
min = as.POSIXct("2019-11-01 00:00"),
max = as.POSIXct("2019-11-01 07:00"),
value = c(as.POSIXct("2019-11-01 00:00"),as.POSIXct("2019-11-01 07:00"))))
))))
server <- function(input, output, session) {
df <- data.frame("x" = c(as.POSIXct("2019-11-01 00:00"),as.POSIXct("2019-11-01 01:00"),
as.POSIXct("2019-11-01 02:00"),as.POSIXct("2019-11-01 03:00"),
as.POSIXct("2019-11-01 04:00"),as.POSIXct("2019-11-01 05:00"),
as.POSIXct("2019-11-01 06:00"),as.POSIXct("2019-11-01 07:00")),
"y" = c(0,1,2,3,4,5,6,7))
observe({
len_date_list <- length(df$x)
min_merge_datetime <- df$x[1]
max_merge_datetime <- df$x[len_date_list]
updateSliderInput(session, "slider",
min = as.POSIXct(min_merge_datetime),
max = as.POSIXct(max_merge_datetime),
timeFormat = "%Y-%m-%d %H:%M")
})
output$plot <- renderPlot({
in_slider_1 <- input$slider[1]
in_slider_2 <- input$slider[2]
ggplot(data=df %>% filter(between(x, in_slider_1, in_slider_2)), aes(x, y, group = 1)) +
theme_bw() +
geom_line(color="black", stat="identity") +
# geom_point() +
scale_x_datetime(labels = date_format("%m-%d %H:%M"),
limits = c(
as.POSIXct(in_slider_1),
as.POSIXct(in_slider_2)))
})
}
shinyApp(server = server, ui = ui)
It looks like you could now actually remove the scale_x_datetime completely and just have:
ggplot(data=df %>% filter(between(x, in_slider_1, in_slider_2)), aes(x, y, group = 1)) +
theme_bw() +
geom_line(color="black", stat="identity")
I know this question already has an answer, but this is another possible solution for you.
If you just want to get rid of it, that implies to me that you are OK with the output. Then you can try the following:
Add na.rm=TRUE to geom_line like : geom_line(..., na.rm=TRUE )
This explicitly tells geom_line and geom_path that is OK to remove NA values.
Reasoning with the warning:
Warning of: Removed k rows containing missing values (geom_path)
This tells you mainly 3 things:
geom_path is being called by another geom_something which is firing the warning. In your case, is geom_line.
It already removed k rows. So if the output is as desired, then you want to those rows removed.
The reason for removal is that some values ARE missing (NA).
What the warning doesn't tells you is WHY those rows have missing (NA) values.
You know that the reason comes from scale_x_datetime. Mainly from the limits argument. In a sense of (X,Y) pairs to be drawn, you set the X scale to values where is no "Y", or Y=NA. Your scale may be continuous, but your data is not. You may want to set a larger scale for a different number of reasons, but ggplot will always find that there isn't an associated Y value, and it makes a unilateral decision and fires a warning instead of an error.
Hopefully, times will come when Errors and Warnings highlights intuitive, language-independent calling trace to the emitter and a link to a correctly explained site with common mistakes, etc.