Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Remove a layer from a ggplot2 chart

Tags:

r

ggplot2

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)

like image 656
Erik Shilts Avatar asked Nov 15 '12 22:11

Erik Shilts


4 Answers

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

like image 120
Kamil Slowikowski Avatar answered Sep 24 '22 03:09

Kamil Slowikowski


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

like image 40
mnel Avatar answered Sep 26 '22 03:09

mnel


@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
}
like image 22
Pedro J. Aphalo Avatar answered Sep 24 '22 03:09

Pedro J. Aphalo


@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

like image 25
eladin Avatar answered Sep 26 '22 03:09

eladin