ggplot - Find coordinates of facet spacing - r

I have, e.g., the following plot:
library(ggplot2)
dat = data.frame(x = rnorm(100), y = rexp(100), grp = factor(sample(1:2, 100, replace = TRUE)))
ggplot(dat, aes(x = x, y = y, color = grp)) +
geom_point() +
facet_wrap(~grp) +
theme(panel.spacing = unit(2, "lines"))
and want to add a vertical line between the two plots - that is, in the middle of the panel spacing. My problem is, I am not sure of how to get the coordinates of the inner plot edges / the panel spacing in native units.
Both panels have unit 0.5 npc -- and I am not sure how I would convert this. I tried using viewports, but that did not work. Is there a way other than arranging plot 1 - plot of vertical line - plot 2 ?

Is this what you had in mind? You can tweak around with the parameter to change the position where the line will appear.
# loading the libraries
library(ggplot2)
library(grid)
library(cowplot)
# preparing the data
dat = data.frame(x = rnorm(100),
y = rexp(100),
grp = factor(sample(1:2, 100, replace = TRUE)))
# preparing the plot
plot <- ggplot(dat, aes(x = x, y = y, color = grp)) +
geom_point() +
facet_wrap( ~ grp) +
theme(panel.spacing = unit(2, "lines"))
# preparing the line
gline <- grid::linesGrob(x = 0.5)
# plotting both the plot and the line
cowplot::ggdraw() +
cowplot::draw_plot(plot) +
cowplot::draw_plot(gline)
Created on 2018-01-24 by the reprex
package (v0.1.1.9000).

library(grid)
library(gtable)
library(magrittr)
ggplotGrob(p) %>%
gtable_add_grob(segmentsGrob(0.5, 0, 0.5, 1),
t = 4, b = 8, l = 7, r = 7) %>%
grid.draw()
enter image description here

Related

Multipanel plot with ggplot2?

I have bar charts faceted by stock symbol name:
I would like to be able to add a little "subplot" of an indicator below each plot, like this:
Is this possible with ggplot2? I thought of a secondary axis, but most often there is no linear relation between the main plot axis and the indicator axis (so using sec_axis is not an option).
Thanks!
Since these appear to be categorically different types of plots, I think you'll have better luck creating separate plots and then rendering them together. Here's one solution using cowplot package:
library(ggplot2)
library(cowplot)
#sample data
df <- data.frame(x = 1:100, y = cumsum(rnorm(100)), volume = sample(1:10, 100, replace = TRUE))
p1 <- ggplot(df, aes(x,y)) +
geom_line()
p2 <- ggplot(df, aes(x,volume)) +
geom_bar(stat = "identity")
plot_grid(p1, p2, align = "v", ncol = 1, rel_heights = c(.8, .2))
Created on 2019-01-25 by the reprex package (v0.2.1)
Edit
Continuing to build on this kludgy example to support the concept of faceting. Ignore the ugly graph, it's smooshed due to image size constraints.
library(ggplot2)
library(cowplot)
library(gridExtra)
#sample data
df <- data.frame(x = 1:100, y = cumsum(rnorm(100)), volume = sample(1:10, 400, replace = TRUE), group = letters[1:4])
plots <- list()
for (j in unique(df$group)){
plot_df <- df[df$group == j, ]
p1 <- ggplot(plot_df, aes(x,y)) +
geom_line() +
facet_wrap(~group) +
xlab("")
p2 <- ggplot(plot_df, aes(x,volume)) +
geom_bar(stat = "identity")
p_out <- plot_grid(p1, p2, align = "v", ncol = 1, rel_heights = c(.7, .3))
plots[[j]] <- p_out
}
do.call(grid.arrange, plots)
Created on 2019-01-25 by the reprex package (v0.2.1)

Setting the last plot in the middle with facet_wrap

I am trying to create some multiplots with facet_wrap. However I am not sure if is the right approach for my graph. Here is a short reproducible example:
ggplot(airquality, aes(x = Day, y = Temp)) +
facet_wrap(~Month) +
geom_line()
This produces this plot here:
Is it possible to "center" the two plots in the 2nd row with the facet_wrap approach ?
Note, that I don't want to reorder the plots, I just want to Center the 2nd row
#Tjebo's suggestion of using cowplot will work:
p <- ggplot(mapping = aes(x = Day, y = Temp)) +
facet_wrap(~Month) +
geom_line()
cowplot::plot_grid(
p %+% subset(airquality, Month < 8),
p %+% subset(airquality, Month > 7),
nrow = 2
)
You might also consider the function set_panel_size from the egg package which lets you set the panel size (width, height) of multiple plots to an absolute measurement, see this vignette for more details.
Using Axeman's code to create the plots
library(egg)
library(gridExtra)
p <- ggplot(mapping = aes(x = Day, y = Temp)) +
facet_wrap(~Month) +
geom_line()
p1 <- p %+% subset(airquality, Month < 8) + labs(x = NULL)
p2 <- p %+% subset(airquality, Month > 7)
Now arrange the plots using grid.arrange after specifying the panel sizes
grid.arrange(grobs = lapply(
list(p1, p2),
set_panel_size,
width = unit(5, "cm"),
height = unit(4, "cm")
))
And just in case anyone wants to know how to do it from scratch in grid...
library(ggplot2)
library(dplyr)
library(grid)
vps <- c("top_left", "top_mid", "top_right", "bottom_left", "bottom_right")
main_vp <- vpTree(viewport(name = "main"), vpList(
viewport(x = 1/6, y = 0.75, width = 1/3, height = 0.5, name = vps[1]),
viewport(x = 3/6, y = 0.75, width = 1/3, height = 0.5, name = vps[2]),
viewport(x = 5/6, y = 0.75, width = 1/3, height = 0.5, name = vps[3]),
viewport(x = 1/3, y = 0.25, width = 1/3, height = 0.5, name = vps[4]),
viewport(x = 2/3, y = 0.25, width = 1/3, height = 0.5, name = vps[5])))
grid.newpage()
pushViewport(main_vp)
plots <- lapply(1:5, function(i){
seekViewport(vps[i])
invisible(grid.draw(ggplotGrob(
ggplot(filter(airquality, Month == levels(as.factor(airquality$Month))[i]),
mapping = aes(Day, Temp)) +
geom_line() +
facet_grid(.~Month)
)))})
Created on 2020-07-03 by the reprex package (v0.3.0)
And yet another option to achieve the desired result would the ggh4x package which via ggh4x::facet_manual adds some flexibility to position the facets via the design argument:
library(ggplot2)
library(ggh4x)
base <- ggplot(airquality, aes(x = Day, y = Temp)) +
geom_line()
design <- c(
"
AABBCC
#DDEE#
"
)
base + ggh4x::facet_manual(~Month, design = design)
design <- c(
"
AABBCC
DDDEEE
"
)
base + ggh4x::facet_manual(~Month, design = design)

Different size facets at x-axis

Length of x-axis is important for my plot because it allows one to compare between facets, therefore I want facets to have different x-axis sizes. Here is my example data:
group1 <- seq(1, 10, 2)
group2 <- seq(1, 20, 3)
x = c(group1, group2)
mydf <- data.frame (X =x , Y = rnorm (length (x),5,1),
groups = c(rep(1, length (group1)), rep(2, length(group2))))
And my code:
p1 = ggplot(data=mydf,aes(x=X,y=Y,color=factor(groups)) )+
geom_point(size=2)+
scale_x_continuous(labels=comma)+
theme_bw()
p1+facet_grid(groups ~ .,scales = "fixed",space="free_x")
And the resulting figure:
Panel-1 has x-axis values less then 10 whereas panel-2 has x-axis value extending to 20. Still both panels and have same size on x-axis. Is there any way to make x-axis panel size different for different panels, so that they correspond to their (x-axis) values?
I found an example from some different package that shows what I am trying to do, here is the figure:
Maybe something like this can get you started. There's still some formatting to do, though.
library(grid)
library(gridExtra)
library(dplyr)
library(ggplot2)
p1 <- ggplot(data=mydf[mydf$groups==1,],aes(x=X,y=Y))+
geom_point(size=2)+
theme_bw()
p2 <- ggplot(data=mydf[mydf$groups==2,],aes(x=X,y=Y))+
geom_point(size=2)+
theme_bw()
summ <- mydf %>% group_by(groups) %>% summarize(len=diff(range(X)))
summ$p <- summ$len/max(summ$len)
summ$q <- 1-summ$p
ng <- nullGrob()
grid.arrange(arrangeGrob(p1,ng,widths=summ[1,3:4]),
arrangeGrob(p2,ng,widths=summ[2,3:4]))
I'm sure there's a way to make this more general, and the axes don't line up perfectly yet, but it's a beginning.
Here is a solution following OP's clarifying comment ("I guess axis will be same but the boxes will be of variable size. Is it possible by plotting them separately and aligning in grid?").
library(plyr); library(ggplot2)
buffer <- 0.5 # Extra space around the box
#Calculate box parameters
mydf.box <- ddply(mydf, .(groups), summarise,
max.X = max(X) + buffer,
min.X = 0,
max.Y = max(Y) + buffer,
min.Y = 0,
X = mean(X), Y = mean(Y)) #Dummy values for X and Y needed for geom_rect
p2 <- ggplot(data=mydf,aes(x=X, y=Y) )+
geom_rect(data = mydf.box, aes( xmax = max.X, xmin = min.X,
ymax = max.Y, ymin = min.Y),
fill = "white", colour = "black", fill = NA) +
geom_point(size=2) + facet_grid(groups ~ .,scales = "free_y") +
theme_classic() +
#Extra formatting to make your plot like the example
theme(panel.background = element_rect(fill = "grey85"),
strip.text.y = element_text(angle = 0),
strip.background = element_rect(colour = NA, fill = "grey65"))

ggplot, nested X-Axis for interaction() factor variables in bar plot

How can I format a faceted, multi-grouped box plot's x axes so that I get something that looks like this (dodgy paint, but shows the idea)...
Here's the code so far.
# Make the dataset
data<-data.frame(cbind(runif(10,1,10),
sample(1:5, 10, replace=TRUE),
sample(1:5, 10, replace=TRUE),
sample(1:2, 10, replace=TRUE),
sample(1:2, 10, replace=TRUE)))
names(data)<-c("DV","Grouping_1", "Grouping_2", "Grouping_3", "Grouping_4")
data$Grouping_1<-as.factor(data$Grouping_1)
data$Grouping_2<-as.factor(data$Grouping_2)
data$Grouping_3<-as.factor(data$Grouping_3)
data$Grouping_4<-as.factor(data$Grouping_4)
# grab the interaction
data$groups<-interaction(data$Grouping_1,data$Grouping_2)
# Sort it (to make things neat)
data$groups<-factor(data$groups, levels = sort(levels(data$group)))
# Plot it
ggplot(data = data, aes(x =groups, y = DV, fill = Grouping_3)) +
geom_bar(stat = "identity", position = position_dodge()) + facet_grid(Grouping_4 ~ .)
Which gives...
This doesn't really work well in ggplot2. You can possibly try something like the code below. It's really not pretty, but it kind of works.
gr <- as.numeric(as.character(data$groups))
# additional data for annotation
df_a <- data.frame(y=-Inf,
xmin = factor(sapply(1:5, function(x) min(gr[gr > x]))),
xmax = factor(sapply(2:6, function(x) max(gr[gr < x]))),
nr = -(sapply(1:5, function(x) sum(gr > x & gr < x+1))-1)*2.5+0.5, # change here to get horizontal adjustment right...
Grouping_4 = 2,
text = 1:5)
# Plot it
p <- ggplot(data = data, aes(x =groups, y = DV, fill = Grouping_3)) +
geom_bar(stat = "identity", position = position_dodge()) + facet_grid(Grouping_4 ~ .) +
geom_segment(data = df_a, aes(x=xmin, xend=xmax, y=y, yend=y, fill=NULL)) +
geom_text(data = df_a, aes(x=xmin, y=y+2, label=text, fill=NULL, hjust=nr), vjust = 1.5) +
theme(plot.margin = unit(c(1,1,2,1), "lines")) +
scale_x_discrete(name = "\ngroups", labels=paste0("\n\n", round(10 * (sort(gr)-round(sort(gr), 0)), 0)))
require(gridExtra)
# turns clipping off
gt <- ggplot_gtable(ggplot_build(p))
gt$layout$clip[gt$layout$name == "panel"] <- "off"
grid.draw(gt)
Another option might be ggvis, a package similar to ggplot2 that supports multiple axes. Or you could manually add the grob with grid directly.

Any way to extend the line in the legend?

Let us say I have the following graph plotted using ggplot:
Is there anyway to extend how much line length is displayed in the legend? Sometimes, it just gets impossible to identify which line correspond to which line in the graph using the legend.
here is an option legend.key.width:
# sample data frame
df <- data.frame(x = c(rnorm(100, -3), rnorm(100), rnorm(100, 3)),
g = gl(3, 100))
df <- ddply(df, .(g), summarize, x = x, y = ecdf(x)(x))
ggplot(df, aes(x, y, colour = g, linetype = g)) +
geom_line() +
theme(legend.key.width = unit(10, "line"))
opts is not working with ggplot2. You need to use theme, so instead you need to type:
+ theme(legend.key.width = unit(10, "line"))

Resources