在函数中使用data.table i和j参数

时间:2012-03-14 16:00:34

标签: r data.table

我正在尝试编写一些包装函数来减少data.table的代码重复。

以下是使用mtcars的示例。首先,设置一些数据:

library(data.table)
data(mtcars)
mtcars$car <- factor(gsub("(.*?) .*", "\\1", rownames(mtcars)), ordered=TRUE)
mtcars <- data.table(mtcars)

现在,我通常会写这些来获取按组计数的摘要。在这种情况下,我按car分组:

mtcars[, list(Total=length(mpg)), by="car"][order(car)]

      car Total
      AMC     1
 Cadillac     1
   Camaro     1
...
   Toyota     2
  Valiant     1
    Volvo     1

复杂的是,由于参数ij是在data.table的框架中评估的,如果你想要传递,必须使用eval(...)变量:

这有效:

group <- "car"
mtcars[, list(Total=length(mpg)), by=eval(group)]

但是现在我想通过相同的分组变量来排序结果。我无法得到以下任何变体给我正确的结果。请注意我总是得到一行结果,而不是有序集。

mtcars[, list(Total=length(mpg)), by=eval(group)][order(group)]
   car Total
 Mazda     2

我知道原因:这是因为group是在parent.frame中评估的,而不是data.table的框架。

如何在group的上下文中评估data.table

更一般地说,我如何在函数内部使用它?我需要以下函数来给我所有结果,而不仅仅是第一行数据:

tableOrder <- function(x, group){
  x[, list(Total=length(mpg)), by=eval(group)][order(group)]
}

tableOrder(mtcars, "car")

3 个答案:

答案 0 :(得分:12)

Gavin和Josh是对的。这个答案只是为了增加更多背景。我们的想法是,您不仅可以使用quote()将变量列名称传递给类似的函数,还可以将列名称的表达式传递给它。

group = quote(car)
mtcars[, list(Total=length(mpg)), by=group][order(group)]
      group Total
        AMC     1
   Cadillac     1
     ...
     Toyota     2
    Valiant     1
      Volvo     1

虽然,开始时更难以开始,但它可以更灵活。无论如何,那是个主意。内部函数需要substitute(),如下所示:

tableOrder = function(x,.expr) {
    .expr = substitute(.expr)
    ans = x[,list(Total=length(mpg)),by=.expr]
    setkeyv(ans, head(names(ans),-1))    # see below re feature request #1780
    ans
}

tableOrder(mtcars, car)
      .expr Total
        AMC     1
   Cadillac     1
     Camaro     1
      ...
     Toyota     2
    Valiant     1
      Volvo     1

tableOrder(mtcars, substring(car,1,1))  # an expression, not just a column name
      .expr Total
 [1,]     A     1
 [2,]     C     3
 [3,]     D     3
 ...
 [8,]     P     2
 [9,]     T     2
[10,]     V     2

tableOrder(mtcars, list(cyl,gear%%2))   # by two expressions, so head(,-1) above
     cyl gear Total
[1,]   4    0     8
[2,]   4    1     3
[3,]   6    0     4
[4,]   6    1     3
[5,]   8    1    14

在v1.8.0(2012年7月)中添加了一个新参数keyby,使其更简单:

tableOrder = function(x,.expr) {
    .expr = substitute(.expr)
    x[,list(Total=length(mpg)),keyby=.expr]
}

欢迎ijby变量表达式中的评论和反馈。您可以做的另一件事是有一个表,其中列包含表达式,然后查找要放在该表的ijby中的表达式。

答案 1 :(得分:11)

使用get(group)来引用group中指定的对象:

> mtcars[, list(Total=length(mpg)), by=eval(group)][order(get(group))]
        car Total
        AMC     1
   Cadillac     1
     Camaro     1
   Chrysler     1
     Datsun     1
      Dodge     1
     Duster     1
    Ferrari     1
       Fiat     2
       Ford     1
      Honda     1
     Hornet     2
    Lincoln     1
      Lotus     1
   Maserati     1
      Mazda     2
       Merc     7
    Pontiac     1
    Porsche     1
     Toyota     2
    Valiant     1
      Volvo     1
cn      car Total
> # vs
> mtcars[, list(Total=length(mpg)), by=eval(group)][order(group)]
       car Total
[1,] Mazda     2

order(get(group))的作用原因是在data.table的框架中评估表达式 。在那里,get(group)将查找查找变量car。如果你在全球环境中评估它不存在

> get(group)
Error in get(group) : object 'car' not found

但它在评估发生的框架中确实存在。 group在那里不存在,但是按照通常的规则,它会搜索父帧,直到找到匹配group的内容,这是本例中的全局环境。因此,您需要注意在实际函数中用作group的对象的名称 - 例如,您不希望使用可能在data.table对象中匹配的内容。使用类似.group之类的东西作为函数arg,我猜是非常安全的。

这是你的功能,修改过:

tableOrder <- function(x, .group){
  x[, list(Total=length(mpg)), by=eval(.group)][order(get(.group))]
}

> tableOrder(mtcars, "car")
        car Total
        AMC     1
   Cadillac     1
     Camaro     1
   Chrysler     1
     Datsun     1
....

答案 2 :(得分:10)

关于如何在 data.table 中控制作用域的一般问题,Gavin的回答让你得到了很好的解决。

要真正充分利用 data.table 软件包的优势,您应该为data.table对象设置密钥。密钥会导致数据被预先排序,以便来自分组因子的相同级别(或级别组合)的行存储在连续的内存块中。与“示例中使用的ad hoc类型”相比,这可以反过来大大加快分组操作。 (在datatable-faq(warning, pdf)中搜索'ad hoc'以获取更多详细信息)。

在许多情况下(包括您的示例)使用键也具有简化操作data.table所需的代码的愉快副作用。此外,它会按照键指定的顺序自动输出结果,这通常也是您想要的。

首先,如果您只需要按'car'列进行分组,则可以执行以下操作:

## Create data.table with a key
group <- "car"
mtcars <- data.table(mtcars, key = group)

## Outputs results in correct order
mtcars[, list(Total=length(mpg)), by = key(mtcars)]
        car Total
        AMC     1
   Cadillac     1
     Camaro     1
   Chrysler     1
     Datsun     1

即使您的密钥包含多个列,使用密钥仍然可以实现更简单的代码(并且您可以获得加速,这可能是您首先使用data.table的真正原因!):

group <- "car"
mtcars <- data.table(mtcars, key = c("car", "gear"))
mtcars[, list(Total=length(mpg)), by = eval(group)]

编辑:谨慎的提示

如果by参数用于根据作为键的一部分的列执行分组但不是键的第一个元素,结果的顺序仍可能需要后期处理。因此,在上面的第二个示例中,如果key = c("gear", "car"),则"Dodge""Datsun"之前排序。在这样的情况下,我可能仍然希望事先重新排序密钥,而不是在事后重新排序结果。也许Matthew Dowle会权衡这两者中的哪一个更好/更快。

相关问题