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.
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.
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.
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).
The plot can be saved in the eps format using the ggplot. save() method, which takes as argument the string name of the plot.
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 = .)
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()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With