Vasily A
Vasily A

Reputation: 8626

Setting 'i' condition passed as argument

I'm trying to make a function which takes as input a data.table and condition and then modifies rows which are selected by condition. Of course I can define the condition explicitly as logical vector, like this:

dt1 <- fread(
       "id,a,b
       id1,1,10
       id2,2,30
       id3,3,40
     ")

test1 <- function(dtIn, condition) {
 dtIn[condition, newcol:="new text"];
 return(dtIn);
}    

test1(dt1, dt1$b>10);

But ideally I would like to be able to pass the condition without the table's name, something like this:

test2 <- function(dtIn, condition) {
 dtIn[substitute(condition), newcol:="new text"];
 return(dtIn);
}

test2(dt1, b>10);

I tried substitute(condition), but it gives an error "i has not evaluated to logical, integer or double". Is it possible to implement desired functionality?


UPD. As answered by @Gregor, the correct code just uses eval in addition to substitute:

test3 <- function(dtIn, condition) {
  dtIn[eval(substitute(condition)), newcol:="new text"];
  return(dtIn);
}

It can happen also that I need to pass only the column name and build the condition inside my function. As advised from @Gregor, I can build the condition by paste and then use eval(parse(...)):

test4p <- function(dtIn, colName) {
  condition <- parse(text=paste0(colName, ">20"))
  dtIn[eval(condition), newcol:="new text"];
  return(dtIn);
}

test4p(dt1, "b");

Myself, I came to another approach which uses get:

test4g <- function(dtIn, colName) {
  dtIn[get(colName)>20, newcol:="new text"];
  return(dtIn);
}

test4g(dt1, "b");

The result is the same, and I am not enough competent to explain the difference here between usage of get and eval(parse(...)), so your comments are welcome. With eval(parse(...)), I was able to make function taking unquoted column name, i.e. for calling test4(dt1, b) (just added substitute(colName) to the code), but failed to do something like this without eval(parse(...)).

Upvotes: 1

Views: 660

Answers (1)

Gregor Thomas
Gregor Thomas

Reputation: 145755

You're just missing an eval().

test3 <- function(dtIn, condition) {
 dtIn[eval(substitute(condition)), newcol:="new text"];
 return(dtIn);
}

> test3(dt1, dt1$b>10);
           id a  b   newcol
1:        id1 1 10       NA
2:        id2 2 30 new text
3:        id3 3 40 new text

Even works without the dt1$

> test3(dt1, b>10);
           id a  b   newcol
1:        id1 1 10       NA
2:        id2 2 30 new text
3:        id3 3 40 new text

See Hadley's Non-Standard Evaluation Section for a thorough explanation.

Upvotes: 3

Related Questions