37

I'd like to remove a layer (in this case the results of geom_ribbon) from a ggplot2 created grid object. Is there a way I can remove it once it's already part of the object?

library(ggplot2)
dat <- data.frame(x=1:3, y=1:3, ymin=0:2, ymax=2:4)
p <- ggplot(dat, aes(x=x, y=y)) + geom_ribbon(aes(ymin=ymin, ymax=ymax), alpha=0.3) 
     + geom_line()

# This has the geom_ribbon
p

# This overlays another ribbon on top
p + geom_ribbon(aes(ymin=ymin, ymax=ymax, fill=NA))

I'd like this functionality to allow me to build more complicated plots on top of less complicated ones. I am using functions that return a grid object and then printing out the final plot once it is fully assembled. The base plot has a single line with a corresponding error bar (geom_ribbon) surrounding it. The more complicated plot will have several lines and the multiple overlapping geom_ribbon objects are distracting. I'd like to remove them from the plots with multiple lines. Additionally, I'll be able to quickly create alternative versions using facets or other ggplot2 functionality.


Edit: Accepting @mnel's answer as it works. Now I need to determine how to dynamically access the geom_ribbon layer, which is captured in the SO question here.


Edit 2: For completeness, this is the function I created to solve this problem:

remove_geom <- function(ggplot2_object, geom_type) {
  layers <- lapply(ggplot2_object$layers, function(x) if(x$geom$objname == geom_type) NULL else x)
  layers <- layers[!sapply(layers, is.null)]

  ggplot2_object$layers <- layers
  ggplot2_object
}

Edit 3: See the accepted answer below for the latest versions of ggplot (>=2.x.y)

Erik Shilts
  • 4,389
  • 2
  • 26
  • 51
  • Can you please describe a use case where you would use this? – Andrie Nov 15 '12 at 22:28
  • Finding the particular layer inside `p$layers` and then obliterating it by assigning `NULL` to it would probably "work" but I make no predictions about reliability. – joran Nov 15 '12 at 22:30
  • I guess I don't understand this right. If you have to have it, but don't want to see it, why not `alpha = 0`? – Mikko Nov 15 '12 at 22:33
  • 1
    @Largh, the ggplot2 object has already been created with a geom_ribbon with an alpha value > 0. I need to dynamically access that layer and set alpha to 0 or remove it. – Erik Shilts Nov 19 '12 at 16:11
  • In principle, I think `geom_blank` should do this, but in practice that doesn't work for me. – Jake Fisher Feb 16 '16 at 00:06
  • This has changed in newer ggplot2 versions. The first line has to be updated to: `layers <- lapply(ggplot2_object$layers, function(x) if(geom_type %in% class(x$geom)) NULL else x)` But geom_type has to be spelled differently, e.g. GeomPath, GeomVline, etc. Could be fixed with a regex of course. – Matherion Aug 17 '17 at 15:21

5 Answers5

24

For ggplot2 version 2.2.1, I had to modify the proposed remove_geom function like this:

remove_geom <- function(ggplot2_object, geom_type) {
  # Delete layers that match the requested type.
  layers <- lapply(ggplot2_object$layers, function(x) {
    if (class(x$geom)[1] == geom_type) {
      NULL
    } else {
      x
    }
  })
  # Delete the unwanted layers.
  layers <- layers[!sapply(layers, is.null)]
  ggplot2_object$layers <- layers
  ggplot2_object
}

Here's an example of how to use it:

library(ggplot2)

set.seed(3000)
d <- data.frame(
  x = runif(10),
  y = runif(10),
  label = sprintf("label%s", 1:10)
)

p <- ggplot(d, aes(x, y, label = label)) + geom_point() + geom_text()

Let's show the original plot:

p

plot with text labels

Now let's remove the labels and show the plot again:

p <- remove_geom(p, "GeomText")
p

plot without text labels

Kamil Slowikowski
  • 4,184
  • 3
  • 31
  • 39
21

If you look at

p$layers
[[1]]
mapping: ymin = ymin, ymax = ymax 
geom_ribbon: na.rm = FALSE, alpha = 0.3 
stat_identity:  
position_identity: (width = NULL, height = NULL)

[[2]]
geom_line:  
stat_identity:  
position_identity: (width = NULL, height = NULL)

You will see that you want to remove the first layer

You can do this by redefining the layers as just the second component in the list.

p$layer <- p$layer[2]

Now build and plot p

p

Note that p$layer[[1]] <- NULL would work as well. I agree with @Andrie and @Joran's comments regarding in wehat cases this might be useful, and would not expect this to be necessarily reliable. enter image description here

mnel
  • 113,303
  • 27
  • 265
  • 254
  • This was my thought too, but I haven't tested it in more complex situations where the layer you're deleting may have had associated scales, legends, etc. – joran Nov 15 '12 at 22:32
  • If you look at how `ggplot_build` works, it bases everything on the `layers` component of the plot, so it should be reasonably robust (unless you were using `qplot`, the inner workings of which I haven't investigated.) – mnel Nov 15 '12 at 22:36
  • I think you may be right; at least my initial tests with some more complicated plots seemed to all work out ok. – joran Nov 15 '12 at 22:38
  • Thanks. Is there a way to dynamically find the `geom_ribbon` layer? I'm not used to working with proto objects. – Erik Shilts Nov 19 '12 at 15:27
  • I see you've asked the layer identification as another question. A good idea. My experience with proto is limited at best. – mnel Nov 19 '12 at 21:33
8

As this problem looked interesting, I have expanded my 'ggpmisc' package with functions to manipulate the layers in a ggplot object (currently in package 'gginnards'). The functions are more polished versions of the example in my earlier answer to this same question. However, be aware that in most cases this is not the best way of working as it violates the Grammar of Graphics. In most cases one can assemble different variations of the same figure in the normal way with operator +, possibly "packaging" groups of layers into lists to have combined building blocks that can simplify the assembly of complex figures. Exceptionally we may want to edit an existing plot or a plot output by a higher level function that whose definition we cannot modify. In such cases these layer manipulation functions can be useful. The example above becomes.

library(gginnards)
p1 <- delete_layers(p, match_type = "GeomText")

See the documentation of the package for other examples, and for information on the companion functions useful for modifying the ordering of layers, and for inserting new layers at arbitrary positions.

Pedro J. Aphalo
  • 5,796
  • 1
  • 22
  • 23
  • 1
    I have moved the low level functions from 'ggpmisc' into a new package 'gginnards' accepted today in CRAN. – Pedro J. Aphalo Jul 13 '18 at 19:22
  • Is there a way to conditionally add elements? For example, I am doing several sets of plots. Where they have common y axis units, it makes sense that the limits are the same. However, if the currency differs, it makes sense for the y scale to be free (for example `scales="free_y"` in facet_wrap). So, rather than add or subtract elements, I want an element to apply (or not) depending on arguments I pass to ggplot. I'm sure I saw an example of this on the interweb, but cannot find it now. – Mark Neal Apr 30 '20 at 05:36
  • This could do what I'm thinking of [here](https://stackoverflow.com/a/37397907/4927395), though I *remember* seeing something a little more elegant. – Mark Neal Apr 30 '20 at 05:41
6

@Kamil Slowikowski Thanks! Very useful. However I could not stop myself from creating a new variation on the same theme... hopefully easier to understand than that in the original post or the updated version by Kamil, also avoiding some assignments.

remove_geoms <- function(x, geom_type) {
  # Find layers that match the requested type.
  selector <- sapply(x$layers,
                     function(y) {
                       class(y$geom)[1] == geom_type
                     })
  # Delete the layers.
  x$layers[selector] <- NULL
  x
}

This version is functionally identical to Kamil's function, so the usage example above does not need to be repeated here.

As an aside, this function can be easily adapted to select the layers based on the class of the stat instead of the class of the geom.

remove_stats <- function(x, stat_type) {
  # Find layers that match the requested type.
  selector <- sapply(x$layers,
                     function(y) {
                       class(y$stat)[1] == stat_type
                     })
  # Delete the layers.
  x$layers[selector] <- NULL
  x
}
Pedro J. Aphalo
  • 5,796
  • 1
  • 22
  • 23
3

@Kamil and @Pedro Thanks a lot! For those interested, one can also augment Pedro's function to select only specific layers, as shown here with a last_only argument:

remove_geoms <- function(x, geom_type, last_only = T) {
  # Find layers that match the requested type.
  selector <- sapply(x$layers,
                     function(y) {
                       class(y$geom)[1] == geom_type
                     })
  if(last_only) 
    selector <- max(which(selector))
  # Delete the layers.
  x$layers[selector] <- NULL
  x
}

Coming back to @Kamil's example plot:

set.seed(3000)
d <- data.frame(
  x = runif(10),
  y = runif(10),
  label = sprintf("label%s", 1:10)
)

p <- ggplot(d, aes(x, y, label = label)) + geom_point() + geom_point(color = "green") + geom_point(size = 5, color = "red")

p

enter image description here

p %>% remove_geoms("GeomPoint")

enter image description here

p %>% remove_geoms("GeomPoint")  %>% remove_geoms("GeomPoint")

enter image description here

eladin
  • 121
  • 6