Skip to content

Handling of aes() mappings that do not refer to the layer data #3359

@paleolimbot

Description

@paleolimbot

There are a number of issues that have been opened expressing surprise at the handling of Layers where aes() does not refer to the data and the data is not specified.

library(ggplot2)
ggplot(data.frame(x = 1:5)) + 
  geom_text(aes(x = 1, y = 1, label = "annotation"))

This plot contains 5 copies of the "annotation" label, since the geom_text() layer inherits the plot data, and the aesthetics are recycled to the length of the layer data (if they are length 1). This usually only noticed for partially transparent objects (#2829, #2529, #2790), but has also caused errors in geom_boxplot() (#3316).

This is usually caused by slightly spurious ggplot2 usage by users who do not create and map a data frame but instead wire vectors directly into one or more layers directly by aes(). The workarounds are:

  • Actually create a data frame and map it. This is obviously preferred but can get verbose for something like geom_boxplot() with stat = "identity", where even aes_all(c("ymin", "lower", "middle", "upper", "ymax")) is pretty verbose.
  • Use annotate(), although this doesn't work with facets when there are non-position aesthetics with a length != 1 ( Annotation breaks with facets #3305 (comment) ), and doesn't always train the position scales properly for position aesthetics that are not (x|y), (x|y)min, or (x|y)max (Consolidate definition of position aesthetics #3342).
  • For the layer data, pass a non-null value for which ggplot2:::empty() returns TRUE (e.g., data = data.frame()). This keeps the layer from inheriting the plot data, since there is no inherit.data argument of a plot layer, although it doesn't really make sense.

I think there are some options to discourage this practice and/or make it less verbose to do the right thing.

  • Add an inherit.data argument that can be set to FALSE, such that one can safely and predictably pass vectors into aes().
  • Make the mapping a function of the data and make a function that returns an automapped data frame. We already sort of do this for geom_sf(), where we automatically map the geometry column for data of class sf. Then, one could do something like geom_text(data = mapped_df(x = 1, y = 1, label = "annotation")), which would generate the appropriate mapping.
  • Fix annotate() so that it works with all position aesthetics and with facets.
  • Warn when users supply a mapping that does not refer to data (this was recently vetoed, see Warn about discouraged aes() usage during plot build #3346)

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions