I'm having trouble adding multiple annotations (using vectors) to a plot with facets.
For example:
library(tidyverse) # ggplot2_3.3.0
tibble(t = 1:100) %>%
crossing(id = LETTERS[1:2]) %>%
group_by(id) %>%
mutate(y = cumsum(rnorm(n()))) %>%
ggplot(aes(t, y)) + # perhaps add `group = id` if you don't facet by `id`
facet_wrap(vars(id)) + # (1)
annotate('rect', xmin = 20, xmax = 30, ymin = -Inf, ymax = Inf, fill = 'grey60') + # (2)
annotate('rect', xmin = 30, xmax = 40, ymin = -Inf, ymax = Inf, fill = 'grey70') + # (2)
annotate('rect', xmin = 40, xmax = 50, ymin = -Inf, ymax = Inf, fill = 'grey80') + # (2)
annotate('rect', xmin = 50, xmax = 60, ymin = -Inf, ymax = Inf, fill = 'grey90') + # (2)
# annotate('rect', ymin = -Inf, ymax = Inf, # (3)
# xmin = seq(20, by=10, len=4), # (3)
# xmax = seq(30, by=10, len=4), # (3)
# fill = paste0('grey', seq(60, by=10, len=4))) + # (3)
geom_line() +
theme_light()
The above code produces the desired plot (in particular, I want the same annotation on all facets). However, the annotate command is repeated four times; furthermore the help page for annotate says "the properties of the geoms are ... passed in as vectors".
So a natural thing to try is to comment out lines (2), and uncomment lines (3).
Unfortunately this generates the error
Error: Aesthetics must be either length 1 or the same as the data (8): fill
Note that if, in addition, you comment out line (1) (and optionally add group = id to the aesthetics) then it does not generate an error.
For a discussion of this behaviour of ggplot2 see gihub. As the issue was closed and as far as I get it there is nothing you can do abozt that by using annotate. However, to achieve what you want you can simply use geom_rect like so:
library(tidyverse) # ggplot2_3.3.0
df_annotate <- data.frame(
xmin = seq(20, 50, 10),
xmax = seq(30, 60, 10),
ymin = -Inf,
ymax = Inf,
fill = paste0("grey", seq(60, 90, 10))
)
tibble(t = 1:100) %>%
crossing(id = LETTERS[1:4]) %>%
group_by(id) %>%
mutate(y = cumsum(rnorm(n()))) %>%
ggplot() +
geom_rect(aes(xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax, fill = fill), data = df_annotate) +
geom_line(aes(t, y)) +
scale_fill_identity() +
facet_wrap(vars(id)) +
theme_light()
Created on 2020-05-28 by the reprex package (v0.3.0)
Edit Using ggnewscale it's possible to have a second or ... fill scale:
library(tidyverse) # ggplot2_3.3.0
library(ggnewscale)
df_annotate <- data.frame(
xmin = seq(20, 50, 10),
xmax = seq(30, 60, 10),
ymin = -Inf,
ymax = Inf,
fill = paste0("grey", seq(60, 90, 10))
)
df <- tibble(t = 1:100) %>%
crossing(id = LETTERS[1:4]) %>%
group_by(id) %>%
mutate(y = cumsum(rnorm(n())))
ggplot() +
geom_rect(aes(xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax, fill = fill), data = df_annotate) +
scale_fill_identity() +
new_scale_fill() +
geom_area(data = df, aes(t, y, fill = id)) +
facet_wrap(vars(id)) +
theme_light()
Created on 2020-05-29 by the reprex package (v0.3.0)
Related
For data of this type:
set.seed(123)
df <- data.frame(
Q = c(rep("q_pol",10), rep("q_wh",10)),
slope = c(rnorm(10,-0.5), rnorm(10, 0.5)),
Recipient = rep(c("A", "B"),10)
)
how can I color the theme (or background) of these boxplots in two different colors: the upper half for values > 0, say, "lightblue" and the lower half for values < 0, say, "darkblue":
library(ggplot2)
ggplot(df,
aes(x = Q, y = slope, color = Recipient)) +
geom_boxplot(notch = TRUE)
One option would be to add different filled backgrounds using geom_rect:
library(ggplot2)
ggplot(df,
aes(x = Q, y = slope, color = Recipient)) +
geom_rect(data = data.frame(
xmin = c(-Inf, -Inf),
xmax = c(Inf, Inf),
ymin = c(-Inf, 0),
ymax = c(0, Inf),
fill = c("darkblue", "lightblue")
), aes(xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax, fill = fill), inherit.aes = FALSE, alpha = .5) +
scale_fill_manual(values = c("darkblue" = "darkblue", "lightblue" = "lightblue"), guide = "none") +
geom_boxplot(notch = TRUE)
I have a ggplot with means and both horizontal and vertical error bars, and I'd like to add a convex hull that encompasses all the error bars - like so:
I have tried with stat_chull from ggpubr but am not sure how to specify the aesthetics when there is a xmin, xmax, ymin and ymax for the error bars of each point. Below is how I coded the plot specifying the stat_chull aes as the X and Y means as an example - I know this is incorrect but I think I am on the right track?
ggplot()+
geom_point(data = df, aes(MeanX,MeanY)) +
geom_errorbar(data = df,
mapping = aes(x = MeanX,
ymin = MeanY - SdY,
ymax = MeanY + SdY),
width = 0, inherit.aes = FALSE)+
geom_errorbarh(data = df,
mapping = aes(y = MeanY,
xmin = MeanX - SdX,
xmax = MeanX + SdX),
height = 0, inherit.aes = FALSE)+
stat_chull(data = df, aes(MeanX,MeanY))+
theme_classic()
This gives the following plot:
I have also tried geom_polygon and got garbage.
Here is a the data:
df<-structure(list(Source = structure(1:5, .Label = c("A", "B", "C", "D",
"E"), class = "factor"), MeanX = c(-18.7066666666667,
-15.8769230769231, -16.8620689655172, -15.72, -17.4333333333333
), SdX = c(1.61072554509115, 0.409201849758959, 1.04811067886951,
0.74057035077327, 1.15902257671425), MeanY = c(9.93666666666667,
14.3230769230769, 9.22758620689655, 11.1, 13.7333333333333),
SdY = c(1.03005970142791, 0.539116085686704, 0.504990221704281,
0.757187779440037, 1.05039675043925)), row.names = c(NA,
-5L), class = "data.frame")
Any help is greatly appreciated!
Does the following work for you?
library(dplyr)
df %>%
mutate(ymin = MeanY - SdY,
ymax = MeanY + SdY,
xmin = MeanX - SdX,
xmax = MeanX + SdX) %>%
ggplot(aes(x = MeanX, y = MeanY))+
geom_point() +
geom_errorbar(aes(ymin = ymin, ymax = ymax),
width = 0)+
geom_errorbarh(aes(xmin = xmin, xmax = xmax),
height = 0)+
stat_chull(data = . %>%
summarise(x = c(MeanX, MeanX, MeanX, xmin, xmax),
y = c(MeanY, ymin, ymax, MeanY, MeanY)),
aes(x = x, y = y),
geom = "polygon", colour = "black", fill = NA)+
theme_classic()
I want to display on the same graph a geom_line and the state (which is in a vector).
The data for example:
Timestamp;Value;State
20190618;1.2;UP
20190619;1.0;DOWN
20190620;1.1;UP
...
This is an example of what i'd like to obtain:
I know how to geom_line, i've already try to use geom_area but none of these try succeed.
Any help ? :-)
library(tidyverse)
ggplot(df) +
geom_rect(aes(xmin = Timestamp, xmax = lead(Timestamp),
ymin = 0, ymax = Inf,
fill = State), alpha = 0.2) +
geom_step(aes(Timestamp, Value))
# based on your data, after converting into table with Timestamp as a date
df <- structure(list(Timestamp = structure(c(18065, 18066, 18067), class = "Date"),
Value = c(1.2, 1, 1.1), State = c("UP", "DOWN", "UP")),
class = "data.frame", row.names = c(NA, -3L))
Is this what you are looking for (alternative to geom_rect)?
Prepare example data
x <- 1:5
y <- c(4,1,6,2,2)
plot.df <- data.frame(Timestamp=x, Value=y)
Code for the plot:
library(ggplot2)
ggplot(plot.df, aes(x=Timestamp,y=Value)) +
annotate("rect", xmin = 1, xmax = 2, ymin = -Inf, ymax = Inf,
alpha = .2, fill = "green") +
annotate("rect", xmin = 2, xmax = 3, ymin = -Inf, ymax = Inf,
alpha = .2, fill = "red") +
annotate("rect", xmin = 3, xmax = 4, ymin = -Inf, ymax = Inf,
alpha = .2, fill = "green") +
geom_step(direction = "h") +
theme_classic()
I am trying to produce a variable number of rectangles (layers) in a ggplot of a zoo object. I would like to do this in a loop since I do not know ahead of time how many rectangles I will need. Here is a toy example.
library("zoo")
library("ggplot2")
set.seed(1)
y <- runif(50, min = 1, max = 2)
start <- as.numeric(as.Date("2018-01-01"))
x <- as.Date(start:(start + 49))
x.zoo <- zoo(y, order.by = x)
## Fill areas
bars <- data.frame(start = c(x[5], x[20], x[35]),
end = c(x[10], x[25], x[40]))
I can plot these manually with this code:
## Plot manually
print(autoplot.zoo(x.zoo, facets = NULL) +
geom_rect(aes(xmin = bars[1,1],
xmax = bars[1,2], ymin = -Inf, ymax = Inf),
fill = "pink", alpha = 0.01) +
geom_rect(aes(xmin = bars[2,1],
xmax = bars[2,2], ymin = -Inf, ymax = Inf),
fill = "pink", alpha = 0.01) +
geom_rect(aes(xmin = bars[3,1],
xmax = bars[3,2], ymin = -Inf, ymax = Inf),
fill = "pink", alpha = 0.01))
This gives me this desired image:
I tried using the loop below but it only plots the last bar. How do I do this??
## This didn't work but illustrates what I am trying to do
p = autoplot.zoo(x.zoo, facets = NULL)
for(i in 1:3) {
p = p + geom_rect(aes(xmin = bars[i,1],
xmax = bars[i,2], ymin = -Inf, ymax = Inf),
fill = "pink", alpha = 0.01)
}
print(p)
You don't need a loop. geom_rect is vectorised
autoplot.zoo(x.zoo, facets = NULL) +
geom_rect(aes(xmin = start, xmax = end, ymin = -Inf, ymax = Inf), data = bars, fill = "pink", alpha = 0.4, inherit.aes = FALSE)
One way to avoid the for loop is to convert x.zoo into a data.frame and map the data to geom_line. This way, you can map the bars data to geom_rect separately.
dat <- data.frame(index = index(x.zoo), data.frame(x.zoo))
ggplot() +
geom_rect(data = bars, aes(xmin = start, xmax = end, ymin =-Inf, ymax = Inf), fill = 'pink', alpha = .5) +
geom_line(data=dat, aes(x = index, y = x.zoo))
I am trying to add a background to a plot to show the light conditions over a 24-hour period (i.e. nighttime, sunrise, daytime, sunset). I would like to use a gradient to denote the light transition periods (8AM-9AM for sunrise and 8PM-9PM for sunset) and solid colours for the day and night.
I am very close, however the gradients are in a vertical orientation and I need it horizontal.
Any help would be much appreciated!
Current working code
library(ggplot2)
library(scales)
## date, start and stop time
datestart <- as.POSIXct(strptime('2017-06-20 00:00:00', format = "%Y-%m-%d %H:%M:%S"))
datestop <- as.POSIXct(strptime('2017-06-20 23:59:59', format = "%Y-%m-%d %H:%M:%S"))
## sunrise
risestart <- as.POSIXct(strptime('2017-06-20 08:00:00', format = "%Y-%m-%d %H:%M:%S"))
risestop <- as.POSIXct(strptime('2017-06-20 09:00:00', format = "%Y-%m-%d %H:%M:%S"))
## sunset
setstart <- as.POSIXct(strptime('2017-06-20 20:00:00', format = "%Y-%m-%d %H:%M:%S"))
setstop <- as.POSIXct(strptime('2017-06-20 21:00:00', format = "%Y-%m-%d %H:%M:%S"))
## data limits
lims <- c(datestart, datestop)
## generate some random data
timelist <- seq(datestart, datestop, by = '15 mins')
act <- runif(length(timelist), min = 0, max = 50)
data <- data.frame(timelist, act)
## colours
nightColour <- c("#9ea5ff")
sunriseColour <- c("#9ea5ff", "#fcffbd")
testColour <- c(c("#9ea5ff"), c("#fcffbd"))
dayColour <- c("#fcffbd")
sunsetColour <- c("#fcffbd","#9ea5ff")
## add background
nightGrob <- rasterGrob(nightColour, width = unit(1,"npc"), height = unit(1,"npc"),
interpolate = TRUE)
sunriseGrob <- rasterGrob(sunriseColour, width = unit(1,"npc"), height = unit(1,"npc"),
interpolate = TRUE)
dayGrob <- rasterGrob(dayColour, width = unit(1,"npc"), height = unit(1,"npc"),
interpolate = TRUE)
sunsetGrob <- rasterGrob(sunsetColour, width = unit(1,"npc"), height = unit(1,"npc"),
interpolate = TRUE)
## plot
ggplot(data = data, aes(x = timelist, y = act)) +
annotation_custom(nightGrob, xmin = as.numeric(datestart), xmax = as.numeric(risestart) + 100, ymin = -Inf, ymax = Inf) +
annotation_custom(sunriseGrob, xmin = as.numeric(risestart), xmax = as.numeric(risestop), ymin = -Inf, ymax = Inf) +
annotation_custom(dayGrob, xmin = as.numeric(risestop), xmax = as.numeric(setstart), ymin = -Inf, ymax = Inf) +
annotation_custom(sunsetGrob, xmin = as.numeric(setstart), xmax = as.numeric(setstop), ymin = -Inf, ymax = Inf) +
annotation_custom(nightGrob, xmin = as.numeric(setstop), xmax = as.numeric(datestop), ymin = -Inf, ymax = Inf) +
geom_bar(stat = "identity", colour = "black", fill = "white") +
scale_x_datetime(limits = lims, expand = c(0,0), breaks = date_breaks('1 hour'), labels = date_format(format = "%H", tz = "Europe/London")) +
scale_y_continuous(expand = c(0,0))
Current progress
You can also make a gradient with lots of geom_rects instead of rasterGrob if you like.
Here's a function that returns a data.frame of plot data for gradient backgrounds that you can plot with geom_rect.
GenerateGradientData <- function(start_hour,
stop_hour,
start_colour,
stop_colour,
x_resolution = 100) {
# define the colour palette
colour_function <- colorRampPalette(
c(start_colour, stop_colour),
alpha = TRUE)
# set up the rect coordinates
x_range <- seq(start_hour,
stop_hour,
length.out = x_resolution + 1)
grad_xmin <- x_range[-length(x_range)]
grad_xmax <- x_range[c(1:x_resolution + 1)]
# define colours
grad_colours <- colour_function(x_resolution)
# return data.frame
data.frame(
xmin = grad_xmin,
xmax = grad_xmax,
ymin = -Inf,
ymax = Inf,
grad_colours = grad_colours
)
}
Here's an example using a numerical x-axis:
# dummy data
set.seed(1)
plot_data <- data.frame(
hours = c(1:24),
value = rnorm(24, 100, 30)
)
# day/night colours
night_colour <- c("#9ea5ff")
day_colour <- c("#fcffbd")
# generate data for a one-hour sunrise gradient
sunrise_pd <- GenerateGradientData(start_hour = 8,
stop_hour = 9,
start_colour = night_colour,
stop_colour = day_colour,
x_resolution = 1000)
# generate data for a one-hour sunset gradient
sunset_pd <- GenerateGradientData(start_hour = 20,
stop_hour = 21,
start_colour = day_colour,
stop_colour = night_colour,
x_resolution = 1000)
# setup plot
ggplot(plot_data, aes(x = hours, y = value)) +
scale_x_continuous(expand = c(0, 0)) +
scale_y_continuous(expand = c(0, 0)) +
# day background
geom_rect(xmin = 9,
xmax = 20,
ymin = -Inf,
ymax = Inf,
fill = day_colour) +
# night background
geom_rect(xmin = -Inf,
xmax = 8,
ymin = -Inf,
ymax = Inf,
fill = night_colour) +
geom_rect(xmin = 21,
xmax = Inf,
ymin = -Inf,
ymax = Inf,
fill = night_colour) +
# gradient backgrounds for sunrise and sunset
geom_rect(data = sunrise_pd,
mapping = aes(xmax = xmax,
xmin = xmin,
ymax = ymax,
ymin = ymin),
fill = sunrise_pd$grad_colours,
inherit.aes = FALSE) +
geom_rect(data = sunset_pd,
mapping = aes(xmax = xmax,
xmin = xmin,
ymax = ymax,
ymin = ymin),
fill = sunset_pd$grad_colours,
inherit.aes = FALSE) +
# finally, plot your data on top
geom_col(fill = NA, colour = "black")
Here's the output:
This could look a bit blocky depending on x_resolution, the graphics device you save with and the image viewer.