2012-07-03 8 views
65

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:¿Agregar varias columnas a R data.table en una llamada de función?

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 

Aquí hay un ejemplo de mi función. Recuerdo que indican que se encuentra de cómputo costoso, además de eso no hay forma de deducir un valor de retorno de los otros valores dados (como en el ejemplo siguiente):

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

Ésta es mi manera de agregar dos columnas en una declaración . Sin embargo, uno necesita llamar a myfun dos veces:

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 

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

+0

¿Por qué su función no toma un marco de datos y devuelve un marco de datos directamente? 'Myfun <- function (y, v) { RET1 = y + v RET2 = y - v retorno (lista (r1 = RET1, r2 = RET2)) } –

+3

@Etienne Debido a que copia las entradas para crear una nueva salida. Florian está utilizando 'data.table' para su eficiencia de memoria con grandes conjuntos de datos; no copia 'x',' y' o 'v' en absoluto, ni siquiera una vez. Piensa en conjuntos de datos de 20 GB en RAM. –

Respuesta

86

Se puede almacenar la salida de su llamada de función:

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 

pero esto también parece funcionar:

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

Nuevo en v1.8.3 data.table en I-Forge, el with = FALSE ya no es necesario aquí, para mayor comodidad:

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

Hasta el minuto en vivo NOTICIAS es here.

+2

guau, ese segundo es increíble, gracias! simplemente lo ejecuté con 'debug (myfun)' para ver con qué frecuencia se llama: es una vez. estupendo. –

+1

+10 de mí también. Acabo de plantear [FR # 2120] (https://r-forge.r-project.org/tracker/index.php?func=detail&aid=2120&group_id=240&atid=978) "Drop need' with = FALSE" para LHS de ': =' " –

+6

Tenga en cuenta que el reciclaje de listas también se realiza; ej., 'c (" a "," b "," c "," d "): = list (1,2)' pone 1 en 'a' y' c', y 2 en 'b' y' d '. Si alguna de las columnas no existe, se agregarán por referencia. No estoy seguro de qué tan útil ': =' el reciclaje es en la práctica. Es más para 'c (" a "," b "," c "): = NULL' que borra esas 3 columnas. Internamente eso es un reciclaje de NULL a una longitud de lista (semántica) 3. –

-5

¿Por qué su función no toma un marco de datos y devuelve un marco de datos directamente?

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

Porque eso copia todo el 'DT', dos veces. Florian está utilizando 'data.table' para su eficiencia de memoria con grandes conjuntos de datos; no copia 'x',' y' o 'v' en absoluto, ni siquiera una vez. –

2

basarse en la respuesta anterior, se puede utilizar lapply con una función que la producción de 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 <- 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

La respuesta no se puede utilizar, como cuando la función no está vectorizada.

Por ejemplo, en la siguiente situación que no funcionará como se pretende:

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 

Siempre será añadir el tamaño de la columna g, no el tamaño de cada vector en g

Una solución en tales caso es:

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 
Cuestiones relacionadas