5

I am trying to make a plot function with a set of default values and the flexibility to change these values by using any argument the plot function accepts inside the dots (...) argument. An example:

PlotIt <- function(x, y, ...) {
  plot(x, y, type = "l", asp = 1, ...)
}

x <- 1:10
y <- 10:1

PlotIt(x = x, y = y)
# Returns a plot
PlotIt(x = x, y = y, asp = NA)
# Error in plot.default(x, y, type = "l", asp = 1, ...) : 
#  formal argument "asp" matched by multiple actual arguments

The error is naturally because I try to pass the asp argument twice into plot. So far my best clumsy attempt is to make an if-else statement to take this into account (the approach is modified from here):

PlotIt2 <- function(x, y, ...) {

  mc <- match.call(expand.dots = FALSE)

  if(names(mc$...) %in% "asp") {
  plot(x, y, type = "l", ...)  
  } else {
  plot(x, y, type = "l", asp = 1, ...)  
  }

}

PlotIt2(x = x, y = y, asp = NA)
# works

To do this for all possible parameters one can set in with the ... argument, I would need to write a long if-else statement. Is there a more elegant way of doing this?

The question is related to this one with the difference that I want to automatically overwrite all parameters set by the ... argument.

Mikko
  • 7,530
  • 8
  • 55
  • 92

2 Answers2

4

If you want to use base R only, you can put everything in a list and remove duplicates based on the argument names (making sure that the defaults are last so that they are removed if they are present in ...):

PlotIt <- function(x, y, ...) {
  arguments <- list(
    x = x,
    y = y,
    ...,
    type = "l",
    asp = 1
  )

  arguments <- arguments[!duplicated(names(arguments))]

  do.call("plot", arguments)
}

If you don't mind depending on rlang, you could also do the following, using .homonyms to get the same functionality (and check the plot's labels for the axes, it'll be different between the base R and rlang versions):

PlotIt <- function(x, y, ...) {
  require("rlang")
  arguments <- rlang::dots_list(
    rlang::expr(x),
    rlang::expr(y),
    ...,
    type = "l",
    asp = 1,
    .homonyms = "first"
  )

  call <- rlang::call2("plot", !!!arguments)
  eval(call)
}
Alexis
  • 4,950
  • 1
  • 18
  • 37
  • would you also be able to use `match.call()` in this example to solve the problem as described [here](https://stackoverflow.com/questions/32486753/why-is-match-call-useful/)? Im trying to learn about `match.call()` but I prefer your approach, its more intuitive – user63230 Apr 20 '20 at 11:25
  • 1
    @user63230 well, what Mikko attempted in his question is what I can think of, I'm not sure how to improve on that. – Alexis Apr 23 '20 at 20:00
0

Instead of hardcoding your argument in the plot function, set an argument within your function's parameters with a default value equal to your previously hardcoded value. To make life easy, use the same name as the plot argument:

PlotIt <- function(x, y, asp = 1, ...) {
  plot(x, y, type = "l", asp = asp, ...)
}

Because you defined a default parameter asp = 1, you can choose to

x <- 1:10; y <- 10:1
PlotIt(x = x, y = y) # not enter the parameter
PlotIt(x = x, y = y, asp = NA) # or enter the parameter

and it will work as desired.

Evan Friedland
  • 3,062
  • 1
  • 11
  • 25