web-dev-qa-db-fra.com

Ajouter plusieurs colonnes à R data.table en un seul appel de fonction?

J'ai une fonction qui renvoie deux valeurs dans une liste. Les deux valeurs doivent être ajoutées à un fichier data.table dans deux nouvelles colonnes. L'évaluation de la fonction étant coûteuse, j'aimerais éviter de devoir la calculer deux fois. Voici l'exemple:

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

Voici un exemple de ma fonction. Rappelez-vous que j'ai dit que c'était un calcul coûteux, en plus de cela, il n'y avait aucun moyen de déduire une valeur de retour des autres valeurs données (comme dans l'exemple ci-dessous):

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

Voici ma façon d'ajouter deux colonnes dans une déclaration. Celui-ci doit cependant appeler myfun à deux reprises:

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

Des suggestions sur la façon de faire ceci? Je pourrais enregistrer r2 dans un environnement distinct chaque fois que j'appelle myfun. J'ai juste besoin d'un moyen d'ajouter deux colonnes par référence à la fois. 

67
Florian Oswald

Vous pouvez stocker la sortie de votre appel de fonction:

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

mais cela semble aussi fonctionner:

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

Nouveau dans data.table v1.8.3 sur R-Forge, le with = FALSE n’est plus nécessaire ici, par souci de commodité:

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

Jusqu'à la minute live NEWS est ici .

88
flodel

Pour compléter la réponse précédente, vous pouvez utiliser lapply avec une fonction générant plusieurs colonnes. Il est alors possible d'utiliser la fonction avec plus de colonnes du fichier 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
2
DJJ

La réponse ne peut pas être utilisée, par exemple lorsque la fonction n'est pas vectorisée.

Par exemple, dans la situation suivante, cela ne fonctionnera pas comme prévu:

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

Cela ajoutera toujours la taille de la colonne g, pas la taille de chaque vecteur dans g

Une solution dans ce cas est:

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
1
Vasco

Si une fonction retourne une matrice, vous pouvez obtenir le même comportement en l'enveloppant en convertissant d'abord la matrice en liste. Je me demande si data.table devrait le gérer automatiquement?

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
0
DJJ