Combine redundant legend items in ggplot2 - r

Here's my data:
# Data:
mydf <- data.frame(
Species = rep(c("Ungulate","Ungulate","Elk","Elk","Rodent","Rodent","Deer","Deer"),
times = 3),
Space = rep(c("W", "C", "E"), each = 8),
Age = rep(c("Adult", "Juvenile"), times = 12),
value = c(0.03,0.17,0.02,0.23,0.33,0.00,0.05,0.12,0.04,0.28,0.09,0.23,0.17,0.00,0.13,
0.17,0.02,0.14,0.01,0.23,0.29,0.00,0.06,0.13))
mydf$spaceage <- as.factor(paste(mydf$Space, mydf$Age))
mydf
myPalette <- c("#f4a582", "#b2182b", "#92c5de", "#2166ac", "#a6dba0", "#1b7837")
For my plot:
example <- ggplot(mydf,
aes(x = factor(Space, levels = c("W", "C", "E")),
y = value,
fill = factor(spaceage))) +
geom_bar(stat = 'identity', position = 'stack') +
facet_grid(~ Species) +
scale_fill_manual(values = myPalette, name = "Age") + #legend
labs(x="") +
theme_bw()
example
Returns:
Is it possible to combine redundant legend items, so dark and light shades are combined, to produce something like this? (couldn't get very even sizes using paint):
I'm open to other ideas for making this legend more concise. Thanks for any advice!

You can assign "" as legend label to some legend entries in order to achieve the effect.
However, I would first of all caution to be very careful with the manual fill scale first, as you want to make sure each colour corresponds to the correct spaceage value before obscuring its label.
Here's an implementation:
# ensure correct mapping between colour & label
names(myPalette) <- levels(mydf$spaceage)
ggplot(mydf,
aes(x = factor(Space, levels = c("W", "C", "E")),
y = value,
fill = factor(spaceage))) +
# minor point, but geom_col() is equivalent to geom_bar(position = "identity"),
# and position = "stack" is default in both cases.
geom_col() +
facet_grid(~ Species) +
scale_fill_manual(values = myPalette, name = "Age",
# ensures colour order follows x-axis order
breaks = c("W Adult", "W Juvenile", "C Adult", "C Juvenile",
"E Adult", "E Juvenile"),
# comment out this line to verify that right colour
# is mapped to the right label
labels = c("", "", "", "", "Adult", "Juvenile"),
# specify 2 rows for legends
guide = guide_legend(nrow = 2, byrow = FALSE)) +
labs(x = "") +
theme_bw()

Create the legend, which is actually a ggplot2.
library(ggplot2)
ds_palette <- tibble::tibble(
fill = c("#a6dba0", "#1b7837", "#f4a582", "#b2182b", "#92c5de", "#2166ac"),
x = c(2, 1, 2, 1, 2, 1),
y = c(3, 3, 2, 2, 1, 1),
text = c("W", "W", "C", "C", "E", "E"),
text_color = c("black", "white", "black", "white", "black", "white")
)
legend_inset <- ggplot(ds_palette, aes(x=x, y=y, fill=fill)) +
geom_tile() +
geom_text(aes(label=text, color=text_color)) +
annotate("text", x=1, y=3.6, label="Juvenile", vjust=0) +
annotate("text", x=2, y=3.6, label="Adult", vjust=0) +
scale_color_identity() +
scale_fill_identity() +
coord_cartesian(ylim=c(0.5, 4), expand = F) +
theme_void() +
labs(x="")
Then put it all together. The objects in vpList define the proportions of the partitioned areas.
grid.newpage()
plot_width <- .8
tree <- vpTree(
viewport(w=1, h=1, name="A"),
vpList(
viewport(x=0, y=0 , w= plot_width, h=1 , just=c("left", "bottom"), name="bar_graph"),
viewport(x=1, y=.5, w=1-plot_width, h=0.3, just=c("right", "top") , name="legend")
)
)
pushViewport(tree)
print(example , vp = "bar_graph")
print(legend_inset, vp = "legend")
I rotated your 2x3 legend so the words would be more space-efficient.

You can add labels and change the position of your legend as the code below.
example<-ggplot(mydf, aes(x = factor(Space, levels=c("W", "C", "E")), y = value, fill = factor(spaceage))) +
geom_bar(stat = 'identity', position = 'stack') + facet_grid(~ Species) +
scale_fill_manual(values = myPalette,name = "Age",labels=c("Adult","Juvenile","Adult","Juvenile","Adult","Juvenile")) + #legend
labs(x="") +
theme(legend.position = "top")
example
The result looks like below.

Related

How to draw lines connecting pair-wise points above dodging bars in R?

I think the question is sufficiently complex that a code example will help:
library(ggplot2)
df <- data.frame(
Group = c("A", "A", "A", "A", "B", "B"),
Subgroup = c("A.1", "A.2", "A.1", "A.2", "B.1", "B.2"),
Value = c(10, 7, 8, 9, 11, 12),
Pair = c(1, 1, 2, 2, 3, 3)
)
dodge <- position_dodge(width = 0.9)
ggplot(data = df, mapping = aes(x = Group, y = Value, fill = Subgroup)) +
geom_bar(stat = "summary", fun = "mean", position = dodge) +
geom_point(position = dodge) +
geom_line(color = "red", mapping = aes(group = Pair), position = dodge)
The point is to have a bar chart with groups and subgroups (success), with the individual dots plotted centered above each bar (success), and with lines connecting pairwise samples (fail). The result is not too far off, but apparently, instead of making the points avoid each other and then drawing the lines, ggplot2 draws the vertical lines and then makes them avoid each other.
Actual:
Expected:
May be easier to facet by Group and forgo position adjustments entirely. The below also futzes with labels, panel spacing, etc to mimic the appearance of your original plot as much as possible.
library(ggplot2)
ggplot(df, aes(x = Subgroup, y = Value, fill = Subgroup)) +
geom_bar(stat = "summary", fun = "mean", width = 1) +
geom_point() +
geom_line(aes(group = Pair), color = "red", ) +
facet_wrap(vars(Group), scales = "free_x", strip.position = "bottom") +
labs(x = "Group") +
theme(
axis.text.x = element_blank(),
axis.ticks.x = element_blank(),
strip.background = element_blank(),
panel.spacing = unit(0, units = "line")
)

How to make log10 ONLY first y-axis (not secondary y-axis) in ggplot in R

I would like to plot ONLY y-axis1 DATA (left axis, Var1, dotted line) as a log10 scale. The dotted line would therefore look higher on the y-axis and differences between 1 and 2 would be noticeable.
I have tried several things, but does not work ( I believe I am using them in the wrong order/place) such as:
+coord_trans(y='log10')--> empty plot
scale_y_continuous(trans = log10_trans(),... --> makes both Var1 and Var 2 log10
scale_y_log10(breaks = trans_breaks("log10", function(x) 10^x),labels = trans_format("log10", math_format(10^.x)))--> makes both y axis log10 and removes y-axis2 (Var2)
data<- data.frame(
Day=c(1,2,3,1,2,3,1,2,3),
Name=rep(c(rep("a",3),rep("b",3),rep("c",3))),
Var1=c(1090,484,64010,1090,484,64010,1090,484,64010),
Var2= c(4,16,39,2,22,39,41,10,3))
ggplot(data) +
geom_bar(aes(fill=Name, y=Var2*1000, x=Day),stat="identity", colour="black", position= position_stack(reverse = TRUE))+
geom_line(aes(x=Day, y=Var1),stat="identity",color="black", linetype="dotted", size=0.8)+
geom_point(aes(Day, Var1), shape=8)+
labs(title= "",
x="",y=expression('Var1'))+
scale_y_continuous(
sec.axis=sec_axis(~./1000, name= expression(paste("Var2"))))+
theme_classic()+
scale_fill_grey(start = 1, end=0.1,name = "", labels = c("a", "b", "c"))
I think the easiest way is to have the primary axis be the linear one, but put it on the right side of the plot. Then, you can have the secondary one be your log-transformed axis.
library(ggplot2)
data<- data.frame(
Day=c(1,2,3,1,2,3,1,2,3),
Name=rep(c(rep("a",3),rep("b",3),rep("c",3))),
Var1=c(1090,484,64010,1090,484,64010,1090,484,64010),
Var2= c(4,16,39,2,22,39,41,10,3))
# Max of secondary divided by max of primary
upper <- log10(3e6) / 80
breakfun <- function(x) {
10^scales::extended_breaks()(log10(x))
}
ggplot(data) +
geom_bar(aes(fill=Name, y=Var2, x=Day),
stat="identity", colour="black", position= position_stack(reverse = TRUE))+
geom_line(aes(x=Day, y=log10(Var1) / upper),
stat="identity",color="black", linetype="dotted", size=0.8)+
geom_point(aes(Day, log10(Var1) / upper), shape=8)+
labs(title= "",
x="",y=expression('Var1'))+
scale_y_continuous(
position = "right",
name = "Var2",
sec.axis = sec_axis(~10^ (. * upper), name= expression(paste("Var1")),
breaks = breakfun)
)+
theme_classic() +
scale_fill_grey(start = 1, end=0.1,name = "", labels = c("a", "b", "c"))
Created on 2022-02-09 by the reprex package (v2.0.1)
Here is a custom breaks function:
br <- function(limits) {
10^(seq(ifelse(limits[1] <= 0,
0,
trunc(log10(limits[1]))),
trunc(log10(limits[2])),
by = 1))}
ggplot(data) +
geom_bar(aes(fill = Name, y = Var2 * 1000, x = Day),
stat = "identity",
colour = "black",
position = position_stack(reverse = TRUE))+
geom_line(aes(x=Day, y=Var1),
stat = "identity",
color = "black",
linetype = "dotted",
size = 0.8)+
geom_point(aes(Day, Var1),
shape = 8)+
labs(title = "",
x = "",
y = expression('Var1'))+
scale_y_continuous(
breaks = br,
sec.axis = sec_axis(~./1000, name= expression(paste("Var2"))))+
theme_classic()+
scale_fill_grey(start = 1,
end = 0.1,
name = "",
labels = c("a", "b", "c"))
Results aren't so pretty but you can customize the breaks as you wish.
You absolutely should read the answer #teunbrand linked to in the comment to your question. But for the matter of displaying log values on the left and original values on the right, you can use:
tibble(Day = 1:10,
Val1 =10*Day) %>%
ggplot(aes(x = Day, y = log10(Val1))) +
geom_col() +
scale_y_log10(name = "log(Val1)",
sec.axis = sec_axis(~ 10^., name = "Val1"))

R: Gaps between specific bars in ggplot

I have this bar graph.
I generate the graph with this code:
# Speedup Graph
p <- ggplot(speedup_df, aes(x= benchmark, y = speedup, fill = factor(technique))) +
geom_bar(stat = "identity", position = "dodge", width = 0.7) +
scale_fill_discrete(name="Technique", labels=c("No Compression", "Compression Perfect", "Compression BDI", "Precompression BDI Hash",
"Precompression BDI Similarity", "Compression CPack", "Precompression CPack Hash",
"Precompression CPack Similarity", "Compression FPCD", "Precompression FPCD Hash",
"Precompression FPCD Similarity")) +
labs(title = plot_name, y="Speedup", x="Benchmarks") +
coord_cartesian(ylim=c(min(speedup_df$speedup), max(speedup_df$speedup))) +
theme(axis.text.x = element_text(angle=45, size=10, hjust=1)) +
geom_text(data=speedup_df, aes(label=sprintf("%0.4f", round(speedup, digits = 4)), fontface = "bold"), size = 5, position=position_dodge(width=0.7),
hjust=0.5, vjust=-0.7)
I want to insert gaps between the bars at arbitrary points. For example I want to have a gap before and after all the "BDI" bars. I tried using breaks in scale_fill_discrete but I get the error that they need to be the same number as the labels.
If you provide a reproducible example, I can test it on my side. The idea is to change the width within geom_bar and the width within position_dodge(). You may need to adjust the values in the following example using mtcars data.
library(ggplot2)
# without space
ggplot(mtcars, aes(x= 1, y = mpg, fill = factor(cyl))) +
geom_bar(stat = "identity", position= "dodge", width = 0.7)
# add space
ggplot(mtcars, aes(x= 1, y = mpg, fill = factor(cyl))) +
geom_bar(stat = "identity", position = position_dodge(width=0.9), width = 0.7)
Created on 2020-01-17 by the reprex package (v0.3.0)
Edit
There may be several ways to insert gap between specific bars. An intuitive way is to use add_row() to a few empty rows and re-set levels:
library(tidyverse)
df <- data.frame(x = c("a", "b", "c", "d", "e"),
y = c(1, 2, 5, 6, 3))
df <- add_row(df, x = c(" ", " "), y = c(NA))
df$x <- factor(df$x, levels = c("a", " ", "b", "c", "d", " ", "e"))
ggplot(df, aes(x= x, y = y, fill = x)) +
geom_bar(stat = "identity", na.rm = TRUE,
position = "dodge", width = 1) +
scale_fill_manual(values=c("red", "white", "green","blue","maroon",
"white","navy"))
Created on 2020-01-18 by the reprex package (v0.3.0)

ggplot2: lines + points with white fill in plot and legend?

I want to create a plot with the ggplot2 package, which combines lines and points. The points should have colors and shapes according to a group indicator. A legend should be created, which displays colors and shapes according to the plot.
This part worked fine. However, all points should have a white fill and I cannot find the right code for that.
A google search suggests to use fill = "white", but this is not working.
Consider the following example data and plot:
library("ggplot2")
# Example data
df <- data.frame(y = 1:100,
x = 1:100,
group = as.factor(c(rep(1, 33), rep(2, 33), rep(3, 34))))
# Create plot --> fill = "white" doesnt work
ggplot(df, aes(x = x, y = y)) +
geom_line(aes(colour = factor(group, labels = c("a", "b", "c")))) +
geom_point(aes(colour = factor(group, labels = c("a", "b", "c")),
shape = factor(group, labels = c("a", "b", "c"))),
fill = "white") + ##### This line is not working #####
theme(legend.title = element_blank())
Question: How could I fill the points of this plot with white (both in the plot and the legend)?
You can use scale_shape_discrete to set solid = FALSE:
ggplot(df, aes(x = x, y = y)) +
geom_line(aes(colour = factor(group, labels = c("a", "b", "c")))) +
scale_shape_discrete(solid = F) +
geom_point(aes(colour = factor(group, labels = c("a", "b", "c")),
shape = factor(group, labels = c("a", "b", "c")))) +
theme(legend.title = element_blank())
The default shapes used by ggplot2 only have a colour: to get both a
colour and a fill, you have to use point shapes from 21 to 25. Then setting
fill = "white" will work:
library(ggplot2)
df <- data.frame(
y = 1:10, x = 1:10,
group = factor(rep(1:3, c(3, 3, 4)), labels = letters[1:3])
)
ggplot(df, aes(x = x, y = y, colour = group)) +
geom_line() +
geom_point(aes(shape = group), fill = "white", size = 3) +
theme(legend.title = element_blank()) +
scale_shape_manual(values = 20 + seq_along(unique(df$group)))
Coming up with a solution if you are not using standard shapes 21:25. The trick is to call geom_point twice, one with shape 21 to clean up the overlapping line, and another to overlay the desired shapes.
library(ggplot2)
library(RColorBrewer)
Paired = brewer.pal(n=10, name="Paired")
unicodeShapes = -10122:-10131
df = data.frame(y = 1:10, x = 1:10, labels = LETTERS[1:10])
ggplot(data=df,aes(x=x, y=y)) +
geom_line(color="gray50") +
geom_point(aes(x=x, y=y), color="white", shape=-9679, fill="white", size=5.0,show.legend=FALSE) +
geom_point(aes(color=labels, shape=labels), size=6.5) +
scale_shape_manual(name="Labels",values=unicodeShapes) +
scale_color_manual(name="Labels",values=Paired) +
theme_classic()+
theme(axis.line.x=element_line(color="gray20", size=1.0),
axis.line.y=element_line(color="gray20", size=0.5),
panel.grid.major.x=element_blank(),
panel.grid.minor=element_blank(),
panel.border=element_rect(colour="gray50",fill=NA,size=1.0),
panel.background = element_rect(colour = "gray50", size=1.0),
legend.position="bottom",
text=element_text(size=18))
Shapes on top of line

Combine legend in ggplot2

I have a plot of multiple geom_point and a single stat_function in ggplot2. Is there a way to show a single legend?
df <- data.frame("x"=c(1:5), "a"=c(1,2,3,3,3), "b"=c(1,1.1,1.3,1.5,1.5))
df <- melt(df, "x")
p <- ggplot(df, aes(x=x, y=value)) +
geom_point(aes(colour=variable, shape=variable)) +
stat_function(aes(colour="log2(x)"), fun=log2)
I want to have a single legend with the blue line and the two colored shapes. I tried
scale_colour_discrete(name="legend", breaks=c("a", "b", "log2(x)")) +
scale_shape_discrete(name="legend", breaks=c("a", "b"))
but this does not work. Is there a way to do this automatically or by hand?
Thanks in advance.
Probably an easier alternative is to use override.aes as follows:
ggplot(df, aes(x = x, y = value)) +
geom_point(aes(colour = variable, shape = variable), size = 3) +
stat_function(aes(colour = "log2(x)"), fun = log2, size = 1.5) +
guides(shape = FALSE,
colour = guide_legend(override.aes = list(shape = c(16, 17, NA),
linetype = c("blank", "blank", "solid"))))
which results in:
Specify a . as the shape symbol for your curve and a blank line for your points:
p <- ggplot(df, aes(x=x, y=value)) +
geom_point(aes(colour=variable, shape=variable, linetype = variable), size = 3) +
stat_function(aes(colour="log2(x)", shape = "log2(x)", linetype = "log2(x)"), fun=log2) +
scale_shape_manual(values = setNames(c(16, 17, 46), c("a", "b", "log2(x)"))) +
scale_linetype_manual(values = setNames(c(0, 0, 1), c("a", "b", "log2(x)")))
print(p)

Resources