I just experienced the same problem and found a solution that can be useful to the community.
The idea is to create a custom theme that substitutes the axis label by a subtitle. The plot used as an example will be the following:
p <- ggplot2::ggplot(iris, ggplot2::aes(x = Sepal.Width, y = Sepal.Length,
color = Species)) +
ggplot2::geom_point()
The minimalist solution
Create a class mytheme
that modifies the theme used by ggplot
. Here, in the minimalist solution, no constraint is made over style (I will present limitations later).
custom_theme <- function(...){
out <- ggplot2::theme_minimal() + ggplot2::theme(...)
class(out) <- c("mytheme",class(ggplot2::theme_minimal()))
return(out)
}
Associate to this class a ggplot_add
method that overrides the default method. This custom method takes the existing y
label and put it into subtitle
. The substitution is made in the first two lines of the function. The third line of the function uses a update_theme
function defined later on:
ggplot_add.mytheme <- function(object, plot, object_name) {
plot$labels$subtitle <- plot$labels$y
plot$labels$y <- NULL
plot$theme <- update_theme(plot$theme, object)
plot
}
update_theme
function was part of ggplot2
package up to recently. The function disappeared recently (see this commit). The hack I implemented is to define the function like this:
update_theme <- function(oldtheme, newtheme) {
# If the newtheme is a complete one, don't bother searching
# the default theme -- just replace everything with newtheme
if (isTRUE(attr(newtheme, "complete", exact = TRUE)))
return(newtheme)
# These are elements in newtheme that aren't already set in oldtheme.
# They will be pulled from the default theme.
newitems <- !names(newtheme) %in% names(oldtheme)
newitem_names <- names(newtheme)[newitems]
oldtheme[newitem_names] <- theme_get()[newitem_names]
# Update the theme elements with the things from newtheme
# Turn the 'theme' list into a proper theme object first, and preserve
# the 'complete' attribute. It's possible that oldtheme is an empty
# list, and in that case, set complete to FALSE.
old.validate <- isTRUE(attr(oldtheme, "validate"))
new.validate <- isTRUE(attr(newtheme, "validate"))
oldtheme <- do.call(theme, c(oldtheme,
complete = isTRUE(attr(oldtheme, "complete")),
validate = old.validate & new.validate))
oldtheme + newtheme
}
The theme
can be used then to create the expected output:
p + ggplot2::labs(y = "A very very long label") + custom_theme()

Limitations
Axis style elements are not reported to subtitle
If you want to change later on some style elements for the axis title (e.g. text size), that will be problematic for the y label since it is a subtitle element. For instance, the user that runs
p + custom_theme(axis.title = ggplot2::element_text(size = 24))

would expect the y label to also be of size 24. I don't think this a real problem because you can change the custom_theme
with more constraints
When setting labs
after calling the theme
This solution will be problematic if a user sets the theme
before changing the labs
, e.g.
p + ggplot2::labs(y = "A very very long label") + custom_theme() + ggplot2::labs(y = "override")
