Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Error: Can't add ggsave to a ggplot object

Tags:

r

ggplot2

I have set up a fresh R installation in a Windows 10 machine and can't run something as simple as:

data.frame(a = rnorm(100), b = rnorm(100)) |> 
  ggplot(aes(a, b)) +
  ggsave("temp.png")

because I get the following error:

Error: Can't add `ggsave("temp.png")` to a ggplot object.

My session info is:

R version 4.1.0 (2021-05-18)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows >= 8 x64 (build 9200)

Matrix products: default

locale:
[1] LC_COLLATE=Catalan_Spain.1252  LC_CTYPE=Catalan_Spain.1252    LC_MONETARY=Catalan_Spain.1252 LC_NUMERIC=C                  
[5] LC_TIME=Catalan_Spain.1252    

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] ggplot2_3.3.4 dplyr_1.0.6  

loaded via a namespace (and not attached):
 [1] magrittr_2.0.1    tidyselect_1.1.1  munsell_0.5.0     colorspace_2.0-1  R6_2.5.0          rlang_0.4.11      fansi_0.5.0       tools_4.1.0      
 [9] grid_4.1.0        data.table_1.14.0 gtable_0.3.0      utf8_1.2.1        withr_2.4.2       ellipsis_0.3.2    digest_0.6.27     tibble_3.1.2     
[17] lifecycle_1.0.0   crayon_1.4.1      purrr_0.3.4       farver_2.1.0      vctrs_0.3.8       glue_1.4.2        labeling_0.4.2    compiler_4.1.0   
[25] pillar_1.6.1      generics_0.1.0    scales_1.1.1      pkgconfig_2.0.3  

I have given permissions to the directory I'm working on and also tried in different directories and run with RScript, RStudio and Pycharm R Console, always with the same issue.

Thanks in advance.

EDIT: this used to work on ggplot2 3.3.3, it's the update to 3.3.4 that breaks things.

like image 814
Eudald Avatar asked Jun 16 '21 13:06

Eudald


People also ask

What is Ggsave?

ggsave() is a convenient function for saving a plot. It defaults to saving the last plot that you displayed, using the size of the current graphics device. It also guesses the type of graphics device from the extension.

How do I save a Ggplot as PDF?

You can either print directly a ggplot into PNG/PDF files or use the convenient function ggsave() for saving a ggplot. The default of ggsave() is to export the last plot that you displayed, using the size of the current graphics device.

What is DPI in Ggsave?

The image saved on a disk is represented also as a matrix of dots. ggplot and ggsave works in physical dimension (in, cm, or mm). To go from the dimension in inches to a number of dots, ggsave uses the number of dots per inches (dpi).

How do I save Ggplot as EPS?

The plot can be saved in the eps format using the ggplot. save() method, which takes as argument the string name of the plot.


2 Answers

You can no longer "add" ggsave to a ggplot addition-pipe.

Edit: this is a recent change in ggplot2-3.3.4. The previous answer is preserved below if you want to work around the new behavior. If you're particular annoyed by it, you might submit a new issue to ggplot2 suggesting that they either (a) undo the breaking change, or (b) better document the change in unintended functionality.

Side note: the day after this answer was posted, commit 389b864 included the following text: "Note that, as a side effect, an unofficial hack <ggplot object> + ggsave() no longer works (#4513)."

(In truth, I don't recall seeing documentation that suggests that + ggsave(.) should work, so the response to a new issue might be that they do not want to preserve an unintended "feature" for the sake of giving up some other elegant completeness.)

The changes from 3.3.3 to 3.3.4 (for save.R) are mostly unrelated to the act of saving the file. However, one functional change is the return value from ggsave:

@@ -90,5 +98,5 @@ ggsave <- function(filename, plot = last_plot(),
   grid.draw(plot)

-  invisible()
+  invisible(filename)
 }

In retrospect, this makes sense: ggplot2's ability to use +-pipes tends to be okay with trying to add NULL-like objects. That is, this works without error:

data.frame(a = rnorm(100), b = rnorm(100)) |>
  ggplot(aes(a, b)) +
  NULL

Why is NULL relevant here? Because the previous (3.3.3) version of ggsave ends with invisible(), which is invisibly returning NULL. (Internally, ggplot2:::add_ggplot begins with if (is.null(object)) return(p), which explains why that works.)

With the change to invisible(filename) (which, imo, is actually a little better), however, this is effectively the same as

data.frame(a = rnorm(100), b = rnorm(100)) |>
  ggplot(aes(a, b)) +
  "temp.png"

which does not make sense, so the +-piping fails.

In ggplot2-3.3.3, one can replicate this error with a hack/ugly code:

data.frame(a = rnorm(100), b = rnorm(100)) |>
  ggplot(aes(a, b)) +
  { ggsave("temp.png"); "temp.png"; }
# Error: Can't add `{` to a ggplot object.
# * Can't add `    ggsave("temp.png")` to a ggplot object.
# * Can't add `    "temp.png"` to a ggplot object.
# * Can't add `}` to a ggplot object.

which is close enough to the error you saw to (I believe) prove my point: the new-and-improved ggplot2-3.3.4 is returning a string, and that is different enough to break your habit-pattern of adding ggsave to a ggplot2 object.

If you're going to submit a new issue to ggplot2, then I suggest you frame it as a "feature request": if the invisible(filename) is instead a class object for which + works, then the previous behavior can be retained while still supporting the string-return. For example (completely untested):

ggsave <- function(file, ...) {
  # .....
  class(filename) <- c("ggplot2_string", "character")
  invisible(filename)
}

and then extend the +.gg-logic to actually work for strings, perhaps something like

`+.gg` <- function (e1, e2) {
    if (missing(e2)) {
        abort("Cannot use `+.gg()` with a single argument. Did you accidentally put + on a new line?")
    }
    if (inherits(e2, "ggplot2_string")) {
      e2 <- NULL
      e2name <- "NULL"
    } else {
      e2name <- deparse(substitute(e2))
    }
    if (is.theme(e1)) 
        add_theme(e1, e2, e2name)
    else if (is.ggplot(e1)) 
        add_ggplot(e1, e2, e2name)
    else if (is.ggproto(e1)) {
        abort("Cannot add ggproto objects together. Did you forget to add this object to a ggplot object?")
    }
}

No, I don't think this is the best way, but it is one way, open for discussion.


Four things you can do:

  1. Plot it, then save it. It will be displayed in your graphic device/pane.

    data.frame(a = rnorm(100), b = rnorm(100)) |>
      ggplot(aes(a, b))
    ggsave("temp.png")
    
  2. Save to an intermediate object without rendering, and save that:

    gg <- data.frame(a = rnorm(100), b = rnorm(100)) |>
      ggplot(aes(a, b))
    ggsave("temp.png", plot = gg)
    
  3. If R-4.1, pipe it do the plot= argument. While I don't have R-4.1 yet, based on comments I am led to believe that while |> will always pass the previous result as the next call's first argument, you can work around this by naming the file= argument, which means that R-4.1 will pass to the first available argument which (in this case) happens to be plot=, what we need.

    data.frame(a = rnorm(100), b = rnorm(100)) |>
      ggplot(aes(a, b)) |>
      ggsave(file = "temp.png")
    
  4. If you're using magrittr pipes, then you can do the same thing a little more succinctly:

    library(magrittr) # or dplyr, if you're using it for other things
    data.frame(a = rnorm(100), b = rnorm(100)) %>% # or |> here
      ggplot(aes(a, b)) %>%                        # but not |> here
      ggsave("temp.png", plot = .)
    
like image 84
r2evans Avatar answered Oct 05 '22 12:10

r2evans


Just remove the plus sign.

data.frame(a = rnorm(100), b = rnorm(100)) |> 
  ggplot(aes(a, b))

ggsave("temp.png")

ggsave has a default input last_plot()

like image 28
Pedro Alencar Avatar answered Oct 05 '22 10:10

Pedro Alencar