ggplot2: Varying facet width with independent `Y` axes - r

Dummy data
d = data.frame(
x = factor(LETTERS[c(1,2,3,4,1,2,3,4,1,2,1,2,1,2,1,2)]),
y = c(100,80,70,60,130,90,65,60,2,3,3,3,2,2,1,2),
grid = rep(letters[1:2], each=8)
)
Issue
ggplot(d, aes(x=x, y=y)) + facet_grid(~grid, scales="free",space="free_x") + geom_point()
I like this graph. My only issue is that both grids use the same Y axis. So, I tried using facet_wrap instead of facet_grid and got
ggplot(d, aes(x=x, y=y)) + facet_wrap(~grid, scales="free") + geom_point()
But unfortunately, facet_wrap does not have a "space" parameter and as a result the right and the left graph are of the same width.
Question
How can I do so that the space between levels of the variable d$x is equal among both facets (leading to facets having different width) AND to have a separate Y axis for each facet. Of course, I would like to keep the facets to be aligned horizontally.

Use ggplot grob and modify the widths in the table
# Capture the plot
q = ggplot(d, aes(x=x, y=y)) + facet_grid(~grid, scales="free",space="free_x") + geom_point()
gt = ggplotGrob(q)
# Modify the widths
gt$widths[5] = unit(8, "cm")
gt$widths[9] = unit(4, "cm")
# Plot the graph
grid.newpage()
grid.draw(gt)

Related

Is it possible to make a column plot using ggplot in which the column fill is controlled by a third variable?

I have a data frame with three continuous variables (x,y,z). I want a column plot in which x defines the x-axis position of the columns, y defines the length of the columns, and the column colors (function of y) are defined by z. The test code below shows the set up.
`require(ggplot2)
require(viridis)
# Create a dummy data frame
x <- c(rep(0.0, 5),rep(0.5,10),rep(1.0,15))
y <- c(seq(0.0,-5,length.out=5),
seq(0.0,-10,length.out=10),
seq(0.0,-15,length.out=15))
z <- c(seq(10,0,length.out=5),
seq(8,0,length.out=10),
seq(6,0,length.out=15))
df <- data.frame(x=x, y=y, z=z)
pbase <- ggplot(df, aes(x=x, y=y, fill=z))
ptest <- pbase + geom_col(width=0.5, position="identity") +
scale_fill_viridis(option="turbo",
limits = c(0,10),
breaks=seq(0,10,2.5),
labels=c("0","2.5","5.0","7.5","10.0"))
print(ptest)`
The legend has the correct colors but the columns do not. Perhaps this is not the correct way to do this type of plot. I tried using geom_bar() which creates a bars with the correct colors but the y-values are incorrect.
It looks like you have 3 X values that each appear 5, 10, or 15 times. Do you want the bars to be overlaid on top of one another, as they are now? If you add an alpha = 0.5 to the geom_col call you'll see the overlapping bars.
Alternatively, you might use dodging to show the bars next to one another instead of on top of one another.
ggplot(df, aes(x=x, y=y, fill=z, group = z)) +
geom_col(width=0.5, position=position_dodge()) +
scale_fill_viridis_c(option="turbo", # added with ggplot 3.x in 2018
limits = c(0,10),
breaks=seq(0,10,2.5),
labels=c("0","2.5","5.0","7.5","10.0"))
Or you might plot the data in order of y so that the smaller bars appear on top, visibly:
ggplot(dplyr::arrange(df,y), aes(x=x, y=y, fill=z))+
geom_col(width=0.5, position="identity") +
scale_fill_viridis_c(option="turbo",
limits = c(0,10),
breaks=seq(0,10,2.5),
labels=c("0","2.5","5.0","7.5","10.0"))
I solved this by using geom_tile() in place of geom_col().

How to get vertical lines in legend key using ggplot2 for geom_pointrange() type graphic

UPDATE: The question is moot. The vertical lines in the legend key are now default for geom_pointrange() in ggplot2.
For ggplot2 graphics that have a symbol for a point estimate and a vertical line representing a range about that estimate (95% confidence interval, Inter-quartile Range, Minimum and Maximum, etc) I cannot get the legend key to show the symbol with a vertical line. Since geom_pointrange() only has arguments for ymin and ymax, I would think the intended (default) functionality of geom_pointrange(show_guide=T) would be to have vertical lines (I say default because I understand that with coord_flip one could make horizontal lines in the plot). I also understand that having vertical lines in the legend key when the legend position is right or left will have the vertical lines "run together"...but for legends in the top or bottom having a vertical line through the symbol means that the key will match what appears in the plot.
Yet the approaches I've tried still put horizontal lines in the legend key:
## set up
library(ggplot2)
set.seed(123)
ru <- 2*runif(10) - 1
dt <- data.frame(x = 1:10,
y = rep(5,10)+ru,
ylo = rep(1,10)+ru,
yhi = rep(9,10)+ru,
s = rep(c("A","B"),each=5),
f = rep(c("facet1", "facet2"), each=5))
Default show_guide=T for geom_pointrange yields desired plot but has horizontal lines in legend key where vertical is desired (so as to match the plot):
ggplot(data=dt)+
geom_pointrange(aes(x = x,
y = y,
ymin = ylo,
ymax = yhi,
shape = s),
size=1.1,
show_guide=T)+
theme(legend.position="bottom")
An attempt with geom_point and geom_segment together yields desired plot but has horizontal lines in legend key where vertical is desired (so as to match the plot):
ggplot(data=dt)+
geom_point(aes( x = x,
y = y,
shape = s),
size=3,
show_guide=T)+
geom_segment(aes( x = x,
xend = x,
y = ylo,
yend = yhi),
show_guide=T)+
theme(legend.position="bottom")
An attempt with geom_point and geom_vline together yields desired legend key but does not respect the ymin and ymax values in the plot:
ggplot(data=dt)+
geom_point(aes(x=x, y=y, shape=s), show_guide=T, size=3)+
geom_vline(aes(xintercept=x, ymin=ylo, ymax=yhi ), show_guide=T)+
theme(legend.position="bottom")
How do I get the legend key of the 3rd graph but the plot of one of the first two?
My solution involves plotting a vertical line with geom_vline(show_guide=T) for an x-value that is out of the bounds of the displayed x-axis along with plotting geom_segment(show_guide=F):
ggplot(data=dt)+
geom_point(aes(x=x, y=y, shape=s), show_guide=T, size=3)+
geom_segment(aes(x=x, xend=x, y=ylo, yend=yhi), show_guide=F)+
geom_vline(xintercept=-1, show_guide=T)+
theme(legend.position="bottom")+
coord_cartesian(xlim=c(0.5,10.5))
The solution with coord_cartesian() for a numeric x axis is fine but facet_grid(scales='free_x') can be problematic:
# problem: coord_cartesian with numeric x and facetting with scales=free_x
ggplot(data=dt)+
geom_point(aes(x=x, y=y, shape=s), show_guide=T, size=3)+
geom_segment(aes(x=x, xend=x, y=ylo, yend=yhi), show_guide=F)+
geom_vline(xintercept=-1, show_guide=T)+
theme(legend.position="bottom")+
coord_cartesian(xlim=c(0.5,10.5))+
facet_grid(.~f, scales="free_x")
So in that situation, another solution that might not apply in every situation, but change x values to some meaningful character of factor and then adjust the xlim:
## hack solution: adjust xlim after change x to factor or character
## (carefully -- double check conversion):
dt$x <- factor(dt$x)
ggplot(data=dt)+
geom_point(aes(x=x, y=y, shape=s), show_guide=T, size=3)+
geom_segment(aes(x=x, xend=x, y=ylo, yend=yhi), show_guide=F)+
geom_vline(xintercept=-1, show_guide=T)+
theme(legend.position="bottom")+
coord_cartesian(xlim=c(0.5,5.5))+
facet_grid(.~f, scales="free_x")
If you don't mind having to use grid to draw the plot, you can manipulate the guide grobs directly:
library(grid)
library(gtable)
library(ggplot2)
set.seed(123)
ru <- 2*runif(10) - 1
dt <- data.frame(x = 1:10,
y = rep(5,10)+ru,
ylo = rep(1,10)+ru,
yhi = rep(9,10)+ru,
s = rep(c("A","B"),each=5),
f = rep(c("facet1", "facet2"), each=5))
ggplot(data=dt)+
geom_pointrange(aes(x = x,
y = y,
ymin = ylo,
ymax = yhi,
shape = s),
size=1.1,
show_guide=T)+
theme(legend.position="bottom") -> gg
gb <- ggplot_build(gg)
gt <- ggplot_gtable(gb)
seg <- grep("segments", names(gt$grobs[[8]]$grobs[[1]]$grobs[[4]]$children))
gt$grobs[[8]]$grobs[[1]]$grobs[[4]]$children[[seg]]$x0 <- unit(0.5, "npc")
gt$grobs[[8]]$grobs[[1]]$grobs[[4]]$children[[seg]]$x1 <- unit(0.5, "npc")
gt$grobs[[8]]$grobs[[1]]$grobs[[4]]$children[[seg]]$y0 <- unit(0.1, "npc")
gt$grobs[[8]]$grobs[[1]]$grobs[[4]]$children[[seg]]$y1 <- unit(0.9, "npc")
seg <- grep("segments", names(gt$grobs[[8]]$grobs[[1]]$grobs[[6]]$children))
gt$grobs[[8]]$grobs[[1]]$grobs[[6]]$children[[seg]]$x0 <- unit(0.5, "npc")
gt$grobs[[8]]$grobs[[1]]$grobs[[6]]$children[[seg]]$x1 <- unit(0.5, "npc")
gt$grobs[[8]]$grobs[[1]]$grobs[[6]]$children[[seg]]$y0 <- unit(0.1, "npc")
gt$grobs[[8]]$grobs[[1]]$grobs[[6]]$children[[seg]]$y1 <- unit(0.9, "npc")
grid.newpage()
grid.draw(gt)

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

Faceted ggplot with y axis in the middle

Suppose I have two plots, side by side, with the same y-axis, generated by the following R code:
df <- data.frame(x=c(5,2,7,3), y=c(11,3,5,6), facet=c(1,1,2,2))
ggplot(df, aes(x, y)) + facet_grid(~facet) + geom_point()
Is it possible to write the y axis text (e.g., 10.0, 7.5, 5.0) in the middle, between the two plots? (Preferentially the text should be centered.)
Here is a way (well almost) using Baptiste's answer from this SO post Display y-axis for each subplot when faceting. Not quite in the middle but its close
library(ggplot2)
library(gtable)
# your data
df <- data.frame(x=c(5,2,7,3), y=c(11,3,5,6), facet=c(1,1,2,2))
# First plot (a bit of extra space between facets)
p <- ggplot(df, aes(x, y)) + facet_grid(~facet) +
geom_point() +
theme(panel.margin = unit(1, "lines"),
axis.text.y = element_text( hjust=0))
# get y-axis labels
g <- ggplotGrob(p)
axis <- gtable_filter(g, "axis-l")[["grobs"]][[1]][["children"]][["axis"]][,1]
# remove axis
g[["grobs"]][[4]][["children"]][["axis"]] <- NULL
# build plot & add axis to LHS of left facet
panels <- subset(g$layout, name == "panel")
g <- gtable_add_grob(g, grobs=axis, t = unique(panels$t), l=tail(panels$l, -1)-1)
grid.newpage()
grid.draw(g)

How can I control the x position of boxplots in ggplot2?

First, a quick example to set the stage:
set.seed(123)
dat <- data.frame(
x=rep( c(1, 2, 4, 7), times=25 ),
y=rnorm(100),
gp=rep(1:2, each=50)
)
p <- ggplot(dat, aes(x=factor(x), y=y))
p + geom_boxplot(aes(fill = factor(gp)))
I would like to produce a similar plot, except with control over the x position of each set of boxplots. My first guess was using a non-factor x aesthetic that controls the position along the x-axis of these box plots. However, once I try to do this it seems like geom_boxplot doesn't interpret the aesthetics as I would hope.
p + geom_boxplot( aes(x=x, y=y, fill=factor(gp)) )
In particular, geom_boxplot seems to collapse over all x values in some way when they're non-factors.
Is there a way to control the x position of boxplots with ggplot2? Either through specifying a distance between each level of a factor aesthetic, some more clever use of non-factor aesthetics, or otherwise?
You can use scale_x_discrete() to set positions (ticks) for the x axis.
p <- ggplot(dat, aes(x=factor(x), y=y))
p + geom_boxplot(aes(fill = factor(gp))) +
scale_x_discrete(limits=1:7)
You can also do this with the group aesthetic. However, I'm not sure why you cannot just pass x to the group. This doesn't work:
ggplot() +
geom_boxplot(data=dat, aes(x=x, y=y, fill=factor(gp), group=x))
But this does:
ggplot() +
geom_boxplot(data=dat, aes(x=x, y=y, fill=factor(gp), group=paste(x, gp)))

Resources