【问题标题】:Add multiple columns to R data.table in one function call?在一个函数调用中向 R data.table 添加多列?
【发布时间】:2012-07-03 18:10:53
【问题描述】:

我有一个在列表中返回两个值的函数。两个值都需要添加到 data.table 的两个新列中。函数的评估成本很高,所以我想避免两次计算函数。示例如下:

library(data.table)
example(data.table)
DT
   x y  v
1: a 1 42
2: a 3 42
3: a 6 42
4: b 1  4
5: b 3  5
6: b 6  6
7: c 1  7
8: c 3  8
9: c 6  9

这是我的函数的一个示例。请记住,我说过它的计算成本很高,除此之外,没有办法从其他给定值中推断出一个返回值(如下例所示):

myfun <- function (y, v) 
{
ret1 = y + v
ret2 = y - v
return(list(r1 = ret1, r2 = ret2))
}

这是我在一个语句中添加两列的方法。但是,那需要调用 myfun 两次:

DT[,new1:=myfun(y,v)$r1][,new2:=myfun(y,v)$r2]

   x y  v new1 new2
1: a 1 42   43  -41
2: a 3 42   45  -39
3: a 6 42   48  -36
4: b 1  4    5   -3
5: b 3  5    8   -2
6: b 6  6   12    0
7: c 1  7    8   -6
8: c 3  8   11   -5
9: c 6  9   15   -3

关于如何做到这一点的任何建议?每次调用 myfun 时,我都可以将 r2 保存在单独的环境中,我只需要一种方法来一次通过引用添加两列。

【问题讨论】:

  • 为什么不让你的函数接收一个数据框并直接返回一个数据框呢? `myfun
  • @Etienne 因为这会复制输入以创建新输出。 Florian 使用data.table 来提高其对大型数据集的内存效率;它根本不会复制xyv,甚至一次。想想 RAM 中的 20GB 数据集。

标签: r data.table


【解决方案1】:

由于data.table v1.8.3,你可以这样做:

DT[, c("new1","new2") := myfun(y,v)]

另一种选择是存储函数的输出并逐一添加列:

z <- myfun(DT$y,DT$v)
head(DT[,new1:=z$r1][,new2:=z$r2])
#      x y  v new1 new2
# [1,] a 1 42   43  -41
# [2,] a 3 42   45  -39
# [3,] a 6 42   48  -36
# [4,] b 1  4    5   -3
# [5,] b 3  5    8   -2
# [6,] b 6  6   12    0

【讨论】:

  • 哇,第二个太棒了,谢谢!只需用debug(myfun) 运行它,看看它被调用的频率:它是一次。很棒。
  • 我也是+10。我刚刚将FR#2120 提高到“删除需要with=FALSE 以获得:= 的LHS”
  • 注意列表回收也做了;例如,c("a","b","c","d"):=list(1,2) 将 1 放入 ac,并将 2 放入 bd。如果任何列不存在,它们将通过引用添加。不确定:= 回收在实践中有多有用。 c("a","b","c"):=NULL 更重要,它删除了这 3 列。在内部,这是将 NULL 循环到(语义)列表长度 3。
  • @MatthewDowle 哦,是的,只是想问这个问题。 c("a","b","c"):=NULL 非常有用。
  • 另一个有用的:= 用法可以是`:=`(colname=colvalue,...)。我通常更喜欢这个,因为您可能只是将:= 替换为list,以便在使用:= 时对要写入的数据进行只读预览。
【解决方案2】:

为什么不让你的函数接收一个数据框并直接返回一个数据框?

myfun <- function (DT) 
{
DT$ret1 = with(DT, y + v)
DT$ret2 = with(DT, y - v)
return(DT)
}

【讨论】:

  • 因为这会复制整个DT 两次。 Florian 使用data.table 来提高其对大型数据集的内存效率;它根本不会复制xyv,甚至一次。
【解决方案3】:

要基于上一个答案,可以将lapply 与输出多列的函数一起使用。然后可以将该函数用于 data.table 的更多列。

 myfun <- function(a,b){
     res1 <- a+b
     res2 <- a-b
     list(res1,res2)
 }

 DT <- data.table(z=1:10,x=seq(3,30,3),t=seq(4,40,4))
 DT

 ## DT
 ##     z  x  t
 ## 1:  1  3  4
 ## 2:  2  6  8
 ## 3:  3  9 12
 ## 4:  4 12 16
 ## 5:  5 15 20
 ## 6:  6 18 24
 ## 7:  7 21 28
 ## 8:  8 24 32
 ## 9:  9 27 36
 ## 10: 10 30 40

 col <- colnames(DT)
 DT[, paste0(c('r1','r2'),rep(col,each=2)):=unlist(lapply(.SD,myfun,z),
                                                   recursive=FALSE),.SDcols=col]
 ## > DT
 ##     z  x  t r1z r2z r1x r2x r1t r2t
 ## 1:  1  3  4   2   0   4   2   5   3
 ## 2:  2  6  8   4   0   8   4  10   6
 ## 3:  3  9 12   6   0  12   6  15   9
 ## 4:  4 12 16   8   0  16   8  20  12
 ## 5:  5 15 20  10   0  20  10  25  15
 ## 6:  6 18 24  12   0  24  12  30  18
 ## 7:  7 21 28  14   0  28  14  35  21
 ## 8:  8 24 32  16   0  32  16  40  24
 ## 9:  9 27 36  18   0  36  18  45  27
 ## 10: 10 30 40  20   0  40  20  50  30

【讨论】:

    【解决方案4】:

    当函数未向量化时,不能使用答案。

    例如在以下情况下,它将无法按预期工作:

    myfun <- function (y, v, g) 
    {
      ret1 = y + v + length(g)
      ret2 = y - v + length(g)
      return(list(r1 = ret1, r2 = ret2))
    }
    DT
    #    v y                  g
    # 1: 1 1                  1
    # 2: 1 3                4,2
    # 3: 1 6              9,8,6
    
    DT[,c("new1","new2"):=myfun(y,v,g)]
    DT
    #    v y     g new1 new2
    # 1: 1 1     1    5    3
    # 2: 1 3   4,2    7    5
    # 3: 1 6 9,8,6   10    8
    

    它将始终添加列 g 的大小,而不是 g 中每个向量的大小

    这种情况下的解决方案是:

    DT[, c("new1","new2") := data.table(t(mapply(myfun,y,v,g)))]
    DT
    #    v y     g new1 new2
    # 1: 1 1     1    3    1
    # 2: 1 3   4,2    6    4
    # 3: 1 6 9,8,6   10    8
    

    【讨论】:

      【解决方案5】:

      如果函数返回一个矩阵,您可以通过将函数包装为先将矩阵转换为列表来实现相同的行为。我想知道 data.table 是否应该自动处理它?

      matrix2list <- function(mat){
      unlist(apply(mat,2,function(x) list(x)),FALSE)
      }
      
      DT <- data.table(A=1:10)
      
      myfun <- function(x) matrix2list(cbind(x+1,x-1))
      
      DT[,c("c","d"):=myfun(A)]
      
      ##>DT
      ##      A  c d
      ##  1:  1  2 0
      ##  2:  2  3 1
      ##  3:  3  4 2
      ##  4:  4  5 3
      ##  5:  5  6 4
      ##  6:  6  7 5
      ##  7:  7  8 6
      ##  8:  8  9 7
      ##  9:  9 10 8
      ## 10: 10 11 9
      

      【讨论】:

        猜你喜欢
        • 2017-06-22
        • 1970-01-01
        • 1970-01-01
        • 2020-07-29
        • 1970-01-01
        • 2020-07-10
        • 2017-02-23
        • 1970-01-01
        相关资源
        最近更新 更多