Passing expression through functions

蹲街弑〆低调 提交于 2019-12-22 06:35:24

问题


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.


回答1:


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.




回答2:


I can't tell from your code what you're trying to achieve, so I'll answer the question the title asks instead; "How to pass an expression through a function?"

If you want to do this (this should be avoided where possible), you can do the following:

f <- function(expression) {
  return(eval(parse(text=expression)))
}

For example:

f("a <- c(1,2,3); sum(a)")
# [1] 6


来源:https://stackoverflow.com/questions/17036775/passing-expression-through-functions

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!