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