How to Position Strip Labels in Facet_Wrap Like in Facet_Grid

How to position strip labels in facet_wrap like in facet_grid

This does not seem easy, but one way is to use grid graphics to insert panel strips from a facet_grid plot into one created as a facet_wrap. Something like this:

First lets create two plots using facet_grid and facet_wrap.

dt <- txhousing[txhousing$year %in% 2000:2002 & txhousing$month %in% 1:3,]

g1 = ggplot(dt, aes(median, sales)) +
geom_point() +
facet_wrap(c("year", "month"), scales = "free") +
theme(strip.background = element_blank(),
strip.text = element_blank())

g2 = ggplot(dt, aes(median, sales)) +
geom_point() +
facet_grid(c("year", "month"), scales = "free")

Now we can fairly easily replace the top facet strips of g1 with those from g2

library(grid)
library(gtable)
gt1 = ggplot_gtable(ggplot_build(g1))
gt2 = ggplot_gtable(ggplot_build(g2))
gt1$grobs[grep('strip-t.+1$', gt1$layout$name)] = gt2$grobs[grep('strip-t', gt2$layout$name)]
grid.draw(gt1)

Sample Image

Adding the right hand panel strips need us to first add a new column in the grid layout, then paste the relevant strip grobs into it:

gt.side1 = gtable_filter(gt2, 'strip-r-1')
gt.side2 = gtable_filter(gt2, 'strip-r-2')
gt.side3 = gtable_filter(gt2, 'strip-r-3')

gt1 = gtable_add_cols(gt1, widths=gt.side1$widths[1], pos = -1)
gt1 = gtable_add_grob(gt1, zeroGrob(), t = 1, l = ncol(gt1), b=nrow(gt1))

panel_id <- gt1$layout[grep('panel-.+1$', gt1$layout$name),]
gt1 = gtable_add_grob(gt1, gt.side1, t = panel_id$t[1], l = ncol(gt1))
gt1 = gtable_add_grob(gt1, gt.side2, t = panel_id$t[2], l = ncol(gt1))
gt1 = gtable_add_grob(gt1, gt.side3, t = panel_id$t[3], l = ncol(gt1))

grid.newpage()
grid.draw(gt1)

Sample Image

Switching position of two facet strip labels and combine one label across columns

This could be easily done with ggh4x package written by teunbrand:
Using facet_nested_wrap function:
You can change which one you want to combine, just change the order in facet_nested_wrap:

library(tidyverse)
#install.packages("ggh4x")
library(ggh4x)

dt %>%
ggplot(aes(x=generation, y= days))+
geom_bar(stat = "identity")+
facet_nested_wrap(~year + device_type, nrow = 1, ncol=4)

Sample Image

Two column facet_grid with strip labels on top

Is this the output you're looking for?

library(tidyverse)
library(gtable)
library(grid)
library(gridExtra)
#>
#> Attaching package: 'gridExtra'
#> The following object is masked from 'package:dplyr':
#>
#> combine

p1 <- mtcars %>%
rownames_to_column() %>%
filter(carb %in% c(1, 3, 6)) %>%
ggplot(aes(x = disp, y = rowname)) +
geom_point() +
xlim(c(0, 450)) +
facet_grid(carb ~ ., scales = "free_y", space = "free_y") +
theme(panel.spacing = unit(1, 'lines'),
strip.text.y = element_text(angle = 0))

gt1 <- ggplotGrob(p1)
panels <-c(subset(gt1$layout, grepl("panel", gt1$layout$name), se=t:r))
for(i in rev(panels$t-1)) {
gt1 = gtable_add_rows(gt1, unit(0.5, "lines"), i)
}
panels <-c(subset(gt1$layout, grepl("panel", gt1$layout$name), se=t:r))
strips <- c(subset(gt1$layout, grepl("strip-r", gt1$layout$name), se=t:r))
stripText = gtable_filter(gt1, "strip-r")
for(i in 1:length(strips$t)) {
gt1 = gtable_add_grob(gt1, stripText$grobs[[i]]$grobs[[1]], t=panels$t[i]-1, l=5)
}
gt1 = gt1[,-6]
for(i in panels$t) {
gt1$heights[i-1] = unit(0.8, "lines")
gt1$heights[i-2] = unit(0.2, "lines")
}

p2 <- mtcars %>%
rownames_to_column() %>%
filter(carb %in% c(2, 4, 8)) %>%
ggplot(aes(x = disp, y = rowname)) +
geom_point() +
xlim(c(0, 450)) +
facet_grid(carb ~ ., scales = "free_y", space = "free_y") +
theme(panel.spacing = unit(1, 'lines'),
strip.text.y = element_text(angle = 0))

gt2 <- ggplotGrob(p2)
#> Warning: Removed 2 rows containing missing values (geom_point).
panels <-c(subset(gt2$layout, grepl("panel", gt2$layout$name), se=t:r))
for(i in rev(panels$t-1)) {
gt2 = gtable_add_rows(gt2, unit(0.5, "lines"), i)
}
panels <-c(subset(gt2$layout, grepl("panel", gt2$layout$name), se=t:r))
strips <- c(subset(gt2$layout, grepl("strip-r", gt2$layout$name), se=t:r))
stripText = gtable_filter(gt2, "strip-r")
for(i in 1:length(strips$t)) {
gt2 = gtable_add_grob(gt2, stripText$grobs[[i]]$grobs[[1]], t=panels$t[i]-1, l=5)
}
gt2 = gt2[,-6]
for(i in panels$t) {
gt2$heights[i-1] = unit(0.8, "lines")
gt2$heights[i-2] = unit(0.2, "lines")
}

grid.arrange(gt1, gt2, ncol = 2)

Sample Image

Created on 2021-12-16 by the reprex package (v2.0.1)

If not, what changes need to be made?

Change facet_wrap() strip positions to place facet strips inside plot

One option to achieve your desired result would be via the gggrid package. Similar to ggplot2::annotation_custom it allows to add grobs to a ggplot but offers much more flexibility, e.g. you could place different grobs to each facet panel. Moreover it allows to access the data and coords objects created by ggplot2 under the hood and allows to pass additional aesthetics.

Basically it requires a function which creates the grob which are then added to the ggplot via gggrid::grid_panel. For the grob I use gridtext::richtext_grob which makes it easy to add a strip text like text box to each panel.

library(ggplot2)
library(gggrid)
#> Loading required package: grid
library(gridtext)

set.seed(123)

ID <- rep(c("ABC123", "DEF456", "GHI789", "JKL012"), each = 10)
Vref <- c((runif(10, 1, 2)), (runif(10, 3, 5)), (runif(10, 6, 9)), (runif(10, 0, 2)))
Time <- rep(c(1:10), 4)
df <- data.frame(ID, Vref, Time)

tg <- function(data, coords) {
y_label <- max(coords$y)
gridtext::richtext_grob(data$label[which.max(coords$y)],
x = unit(0, "npc") + unit(.045, "npc"),
y = unit(y_label, "npc") + unit(2, "mm"),
hjust = 0,
vjust = 0,
valign = .5,
padding = unit(rep(4.4, 4), "pt"),
gp = grid::gpar(fontsize = 8, fill = "grey85"),
box_gp = grid::gpar(col = "grey85")
)
}

ggplot(df, aes(x = Time, y = Vref)) +
geom_col() +
scale_y_continuous(expand = expansion(mult = c(.05, .2))) +
facet_wrap(~ID, nrow = 2) +
gggrid::grid_panel(mapping = aes(label = ID), tg) +
theme(strip.text = element_blank())

Sample Image

Change the position of the strip label in ggplot from the top to the bottom?

An answer for those searching in 2016.

As of ggplot2 2.0, the switch argument will do this for facet_grid or facet_wrap:

By default, the labels are displayed on the top and right of the plot. If "x", the top labels will be displayed to the bottom. If "y", the right-hand side labels will be displayed to the left. Can also be set to "both".

ggplot(...) + ... + facet_grid(facets, switch="both")

As of ggplot2 2.2.0,

Strips can now be freely positioned in facet_wrap() using the
strip.position argument (deprecates switch).

Current docs, are still at 2.1, but strip.position is documented on the dev docs.

By default, the labels are displayed on the top of the plot. Using strip.position it is possible to place the labels on either of the four sides by setting strip.position = c("top", "bottom", "left", "right")

ggplot(...) + ... + facet_wrap(facets, strip.position="right")

strip.position external in two columns facet_wrap plot

As far as I know, there is no way to do this in vanilla ggplot2. If you're comfortable with gtables, you might find the following doable.

library(ggplot2)

# Base plot
p <- ggplot(economics_long, aes(date, value)) +
geom_line() +
labs(y="") +
theme(strip.background = element_blank(), strip.placement = "outside")

# Left aligned strips/axes
left <- p +
facet_wrap(~variable, scales = "free_y", ncol = 2, strip.position = "left")

# Right aligned strips/axes
right <- p +
facet_wrap(~variable, scales = "free_y", ncol = 2, strip.position = "right") +
scale_y_continuous(position = "right")

# Convert to gtables
left <- ggplotGrob(left)
right <- ggplotGrob(right)

# Combine parts of left and right
# Column numbers found by browsing through layout
gt <- cbind(left[, 1:7], right[, 9:ncol(right)])

# Render
grid::grid.newpage(); grid::grid.draw(gt)

Sample Image

Created on 2021-10-20 by the reprex package (v2.0.1)

To find the panel positions in a more programmatically than judging the table layout manually, my best guess is to do this:

panels_left  <- panel_cols(left)$l
panels_right <- panel_cols(right)$l

# The -2 is to include the left axis space (zero width because of absence)
# and the panel spacing
gt <- cbind(left[, 1:panels_left[1]],
right[, (panels_right[2]-2):ncol(right)])
grid::grid.newpage(); grid::grid.draw(gt)

X labels gets cut out for faceted ggplot in R

You have a few options. I'll save your plot as p to demonstrate:

library(ggplot2)

df <- data.frame(
a = seq(1:10),
b = seq(1:10),
group = c(rep('group1', 5), rep('really_really_really_long_lable_here', 5)),
sex = c(rep(c('M', 'F'), 5))
)


p <- ggplot(df, aes(sex, b, group = sex)) +
geom_boxplot() +
facet_wrap(~group, strip.position = "bottom") +
theme(strip.placement = "outside",
strip.background = element_blank(),
text = element_text(size = 16)) +
xlab(NULL)

1. Change your facet layout:

p +
facet_wrap(~group, ncol = 1, strip.position = "bottom")

Sample Image

2. Change the font size:

p +
theme(strip.text = element_text(size = 6))

Sample Image

3. Use text wrapping:

# Wrapping function that replaces underscores with spaces
wrap_text <- function(x, chars = 10) {
x <- gsub("_", " ", x)
stringr::str_wrap(x, chars)
}

# Alternative wrapping function that just drops a linebreak in every 10
# chars
wrap_text2 <- function(x, chars = 10) {
regex <- sprintf("(.{%s})", chars)
gsub(regex, "\\1\n", x)
}

p +
facet_wrap(
~group, strip.position = "bottom",
labeller = as_labeller(wrap_text)
)

Sample Image

4. Use strip.clip

As of July 2022, the (unreleased) development version of ggplot2 includes a strip.clip argument to theme(). Setting this to "off" means the strip text will be layered on top of the panel like so:

p +
theme(strip.clip = "off")

Sample Image

Note: you can get the development version of ggplot2 using remotes::install_github("tidyverse/ggplot2"). Or you can wait a few months for the next version to arrive on CRAN.

My favourite approach is #3 - or to just use shorter labels!



Related Topics



Leave a reply



Submit