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

And*_*rie 19 r data.table

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

这是一个使用的例子mtcars.首先,设置一些数据:

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

现在,我通常会写这个以获得按组计算的总结.在这种情况下,我分组car:

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

      car Total
      AMC     1
 Cadillac     1
   Camaro     1
...
   Toyota     2
  Valiant     1
    Volvo     1
Run Code Online (Sandbox Code Playgroud)

复杂的是,因为参数ij在框架中进行评估,如果你想传入变量data.table,就必须使用eval(...)它们:

这有效:

group <- "car"
mtcars[, list(Total=length(mpg)), by=eval(group)]
Run Code Online (Sandbox Code Playgroud)

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

mtcars[, list(Total=length(mpg)), by=eval(group)][order(group)]
   car Total
 Mazda     2
Run Code Online (Sandbox Code Playgroud)

我知道为什么:它是因为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")
Run Code Online (Sandbox Code Playgroud)

Mat*_*wle 13

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
Run Code Online (Sandbox Code Playgroud)

虽然,开始时更难以开始,但它可以更灵活.无论如何,那是个主意.你需要的内部功能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
Run Code Online (Sandbox Code Playgroud)

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

tableOrder = function(x,.expr) {
    .expr = substitute(.expr)
    x[,list(Total=length(mpg)),keyby=.expr]
}
Run Code Online (Sandbox Code Playgroud)

评论和在该地区的反馈i,jby变量表达式是最欢迎的.您可以做的另一件事是有一个表,其中一列包含表达式,然后查找要放入的表达式i,j或者by从该表中查找.


Rei*_*son 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
Run Code Online (Sandbox Code Playgroud)

原因order(get(group))在于表达式在框架中进行评估的data.table.在那里,get(group)将寻找一个find变量car.如果你在全球环境中评估它,那就不存在了

> get(group)
Error in get(group) : object 'car' not found
Run Code Online (Sandbox Code Playgroud)

但它确实在评估发生的框架中.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
....
Run Code Online (Sandbox Code Playgroud)


Jos*_*ien 10

对于如何控制划定范围内的一般问题data.table,加文的答案已经得到了你很好覆盖.

但是,要真正充分利用data.table包的优势,您应该为data.table对象设置密钥.密钥会导致数据被预先排序,以便来自分组因子的相同级别(或级别组合)的行存储在连续的内存块中.与"示例中使用的ad hoc类型"相比,这可以反过来大大加快分组操作.(有关详细信息,请在datatable-faq(警告,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
Run Code Online (Sandbox Code Playgroud)

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

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

编辑:谨慎的谨慎

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