5

我正在使用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()。因此,如果您还可以解释如何使其工作,那就太好了。

4

2 回答 2

10

首先,让我们看看这个函数是如何从包setkey中做事的:data.table

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

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

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

并且由于它是一个列表,因此您可以访问...参数 with$...以获取 1 个元素及其值的ID列表,并将此列表转换为as.character结果为"ID"(字符向量)的字符。然后将其setkey传递给setkeyv内部以设置密钥。


现在,当您在函数内部编写时,为什么这不起作用setkey(table, key)

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

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

当然,这不适用于您的情况,因为您希望在key= ID 中提供值setkey。至少我想不出办法做到这一点。


如何解决这个问题?

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

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,然后将其转换为 a character,然后将其传递给setkeyv.

简而言之,setkey内部使用setkeyv. data.table而且恕我直言,当您已经知道需要为其设置密钥的列名时,setkey 是一个方便的函数。希望这可以帮助。

于 2013-06-11T06:44:30.193 回答
2

我无法从您的代码中看出您要实现的目标,因此我将回答标题提出的问题;“如何通过函数传递表达式?”

如果您想这样做(应尽可能避免),您可以执行以下操作:

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

例如:

f("a <- c(1,2,3); sum(a)")
# [1] 6
于 2013-06-11T06:01:35.597 回答