This question already has answers here:
geom_rect and alpha - does this work with hard coded values?
(4 answers)
Closed 2 years ago.
I recently upgraded to R version 3.2.3 and also to ggplot version 2.0.0.
Trying to upgrade some old code to the newer versions I encountered a weird behaviour with ggplot2 and its transparency settings.
Now my question is, is this a bug or a feature (if so, can someone enlighten me as to why its good to have it this way)? The result I want to have is (obviously) plot 2.
Say I plot a line and lay a rectangle with transparency over it like this:
library(ggplot2)
plot_data <- data.frame(x = 1:100, y = rnorm(100))
# Plot 1
ggplot(data = plot_data, aes(x = x, y = y)) +
geom_line() +
geom_rect(aes(xmin = 20, xmax = 50, ymin = -Inf, ymax = Inf), fill = "red",
alpha = 0.1) + ggtitle("Plot 1")
# Plot 2
ggplot() +
geom_line(data = plot_data, aes(x = x, y = y)) +
geom_rect(aes(xmin = 20, xmax = 50, ymin = -Inf, ymax = Inf), fill = "red",
alpha = 0.1) + ggtitle("Plot 2")
To my understanding plot 1 and 2 should be identical. However, I get the following plots:
Plot 1:
and plot 2:
Additionally, if I play around with the alpha-values (for example setting them to 0.01, I get the two following plots:
and
I believe that calling geom_rect without a data parameter will effectively draw an individual rectangle for each row of the data.frame which is why the alpha is "working", but not quite as expected. I have not been able to replicate and get to parity/agreement between the methods, but as you noted, I think it is doing something along the lines of drawing either 100 individual rectangles, or 30 (the width of the rectangles; from 20 to 50) which is why alpha = 0.1 / 100 and alpha = 0.1 / 30 gets you closer, but not quite matching.
Regardless, I would probably use annotate, as that better describes the behavior/result you are trying to achieve without issues and works, as expected, in both cases -- annotations will draw a single instance per geom:
ggplot(data = plot_data, aes(x = x, y = y)) +
# geom_rect(aes(xmin = 20, xmax = 50, ymin = -Inf, ymax = Inf, alpha = 0.1, fill = "red")) +
annotate("rect", xmin = 20, xmax = 50, ymin = -Inf, ymax = Inf, alpha = 0.1, fill = "red") +
geom_line() +
ggtitle("Plot 1")
ggplot() +
geom_line(data = plot_data, aes(x = x, y = y)) +
# geom_rect(aes(xmin = 20, xmax = 50, ymin = -Inf, ymax = Inf), fill = "red", alpha = 0.1) +
annotate("rect", xmin = 20, xmax = 50, ymin = -Inf, ymax = Inf, fill = "red", alpha = 0.1) +
ggtitle("Plot 2")
Related
I am trying to get shaded rectangles on every even-numbered panel of my facet_wrap plot. However, when I use geom_rect, it produces the rectangles only on the second panel. I tried using annotate and geom_tile but with no success. I presume I am missing some simple detail here, probably related to the fact that my X variable is categorical and not numeric, but I am fighting this for a few hours already...
Here is my code:
even_numbers <- seq(2,nrow(df.plt),2)
ggplot(df.plt) +
geom_rect(data = df.plt[even_numbers, ],
xmin = even_numbers - 0.5, xmax = even_numbers + 0.5,
ymin = -Inf, ymax = Inf, alpha = 0.3, fill = 'grey') +
geom_boxplot(aes(x = Cnd, y = nst, fill = Srs), position = position_dodge(0.9), outlier.shape = 1) +
facet_wrap(vars(Grp), ncol=1)
And the resulting plot:
resulting plot with geom_rect and facet_wrap not working as expected
Edit:
I have created a dummy dataset example which replicates my issue:
set.seed(002) # just to make it reproducible
df.tmp = data.frame(nst = rnorm(100*2), Srs = sample(rep(c("S3","S4"),100)), Cnd = sample(rep(c("DN","DA","DV","DAV"),50)), Grp = sample(rep(c("close","far"),100)))
even_numbers <- seq(2,nrow(df.tmp),2)
ggplot(df.tmp) +
geom_rect(data = df.tmp[even_numbers, ],
xmin = even_numbers - 0.5, xmax = even_numbers + 0.5,
ymin = -Inf, ymax = Inf, alpha = 0.3, fill = 'grey') +
geom_boxplot(aes(x = Cnd, y = nst, fill = Srs), position = position_dodge(0.9), outlier.shape = 1) +
facet_wrap(vars(Grp), ncol=1)
While your idea was right IMHO you could achieve your desired result more easily by putting the xmin and xmax values in a dataframe and by mapping on aesthetics. First note that we only need a vector of even numbers of length(unique(df.tmp$Cnd)), i.e. the number of categories of Cnd. Second, as we are mixing discrete and continuous x variables I added an scale_x_discrete before geom_rect as otherwise we will get an error.
library(ggplot2)
even_numbers <- seq(2, length(unique(df.tmp$Cnd)), 2)
rects <- data.frame(
xmin = even_numbers - 0.5,
xmax = even_numbers + 0.5
)
ggplot(df.tmp) +
scale_x_discrete() +
geom_rect(
data = rects, aes(xmin = xmin, xmax = xmax),
ymin = -Inf, ymax = Inf, alpha = 0.3, fill = "grey"
) +
geom_boxplot(aes(x = Cnd, y = nst, fill = Srs), position = position_dodge(0.9), outlier.shape = 1) +
facet_wrap(vars(Grp), ncol = 1)
EDIT Just in case. The reason why your approach did not work is that the relevant part of the data used for the rects contains only the far group. The issue is that basically only rects corresponding to even numbers in the range 1 to 4 (the number of Cnd categories) are displayed. As can be seen from the following code snippet which replicates the data which under the hood is used for the rects in your approach only the far grp is present (after filtering for even numbers in the range 1 to 4):
even_numbers <- seq(2,nrow(df.tmp),2)
dplyr::bind_cols(df.tmp[even_numbers, ], data.frame(even_number = even_numbers)) |>
dplyr::filter(even_number <= 4)
#> nst Srs Cnd Grp even_number
#> 1 0.1848492 S3 DV far 2
#> 2 -1.1303757 S3 DA far 4
I have been trying to understand why only the second segment gets shaded. Eventually I prepared a second example, in which I have found another possible solution. However, I am not fully satisfied as I did not understand the issue with geom_rect / facet_wrap; I only found some workaround.
Here's the example:
# constructing the dataframe so all the combinations are present for both even and odd rows
df.tmp = data.frame(nst = rnorm(16*6),
Srs = rep(c("S3", "S4"), each=8, 6),
Cnd = rep(c("DN", "DA", "DV", "DAV"), each=2, 12),
Grp = rep(c(rep(c("close","far"), 8), rev(rep(c("close","far"), 8))),3) )
even_numbers <- seq(2,nrow(df.tmp),2) # so the df.tmp[even_numbers, ] contains all the combinations
ggplot(df.tmp) +
geom_rect(data = df.tmp[even_numbers, ],
xmin = even_numbers - 0.5, xmax = even_numbers + 0.5,
ymin = -Inf, ymax = Inf, alpha = 0.3, fill = 'grey') +
geom_boxplot(aes(x = Cnd, y = nst, fill = Srs), position = position_dodge(0.9), outlier.shape = 1) +
facet_wrap(vars(Grp), ncol=1)
As you can see here, the plot has shaded rectangles only in the second row, despite ensuring the df.tmp[even_numbers, ] includes close datapoints as well:
Here I change the ggplot so it contains geom_rect separately for close and far segments:
even_numbers <- seq(2,length(unique(df.tmp$Cnd)),2) # here the df.tmp[even_numbers, ] doesn't need to have all the combinations
ggplot(df.tmp) +
geom_rect(data = df.tmp[df.tmp$Grp=="close", ][even_numbers, ],
xmin = even_numbers - 0.5, xmax = even_numbers + 0.5,
ymin = -Inf, ymax = Inf, alpha = 0.3, fill = 'grey') +
geom_rect(data = df.tmp[df.tmp$Grp=="far", ][even_numbers, ],
xmin = even_numbers - 0.5, xmax = even_numbers + 0.5,
ymin = -Inf, ymax = Inf, alpha = 0.3, fill = 'grey') +
geom_boxplot(aes(x = Cnd, y = nst, fill = Srs), position = position_dodge(0.9), outlier.shape = 1) +
facet_wrap(vars(Grp), ncol=1)
As you can see below, it works now:
As I mentioned earlier, I am still not sure why geom_rect did not work in the first place. In my solution, a separate geom_rect needs to be prepared for each segment, so it's definitely not a solution for a plot with many of them. I was trying to find a more elegant way, so one wouldn't have to bother how many segments or other groupings are declared.
I want to shade part of the background in each facet of a simple plot. If I omit faceting and run geom_rect + geom_point, the expected results appear as shown in the MRE below. If I omit the rectangle and run geom_point + facet_grid, the expected 4 panels have each point in the correct facet. But when I combine geom_rect + geom_point + and facet_grid, the points in the first category and only those get plotted in every facet. What is going on please???
library(ggplot2)
set.seed(42)
syn.dat <- data.frame(
category.1 = as.factor(rep(c("1A", "1B"), each = 8)),
category.2 = as.factor(rep(rep(c("2A", "2B"), times = 2), each = 4)),
x = rep(-1:2, each = 4) + runif(8, max = .4),
y = rep(-1:2, each = 4) + runif(8, max = .4))
ggplot() +
geom_rect(aes(xmin = -Inf, xmax = Inf, ymin = .5,
ymax = Inf), fill = "lightyellow") +
geom_point(data = syn.dat, aes(x = x, y = y)) +
facet_grid(cols = vars(category.1),
rows = vars(category.2))
I'm not totally sure about this, but it may be that you need to explicitly provide the data argument to ggplot itself, in order for facet_grid to correctly pick up all the values?
ggplot(syn.dat) +
geom_rect(aes(xmin = -Inf, xmax = Inf, ymin = 0.5, ymax = Inf), fill = "lightyellow") +
geom_point(aes(x = x, y = y)) +
facet_grid(rows = vars(category.2), vars(cols = category.1))
This question already has answers here:
Conditionally change panel background with facet_grid?
(2 answers)
Closed 2 years ago.
I am attempting to shade multiple regions of interest on a scatter plot. Based on this answer, I believe I am forced to leave the intial ggplot() call empty, and supplie the geom_rect() calls before the geom_point() call. I have gotten all of this to work. I can not however apply a facet based on data passed to geom_point().
The following correctly plots my data (scatter plot over red and blue regions of different sizes):
ggplot() +
geom_rect(aes(xmin=885, xmax=1544, ymin=-Inf, ymax=Inf), alpha=.2, fill = "red") +
geom_rect(aes(xmin=1858, xmax=2580, ymin=-Inf, ymax=Inf), alpha=.2, fill = "blue") +
geom_point(data=df, aes(x=Position, y=Max_Freq))
However all of the following produce errors as follows:
+ facet_wrap(~Replicate)
#Error in if (empty(data)) { : missing value where TRUE/FALSE needed
+ facet_wrap(data~Replicate)
#Error in combine_vars(data, params$plot_env, rows, drop = params$drop):
# At least one layer must contain all variables used for facetting
+ facet_wrap(data$Replicate)
#Error in data$Replicate : object of type 'closure' is not subsettable
In other graphs when df is supplied in the ggplot() call (ie ggplot(df, aes(x=Position, y=Max_Freq)) the first option correctly facets the data. Admittedly, I am not well versed in R, but it seems like this should have a simple solution.
One approach is to create a data frame with the rect coordinates that will also have the facet variable. Example:
some data:
df <- data.frame(x = rnorm(20),
y = runif(20),
facet = sample(c("A", "B"),
20,
replace = TRUE))
create a data frame with geom_rect coordinates:
rect1 <- data.frame(xmin = -1,
xmax = 1,
ymin = -Inf,
ymax = Inf,
facet = c("A", "B"))
plot it:
ggplot() +
geom_rect(data = rect1 , aes(xmin = xmin,
xmax = xmax,
ymin = ymin,
ymax = ymax),
alpha = 0.2, fill = "blue") +
geom_point(data = df, aes(x = x, y = y))+
facet_wrap(~facet)
This allows for per facet customization of rectangles. Example:
rect2 <- data.frame(xmin = c(-1, 0),
xmax = c(0, 2),
ymin = c(-Inf, 0.25),
ymax = Inf,
facet = c("A", "B"))
ggplot() +
geom_rect(data = rect2 , aes(xmin = xmin,
xmax = xmax,
ymin = ymin,
ymax = ymax,
fill = facet),
alpha = 0.2) +
geom_point(data = df, aes(x = x, y = y))+
facet_wrap(~facet)
or just to plot rectangles in some facets:
rect3 <- data.frame(xmin = -1,
xmax = 0,
ymin = 0.25,
ymax = Inf,
facet = "A")
I have a data frame in this kind of format:
df <- data.frame(
time = rep(seq(from = as.POSIXct("2016-08-10 11:00:00"),
to = as.POSIXct("2016-08-10 12:00:00"), by="sec"), 2),
value = c(diffinv(rnorm(3601)), diff(rnorm(3601))),
facets = c(rep("A",3601), rep("B", 3601)),
shading = rep(c(rep("x", 1500), rep("y", 750), rep("z", 1351)), 2),
stringsAsFactors = FALSE
)
I can plot the value time series on separate graphs sharing the x-axis using ggplot2's facet_grid function. I also want to include another dimension in my plot - the variable shading to shade the background.
I know I can do this by specifying the ranges of the x-axis the shaded regions will cover:
xRange1 <- range(df$time[df$shading=="x"])
xRange2 <- range(df$time[df$shading=="y"])
xRange3 <- range(df$time[df$shading=="z"])
yRange <- range(df$value)
When I first set this up I include alpha in each of my geom_rect
ggplot(df, aes(x = time, y = value)) +
geom_line() +
facet_grid(facets ~ ., scales = "free_y") +
geom_rect(aes(xmin = xRange1[1], xmax = xRange1[2]),
ymin = yRange[1], ymax = yRange[2],
alpha = 0.3, fill = "#EEF2BF") +
geom_rect(aes(xmin = xRange2[1], xmax = xRange2[2]),
ymin = yRange[1], ymax = yRange[2],
alpha = 0.3, fill = "#A3BAB6",) +
geom_rect(aes(xmin = xRange3[1], xmax = xRange3[2]),
ymin = yRange[1], ymax = yRange[2],
alpha = 0.3, fill = "#BFA67E")
Obviously the alpha didn't work.
One way to get around this is to put geom_line() at the end:
ggplot(df, aes(x = time, y = value)) +
facet_grid(facets ~ ., scales = "free_y") +
geom_rect(aes(xmin = xRange1[1], xmax = xRange1[2]),
ymin = yRange[1], ymax = yRange[2],
alpha = 0.3, fill = "#EEF2BF") +
geom_rect(aes(xmin = xRange2[1], xmax = xRange2[2]),
ymin = yRange[1], ymax = yRange[2],
alpha = 0.3, fill = "#A3BAB6",) +
geom_rect(aes(xmin = xRange3[1], xmax = xRange3[2]),
ymin = yRange[1], ymax = yRange[2],
alpha = 0.3, fill = "#BFA67E") +
geom_line()
But that hides the grid and doesn't solve the underlying problem.
I have looked at several posts and none of them address this directly. I have looked at using other functions in my plot including scale_fill_manual
(last example on page) and scale_alpha
Edit: I suspect the best solution also involves setting up the geom_rect in a less manual way. My actual data frame has more than 3 character values I want to shade with.
I am trying to shade a certain section of a time series plot (a bit like recession shading - similarly to the graph at the bottom of this article on recession shading in excel). I have put a little, possibly clumsy, sample together to illustrate.
I first create a time series, plot it with ggplot2 and then want to use geom_rect to provide the shading. But I must get something wrong in the arguments.
a<-rnorm(300)
a_ts<-ts(a, start=c(1910, 1), frequency=12)
a_time<-time(a_ts)
a_series<-ts.union(big=a_ts, month=a_time)
a_series_df<-as.data.frame(a_series)
ggplot(a_series)+
geom_line(mapping=aes_string(x="month", y="big"))+
geom_rect(
fill="red",alpha=0.5,
mapping=aes_string(x="month", y="big"),
xmin=as.numeric(as.Date(c("1924-01-01"))),
xmax=as.numeric(as.Date(c("1928-12-31"))),
ymin=0,
ymax=2
)
Note that I have also tried which also did not work.
geom_rect(
fill="red",alpha=0.5,
mapping=aes_string(x="month", y="big"),
aes(
xmin=as.numeric(as.Date(c("1924-01-01"))),
xmax=as.numeric(as.Date(c("1928-12-31"))),
ymin=0,
ymax=2)
)
Its a bit easier using annotate and also note that the bounds for the rectange can be specified as shown:
ggplot(a_series_df, aes(month, big)) +
geom_line() +
annotate("rect", fill = "red", alpha = 0.5,
xmin = 1924, xmax = 1928 + 11/12,
ymin = -Inf, ymax = Inf) +
xlab("time")
This would also work:
library(zoo)
z <- read.zoo(a_series_df, index = 2)
autoplot(z) +
annotate("rect", fill = "red", alpha = 0.5,
xmin = 1924, xmax = 1928 + 11/12,
ymin = -Inf, ymax = Inf) +
xlab("time") +
ylab("big")
Either one gives this:
Code works fine, conversion to decimal date is needed for xmin and xmax, see below, requires lubridate package.
library("lubridate")
library("ggplot2")
ggplot(a_series_df)+
geom_line(mapping = aes_string(x = "month", y = "big")) +
geom_rect(
fill = "red", alpha = 0.5,
mapping = aes_string(x = "month", y = "big"),
xmin = decimal_date(as.Date(c("1924-01-01"))),
xmax = decimal_date(as.Date(c("1928-12-31"))),
ymin = 0,
ymax = 2
)
Cleaner version, shading plotted first so the line colour doesn't change.
ggplot() +
geom_rect(data = data.frame(xmin = decimal_date(as.Date(c("1924-01-01"))),
xmax = decimal_date(as.Date(c("1928-12-31"))),
ymin = -Inf,
ymax = Inf),
aes(xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax),
fill = "grey", alpha = 0.5) +
geom_line(data = a_series_df,aes(month, big), colour = "blue") +
theme_classic()
To use geom_rect you need to define your rectangle coordinate through a data.frame:
shade = data.frame(x1=c(1918,1930), x2=c(1921,1932), y1=c(-3,-3), y2=c(4,4))
# x1 x2 y1 y2
#1 1918 1921 -3 4
#2 1930 1932 -3 4
Then you give ggplot your data and the shade data.frame:
ggplot() +
geom_line(aes(x=month, y=big), color='red',data=a_series_df)+
geom_rect(data=shade,
mapping=aes(xmin=x1, xmax=x2, ymin=y1, ymax=y2), color='grey', alpha=0.2)
library(xts)
library(zoo)
library(ggts)
Creating an xts object
data<-as.xts(x=runif(228,20,40),order.by = seq(as.Date("2000/01/01"), by = "month", length.out = 228))
Creating data frame of dates for which you want to crate shades
date<-data.frame(as.Date("2008-01-01"),as.Date("2009-01-01"))
Now create plot with shaded area
plot_data<-ggts(data)+geom_cycle(date)