Break legend in two columns while keeping shape override - r

I have a ggplot problem. Here is the example data:
df <- data.frame(x = rep(1:5,5),
type2 = c(rep(letters[1:2],each = 10),rep("c",5)),
type1 = rep(LETTERS[1:5],each = 5),
value = unlist(lapply(-2:2,function(a){rnorm(5,mean = a, sd = 1)})))
library(ggplot2)
plotcolor <- c( "#99d8c9","#2ca25f","#cbc9e2","#9e9ac8","#e34a33")
p <- ggplot(df,aes(x,value,color = type1,fill = type1,shape = type2))+
geom_point(size = 5)+
theme_light()+
labs(title = "",
color = "Method",
fill = "Method",
shape = "")+
geom_hline(yintercept = 0)+
guides(colour = guide_legend(override.aes = list(shape = c(21,21,24,24,22),
linetype = c(rep("blank",5)),
fill = plotcolor,
color = plotcolor)))+
scale_shape(guide = FALSE)+
scale_colour_manual(values = plotcolor)
p
which gives
Now I want to split the legend into two columns, for space reasons. I tried
p + guides(color=guide_legend(ncol=2))
but it remove the override part of my legend, letting just points:
p + guides(color=guide_legend(ncol=2),
fill =guide_legend(ncol=2) ,
shape = guide_legend(ncol=2))
didn't work either. Does anyone have an idea on how to deal with this particular problem?

You can specify ncol within the existing guide_legend (do not use it multiple times):
guides(colour = guide_legend(override.aes = list(shape = c(24,24,22,22,21),
linetype = c(rep("blank",5)),
fill = plotcolor,
color = plotcolor),
ncol = 2))+

Related

How to supress/combine the legend for two geoms in R

Im making a scatterplot which shows a value plotted against the date since symptom onset. These patients are categorised based on disease severity, and i wanted to show how the values change over time in each severity category. I have coloured the dots based on severity score, but i prefer to use shape =21 so i can have a border. I also draw a line to see the trend, and i want that coloured in the same way, however, this has added another legend and it looks complicated. This issue doesnt happen if use a different shape that isnt filled, because scale_colour_manual can be used for both the lines and the dots, but i dont think it looks as nice. Any idea how i can fix this?
IC50SymObySS <- ggplot(data = isaric) +
geom_point(mapping = aes(x = Days_since_onset, y = log2IC50, fill = Severity_score), size = 2, colour = "black", shape = 21)+
geom_smooth(mapping = aes(x = Days_since_onset, y = log2IC50, colour = Severity_score), se = FALSE)+
scale_fill_manual(breaks=c("1","2","3","4","5"),
values=c("1" = "lightblue1","2" = "lightblue3","3" = "lightblue4","4" = "lightcoral","5" = "firebrick2"),
labels=c("1","2","3","4","5"),
name = "Severity Score")+
scale_colour_manual(values=c("1" = "lightblue1","2" = "lightblue3","3" = "lightblue4","4" = "lightcoral","5" = "firebrick2"))+
theme_minimal()+
JTheme+
ylab("Serum Log2 IC50")+
xlab("Days Since Symptom Onset")+
guides(colour = guide_legend(title.position = "top", title.hjust = 0.5))
IC50SymObySS
As per this answer, you need to use identical name and labels values for both fill and colour scale.
library(ggplot2)
library(dplyr)
isaric <- transmute(iris,
Days_since_onset = (Sepal.Length - 4)^3,
log2IC50 = Sepal.Width * 3,
Severity_score = cut(Petal.Length, breaks = quantile(Petal.Length, prob = 0:5 / 5), labels = 1:5))
ggplot(data = isaric) +
geom_smooth(mapping = aes(x = Days_since_onset, y = log2IC50, colour = Severity_score), se = FALSE)+
geom_point(mapping = aes(x = Days_since_onset, y = log2IC50, fill = Severity_score), size = 2, colour = "black", shape = 21)+
scale_colour_manual(
name = "Severity Score",
values=c("1" = "lightblue1","2" = "lightblue3","3" = "lightblue4","4" = "lightcoral","5" = "firebrick2"),
labels=c("1","2","3","4","5"))+
scale_fill_manual(
name = "Severity Score",
breaks=c("1","2","3","4","5"),
values=c("1" = "lightblue1","2" = "lightblue3","3" = "lightblue4","4" = "lightcoral","5" = "firebrick2"),
labels=c("1","2","3","4","5"))+
theme_minimal()+
ylab("Serum Log2 IC50")+
xlab("Days Since Symptom Onset")+
guides(colour = guide_legend(title.position = "top", title.hjust = 0.5))

How can I add hatches, stripes or another pattern or texture to a barplot in ggplot?

Suppose I have data with both an ordinal variable and a categorical variable:
set.seed(35)
df <- data.frame(Class = factor(rep(c(1,2),times = 80), labels = c("Math","Science")),
StudyTime = factor(sort(sample(1:4, 16, prob = c(0.25,0.3,0.3,0.15), replace = TRUE)),labels = c("<5","5-10","10-20",">20")),
Nerd = factor(sapply(rep(c(0.1,0.3,0.5,0.8),c(30,50,50,30)), function(x)sample(c("Nerd","NotNerd"),size = 1, prob = c(x,1-x))),levels = c("NotNerd","Nerd")))
One could use ggplot and geom_bar with x, fill and alpha (or color) aesthetic mappings to visualize the relationship between these variables.
ggplot(data = df, aes(x = Class, fill = StudyTime, alpha = Nerd)) +
geom_bar(position = "dodge", color = "black") +
scale_alpha_manual(values = c(Nerd = 0.5, NotNerd = 1)) +
scale_fill_manual(values = colorRampPalette(c("#0066CC","#FFFFFF","#FF8C00"))(4)) +
labs(x = "Class", y = "Number of Students", alpha = "Nerd?") +
theme(legend.key.height = unit(1, "cm"))
However, alpha and color are not ideal. A better alternative might be to apply a pattern such as stripes or a crosshatch.
The accepted answer to this question from over 10 years ago says to use colors, and the most upvoted answer (while clever) uses over 100 lines of code.
This question received some upvotes but no new answers.
Is there any better alternative to adding a pattern such as can be seen here?
One approach is to use the ggpattern package written by Mike FC (no affiliation):
library(ggplot2)
#remotes::install_github("coolbutuseless/ggpattern")
library(ggpattern)
ggplot(data = df, aes(x = Class, fill = StudyTime, pattern = Nerd)) +
geom_bar_pattern(position = position_dodge(preserve = "single"),
color = "black",
pattern_fill = "black",
pattern_angle = 45,
pattern_density = 0.1,
pattern_spacing = 0.025,
pattern_key_scale_factor = 0.6) +
scale_fill_manual(values = colorRampPalette(c("#0066CC","#FFFFFF","#FF8C00"))(4)) +
scale_pattern_manual(values = c(Nerd = "stripe", NotNerd = "none")) +
labs(x = "Class", y = "Number of Students", pattern = "Nerd?") +
guides(pattern = guide_legend(override.aes = list(fill = "white")),
fill = guide_legend(override.aes = list(pattern = "none")))
The package appears to support a number of common geometries. Here is an example of using geom_tile to combine a continuous variable with a categorical variable:
set.seed(40)
df2 <- data.frame(Row = rep(1:9,times=9), Column = rep(1:9,each=9),
Evaporation = runif(81,50,100),
TreeCover = sample(c("Yes", "No"), 81, prob = c(0.3,0.7), replace = TRUE))
ggplot(data=df2, aes(x=as.factor(Row), y=as.factor(Column),
pattern = TreeCover, fill= Evaporation)) +
geom_tile_pattern(pattern_color = NA,
pattern_fill = "black",
pattern_angle = 45,
pattern_density = 0.5,
pattern_spacing = 0.025,
pattern_key_scale_factor = 1) +
scale_pattern_manual(values = c(Yes = "circle", No = "none")) +
scale_fill_gradient(low="#0066CC", high="#FF8C00") +
coord_equal() +
labs(x = "Row",y = "Column") +
guides(pattern = guide_legend(override.aes = list(fill = "white")))

ggplot2's line legends appear "crossed-out"

I'm creating a ggplot with two lines, each from separate geoms. As an example:
df = data.frame(
x.v = seq(0, 1, 0.025),
y.v = runif(41)
)
straight.line = data.frame(
Inter = c(0),
Slope = c(1)
)
p = ggplot() +
geom_point(
mapping = aes(
x = x.v,
y = y.v
),
data = df,
colour = "blue"
) +
geom_smooth(
mapping = aes(
x = x.v,
y = y.v,
colour = "line of best fit"
),
data = df,
method = "lm",
show.legend = NA
) +
geom_abline(
mapping = aes(
intercept = Inter,
slope = Slope,
colour = "y = x"
),
data = straight.line,
show.legend = NA
) +
guides(
fill = "none",
linetype = "none",
shape = "none",
size = "none"
)
This gives the output:
As you can see, the legend has weird diagonal lines through it. An answer to a similar question says this can be fixed by using show.legend = NA. However, as you can see in the code above, I did this and it did not change the result.
Does anybody know what is adding the diagonal lines in the legend and how else I can fix it please? Thanks.
EDIT: A question of if this is a duplicate of this. This may be the answer but how do I apply this when the answer in the link uses fill, and I use colour, please?
If I try
+ guides(colour = guide_legend(override.aes = list(colour = NULL)))
I get the error
Error in check.length("col") : 'gpar' element 'col' must not be length 0
and if I try
+ guides(colour = guide_legend(override.aes = listfill = NULL)))
I get the error
Error in `$<-.data.frame`(`*tmp*`, "fill", value = character(0)) :
replacement has 0 rows, data has 1
The following works:
library(ggplot2)
ggplot() +
geom_point(mapping = aes(x = x.v, y = y.v),
data = df, colour = "blue") +
geom_smooth(mapping = aes(x = x.v, y = y.v, colour = "line of best fit"),
data = df, method = "lm", show.legend = NA) +
geom_abline(mapping = aes(intercept = Inter, slope = Slope, colour = "y = x"),
data = straight.line, show.legend = FALSE) +
guides(fill = "none", linetype = "none", shape = "none", size = "none")
The code can be made a little bit less repetitive and we can leave out some things (liek the guide-call):
ggplot(data = df, mapping = aes(x = x.v, y = y.v)) +
geom_point(colour = "blue") +
geom_smooth(aes(colour = "line of best fit"), method = "lm") +
geom_abline(mapping = aes(intercept = Inter, slope = Slope, colour = "y = x"),
data = straight.line, show.legend = FALSE)
Why do we need to use show.legend = FALSE here and not show.legend = NA?
From the documentation:
show.legend
logical. Should this layer be included in the legends? NA, the default, includes if any aesthetics are mapped. FALSE never includes, and TRUE always includes. It can also be a named logical vector to finely select the aesthetics to display
This means that is we use show.legend = NA for the geom_abline-call we use this layer in the legend. However, we don't want to use this layer and therefore need show.legend = FALSE. You can see that this does not influence, which colors are included in the legend, only the layer.
Data
set.seed(42) # For reproducibilty
df = data.frame(x.v = seq(0, 1, 0.025),
y.v = runif(41))
straight.line = data.frame(Inter = 0, Slope = 1)

customize legend in ggplot2 with sf objects

I am plotting (mapping) sf objects with ggplot2. My understanding is that since version 2.2.1 ggplot2 contains the geom geom_sf, for simple feature objects.
I can produce the exact map that I want by doing the following:
library(sf)
library(ggplot2)
# some points to start with
a <- st_as_sf(data.frame(lon = c(1,4,6), lat = c(0,0,-3)), coords = c('lon', 'lat'))
b <- st_as_sf(data.frame(lon = c(2.5,4), lat = c(-4.5,-5)), coords = c('lon', 'lat'))
# circles around those points
buf.a <- st_buffer(a, 1)
buf.b <- st_buffer(b, 1)
# colors to mark the points
sol.a = rgb(1,0,0)
sol.b = rgb(0,0,1)
# colors to fill the circles
fil.a = adjustcolor(sol.a, alpha.f = .25)
fil.b = adjustcolor(sol.b, alpha.f = .25)
# the plot I want
g = ggplot() +
geom_sf(data = buf.a, fill = fil.a, color = NA) +
geom_sf(data = buf.b, fill = fil.b, color = NA) +
geom_sf(data = a, color = sol.a, shape = 20, size = 3) +
geom_sf(data = b, color = sol.b, shape = 20, size = 3)
g
which produces
This is what I want except that it is missing a legend. For that, I am doing
cols.fill = c("GROUP A" = fil.a, "GROUP B" = fil.b)
cols.sol = c("GROUP A" = sol.a, "GROUP B" = sol.b)
g = ggplot() +
geom_sf(data = buf.a, color = NA, aes(fill = 'GROUP A')) +
geom_sf(data = buf.b, color = NA, aes(fill = 'GROUP B')) +
geom_sf(data = a, shape = 20, size = 3, aes(color = 'GROUP A')) +
geom_sf(data = b, shape = 20, size = 3, aes(color = 'GROUP B')) +
scale_fill_manual(name = "circles", values = cols.fill) +
scale_color_manual(name = "points", values = cols.sol)
g
which gives
That's not what I want, because in the legend:
'points' should be points (not squares); and
'circles' should be, well, circles (again, not squares)
Would be nice if the legend could respect the transparency of my colors (which it did in this example).
I tried to change the last couple of lines of the above to something like
scale_fill_manual(name = "circles", values = cols.fill,
guide = guide_legend(override.aes = list(shape = c(19, 19)))) +
scale_color_manual(name = "points", values = cols.sol,
guide = guide_legend(override.aes = list(shape = c(20, 20))))
but that didn't do anything to my plot.
Ideas?
Note: If it ends up being simpler for the plot, I could change the structure of the data, e.g., by combining objects a and b in the same simple feature object and add a column indicating the group (same for buf.a and buf.b).
Here's how far I managed to get to.
g = ggplot() +
geom_sf(data = buf.a, color = NA, aes(fill = 'GROUP A'), show.legend = "point") +
geom_sf(data = buf.b, color = NA, aes(fill = 'GROUP B'), show.legend = "point") +
geom_sf(data = a, shape = 20, size = 3, aes(color = 'GROUP A'), show.legend = "point") +
geom_sf(data = b, shape = 20, size = 3, aes(color = 'GROUP B'), show.legend = "point") +
scale_color_manual(name = "points", values = cols.sol,
guide = guide_legend(override.aes = list(shape = c(20, 20)))) +
scale_fill_manual(name = "circles", values = cols.fill,
guide = guide_legend(override.aes = list(shape = c(20, 20), color = cols.fill, size = 8)))
g
To get rid of the gray background in the legend symbols,
g + theme(legend.key = element_rect(fill = "white"))
The only issue here is that the circles do not have the transparency I wanted. This is odd.
To get the transparency in the legend need to add it to the override.aes, try:
guide = guide_legend(override.aes = list(alpha = 0.5, shape = c(20, 20), color = cols.fill, size = 8, )))

ggplot2 remove legend.key box after combining multiple geom layers

I am quite new to ggplot2 and it's been challenging to reproduce a similar chart in Excel. I almost got it to work, but now I need to figure out a way to make the geom_point/line's legend key (3rd item in the legend) to not show the box around it.
Note: I know there are answers to similar problem by using + theme(legend.key = element_blank()), but it has no effect on the legend. I suspect it has something to do with the scale_*_manual in the code. Any other solutions would be truly appreciated!
test <- data.frame(
group = 1:5,
cnt = rep(600, 5),
pct_cnt = rep(0.2, 5),
prem = c(12000000, 9800000, 8700000, 11000000, 3500000),
pct_prem = c(0.266666667, 0.217777778, 0.193333333, 0.244444444,
0.077777778),
relativity = c(1.5, 1.2, 1, 0.8, 0.4)
)
theme_set(theme_minimal())
normalizer <- round(max(test$relativity) / max(test$pct_prem), 0)
ggplot(test, aes(x = group)) +
geom_bar(aes(y = pct_prem, fill = 'prem', color = 'prem'), stat = 'identity', position = position_nudge(x = -0.1), width = 0.2) +
geom_bar(aes(y = pct_cnt, fill = 'cnt', color = 'cnt'), stat = 'identity', position = position_nudge(x = 0.1), width = 0.2) +
geom_point(aes(y = relativity / normalizer, color = 'rel', fill = 'rel'), size = 5) +
geom_line(aes(y = relativity / normalizer, color = 'rel'), size = 2) +
scale_color_manual(name = 'metric', values = c('prem' = NA, 'cnt' = NA, 'rel' = 'skyblue'),
labels = c('prem' = '%Prem', 'cnt' = '%Count', 'rel' = 'LRR')) +
scale_fill_manual(name = 'metric', values = c('prem' = 'orange', 'cnt' = 'dark green', 'rel' = NA),
labels = c('prem' = '%Prem', 'cnt' = '%Count', 'rel' = 'LRR')) +
scale_y_continuous(limits = c(0, 0.4), sec.axis = sec_axis(~.*normalizer, breaks = seq(0, 0.4, 0.1) * normalizer, name = 'relativity'))
I'm not sure if there is a method using just ggplot, since the color of the box and the color of your legend key itself change simultaneously when using the common override.aes fix. Going into the gtable, you could do it this way (after assigning your plot to p):
library(grid)
grb <- ggplotGrob(p)
#get the index of the legend-grob and store grob as leg
leg_index <- grep("guide-box", sapply(grb$grobs, function(x) x$name))
leg <- grb$grobs[[leg_index]]
Then, you want to look in the legend's gtable. The key bg to be changed is the last one, so check at the bottom for rect backgrounds. I.e., here
13 13 (6-6,2-2) key-5-1-bg zeroGrob[legend.key..zeroGrob.3081]
14 14 (6-6,2-2) key-5-1-1 rect[GRID.rect.3082]
15 15 (6-6,2-2) key-5-1-2 rect[GRID.rect.3083]
16 16 (6-6,2-2) key-5-1-3 points[GRID.points.3084]
17 17 (6-6,2-2) key-5-1-4 segments[GRID.segments.3085]
Indices 14 and 15 are the ones belonging to the last key. To make sure the bg is removed, just change the graphic parameters of both of them. Then replace the old legend with your changed one.
leg$grobs[[1]]$grobs[[14]]$gp$col <- "white"
leg$grobs[[1]]$grobs[[15]]$gp$col <- "white"
grb$grobs[[leg_index]] <- leg
grid.newpage()
grid.draw(grb)
To move a legend on the bottom of the graph you add "bottom" to legend.position like this....
theme(legend.position="bottom")
here is your original code adjusted..
ggplot(test, aes(x = group)) +
geom_bar(aes(y = pct_prem, fill = 'prem', color = 'prem'), stat = 'identity', position = position_nudge(x = -0.1), width = 0.2, alpha = 1) +
geom_bar(aes(y = pct_cnt, fill = 'cnt', color = 'cnt'), stat = 'identity', position = position_nudge(x = 0.1), width = 0.2) +
geom_point(aes(y = relativity / normalizer, color = 'rel', fill = 'rel'), size = 5) +
geom_line(aes(y = relativity / normalizer, color = 'rel'), size = 2) +
scale_color_manual(name = 'metric', values = c('prem' = NA, 'cnt' = NA, 'rel' = 'skyblue'),
labels = c('prem' = '%Prem', 'cnt' = '%Count', 'rel' = 'LRR')) +
scale_fill_manual(name = 'metric', values = c('prem' = 'orange', 'cnt' = 'dark green', 'rel' = NA),
labels = c('prem' = '%Prem', 'cnt' = '%Count', 'rel' = 'LRR')) +
scale_y_continuous(limits = c(0, 0.4) , sec.axis = sec_axis(~.*normalizer, breaks = seq(0, 0.4, 0.1) * normalizer, name = 'relativity'))+
theme(legend.position="bottom")
For further adjustments that can be made look up theme (tons of options)
?theme
Hope this helps!

Resources