29

I am trying to understand how I can make changes to the internals of a ggplot2 chart. I started reading the few ressources I could find about ggplot_built and ggplot_gtable, but I could not answer the question below.

Given a plot g with 2 geom.

g <- ggplot(iris, aes(Petal.Length, Petal.Width)) + 
  geom_point() + 
  geom_text(aes(label=Sepal.Width))
g

enter image description here

Is there a way to dive into the g object and remove one/multiple geoms?

Can I get, starting from g, a plot with no geoms?

enter image description here

Or with just the geom_text removed?

enter image description here


UPDATE after more thorough testing of the answers

I just noticed that removing geoms can have an impact on the layout of the other geoms. Probably great as a default and the intended behaviour for most use cases, but I actually need the exact same chart "layout" (axis and positions of remaining geoms).

Example, before removing one geom:

library(dplyr)
library(ggplot2)
count(mpg, class) %>%
  mutate(pct=n/sum(n)) %>%
  ggplot(aes(class, pct)) +
  geom_col(fill="blue") +
  geom_line(group=1) +
  geom_point(size=4) 

enter image description here

after removing one geom (note that the y axis doesn't start at 0 anymore, I guess the default behaviour for line/point without bars):

library(dplyr)
library(ggplot2)
count(mpg, class) %>%
  mutate(pct=n/sum(n)) %>%
  ggplot(aes(class, pct)) +
  geom_col(fill="blue") +
  geom_line(group=1) +
  geom_point(size=4) -> p
p$layers[[1]] <- NULL
p

enter image description here

Any ways to force ggplot to keep the exact same layout?

xav
  • 4,101
  • 5
  • 26
  • 32

4 Answers4

34

You can access / manipulate g's elements the way you would do with other R object.

g$layers
#[[1]]
#geom_point: na.rm = FALSE
#stat_identity: na.rm = FALSE
#position_identity 

#[[2]]
#mapping: label = Sepal.Width 
#geom_text: parse = FALSE, check_overlap = FALSE, na.rm = FALSE
#stat_identity: na.rm = FALSE
#position_identity 

Remove geom_text:

g$layers[[2]] <- NULL

Remove all layers

g$layers <- NULL
g

enter image description here


The gginnards package offers some functions to manipulate ggplot layers, see the vignette User Guide: 4 Manipulation of plot layers for details.


edit

Following the question of How can I extract plot axes' ranges for a ggplot2 object? I came to a solution that uses ggplot_build and ggplot_gtable. The idea is simply to copy the layout parameters taken from ggplot_built(p) to the new plot, for which we deleted a layer.

# create a copy of p
p_new <- p

# delete the first layer
p_new$layers[[1]] <- NULL
p_new_build <- ggplot_build(p_new)

# this is the important line
p_new_build$layout$panel_params <- ggplot_build(p)$layout$panel_params

library(gridExtra)
grid.arrange(p, ggplot_gtable(p_new_build), ncol = 2)

enter image description here

markus
  • 25,843
  • 5
  • 39
  • 58
11

You can use gginnards package to make life easier

library(ggplot2)

### sample plot w/ both points and labels
g <- ggplot(iris, aes(Petal.Length, Petal.Width)) + 
  geom_point() + 
  geom_text(aes(label = Sepal.Width))
g

### https://cran.rstudio.com/web/packages/gginnards/vignettes/user-guide-2.html
library(gginnards)

### remove points
delete_layers(g, "GeomPoint")

### remove text
delete_layers(g, "GeomText")

Tung
  • 26,371
  • 7
  • 91
  • 115
  • 1
    Thanks a lot for the demo! – xav May 22 '18 at 09:03
  • 2
    @Tung As of version 0.3.0 the `which_layer` function is no longer part of `ggpmisc` but of the new package [`gginnards`](https://cran.rstudio.com/web/packages/gginnards/). Best – markus Sep 17 '18 at 18:54
4

You can try to include some alpha. Instead of removing the layer, it will not show up.

count(mpg, class) %>%
  mutate(pct=n/sum(n)) %>%
  ggplot(aes(class, pct)) +
  geom_col(fill="blue", alpha=0) +
  geom_line(group=1) +
  geom_point(size=4)

enter image description here

You can of course add the alpha afterwards like

p <- ggplot_build(p)
p$data[[1]]$alpha <- 0
plot(ggplot_gtable(p))
Roman
  • 17,008
  • 3
  • 36
  • 49
  • Thanks a lot, but can you add it on a per geom basis on an existing chart object? – xav May 30 '18 at 12:29
  • I should do my homework better: this was helpful: https://stackoverflow.com/a/41940643/2008527 Something like: `ggplot_build(p) -> q; q$data[[1]]$alpha <- 0; plot(ggplot_gtable(q))` – xav May 30 '18 at 12:39
  • Also found that you need to apply `p$data[[1]]$size <- 0` to remove things like the border of GeomCol... – xav Jun 03 '18 at 13:54
1

Build the plot to have all the waiver() stuff (axis limits and labels, etc.) resolve, tamper with that, then convert it to a gtable for plotting.

p_built <- ggplot_build(p)
p_built$plot$layers[[1]] <- NULL
p_built$data[[1]] <- NULL
plot(ggplot_gtable(p_built))

Note that you need to remove not only the layer but also the dataset for that layer.

Nathan Werth
  • 5,093
  • 18
  • 25