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:
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")
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)
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")
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 = .)