0

Code:

example <- function(x, y){
  Plot <- plot(x, y)
  return(list(Plot = Plot, Mean = c(mean(x), mean(y))))
}

set.seed(1)
x <- rnorm(10)
set.seed(2)
y <- rnorm(10)
example(x, y)

The plot will come out every time we call the function example(x, y). How can I not print the plot when I call the function example(x, y), but could obtain the plot thru example(x, y)$Plot when needed?

Thanks!

J.Z.
  • 415
  • 2
  • 11
  • 2
    https://stackoverflow.com/questions/29583849/save-a-plot-in-an-object – user20650 May 08 '23 at 17:30
  • 1
    @user20650 from what I have tried, ```recordPlot()``` does not work in my R 4.3.0. The ```example(x,y)``` still prints the plot immediately. – J.Z. May 08 '23 at 17:43
  • Why not use `example(x,y)$Mean` to get the mean and then `example(x,y)$Plot` for the plot? – Jamie May 08 '23 at 17:46
  • @Jamie because that is not the most intuitive way how most people use R, and I am writing an R function for others to use. – J.Z. May 08 '23 at 17:49
  • I didnt try recordPlot, but you could use the other answer by chaging the relevant line of your code to `Plot <- function() plot(x, y)` , then call with `ex = example(1:3, 1:3); ex$Plot()` – user20650 May 08 '23 at 17:50
  • @user20650 Your code works but I would rather find an option with ```ex$Plot```, without the ```()```, because most users won't call an object in that way. As mentioned, I am writing a function for others to use and I wish to make it as usual as it can be. – J.Z. May 08 '23 at 17:53
  • I suppose the easiest option is to use grid graphics then ... id suggest the `lattice` package as it ships with R – user20650 May 08 '23 at 17:54
  • @user20650 I am not familiar with that option. Could you elaborate it more with the ```example()```? Thanks! – J.Z. May 08 '23 at 17:55
  • One option would be to include a new param such as `incl_plot = T`. Then use a conditional to either include or exclude the plot in the output. – Jamie May 08 '23 at 17:59
  • @Jamie If the plot is excluded in the output, how can one call it? My point was to include the option to call the plot if someone wants to grab it. But in general, it won't print when people call the function. – J.Z. May 08 '23 at 18:00
  • `Plot <- lattice::xyplot(y ~ x)` – user20650 May 08 '23 at 18:00
  • @user20650 The plot still prints when I call ```example(x, y)```. – J.Z. May 08 '23 at 18:01
  • not if you assign it to an object. if you then want the plot call `ex$Plot` – user20650 May 08 '23 at 18:02
  • @user20650 ```example <- function(x, y){ Plot <- lattice::xyplot(y ~ x) return(list(Plot = Plot, Mean = c(mean(x), mean(y)))) } set.seed(1) x <- rnorm(10) set.seed(2) y <- rnorm(10) example(x, y) ``` It still prints. – J.Z. May 08 '23 at 18:03
  • `ex = example(x, y)` . – user20650 May 08 '23 at 18:03
  • @user20650 sorry that is not what I want because most R users won't use a function that way. Also, ```ex <- example(x,y)``` does not print the plot without using ```lattice``` either. – J.Z. May 08 '23 at 18:06
  • 1
    "*most R users won't use a function that way.*" ... disagree. But it is not what you want. Sorry I dont understand your second sentence .. to plot use `ex$Plot` – user20650 May 08 '23 at 18:08
  • @user20650 I could be wrong in understanding how people without much programming background use R. My second sentence wanted to say that , if we use the original ```example()``` in my question, and call it in the way of ```ex <- example(x,y)```, the plot will not print. So we don't need ```lattice``` if we are to do ```ex <- example (x,y)```. Thank you! – J.Z. May 08 '23 at 18:15
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/253572/discussion-between-j-z-and-user20650). – J.Z. May 09 '23 at 18:16

2 Answers2

1

A simple way would be to add a parameter to let the user decide whether they want to include the plot in the output.

example <- function(x, y, incl_plot = T) {
  
  Mean = c(mean(x), mean(y))

  if(incl_plot) {
    Plot = plot(x,y)
    list(Plot = Plot, Mean = Mean)
  } else {
    Mean
  }
}

example(x,y, incl_plot = F) # only the Mean is returned
example(x,y) # both objects are returned
Jamie
  • 1,793
  • 6
  • 16
  • Thanks! This would work. I look forward to seeing if others have other options that may be neater. – J.Z. May 08 '23 at 18:08
1

A more sophisticated solution would be to use S3 dispatch, so that when you look at the whole output you only see the plot call, but when you look at the plot object, you get the actual plot drawn. This requires your main function and two print methods:

example <- function(x, y, ...) {
  
  plot_call <- substitute(
    plot(x = x, y = y, ...)
    )
  
  args <- as.list(plot_call)[-1]
  args$x <- x
  args$y <- y
  if(! "xlab" %in% names(args)) args$xlab <- deparse(substitute(x))
  if(! "ylab" %in% names(args)) args$ylab <- deparse(substitute(y))
  recording <- as.call(c(quote(plot), args))

  Plot <- structure(list(plot_call = plot_call, 
                         recording = recording),
                    class = "Plot")
  
  structure(list(Plot = Plot, 
                 Mean = c(mean(x), mean(y))),
            class = "myclass")
}

print.Plot <- function(obj, ...) {
  eval(obj$recording)
  invisible(obj)
}

print.myclass <- function(obj, ...) {
  print(list(Plot = obj$Plot$plot_call,
             Mean = obj$Mean))
  invisible(obj)
}

This gives the following behaviour:

example(x, y)
#> $Plot
#> plot(x = x, y = y)
#> 
#> $Mean
#> [1] 0.1322028 0.2111516

But

example(x, y)$Plot

and

my_obj <- example(mtcars$wt, mtcars$mpg, col = "red")

my_obj
#> $Plot
#> plot(x = mtcars$wt, y = mtcars$mpg, col = "red")
#> 
#> $Mean
#> [1]  3.21725 20.09062

but

my_obj$Plot

Created on 2023-05-08 with reprex v2.0.2


Edit

It seems the OP is looking for a function that outputs a multiple plot page using cowplot but with the option of the user accessing individual plots. This could be done more easily, this time using a single print method:

library(ggplot2)

example <- function(x, y) {

  df <- data.frame(x, y)

  plot1 <- ggplot(df, aes(x, y)) + geom_point()
  plot2 <- ggplot(df, aes(x)) + geom_density()
  plot3 <- ggplot(df, aes(y)) + geom_density()
  plot4 <- ggplot(df, aes(sort(x), sort(y))) + geom_point() + geom_abline()

  structure(list(plot1, plot2, plot3, plot4), class = "multiplot")
}

print.multiplot <- function(x, ...) {
  print(cowplot::plot_grid(x[[1]], x[[2]], x[[3]], x[[4]]))
}

This allows:

example(x, y)

enter image description here

but allows individual plots to be accessed too:

example(x, y)[[1]]

enter image description here

Allan Cameron
  • 147,086
  • 7
  • 49
  • 87
  • Thank you! Is there a way to modify this to accommodate ```ggplot```? For example, suppose I have a ```ggplot``` named ```Plot```, could you please help on how can I use your method to achieve our goal? – J.Z. May 08 '23 at 18:22
  • @J.Z. It's much easier with ggplot because it is already an object. Are you planning to use ggplot in your actual application? What exactly would you want the output of `example(x, y)` to display? – Allan Cameron May 08 '23 at 18:27
  • I actually was using the ggplot in my function but thought it would be easier to demonstrate the question here with ```plot```, because I thought my ggplots are quite complicated to be included here. To try to describe it, my function constructs four ggplots, and they are combined together with ```cowplot::plot_grid(plot0, plot1, plot2, plot3)```. While my function is to print the ```cowplot``` and some other output, I wish to add an option so that users can call individual plot when needed. – J.Z. May 08 '23 at 18:42
  • 1
    @J.Z. see my edit - is that what you mean? – Allan Cameron May 08 '23 at 20:19
  • It can't be better! Exactly what I wish to have! Neat and nice! Thank you! – J.Z. May 08 '23 at 23:27
  • I notice that you put ```print.multiplot``` outside the function ```example```. I tried to move it into the function ```example```, and it does not work. Is there a way to pack the ```print.multiplot``` into the function ```example```? Thank you! – J.Z. May 10 '23 at 19:41
  • @J.Z. no, the whole point is that the function produces a special type of object, but outside of the function R needs to know how to deal with these objects. If you are writing a package, you can have it so that `print.multiplot` is not exported. This means it isn't visible to end users. This is true of most R objects, including ggplot. – Allan Cameron May 10 '23 at 20:04
  • Thank you! May I ask for a resource that can help me to include it in a package but is not visible to end users? – J.Z. May 10 '23 at 20:11
  • @J.Z. Sure, you just need to ensure it is not exported. If you are using roxygen, just leave out the `@export` from the roxygen header. Otherwise, just leave it out from your NAMESPACE exports – Allan Cameron May 10 '23 at 20:44