Question

As part of an effort to remove a specific geom from a plot I've already created (SO link here), I'd like to dynamically determine the geom type of each layer of a ggplot2 object.

Assuming I don't know the order in which I added layers, is there a way to dynamically find layers with a specific geom? If I print out the layers like I do below I can see that the layers are stored in a list, but I can't seem to access the geom type.

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()
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)

I'm not familiar with proto objects and things I've tried from the proto documentation don't seem to work (e.g. p$layers[[1]]$str()).


Thanks to the answers below I was able to come up with a function that removes a layer dynamically:

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
}
Was it helpful?

Solution

ggplot 2.2 update: If what you want is a character string naming the geom type, you can use:

sapply(p$layers, function(x) class(x$geom)[1])

which yields the first class name for the geom object of each layer. In the OP's example:

[1] "GeomRibbon" "GeomLine" 

The code in the answers above no longer give the results shown for version 2.2 The accepted answer yields two NULL values, and the other answer yields full ggproto objects.

OTHER TIPS

Edit: This answer is no longer current. It worked when the question was asked and presumably for quite some time after but for an answer that works with ggplot2 >= 2.2.0 see https://stackoverflow.com/a/43982598/1003565


If we're just looking to get the geom name for each item this appears to be in the $geom$objname part of each layer.

p$layers[[1]]$geom$objname
#[1] "ribbon"
lapply(p$layers, function(x){x$geom$objname})
#[[1]]
#[1] "ribbon"
#
#[[2]]
#[1] "line"

As an added note - the reason you couldn't use the p$layers[[1]]$str() syntax is (probably) because you didn't explicitly load the proto package. ggplot2 uses it internally but it imports it instead of using Depends. Notice the difference:

> 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()
> 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)

> p$layers[[1]]$str()
Error: attempt to apply non-function
> library(proto)
> p$layers[[1]]$str()
proto object 
 $ geom_params:List of 2 
 $ mapping    :List of 2 
 $ stat_params: Named list() 
 $ stat       :proto object  
  ..parent: proto object  
 .. .. parent: proto object  
 $ inherit.aes: logi TRUE 
 $ geom       :proto object  
  ..parent: proto object  
 .. .. parent: proto object  
 $ position   :proto object  
  ..parent: proto object  
 .. .. parent: proto object  
 .. .. .. parent: proto object  
 $ subset     : NULL 
 $ data       : list() 
  ..- attr(*, "class")= chr "waiver" 
 $ show_guide : logi NA 

Here's one way of looking at proto objects. They are environments as I understand it, so using ls gives you the names (apparently even for items extracted from the parent environment which is the plot object, p.):

 ls(p$layers[[1]])
# [1] "data"        "geom"        "geom_params" "inherit.aes" "mapping"    
# [6] "position"    "show_guide"  "stat"        "stat_params" "subset" 

 p$layers[[1]][["geom"]]
#geom_ribbon: 

 sapply( p$layers, "[[", "geom")
#---------------
[[1]]
geom_ribbon: 

[[2]]
geom_line: 

@Dason points out that you might have wanted a character vector as a result, so using sapply with "[[" again should satisfy that possible desire:

 sapply( sapply( p$layers, "[[", "geom"), "[[", 'objname')
#[1] "ribbon" "line"

The changes in ggproto design consisted of making the names reside an extra layer deeper inside the class-attributes:

 lapply( sapply( p$layers, "[[", "geom"), function(x) attributes(x) )
#----------------
[[1]]
[[1]]$class
[1] "GeomRibbon" "Geom"       "ggproto"   


[[2]]
[[2]]$class
[1] "GeomLine" "GeomPath" "Geom"     "ggproto" 

sapply( sapply( p$layers, "[[", "geom"), function(x) class(x)[[1]][1] )
[1] "GeomRibbon" "GeomLine"  
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top