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
Related
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")
)
Problem
In ggplot2, legends for different scales are usually integrated into a single, combined legend whenever possible. This worked fine for me so far. However, when I try parsing the scale labels to include mathematical symbols in the legend, this does not seem to work.
See this example:
# example data
d <- data.frame(x = 1:3, y = rep(0,3), f = c("a[1]", "a[2]", "a[3]"))
# plot
p <- ggplot(data = d, aes(x = x, y = y, color = f, shape = f)) +
geom_point() +
guides(
color = guide_legend(title = "F"),
shape = guide_legend(title = "F")
)
The following gives the plot with custom values for shapes/colors and with the legends combined as intended.
# plot + custom shapes/colors
p +
scale_color_manual(name = "F", values = c("red", "blue", "green")) +
scale_shape_manual(name = "F", values = c(16, 15, 18))
However, when parsing the labels, the labels come out as expected, but the legends are no longer combined.
# plot + custom shapes/colors + parsed labels
parse.labels <- function(x) parse(text = x)
p +
scale_color_manual(name = "F", labels = parse.labels, values = c("red", "blue", "green")) +
scale_shape_manual(name = "F", labels = parse.labels, values = c(16, 15, 18))
Note that the result is the same with scale_._discrete instead of scale_._manual. Similarly, specifying identical names for the two scales with guides(shape = guide_legend(title = "F"), color = guide_legend(title = "F")) does not change this behavior.
Question
How can I integrate the two legends while maintaining the parsed labels?
Use scales::parse_format() instead of the parse() function from base R, and you should be fine:
library(scales)
ggplot(data = d, aes(x = x, y = y, color = f, shape = f)) +
geom_point() +
scale_color_manual(name = "F",
labels = parse_format(),
values = c("red", "blue", "green")) +
scale_shape_manual(name = "F",
labels = parse_format(),
values = c(16, 15, 18))
I think this has something to do with how parse returns an expression tagged with automatically-generated srcfile / wholeSrcref attributes by default, while parse_format does not. These additional attributes prevent the two scales from being merged together, since they are not identical.
(Using function(x) parse(x = text, srcfile = NULL) in both scales will also work, same as above, but I find the function from scales to be less verbose.)
I would suggest this approach using the labels argument in scale_*_discrete() and saving your values for labels in a new vector:
library(ggplot2)
# example data
d <- data.frame(x = 1:3, y = rep(0,3), f = c("a[1]", "a[2]", "a[3]"))
#Labs
lab1 <- c(expression(a[1]),
expression(a[2]),
expression(a[3]))
# plot
ggplot(data = d, aes(x = x, y = y, color = f, shape = f)) +
geom_point() +
guides(
color = guide_legend(title = "F"),
shape = guide_legend(title = "F")
)+
scale_color_discrete(labels = lab1) +
scale_shape_discrete(labels = lab1)
Output:
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.
I have a problem where the legend of my ggplot() does not appear. Here's my code:
plot_bt <- ggplot(NULL, aes(x, v1)) +
geom_line(data = nig_bt_1, colour = "black") +
geom_line(data = nig_bt_2, colour = "blue") +
geom_line(data = nig_bt_3, colour = "red") +
labs(x = "X", y = "Probability")
I tried to make a legend inside this graph but I could not do it. It just does not appear. I try to make a plot of three different types of NIG distribution. In nig_bt_1 etc. I have my values. Those three densities appear but the legend doesn't. I tried the scale_color_manual function too with no success.
Thank you very much.
x <- seq(-7.5,7.5,0.001)
nig_bt_1 <- data.frame(x ,v1 = dnig(x, param = pr_bt_1))
nig_bt_2 <- data.frame(x ,v1 = dnig(x, param = pr_bt_2))
nig_bt_3 <- data.frame(x ,v1 = dnig(x, param = pr_bt_3))
Just do this:
plot_bt <- ggplot(NULL, aes(x, v1)) +
geom_line(data = nig_bt_1, aes(colour = "a")) +
geom_line(data = nig_bt_2, aes(colour = "b")) +
geom_line(data = nig_bt_3, aes(colour = "c")) +
labs(x = "X", y = "Probability") +
scales_color_manual(values= c("a" = "black", "b" = "blue", "c" = "red"))
A guide can only depict mappings you've defined using aes. The ggplot2 way is of course to first combine the data and use a grouping variable.
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)