4

I am trying to write a function:

myfunc <- function(df, out, ...) {   # ... = variables in a dataframe
df <- arrange(df, ...)               # plyr function that orders rows
out <- df[!duplicated(df[,c(...)]),] # remove duplicates
}

I can't figure out how to get the third line to work. The "..." arguments just need to be converted into a string vector so that the !duplicated() function can work.

I know deparse(substitute(x)) works for 1 argument:

> foo <- function(x) deparse(substitute(x))
> foo(bar)
[1] "bar"

But it doesn't work for multiple arguments. How can I change that so that multiple arguments will work?

> foo <- function(...) deparse(substitute(...))
> foo(bar,goo,poo)
[1] "bar" "goo" "poo"

I would also welcome other solutions that modify the original function (myfunc) if that makes it easier. Thanks.

winampman
  • 484
  • 5
  • 15

2 Answers2

3

The deparse(substitute()) does not work because the substituted expression is still an expression, and expects the individual elements to be variables. The trick is to convert the ellipsis to a list, then substitute to get its elements as an expression, and finally deparse each element in the expression tree:

ellipsis_to_vector <- function(...) {
  # Convert to a list, but with the variables still as an expression
  args_as_expression = substitute(list(...))
  # Deparse each item in the expression, turning it into a char vector
  # Why the [-1]? Because the first element in the list expression is "list"! :-)
  args_as_char_vector = sapply(args_as_expression,deparse)[-1]  
  args_as_char_vector
}
ellipsis_to_vector(this, and, that)
#> [1] "this" "and"  "that"
ellipsis_to_vector(single)
#> [1] "single"
ellipsis_to_vector() # Works for empty ellipsis as well
#> character(0)
Magnus
  • 23,900
  • 1
  • 30
  • 28
1

I think match.call will work better for you in this situation. Observe

foo <- function(df, ...) {
    mycall<-as.list(match.call())
    cols<-sapply(mycall[-(1:2)], deparse)
      df<-arrange(df, ...)
      df[!duplicated(df[, cols]),]
}

#test data    
set.seed(15)
dd<-data.frame(a=1:20,
    b=sample(1:50, 20, replace=T),
    c=sample(1:50, 20, replace=T)
)
dd <-rbind(dd, cbind(a=21:24, dd[9:12, 2:3])) # add dups
dd <-dd[sample.int(nrow(dd)),]   #shuffle

#try out function
out<-foo(dd, b,c)
out

I left off out since you really should assign the result outside of the function otherwise changes to the variable disappear after the function call is complete.

MrFlick
  • 195,160
  • 17
  • 277
  • 295
  • 1
    My approach was similar so I won't post as an answer: ` `cols <- as.character(match.call(expand.dots = FALSE)[[3]])` or even `cols <- as.character(substitute(...()))` – Tyler Rinker May 16 '14 at 19:51
  • @TylerRinker the `...()` part is interesting. Do you know what the class of `...` is? Is it a function? – MrFlick May 16 '14 at 20:25