How to make ggplot legend add objects horizontally (vs vertically) - r

The legend in ggplot can be moved to the bottom of the graphic as a horizontal legend by adding the following arguments to the theme function:
legend.position="bottom" moves the legend below the graph
legend.direction="horizontal" orients the legend to be horizontal.
However, not really...
The legend.direction="horizontal" simply seems to decrease the number of rows in the legend and the number of legend objects in each row.
This can be done manually using guides(color=guide_legend(nrow=x)
dat <- data.frame(plot = rep(letters,2), val = rep(1:length(letters),2))
library(ggplot2)
ggplot(dat, aes(x = val, y = val, color = plot)) +
geom_point() +
theme(legend.position="bottom") +
guides(color=guide_legend(nrow=2))
Regardless....
If you notice in the graphic output of the above code, even though I can control the "dimensions" of my legend (i.e., the number of rows), I can't figure out how to change the ordering of the legend from vertical to horizontal.
So instead of a being above b etc. ("vertically" sorted) as above, I want b to be added next to a ("horizontally" sorted).
How do I make my legend add objects horizontally vs vertically?
Like so:

Try adding byrow = TRUE to guide_legend:
ggplot(dat, aes(x = val, y = val, color = plot)) +
geom_point() +
theme(legend.position="bottom") +
guides(color=guide_legend(nrow=2, byrow = TRUE))

Related

ggplot fill property changes scale

I have a simple dataframe and using ggplot to create a bar graph using the code:
ggplot(data=data_cases,aes(x = k,y = val)) +
stat_summary(fun.y=sum, geom = "bar") +
scale_x_discrete(name="Type",
labels=c('A&R','A&E','C&E'))
This code generates the desired result. However when i add a fill property to color the portions of the graph, it changes the y scale. In the image below, the picture on the left has the correct scale, the one on the right is what is produced if the fill property is set (ggplot(data=data_cases,aes(x = k,y = val, fill=state)))
Data:
"k","state","val"
"A&C","SA ",3
"C&E","SA ",2
"A&C","NSW",29
"A&E","NSW",10
"C&E","NSW",11
"C&E","NT ",1
"A&C","WA ",3
"A&E","WA ",1
"C&E","WA ",4
"A&C","VIC",24
"A&E","VIC",1
"C&E","VIC",15
"A&C","QLD",7
"A&E","QLD",2
"C&E","QLD",17
It is because this second chart is showing the number of cases per state, e.g. almost 30 for NSW with type A&R. Each bar is starting from 0.
If you want to be like the original then all the bars should be stacked on top of each other: use position='stack'
ggplot(data=data_cases,aes(x = k,y = val)) +
stat_summary(fun.y=sum, geom = "bar", position="stack") + # <---
scale_x_discrete(name="Type",
labels=c('A&R','A&E','C&E'))
ggplot has a bunch of positions like this. ?position_dodge, ?position_fill, ?position_stack, ?position_identity, ...
can also use geom_col
ggplot(df, aes(k, val, fill = state)) +
geom_col()

How to specify the legend box size in ggplot/ggplot2

In R/ggplot2, I have multiple plots, each of which has a legend box.
I want the legend box to be the same width for each plot, but ggplot2 tries to dynamically size the legend box based on the legend name, key values, etc. (which are unique to each plot).
The various plots must fit into a specified publication slot, with a specified width for the legend, and the plots must be made separately (so faceting to guarantee identical legend widths across the plots isn't possible).
Looking at theme I couldn't find an option to specify the legend box width ... any ideas?
To specify the legend box size you could use + theme(legend.key.size = unit(2, "cm")).
library(tidyverse)
tb <- tibble(a = 1:10, b = 10:1, c = rep(1:2, 5))
ggplot(tb, aes(a, b, colour = c)) +
geom_point() +
theme(legend.key.size = unit(0.2, "cm"))
More details and additional modifications are here and under the keywidth argument here.
#Z.lin had the right approach in the comments. Based on https://wilkelab.org/cowplot/articles/shared_legends.html this might look something like:
library(ggplot2)
library(cowplot)
Make a ggplot object
my_plot <- ggplot(iris, aes(x = Sepal.Length, y = Petal.Length, colour = Species))+
geom_point()
Extract the legend
my_legend <- get_legend(
# create some space to the left of the legend
my_plot + theme(legend.box.margin = margin(0, 0, 0, 12))
)
Re-plot your plot in a grid without the legend (can combine multiple plots here if desired)
my_plot_nl <- <- plot_grid(
my_plot + theme(legend.position="none"),
align = 'vh',
hjust = -1,
nrow = 1
)
Recombine your legend-free plot and legend and specify the relative width of each. The plot now takes up 3/4 of the plot width and the legend 1/4.
plot_grid(my_plot_nl, my_legend, rel_widths = c(3,1))
If you do this for each plot, making sure to use the same rel_widths and saving the figures using the same dimensions, the plot area and legend should be consistent across them.
You might attempt to change your theme call as follows:
theme(legend.margin =margin(r=10,l=5,t=5,b=5))?

Horizontal geom_bar with no overlaps, equal bar widths, and customized axis tick labels

Probably a simple ggplot2 question.
I have a data.frame with a numeric value, a categorical (factor) value, and a character value:
library(dplyr)
set.seed(1)
df <- data.frame(log10.p.value=c(-2.5,-2.5,-2.5,-2.39,-2,-1.85,-1.6,-1.3,-1.3,-1),
direction=sample(c("up","down"),10,replace = T),
label=paste0("label",1:10),stringsAsFactors = F) %>% dplyr::arrange(log10.p.value)
df$direction <- factor(df$direction,levels=c("up","down"))
I want to plot these data as a barplot using geom_bar, where the bars are horizontal and their lengths are determined by df$log10.p.value, their color by df$direction, and the y-axis tick labels are df$label, where the bars are vertically ordered by df$log10.p.value.
As you can see df$log10.p.value are not unique, hence:
ggplot(df,aes(log10.p.value))+geom_bar(aes(fill=direction))+theme_minimal()+coord_flip()+ylab("log10(p-value)")+xlab("")
Gives me:
How do I:
Make the bars not overlap each other.
Have the same width.
Be separated by a small margin?
Have the y-axis tick labels be df$label?
Thanks
Here is one possible solution. Please note that, by default, geom_bar determines the bar length using frequency/count. So, you need to specify stat = "identity" for value mapping.
# since all of your values are negative the graph is on the left side
ggplot(df, aes(x = label, y = log10.p.value, fill = direction)) +
geom_bar(stat = "identity") +
theme_minimal() +
coord_flip() +
ylab("log10(p-value)") +
xlab("")

Line up columns of bar graph with points of line plot with ggplot

Is there any way to line up the points of a line plot with the bars of a bar graph using ggplot when they have the same x-axis? Here is the sample data I'm trying to do it with.
library(ggplot2)
library(gridExtra)
data=data.frame(x=rep(1:27, each=5), y = rep(1:5, times = 27))
yes <- ggplot(data, aes(x = x, y = y))
yes <- yes + geom_point() + geom_line()
other_data = data.frame(x = 1:27, y = 50:76 )
no <- ggplot(other_data, aes(x=x, y=y))
no <- no + geom_bar(stat = "identity")
grid.arrange(no, yes)
Here is the output:
The first point of the line plot is to the left of the first bar, and the last point of the line plot is to the right of the last bar.
Thank you for your time.
Extending #Stibu's post a little: To align the plots, use gtable (Or see answers to your earlier question)
library(ggplot2)
library(gtable)
data=data.frame(x=rep(1:27, each=5), y = rep(1:5, times = 27))
yes <- ggplot(data, aes(x = x, y = y))
yes <- yes + geom_point() + geom_line() +
scale_x_continuous(limits = c(0,28), expand = c(0,0))
other_data = data.frame(x = 1:27, y = 50:76 )
no <- ggplot(other_data, aes(x=x, y=y))
no <- no + geom_bar(stat = "identity") +
scale_x_continuous(limits = c(0,28), expand = c(0,0))
gYes = ggplotGrob(yes) # get the ggplot grobs
gNo = ggplotGrob(no)
plot(rbind(gNo, gYes, size = "first")) # Arrange and plot the grobs
Edit To change heights of plots:
g = rbind(gNo, gYes, size = "first") # Combine the plots
panels <- g$layout$t[grepl("panel", g$layout$name)] # Get the positions for plot panels
g$heights[panels] <- unit(c(0.7, 0.3), "null") # Replace heights with your relative heights
plot(g)
I can think of (at least) two ways to align the x-axes in the two plots:
The two axis do not align because in the bar plot, the geoms cover the x-axis from 0.5 to 27.5, while in the other plot, the data only ranges from 1 to 27. The reason is that the bars have a width and the points don't. You can force the axex to align by explicitly specifying an x-axis range. Using the definitions from your plot, this can be achieved by
yes <- yes + scale_x_continuous(limits=c(0,28))
no <- no + scale_x_continuous(limits=c(0,28))
grid.arrange(no, yes)
limits sets the range of the x-axis. Note, though, that the alginment is still not quite perfect. The y-axis labels take up a little more space in the upper plot, because the numbers have two digits. The plot looks as follows:
The other solution is a bit more complicated but it has the advantage that the x-axis is drawn only once and that ggplot makes sure that the alignment is perfect. It makes use of faceting and the trick described in this answer. First, the data must be combined into a single data frame by
all <- rbind(data.frame(other_data,type="other"),data.frame(data,type="data"))
and then the plot can be created as follows:
ggplot(all,aes(x=x,y=y)) + facet_grid(type~.,scales = "free_y") +
geom_bar(data=subset(all,type=="other"),stat="identity") +
geom_point(data=subset(all,type=="data")) +
geom_line(data=subset(all,type=="data"))
The trick is to let the facets be constructed by the variable type which was used before to label the two data sets. But then each geom only gets the subset of the data that should be drawn with that specific geom. In facet_grid, I also used scales = "free_y" because the two y-axes should be independent. This plot looks as follows:
You can change the labels of the facets by giving other names when you define the data frame all. If you want to remove them alltogether, then add the following to your plot:
+ theme(strip.background = element_blank(), strip.text = element_blank())

Using coord_flip() with facet_wrap(scales = "free_y") in ggplot2 seems to give unexpected facet axis tick marks and tick labels

I am trying to create a faceted plot with flipped co-ordinates where one and only one of the axes are allowed to vary for each facet:
require(ggplot2)
p <- qplot(displ, hwy, data = mpg)
p + facet_wrap(~ cyl, scales = "free_y") + coord_flip()
This plot is not satisfactory to me because the wrong tick marks and tick labels are repeated for each plot. I want tick marks on every horizontal axis not on every vertical axis.
This is unexpected behaviour because the plot implies that the horizontal axis tick marks are the same for the top panels as they are for the bottom ones, but they are not. To see this run:
p <- qplot(displ, hwy, data = mpg)
p + facet_wrap(~ cyl, scales = "fixed") + coord_flip()
So my question is: is there a way to remove the vertical axis tick marks for the right facets and add horizontal axis tick marks and labels to the top facets?
As Paul insightfully points out below, the example I gave can be addressed by swapping x and y in qplot() and avoiding coord_flip(), however this does not work for all geoms for example, if I want a horizontal faceted bar plot with free horizontal axes I could run:
c <- ggplot(diamonds, aes(clarity, fill=cut)) + geom_bar()
c + facet_wrap(~cut, scales = "free_y") + coord_flip()
These facets have a variable horizontal axes but repeated vertical axis tick marks instead of repeated horizontal axes tick marks. I do not think Paul's trick will work here, because unlike scatter plots, bar plots are not rotationally symmetric.
I would be very interested to hear any partial or complete solutions.
Using coord_flip in conjunction with facet_wrap is the problem. First you define a certain axis to be free (the x axis) and then you swap the axis, making the y axis free. Right now this is not reproduced well in ggplot2.
In your first example, I would recommend not using coord_flip, but just swapping the variables around in your call to qplot, and using free_x:
p <- qplot(hwy, displ, data = mpg)
p + facet_wrap(~ cyl, scales = "free_x")
This is the second or third time I have run into this problem myself. I have found that I can hack my own solution by defining a custom geom.
geom_bar_horz <- function (mapping = NULL, data = NULL, stat = "bin", position = "stack", ...) {
GeomBar_horz$new(mapping = mapping, data = data, stat = stat, position = position, ...)
}
GeomBar_horz <- proto(ggplot2:::Geom, {
objname <- "bar_horz"
default_stat <- function(.) StatBin
default_pos <- function(.) PositionStack
default_aes <- function(.) aes(colour=NA, fill="grey20", size=0.5, linetype=1, weight = 1, alpha = NA)
required_aes <- c("y")
reparameterise <- function(., df, params) {
df$width <- df$width %||%
params$width %||% (resolution(df$x, FALSE) * 0.9)
OUT <- transform(df,
xmin = pmin(x, 0), xmax = pmax(x, 0),
ymin = y - .45, ymax = y + .45, width = NULL
)
return(OUT)
}
draw_groups <- function(., data, scales, coordinates, ...) {
GeomRect$draw_groups(data, scales, coordinates, ...)
}
guide_geom <- function(.) "polygon"
})
This is just copying the geom_bar code from the ggplot2 github and then switching the x and y references to make a horizontal barplot in the standard Cartesian coordinators.
Note that you must use position='identity' and possibly also stat='identity' for this to work. If you need to use a position other than identity then you will have to eddit the collide function for it to work properly.
I've just been trying to do a horizontal barplot, and run into this problem where I wanted to scales = "free_x". In the end, it seemed easier to create the conventional (vertical) barplot), rotate the text so that if you tip your head to the left, it looks like the plot that you want. And then, once your plot is completed, rotate the PDF/image output(!)
ggplot(data, aes(x, y)) +
geom_bar(stat = "identity") +
facet_grid(var ~ group, scale = "free", space = "free_x", switch = "both") +
theme(axis.text.y = element_text(angle=90), axis.text.x = element_text(angle = 90),
strip.text.x = element_text(angle = 180))
The main keys to do this are to switch = "both", which moves the facet labels to the other axis, and the element_text(angle=90) which rotates the axis labels and text.

Resources