Añadir varias columnas a R data.table en una llamada de función?

Tengo una función que devuelve dos valores en una lista. Ambos valores deben agregarse a una tabla de datos en dos columnas nuevas. La evaluación de la función es costosa, por lo que me gustaría evitar tener que calcular la función dos veces. Aquí está el ejemplo:

library(data.table) example(data.table) DT xyv 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 

Aquí hay un ejemplo de mi función. Recuerde que dije que es un cálculo costoso, además de eso, no hay forma de deducir un valor de retorno de los otros valores dados (como en el ejemplo a continuación):

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

Esta es mi manera de agregar dos columnas en una statement. Sin embargo, uno necesita llamar a myfun dos veces:

 DT[,new1:=myfun(y,v)$r1][,new2:=myfun(y,v)$r2] xyv 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 

¿Alguna sugerencia sobre cómo hacer esto? Podría guardar r2 en un entorno diferente cada vez que llamo a myfun, solo necesito una forma de agregar dos columnas por referencia a la vez.

Puede almacenar la salida de su llamada de función:

 z < - myfun(DT$y,DT$v) head(DT[,new1:=z$r1][,new2:=z$r2]) # xyv 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 

pero esto también parece funcionar:

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

Nuevo en data.table v1.8.3 en R-Forge, with = FALSE ya no es necesario aquí, por conveniencia:

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

Hasta el minuto en vivo NOTICIAS está aquí .

Para construir sobre la respuesta anterior, uno puede usar lapply con una función que lapply más de una columna. Entonces es posible usar la función con más columnas de data.table.

  myfun < - function(a,b){ res1 <- a+b res2 <- ab list(res1,res2) } DT <- data.table(z=1:10,x=seq(3,30,3),t=seq(4,40,4)) DT ## DT ## zxt ## 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 ## zxt 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 respuesta no se puede usar como cuando la función no está vectorizada.

Por ejemplo, en la siguiente situación, no funcionará según lo previsto:

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

Siempre agregará el tamaño de la columna g , no el tamaño de cada vector en g

Una solución en tal caso es:

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

En el caso de que una función devuelva una matriz, puede lograr el mismo comportamiento envolviendo la función con una convirtiendo la matriz en una lista primero. Me pregunto si data.table debería manejarlo automáticamente?

 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 cd ## 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 

¿Por qué no hacer que su función tome un dataframe y devuelva un dataframe directamente?

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