Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing expression through functions

I'm using data.table package and trying to write a function (shown below):

require(data.table)
# Function definition
f = function(path, key) {
  table = data.table(read.delim(path, header=TRUE))
  e = substitute(key)
  setkey(table, e) # <- Error in setkeyv(x, cols, verbose = verbose) : some columns are not in the data.table: e
  return(table)
}

# Usage
f("table.csv", ID)

Here I try to pass an expression to the function. Why this code doesn't work?

I've already tried different combinations of substitute(), quote() and eval(). So, it'd be great if you could also explain how to get this to work.

like image 272
levanovd Avatar asked Jun 11 '13 05:06

levanovd


People also ask

Can a expression be passed in a function?

Short answer: You can't. The result of the expression evaluation is passed to the function rather than the expression itself.

How do you pass a function to a function?

1. Passing Pointer to a Function. A function can also be passed to another function by passing its address to that function; In simple terms, it could be achieved via pointers.

What is meant by passing arguments to function?

Passing arguments to function is a very important aspect of C++ programming. Arguments refer to values that can be passed to a function. Furthermore, this passing of arguments takes place for the purpose of being used as input information.


1 Answers

First, let's look at how the setkey function does things from the data.table package:

# setkey function
function (x, ..., verbose = getOption("datatable.verbose")) 
{
    if (is.character(x)) 
        stop("x may no longer be the character name of the data.table. The possibility was undocumented and has been removed.")
    cols = getdots()
    if (!length(cols)) 
        cols = colnames(x)
    else if (identical(cols, "NULL")) 
        cols = NULL
    setkeyv(x, cols, verbose = verbose)
}

So, when you do:

require(data.table)
dt <- data.table(ID=c(1,1,2,2,3), y = 1:5)
setkey(dt, ID)

It calls the function getdots which is internal to data.table (that is, it's not exported). Let's have a look at that function:

# data.table:::getdots
function () 
{
    as.character(match.call(sys.function(-1), call = sys.call(-1), 
        expand.dots = FALSE)$...)
}

So, what does this do? It takes the parameter you entered in setkey and it uses match.call to extract the arguments separately. That is, the match.call argument for this example case would be:

setkey(x = dt, ... = list(ID))

and since it's a list, you can access the ... parameter with $... to get a list of 1 element with its value ID and converting to this list to a character with as.character results in "ID" (a character vector). And then setkey passes this to setkeyv internally to set the keys.


Now why doesn't this work when you write setkey(table, key) inside your function?

This is precisely because of the way setkey/getdots is. The setkey function is designed to take any argument after the first argument (which is a data.table) and then return the ... argument as a character.

That is, if you give setkey(dt, key) then it'll return cols <- "key". If you give setkey(dt, e), it'll give back cols <- "e". It doesn't look for if "key" is an existing variable and then if so substitute the value of the variable. All it does is convert the value you provide (whether it be a symbol or character) back to a character.

Of course this won't work in your case because you want the value in key = ID to be provided in setkey. At least I can't think of a way to do this.


How to get around this?

As @agstudy already mentions, the best/easiest way is to pass "ID" and use setkeyv. But, if you really insist on using f("table.csv", ID) then, this is what you could do:

f <- function(path, key) {
    table = data.table(read.delim(path, header=TRUE))
    e = as.character(match.call(f)$key)
    setkeyv(table, e)
    return(table)
}

Here, you first use match.call to get the value corresponding to argument key and then convert it to a character and then pass that to setkeyv.

In short, setkey internally uses setkeyv. And imho, setkey is a convenient function to be used when you already know the column name of the data.table for which you need to set the key. Hope this helps.

like image 134
Arun Avatar answered Nov 15 '22 05:11

Arun