34

I'm sure this is simple, but I cannot find a solution ... I would like to use a variable containing a character string as argument for a function.

x <- c(1:10)
myoptions <- "trim=0, na.rm=FALSE"

Now, something like

foo <- mean(x, myoptions)

should be the same as

foo <- mean(x, trim=0, na.rm=FALSE)

Thanks in advance!

Triad sou.
  • 2,969
  • 3
  • 23
  • 27
Kilian
  • 399
  • 1
  • 4
  • 8
  • 1
    Although I am sure someone will post a solution to this, I think doing this is a bit unusual. Do you want to give a bit more background on what really you want to do? – Andrie Oct 20 '11 at 13:59
  • I have a CSV file written by an external program which contains one string of arguments per line. This should be used as input for the svm() function to build SVM models with different settings. So the first line of the CSV file is "cost=1, gamma=0.001", the second line is "cost=5, gamma=0.001" and so on. I want to loop over all lines of the CSV file. – Kilian Oct 20 '11 at 14:09
  • 1
    If you paste the first few line of the CSV file, probably you will get better answer. – kohske Oct 20 '11 at 14:25

4 Answers4

39

You can use eval and parse:

foo <- eval(parse(text = paste("mean(x,", myoptions, ")")))
kohske
  • 65,572
  • 8
  • 165
  • 155
  • Thank you very much, this works! Perhaps my thought were in the wrong direction -- so there is no way to do it in a way like `foo <- mean(x, take.this.as.arguments(myoptions))` ? – Kilian Oct 20 '11 at 14:21
  • @Kilian it is difficult, maybe impossible. – kohske Oct 21 '11 at 01:02
  • I suspect that @koshke would no longer say this to be even difficult. I think that csgillespie's answer is the correct one. – IRTFM Dec 30 '16 at 20:48
14

A more natural way to do what you want is to use do.call. For example,

R> l[["trim"]] = 0
R> l[["na.rm"]] = FALSE
R> l[["x"]] = 1:10
##Or l <- list(trim = 0, na.rm = FALSE, x = 1:10)
R> do.call(mean, l)
 [1] 5.5

If for some reason you really want to use a myoptions string, you could always use strsplit to coarce it into a list form. For example,

R> y = "trim=0, na.rm=FALSE"
R> strsplit(y, ", ")
[[1]]
[1] "trim=0"      "na.rm=FALSE" 
R> strsplit(y, ", ")[[1]][1]
[1] "trim=0"
csgillespie
  • 59,189
  • 14
  • 150
  • 185
  • 1
    Is there a reason for not using `l <- list(trim = 0, na.rm = FALSE, x = 1:10)`? – Gavin Simpson Oct 20 '11 at 14:02
  • 1
    I'm voting for the second method: `do.call` with `strsplit`. – IRTFM Oct 20 '11 at 16:51
  • @DWin Why do you prefer `do.call`/`strsplit` than `eval`/`parse`? I'm aware that some people don't like to use `eval`/`parse`. Is there any reason? Thanks. – kohske Oct 21 '11 at 01:04
  • I'm just trying to follow the of advice of the majority of my betters. Since I consider you to also be one of my betters, I admit to being conflicted. – IRTFM Oct 21 '11 at 01:44
  • I suspect at the end of the day, `do.call` vs `parse` is sort of like vi vs emacs. But in most cases, the `do.call` structure is cleaner and easier than using `parse`, and there's always a risk that parsing a string can cause accidental undesired splitting of arguments. – Carl Witthoft Oct 21 '11 at 02:35
  • 1
    I agree that `do.call` is cleaner in most cases. But here, as the arguments (i.e., R expression) are provided as a string, probably `parse` is cleaner. So it think it's on a case by case basis. – kohske Oct 21 '11 at 12:31
  • what about the case when the number of arguments in the string is unknown? – tic-toc-choc Jul 13 '17 at 04:15
  • strsplit is not that smart, what if `ylim=c(4,4), xlim=1:2` is passed? – Soren Havelund Welling Oct 30 '17 at 11:01
3

Here's a third answer that both uses parse, alist and do.call. My motivation for this new answer, is in the case where arguments are passed interactively from a client-side as chars. Then I guess, there is no good way around not using parse. Suggested solution with strsplit, cannot understand the context whether a comma , means next argument or next argument within an argument. strsplit does not understand context as strsplit is not a parser.

here arguments can be passed as "a=c(2,4), b=3,5" or list("c(a=(2,4)","b=3","5")

#' convert and evaluate a list of char args to a list of arguments
#'
#' @param listOfCharArgs a list of chars 
#'
#' @return
#' @export
#'
#' @examples
#' myCharArgs = list('x=c(1:3,NA)',"trim=0","TRUE")
#' myArgs = callMeMaybe(myCharArgs)
#' do.call(mean,myArgs)
callMeMaybe2 = function(listOfCharArgs) {
  CharArgs = unlist(listOfCharArgs)
  if(is.null(CharArgs)) return(alist())
    .out = eval(parse(text = paste0("alist(",
      paste(parse(text=CharArgs),collapse = ","),")")))
}

myCharArgs = list('x=c(1:3,NA)',"trim=0","TRUE")
myArgs = callMeMaybe2(myCharArgs)
do.call(mean,myArgs)
 [1] 2
Soren Havelund Welling
  • 1,823
  • 1
  • 16
  • 23
1

Using all of do.call, eval and parse (combining kohske's and csgillespie's answers, and also WoDoSc's answer to 'Pass a comma separated string as a list'):

x <- c(1:10)
myoptions <- "trim = 0, na.rm = FALSE"

do.call(
  what = mean,
  args = append(list(x = x), eval(parse(text = paste0("list(", myoptions, ")"))))
)

This solution can be quite resilient in a more complex case, such as shown below.

myfn <- function(x, y = 0, z = 0, ...) {
  print(paste("x:", x))
  print(paste("y:", y))
  print(paste("z:", z))
  if (length(list(...)) > 0) {
    print("other:")
    print(list(...))
  }
}

myextraargs <- paste(
  "y = c(11, 14), z = 47,",
  "t = data.frame(p = c('apple', 'plum'), j = c(7, 2), k = c(3, 21))"
)

do.call(
  what = myfn,
  args = append(
    list(x = 7),
    eval(parse(text = paste0("list(", myextraargs, ")")))
  )
)

results in:

[1] "x: 7"
[1] "y: 11" "y: 14"
[1] "z: 47"
[1] "other:"
$t
      p j  k
1 apple 7  3
2  plum 2 21

...and...

myextraargs <- NULL

do.call(
  what = myfn,
  args = append(
    list(x = 7),
    eval(parse(text = paste0("list(", myextraargs, ")")))
  )
)

results in

[1] "x: 7"
[1] "y: 0"
[1] "z: 0"
David Fong
  • 506
  • 4
  • 3