trouble combining geom_rect and facet_grid in ggplot - r

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))

Related

R: Reduce axis to geom_density_ridges distance after flipping plot with coord_flip in ggplot2

First we prepare some toy data that sufficiently resembles the one I am working with.
rawdata <- data.frame(Score = rnorm(1000, seq(1, 0, length.out = 10), sd = 1),
Group = rep(LETTERS[1:3], 10000))
stdev <- c(10.78,10.51,9.42)
Now we plot the estimated densities via geom_density_ridges. I also add a grey highlight around zero via geom_rect. I also flip the chart with coord_flip.
p <- ggplot(rawdata, aes(x = Score, y = Group)) +
scale_y_discrete() +
geom_rect(inherit.aes = FALSE, mapping = aes(ymin = 0, ymax = Inf, xmin = -0.1 * min(stdev), xmax = 0.1 * max(stdev)),
fill = "grey", alpha = 0.5) +
geom_density_ridges(aes(fill = Group), scale = 0.5, size = 1, alpha=0.5) +
scale_color_manual(values = col) +
scale_fill_manual(values = col) +
labs(title="Toy Graph", y="Group", x="Value") +
coord_flip(xlim = c(-8, 8), ylim = NULL, expand = TRUE, clip = "on")
p
And this is the solution I get, which is close to what I was expecting, despite the detail of this enormous gap between the y axis an the start of the first factor in the x axis A. I tried using expand=c(0,0) inside scale_y_discrete() following some suggestions from other posts, but it does not make the gap smaller at all. If possible I would still like to have a certain gap, although minimal. I've been also trying to flip the densities in the y axis so the gap is filled by first factor density plot but I have been unsuccessful as it does not seem as trivial as one could expect.
Sorry, I know this might be technically two different questions, "How to reduce the gap from the y axis to the first density plot?" and "How to flip the densities from y axis to reduce the gap?" But I would really be happy with the first one as I understand the second question seems to be apparently less straightforward.
Thanks in advance! Any help is appreciated.
Flipping the densities also effectively reduces the space, so this might be all you need to do. You can achieve it with a negative scale parameter:
ggplot(rawdata, aes(x = Score, y = Group)) +
scale_y_discrete() +
geom_rect(inherit.aes = FALSE,
mapping = aes(ymin = 0, ymax = Inf,
xmin = -0.1 * min(stdev),
xmax = 0.1 * max(stdev)),
fill = "grey", alpha = 0.5) +
geom_density_ridges(aes(fill = Group), scale = -0.5, size = 1, alpha = 0.5) +
scale_color_manual(values = col) +
scale_fill_manual(values = col) +
labs(title = "Toy Graph", y = "Group", x = "Value") +
coord_flip(xlim = c(-8, 8), ylim = NULL, expand = TRUE, clip = "on")
If you want to keep the densities pointing the same way but just reduce space on the left side, simply set hard limits in your coord_flip, with no expansion:
ggplot(rawdata, aes(x = Score, y = Group)) +
geom_rect(inherit.aes = FALSE,
mapping = aes(ymin = 0, ymax = Inf,
xmin = -0.1 * min(stdev),
xmax = 0.1 * max(stdev)),
fill = "grey", alpha = 0.5) +
geom_density_ridges(aes(fill = Group), scale = 0.5, size = 1, alpha = 0.5) +
scale_color_manual(values = col) +
scale_fill_manual(values = col) +
scale_y_discrete() +
labs(title = "Toy Graph", y = "Group", x = "Value") +
coord_flip(xlim = c(-8, 8), ylim = c(0.8, 4), expand = FALSE)

Why geom_rect colours only first row of facet_wrap?

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.

geom_rect missing when converting ggplot2 to ggplotly

I'm trying to put together a ggplotly graph with three elements (geom_point, geom_line, and geom_rect) and it looks fine in ggplot2. However, when I convert to ggplotly, the geom_rect disappears. I'm thinking it's something with the inherit.aes function?
The code to build the test data is below.
library(ggplot2)
library(plotly)
dates_seq = seq.Date(as.Date("2019-03-13"), as.Date("2019-04-21"), by = "1 day")
df = data.frame(ds = dates_seq,
y = rnorm(length(dates_seq), mean = 50, sd = 5),
yhat = rnorm(length(dates_seq), mean = 50, sd = 5)
)
df$yhat_lower = df$yhat - 5
df$yhat_upper = df$yhat + 5
gg <- ggplot(df, aes(x = ds, y = y)) +
labs(x = 'Date', y = 'Sales') +
geom_ribbon(aes(ymin = yhat_lower, ymax = yhat_upper), fill = 'blue',
alpha = 0.2,
na.rm = TRUE)
start_date = as.Date("2019-04-19")
gg <- gg +
geom_point(na.rm=TRUE) +
geom_vline(xintercept = as.numeric(as.Date(start_date - lubridate::days(1))), linetype = 2, color = "black") +
geom_line(aes(y = yhat), color = 'blue',
na.rm = TRUE) +
theme_classic()
promo_df = data.frame(xmin = c("2019-03-15", "2019-04-01"), xmax = c("2019-03-18", "2019-04-08"),
ymin = -Inf, ymax = Inf, Promo = "Yes")
promo_df$id = 1:nrow(promo_df)
gg = gg +
geom_rect(data=promo_df, inherit.aes=FALSE,
aes(xmin=as.Date(xmin),
xmax=as.Date(xmax),
ymin=ymin,ymax=ymax,
group=id, fill = factor(Promo)), alpha=0.2) +
scale_fill_discrete(name = "On Promo?")
The ggplot image shows the desired output with the geom_rect.
gg
And now the ggplotly version:
ggplotly(gg)
Is there any way to get the ggplotly image to look like the basic ggplot2 chart?
Clara is right with respect to ggplotly's inability to support the ymin/max parameters. The best work around is to just manually set the parameters equal to the scale of your previous (main) layer. So in this case, it would be equal to 0/65.

Shade background of a ggplot chart using geom_rect with categorical variables

This is my dataset example:
df <- data.frame(group = rep(c("group1","group2","group3", "group4", "group5", "group6"), each=3),
X = paste(letters[1:18]),
Y = c(1:18))
As you can see, there are three variables, two of them categorical (group and X). I have constructed a line chart using ggplot2 where the X axis is X and Y axis is Y.
I want to shade the background using the group variable, so that 6 different colors must appear.
I tried this code:
ggplot(df, aes(x = X, y = Y)) +
geom_rect(xmin = 0, xmax = 3, ymin = -0.5, ymax = Inf,
fill = 'blue', alpha = 0.05) +
geom_point(size = 2.5)
But geom_rect() only colorize the area between 0 and 3, in the X axis.
I guess I can do it manually by replicating the the geom_rect() so many times as groups I have. But I am sure there must be a more beautiful code using the variable itself. Any idea?
To get shading for the entire graph, geom_rect needs the xmin and xmax locations for all the rectangles, so these need to be provided by mapping xmin and xmax to columns in the data, rather than hard-coding them.
ggplot(df, aes(x = X, y = Y)) +
geom_rect(aes(xmin = X, xmax = dplyr::lead(X), ymin = -0.5, ymax = Inf, fill = group),
alpha = 0.5) +
geom_point(size = 2.5) +
theme_classic()
Here is one way:
df2 <- df %>% mutate(Xn=as.numeric(X))
ggplot(df2) +
geom_rect(aes(xmin=Xn-.5, xmax=Xn+.5, ymin=-Inf, ymax=Inf, fill = group), alpha=0.5, stat="identity") +
geom_point(aes(x = Xn, y = Y), size = 2.5) + scale_x_continuous(breaks=df2$Xn, labels=df2$X)
This will get you close - need to add a couple columns to your data frame. Using dplyr here.
df <- df %>%
group_by(group) %>%
mutate(xmin = sort(X)[1],
xmax = sort(X, decreasing = T)[1])
ggplot(df, aes(x = X, y = Y)) +
geom_point(size = 2.5) +
geom_rect(aes(xmin=xmin, xmax = xmax, fill = group), ymin = -0.5, ymax = Inf,
alpha = 0.05)

alpha not working on facetted line graph with x-varying geom_rect()

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.

Resources