Search code examples
rggplot2facet

'Labels on top' with facet_grid, or 'space option' with facet_wrap


facet_grid allow me to size each facet width according to number of items on y axis (space argument):

df <- data.frame(label = c("Variable one", rep("Variable two", 2), rep("Variable three", 3)), item = c("A", "B", "C", "D", "E", "F"), value = rnorm(6))
ggplot(df, aes(x = value, y = item)) + 
geom_point() + 
facet_grid(label ~ ., scales = "free_y", space = "free_y") + 
ylab("") + 
theme(strip.text.y = element_text(angle=0))

enter image description here

But I would like facet labels on top, so I switch to facet_wrap, and lost the space argument (facets have all the same width):

ggplot(df, aes(x = value, y = item)) + 
geom_point() + 
facet_wrap(~ label, scales = "free_y", ncol = 1) + 
ylab("")

enter image description here

Is it possible to obtain the best of both worlds?

Thank you in advance for your help.


Solution

  • It can be done manually. The ratio of the heights of the three panels in the desired plot is roughly 1:3:2. The heights of the three panels can be adjusted by changing the grobs:

    library(ggplot2)
    library(grid)
    df <- data.frame(label = c("Variable one", rep("Variable two", 2), rep("Variable three", 3)), item = c("A", "B", "C", "D", "E", "F"), value = rnorm(6))
    
    p1 = ggplot(df, aes(x = value, y = item)) + 
    geom_point() + 
    facet_wrap(~ label, scales = "free_y", ncol = 1) + 
    ylab("")
    
    g1 = ggplotGrob(p1) 
    
    g1$heights[[7]] = unit(1, "null")
    g1$heights[[12]] = unit(3, "null")
    g1$heights[[17]] = unit(2, "null")
    
    grid.newpage()
    grid.draw(g1)
    

    Or, the heights can be set to be the same as those in the original plot:

    p2 = ggplot(df, aes(x = value, y = item)) + 
    geom_point() + 
    facet_grid(label ~ ., scales = "free_y", space = "free_y") + 
    ylab("") + 
    theme(strip.text.y = element_text(angle=0))
    
    g2 = ggplotGrob(p2) 
    
    g1$heights[[7]] = g2$heights[[6]]
    g1$heights[[12]] = g2$heights[[8]]
    g1$heights[[17]] = g2$heights[[10]]
    
    grid.newpage()
    grid.draw(g1)
    

    Or, the heights can be set without reference to the original plot. They can be set according to the number of items for each label in df. And borrowing some code from @baptiste's answer here to select the items from the layout corresponding to the panels:

    # From 'df', get the number of 'items' for each 'label'.
    # That is, the number y-breaks in each panel.
    library(plyr)
    N = dlply(df, .(label), function(x) length(row.names(x)))
    
    # Get the items in the g1 layout corresponding to the panels.
    panels1 <- g1$layout$t[grepl("panel", g1$layout$name)]
    
    # Replace the default panel heights with relative heights
    g1$heights[panels1] <- unit(N, "null")
    
    ## Draw g1
    grid.newpage()
    grid.draw(g1)
    

    enter image description here