diff --git a/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-10-1.png b/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-10-1.png new file mode 100644 index 000000000..987c3d9e5 Binary files /dev/null and b/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-10-1.png differ diff --git a/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-11-1.png b/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-11-1.png new file mode 100644 index 000000000..db5080a6d Binary files /dev/null and b/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-11-1.png differ diff --git a/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-12-1.png b/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-12-1.png new file mode 100644 index 000000000..1956bbaf2 Binary files /dev/null and b/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-12-1.png differ diff --git a/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-13-1.png b/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-13-1.png new file mode 100644 index 000000000..7d2390774 Binary files /dev/null and b/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-13-1.png differ diff --git a/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-14-1.png b/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-14-1.png new file mode 100644 index 000000000..a823d7c11 Binary files /dev/null and b/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-14-1.png differ diff --git a/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-15-1.png b/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-15-1.png new file mode 100644 index 000000000..ed24b606c Binary files /dev/null and b/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-15-1.png differ diff --git a/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-16-1.png b/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-16-1.png new file mode 100644 index 000000000..bdf6dc962 Binary files /dev/null and b/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-16-1.png differ diff --git a/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-17-1.png b/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-17-1.png new file mode 100644 index 000000000..9b8fe853b Binary files /dev/null and b/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-17-1.png differ diff --git a/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-18-1.png b/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-18-1.png new file mode 100644 index 000000000..29ea8ce31 Binary files /dev/null and b/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-18-1.png differ diff --git a/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-19-1.png b/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-19-1.png new file mode 100644 index 000000000..755981c83 Binary files /dev/null and b/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-19-1.png differ diff --git a/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-20-1.png b/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-20-1.png new file mode 100644 index 000000000..832e3958a Binary files /dev/null and b/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-20-1.png differ diff --git a/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-21-1.png b/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-21-1.png new file mode 100644 index 000000000..cbdc51cc6 Binary files /dev/null and b/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-21-1.png differ diff --git a/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-22-1.png b/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-22-1.png new file mode 100644 index 000000000..cf4c7fbf5 Binary files /dev/null and b/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-22-1.png differ diff --git a/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-23-1.png b/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-23-1.png new file mode 100644 index 000000000..3e047a2c0 Binary files /dev/null and b/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-23-1.png differ diff --git a/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-24-1.png b/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-24-1.png new file mode 100644 index 000000000..c31224ba3 Binary files /dev/null and b/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-24-1.png differ diff --git a/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-25-1.png b/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-25-1.png new file mode 100644 index 000000000..a52404b6e Binary files /dev/null and b/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-25-1.png differ diff --git a/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-26-1.png b/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-26-1.png new file mode 100644 index 000000000..182376033 Binary files /dev/null and b/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-26-1.png differ diff --git a/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-27-1.png b/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-27-1.png new file mode 100644 index 000000000..db4b84ce5 Binary files /dev/null and b/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-27-1.png differ diff --git a/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-28-1.png b/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-28-1.png new file mode 100644 index 000000000..5a3070295 Binary files /dev/null and b/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-28-1.png differ diff --git a/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-29-1.png b/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-29-1.png new file mode 100644 index 000000000..d6688ac2b Binary files /dev/null and b/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-29-1.png differ diff --git a/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-3-1.png b/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-3-1.png new file mode 100644 index 000000000..3bc445a3b Binary files /dev/null and b/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-3-1.png differ diff --git a/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-30-1.png b/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-30-1.png new file mode 100644 index 000000000..4e806b341 Binary files /dev/null and b/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-30-1.png differ diff --git a/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-31-1.png b/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-31-1.png new file mode 100644 index 000000000..6a30166b2 Binary files /dev/null and b/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-31-1.png differ diff --git a/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-32-1.png b/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-32-1.png new file mode 100644 index 000000000..bd7446d1d Binary files /dev/null and b/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-32-1.png differ diff --git a/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-33-1.png b/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-33-1.png new file mode 100644 index 000000000..0b7a35340 Binary files /dev/null and b/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-33-1.png differ diff --git a/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-38-1.png b/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-38-1.png new file mode 100644 index 000000000..04698f8e6 Binary files /dev/null and b/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-38-1.png differ diff --git a/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-4-1.png b/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-4-1.png new file mode 100644 index 000000000..068808b9f Binary files /dev/null and b/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-4-1.png differ diff --git a/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-5-1.png b/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-5-1.png new file mode 100644 index 000000000..edc87120f Binary files /dev/null and b/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-5-1.png differ diff --git a/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-6-1.png b/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-6-1.png new file mode 100644 index 000000000..a62e6ebb4 Binary files /dev/null and b/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-6-1.png differ diff --git a/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-9-1.png b/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-9-1.png new file mode 100644 index 000000000..07d8ca104 Binary files /dev/null and b/content/blog/ggplot2-4-0-0/figs/unnamed-chunk-9-1.png differ diff --git a/content/blog/ggplot2-4-0-0/index.Rmd b/content/blog/ggplot2-4-0-0/index.Rmd new file mode 100644 index 000000000..856d697a8 --- /dev/null +++ b/content/blog/ggplot2-4-0-0/index.Rmd @@ -0,0 +1,894 @@ +--- +output: hugodown::hugo_document + +slug: ggplot2-4-0-0 +title: ggplot2 4.0.0 +date: 2025-09-11 +author: Teun van den Brand +description: > + A new major version of ggplot2 has been released on CRAN. Find out what is new here. + +photo: + url: https://unsplash.com/photos/selective-focus-photography-of-water-droplets-on-grasses--N_UwPdUs7E + author: Jonas Weckschmied + +# one of: "deep-dive", "learn", "package", "programming", "roundup", or "other" +categories: [package] +tags: [ggplot2] +--- + + + +We're tickled pink to announce the release of [ggplot2](https://ggplot2.tidyverse.org) 4.0.0. +ggplot2 is a system for declaratively creating graphics, based on The Grammar of Graphics. +You provide the data, tell ggplot2 how to map variables to aesthetics, what graphical primitives to use, and it takes care of the details. + +The new version can be installed from CRAN using: + +```{r, eval = FALSE} +install.packages("ggplot2") +``` + +This is a substantial release meriting a new major version, and contains a series of changes from a rewrite of the object oriented system from S3 to S7, large new features to smaller quality of life improvements and bugfixes. +It is also the 18th anniversary of ggplot2 which is cause for celebration! +In this blog post, we will highlight the most salient new features that come with this release. +You can see a full list of changes in the [release notes](https://ggplot2.tidyverse.org/news/index.html) + +```{r secret_setup, include=FALSE} +knitr::opts_chunk$set(dev = "ragg_png") +``` + +```{r setup} +library(ggplot2) +library(patchwork) +``` + +## Adopting S7 + +In ggplot2, we use major version increments to indicate that something at the core of the package has changed. +In this release, we have replaced many of ggplot2's S3 objects with S7 objects. +Like S3 and S4, S7 is also an object oriented system that uses classes, generics and methods. +S7 is a newer system that aims to strike a good balance between the flexibility of S3 and formality of S4. + +Mostly, this change shouldn't be very noticeable when you're just using ggplot2 for building regular plots. +At best, you may notice that we're more strictly enforcing types for certain arguments. +For example, most ludicrous input is now rejected right away. +This is due to how properties in S7 work, which get validated when a new object is instantiated. + +```{r, error=TRUE} +element_text(hjust = "foo") +``` + +However, it may require some adaptation on your end if you use ggplot2's innards in unusual ways. +For extension builders, a major benefit of using S7 is that one can now use double dispatch. +This is most important for the `update_ggplot()` function (the successor of `ggplot_add()`), which determines what happens when you `+` an object to a plot. +Now with S7, you can control what happens not only for right-hand side objects (which is how it used to work in S3), but also for the left-hand side objects. + +We have put various pieces of backwards compatibility in to not break many packages that assumed the S3 structures of ggplot2. +For example, we still return the data property with `ggplot()$data`, whereas the S7 way of accessing this should be `ggplot()@data`. +Expect these to be phased out over time in favour of S7. +We are preparing another blog post to help migrating from S3 to S7 for ggplot2 related packages. + +## Theme improvements + +Themes in ggplot2 have long served the role of capturing any non-data aspects of styling plots. +We have come to realise that the default look of layers, from what the default shape of points is to what the default colour palette is, are also not truly data-driven choices. +The idea to put these defaults into themes has been around for a while and Dana Page Seidel did pioneering work implementing this as early as 2018. +Now, years of waiting have come to fruition and we're proud to announce this new functionality. + +### Ink and paper + +The way layer defaults are now implemented differs slightly from typical aesthetics you know and love. +Whereas layers aesthetics distinguish `colour` and `fill`, the theme defaults distinguish `ink` (foreground) and `paper` (background). +A boxplot is unreadable without `colour`, but is perfectly interpretable without `fill`. +In the boxplot case, the `ink` is thus clearly the `colour` whereas `paper` is the `fill`. +In bar charts or histograms, the [proportional ink](https://clauswilke.com/dataviz/proportional-ink.html) principle prescribes that the `fill` aesthetic is considered foreground, and thus count as `ink`. +To accommodate special cases, like lines in `geom_smooth()` or `geom_contour()`, we also added a third `accent` option. +In short, the theme defaults have role-oriented settings that differ from the property-oriented settings in layers. + +We've added these three options to all built-in complete themes. +Not only propagate these automatically to the layer defaults, they are also used to style additional theme components. +You may notice that the panel background colour is a blend between `paper` and `ink`, which is now how many elements are parametrised in complete themes. + +```{r} +ggplot(mpg, aes(displ, hwy)) + + geom_point() + + geom_smooth(method = "lm", formula = y ~ x) + + theme_gray(paper = "cornsilk", ink = "navy", accent = "tomato") +``` + +If you're customising a theme, you can use the `theme(geom)` argument to set a collection of defaults. +The new function `element_geom()` can be used to set these properties. +Additionally, if you want a layer to read the property from this theme element, you can use the `from_theme()` function in the mapping to access these variables^[Normally, `aes()` is strictly used to map data instead of setting a fixed property. We diverge from this API for pragmatic reasons, not theoretical ones.]. + + +```{r} +ggplot(mpg, aes(class, displ)) + + geom_boxplot(aes(colour = from_theme(accent))) + + theme( + geom = element_geom(accent = "tomato", paper = "cornsilk") + ) +``` + +A second conceptual difference in `element_geom()` pertains the the use of lines. +In one role, like in a line graph, the line represents the data directly. +In a second role, a line serves as separation between two units. +For example, you can display countries as polygons and the line connecting the vertices separate out places that are inside a country versus places that are outside that country. +These two roles are captured in a `linewidth` and `linetype` pair and a `borderwidth` and `bordertype` pair. + +```{r} +ggplot(faithful, aes(waiting)) + + geom_histogram(bins = 30, colour = "black") + + geom_freqpoly(bins = 30) + + theme(geom = element_geom( + bordertype = "dashed", + borderwidth = 0.2, + linewidth = 2, + linetype = "solid" + )) +``` + +### Scales and palettes + +In addition to the defaults for layers, default palettes are now also encapsulated in the theme. +The relevant theme settings have the pattern `palette.{aesthetic}.{type}`, where `type` can be either discrete or continuous. +This allows you to coordinate your colour palettes with the rest of the theme. + +```{r} +ggplot(mpg, aes(displ, hwy, shape = drv, colour = cty)) + + geom_point() + + theme( + palette.colour.continuous = c("chartreuse", "forestgreen"), + palette.shape.discrete = c("triangle", "triangle open", "triangle down open") + ) +``` + +The way this works is that all defaults scales now have `palette = NULL` as their default. +During plot building, any `NULL` palettes are replaced by those declared in the theme. + +### Shortcuts + +We like to introduce a new family of short cuts. +Looking at code in the wild, we've come to realise that theme declarations are very often chaotic. +The `theme()` functions has lots of arguments, long argument names (hello there, `axis.minor.ticks.length.x.bottom`!) and very little structure. +To make themes a little bit more digestible, we've created the following helper functions: + +* `theme_sub_axis()` + * `theme_sub_axis_x()` + * `theme_sub_axis_bottom()` + * `theme_sub_axis_top()` + * `theme_sub_axis_y()` + * `theme_sub_axis_left()` + * `theme_sub_axis_right()` +* `theme_sub_legend()` +* `theme_sub_panel()` +* `theme_sub_plot()` +* `theme_sub_strip()` + +These helper functions pass on their arguments to `theme()` after they've prepended a relevant prefix. +For example, using `theme_sub_legend(justification)` will translate to `theme(legend.justification)`. +When you have >1 theme element to change in a cluster of settings, it quickly becomes less typing to enlist the relevant shortcut. +As a bonus, your theme code will tend to self-organise and become somewhat more readable. + +```{r, results='hide'} +# Tired, verbose, chaotic +theme( + panel.widths = unit(5, "cm"), + axis.ticks.x = element_line(colour = "red"), + axis.ticks.length.x = unit(5, "mm"), + panel.background = element_rect(fill = NA), + panel.spacing.x = unit(5, "mm") +) + +# Wired, terse, orderly +theme_sub_axis_x( + ticks = element_line(colour = "red"), + ticks.length = unit(5, "mm") +) + +theme_sub_panel( + widths = unit(5, "cm"), + spacing.x = unit(5, "mm"), + background = element_rect(fill = NA) +) +``` + +In addition to shortcuts for clusters of theme elements, we've also added a few variants to declare margins. + +* `margin_auto()` sets the margins in a CSS-like fashion similar to the [`margin`](https://developer.mozilla.org/en-US/docs/Web/CSS/margin) and [`padding`](https://developer.mozilla.org/en-US/docs/Web/CSS/padding) property. + * `margin_auto(1)` sets all four sides at once. + It expands to `margin(t = 1, r = 1, b = 1, l = 1)`. + * `margin_auto(1, 2)` sets horizontal and vertical sides. + It expands to `margin(t = 1, r = 2, b = 1, l = 2)`. + * `margin_auto(1, 2, 3)` expands to `margin(t = 1, r = 2, b = 3, l = 2)`. +* `margin_part()` has `NA` units as default, which will get replaced when the theme gets resolved. + It roughly equates to 'set some of the sides, keep others as they are'. + +```{r} +merge_element( + margin_part(r = 20), # child + margin_auto(10) # parent +) +``` + +### New settings + +To coordinate (non-text) margins and spacings in a theme, we've introduced `spacing` and `margins` as new root elements in the theme. +Other spacings and margins at the leaf elements inherit from (scale with) these root elements. +To facilitate the different spacings in ggplot2, unit elements can now use `rel()` to modify the inherited value. +For example the default `axis.ticks.length` is now `rel(0.5)`, making the y-axis ticks 0.5 cm in the plot below. +If we set the `axis.ticks.length.x` to `rel(2)`, it will double the value coming from `axis.ticks.length`, not double the value of `spacing`. + +```{r} +p <- ggplot(penguins, aes(bill_dep, bill_len, colour = species)) + + geom_point(na.rm = TRUE) + +p + theme( + spacing = unit(1, "cm"), + margins = margin_auto(1, unit = "cm"), + axis.ticks.length.x = rel(2) +) +``` + +We also made it easier to set plot sizes. +Using the `panel.widths` and `panel.heights` arguments, you can control the sizes of the panels. +This mechanism is distinct from using `ggsave(width, height)`, where the whole plot, including annotations such as axes and titles is included. +There are two ways to use these arguments: + +* Give a vector of units: each one will be applied to a panel separately and the vector will be recycled to fit the number of panels. +* Give a single unit: which sets the total panel area (including panel spacings and inner axes) to that size. + +Naturally, if you only have a single panel, these approaches are identical. +If you have multiple panels and you want to set individual panels all to the same size (as opposed to the total size), you can take advantage of the recycling and use a length 2 unit vector. + +In the plots below, you can notice that the panels span a different width despite the units adding up to the same amount (9 cm). +This is because the 'single unit' approach also includes the panel spacings, but not the 'separate units' approach. + +```{r} +p1 <- p + facet_grid(~ island) + + labs(title = "Separate units (per panel)") + + # Using the new shortcut for panels + theme_sub_panel( + widths = unit(c(2, 3, 4), "cm"), + heights = unit(3, "cm") + ) + +p2 <- p + facet_grid(~ island) + + labs(title = "Single unit (all panels)") + + theme_sub_panel( + widths = unit(9, "cm"), + heights = unit(3, "cm") + ) + +p1 / p2 +``` + +## Labels + +We have added new ways that a plot retrieves labels for your variables. +It is an informal convention in several packages including gt, Hmisc, labelled and others to use the 'label' attribute to store human readable labels for vectors. +Now ggplot2 joins this convention and uses the 'label' attribute as the default label for a variable if present. + +```{r} +# The penguins dataset was incorporated into base R 4.5 +df <- penguins + +# Manually set label attributes. +# Other packages may offer better tooling than this. +attr(df$species, "label") <- "Penguin Species" +attr(df$bill_dep, "label") <- "Bill depth (mm)" +attr(df$bill_len, "label") <- "Bill length (mm)" +attr(df$body_mass, "label") <- "Body mass (g)" + +ggplot(df, aes(bill_dep, bill_len, colour = sqrt(body_mass))) + + geom_point(na.rm = TRUE) +``` + +It has also been entrenched in some workflows to use a 'data dictionary' or codebook. +For labelling purposes these dictionaries often contain column metadata that include labels or descriptions for variables (columns) in the dataset. +To make it easier to work with column labels, we added the `labs(dictionary)` argument. +It takes a named vector of labels, that can easily be generated from a data dictionary by `setNames()` or `dplyr::pull()`. + +```{r} +dict <- tibble::tribble( + ~var, ~label, + "species", "Penguin Species", + "bill_dep", "Bill depth (mm)", + "bill_len", "Bill length (mm)", + "body_mass", "Body mass (g)" +) + +ggplot(penguins, aes(bill_dep, bill_len, colour = body_mass)) + + geom_point(na.rm = TRUE) + + # Or: + # labs(dictionary = dplyr::pull(dict, label, name = var)) + labs(dictionary = setNames(dict$label, dict$var)) +``` + +One benefit to the label attributes or data dictionary approaches is that it is linked to your variables, not aesthetics. +This means you can easily rearrange your aesthetics for a different plot, without having to painstakingly reorient the labels towards the correct aesthetics. + +```{r} +last_plot() + + aes(body_mass, bill_len, colour = species) +``` + +There are a few caveats to these label attributes and data dictionary approaches though: + +* If the aesthetic is not a pure variable name the label is not used. + You can see this in the `sqrt(body_mass)` in the first example, which does not use the 'Body mass (g)' label. + We assume when a variable is adjusted in this way, this would need to be reflected in the label itself. + It would therefore be inappropriate to use the label of the unadjusted variable. + Use of the [`.data`-pronoun](https://ggplot2.tidyverse.org/articles/ggplot2-in-packages.html#using-aes-and-vars-in-a-package-function) counts as a pure variable name for labelling purposes. +* Some attributes are more stable than others, and it is not ggplot2's responsibility to babysit attributes. + For example using `head()` will typically drop attributes from atomic columns, whereas `head()` will not. + +In addition, we're also allowing to use functions in all the places you can declare labels. +The `labs()` function, scale names and guide titles now accept functions that take in the labels generated by the lower hierarchies and return amended labels. +It should be spelled out that the hierarchy from lowest priority to highest priority is the following: + +1. The expression given in `aes()`. +2. The entry in `labs(dictionary)`. +3. The label attribute of the column. +4. The entry in `labs( =