通过函数传递表达式

6

我正在使用data.table包并尝试编写一个函数(如下所示):

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)

我在这里尝试将表达式传递给函数。为什么这段代码不起作用?

我已经尝试过不同的substitute()quote()eval()组合。因此,如果你能够解释如何使它起作用,那就太好了。


你能解释一下你想做什么吗?此外,你的代码目前甚至无法到达那一步;data.table不是一个函数。 - Scott Ritchie
1
为什么不在这里简单地使用 setkeyv 并将 ID 给定为字符? - agstudy
1
@Manetheran,可能原帖作者应该提到他正在使用data.table包。我刚刚进行了编辑并添加了标签。 - Arun
啊,我不熟悉那个包,所以这个问题对我来说没有意义! - Scott Ritchie
@Arun 谢谢,我忘了提到这一点。 - levanovd
@agstudy 的目的不仅是为了达到目的,而且要理解这种事情是如何工作的。 - levanovd
2个回答

10

首先,让我们看一下 data.table 包中的 setkey 函数是如何工作的:

# 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)
}

因此,当您执行以下操作时:

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

它调用了data.table内部的函数getdots(也就是说,它没有被导出)。让我们来看看这个函数:

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

那么,这是做什么的?它将您在 setkey 中输入的参数提取,并使用 match.call 分别提取参数。也就是说,对于此示例情况,match.call 的参数将是:

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

由于它是一个列表,因此您可以使用$...访问...参数以获取值为ID的1个元素的列表,并将此列表转换为字符向量"ID"(通过as.character),然后setkey将其传递给setkeyv来设置键。


那么为什么在函数内部写setkey(table, key)不起作用呢?

这正是由于setkey/getdots的方式。 setkey函数旨在接受第一个参数(即)之后的任何参数,然后将...参数作为字符返回。

也就是说,如果你提供setkey(dt, key),那么它将返回cols <- "key"。如果你提供setkey(dt, e),它会返回cols <- "e"。它不会查找"key"是否是现有变量,如果是,则替换变量的值。它所做的就是将您提供的值(无论是符号还是字符)转换回字符。

当然,在您的情况下,这样做行不通,因为您希望在setkey中提供key= ID的值。至少我想不到一种方法可以这样做。


如何解决这个问题?

正如@agstudy已经提到的,最好/最简单的方法是传递"ID"并使用setkeyv。但是,如果您真的坚持要使用f("table.csv", ID),那么您可以这样做:

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

在这里,您首先使用 match.call 获取与参数 key 相对应的值,然后将其转换为 character,然后将其传递给 setkeyv

简而言之,setkey 内部使用 setkeyv。并且在我看来,当您已经知道需要设置键的 data.table 的列名时,setkey 是一个方便的函数。希望这有所帮助。


1
阿伦,我能说 setkey 实际上是 setkeyv 的一个方便函数/快捷方式吗?我的意思是它被设计用于控制台级别的使用,比如在命令行中使用?+1 - agstudy
2
@agstudy,确切地说。这也是帖子最后一段所说的:)。 - Arun
谢谢,现在清楚了。我已经看过setkey()的实现,但我应该更深入地了解getdots() - levanovd

3

从您的代码中我无法判断您试图实现什么,因此我将回答标题所问的问题:“如何通过函数传递表达式?”

如果您想这么做(应该尽可能避免),可以按照以下步骤进行:

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

例如:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接