2
dt <- data.table(x=1:4, y=c(1,1,2,2), z=c(1,2,1,2))

I would likt to achieve this:

dt[,list(z, p=cumsum(x)), by=y]
   y z p
1: 1 1 1
2: 1 2 3
3: 2 1 3
4: 2 2 7

But via a function call like test(dt, z, x, y)

None of the following 2 ways works. data.table 1.8.10

test1 <- function(dt, a, b, c){
    dt[,list(eval(substitute(a), parent.frame()),
             p=cumsum(eval(substitute(b), parent.frame()))),
        by=eval(substitute(c)), verbose=TRUE]
}
test1(dt, z, x, y)
# Error in eval(expr, envir, enclos) : object 'a' not found
test2 <- function(dt, a, b, c){
    dt[,list(eval(substitute(a)),
             p=cumsum(eval(substitute(b)))),
        by=eval(substitute(c)), verbose=TRUE]
}
test2(dt, z, x, y)
# Error in eval(expr, envir, enclos) : object 'z' not found

What is a correct way to make it work?

colinfang
  • 20,909
  • 19
  • 90
  • 173
  • Have a look at `?eval`. It's default argument to "envir" is `parent.frame()`. – Arun Nov 21 '13 at 15:16
  • @Arun Default and explicit arguments are evaluated in different frames https://stat.ethz.ch/pipermail/r-help/2010-February/227582.html – colinfang Nov 21 '13 at 15:47
  • Yes, seems I've misunderstood. But both `test1` and `test2` gives the right output for me (in 1.8.11). What does "not work" mean here? What do you get? Which version of data.table are you using? – Arun Nov 21 '13 at 15:55
  • @Arun It doesn't work for me `Error in eval(expr, envir, enclos) : object 'z' not found` in 1.8.10 Are you using a pre release version? – colinfang Nov 21 '13 at 15:57
  • 1
    I think this was fixed sometime after 1.8.10. I don't have access to the commits now. I'll post on it later. I guess you could either use 1.8.11 or you'll have to wait for the next release. – Arun Nov 21 '13 at 16:04
  • 1
    @Arun I saw http://stackoverflow.com/questions/18772277/installing-new-version-of-data-table-specifically-1-8-11-from-rforge Guess I have to wait. But thank you for bring `melt` into `data.table`. I hope someday `ggplot` can be made optimized for `data.table` as well – colinfang Nov 21 '13 at 16:44

2 Answers2

3

You can use deparse, substitute, eval and parse in following way. There maybe simpler solution, but following seem to work.

test1 <- function(dt, a, b, c){
  jvar <- paste0('list(',deparse(substitute(a)),', p=cumsum(',deparse(substitute(b)),'))')
  byvar <- paste0('list(', deparse(substitute(c)),')')
  dt[, eval(parse(text=jvar)), by=eval(parse(text=byvar))]

}

test1(dt, z, x, y)

##    y z p
## 1: 1 1 1
## 2: 1 2 3
## 3: 2 1 3
## 4: 2 2 7

or as @eddi sugguested

test2 <- function(dt, a, b, c){
  eval(parse(text = paste0('dt[,', 'list(',deparse(substitute(a)),', p=cumsum(',deparse(substitute(b)),'))', ',by=', 'list(', deparse(substitute(c)),')', ']') ))  
}


test2(dt, z, x, y)
##    y z p
## 1: 1 1 1
## 2: 1 2 3
## 3: 2 1 3
## 4: 2 2 7
CHP
  • 16,981
  • 4
  • 38
  • 57
  • +1 and I would go even further and just construct the entire `data.table` expression as a string and then `eval(parse` that – eddi Nov 21 '13 at 17:19
1

Here's a (IMO) cleaner version of @Chinmay Patil's second answer. It builds up the expression in a lispy way using R's backquote operator, and then evals the quoted expression.

test = function(dt, a, b, c) {
    z = substitute(a)
    x = substitute(b)
    y = substitute(c)
    expr = bquote(dt[, list(.(z), p=cumsum(.(x))), by=.(y)])
    eval(expr)
}

> test(dt, z, x, y)
   y z p
1: 1 1 1
2: 1 2 3
3: 2 1 3
4: 2 2 7
>  
Clayton Stanley
  • 7,513
  • 9
  • 32
  • 46