在一个函数调用中将多列添加到R data.table?


73

我有一个函数,该函数在列表中返回两个值。这两个值都需要在两个新列中添加到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

有关如何执行此操作的任何建议?r2每次调用myfun时,我都可以保存在单独的环境中,只需要一种通过引用一次添加两列的方法。


为什么不让您的函数接受数据帧并直接返回数据帧?`myfun <-函数(y,v){ret1 = y + v ret2 = y-v return(list(r1 = ret1,r2 = ret2))}
-Décarie

3
@Etienne,因为那样会复制输入以创建新的输出。弗洛里安(Florian)使用data.table大型数据集来提高存储效率。它不会复制xyv在所有,甚至一度。考虑一下RAM中的20GB数据集。
马特·道尔

Answers:


99

data.tablev1.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

2
哇,第二个太棒了,谢谢!只需运行一下debug(myfun)即可查看它被调用的频率:只有一次。大。
Florian Oswald

1
我也+10。我刚刚将FR#2120提升为“降低with=FALSELHS的需求:=
Matt Dowle 2012年

7
请注意,列表回收也已完成。例如,c("a","b","c","d"):=list(1,2)将1放入ac,将2放入bd。如果不存在任何列,则将通过引用将其添加。不确定:=回收在实践中有多有用。c("a","b","c"):=NULL删除这三列的更多信息。在内部这是空的,以(语义)列表长度3.循环
马特Dowle

1
@MatthewDowle哦,是的,只是想问那个。这c("a","b","c"):=NULL是非常有用的。
弗洛里安·奥斯瓦尔德

7
另一个有用的:=用法是`:=`(colname=colvalue,...)。我经常喜欢这一点,因为您可能会替换为:=使用时list要通过引用写入的数据的只读预览:=
jangorecki 2015年

2

要建立在上一个答案的基础上,可以将其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

1

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

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

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

它将始终添加columng的大小,而不是in中每个向量的大小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

0

如果函数返回矩阵,则可以通过包装该函数并将其首先转换为列表来实现相同的行为。我想知道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

-5

为什么不让您的函数接受数据帧并直接返回数据帧?

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

25
因为那会复制整个DT两次。弗洛里安(Florian)使用data.table大型数据集来提高存储效率。它不会复制xyv在所有,甚至一度。
马特·道尔
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.