Asigne múltiples columnas usando: = en data.table, por grupo

130

¿Cuál es la mejor manera de asignar a múltiples columnas usando data.table? Por ejemplo:

f <- function(x) {c("hi", "hello")}
x <- data.table(id = 1:10)

Me gustaría hacer algo como esto (por supuesto, esta sintaxis es incorrecta):

x[ , (col1, col2) := f(), by = "id"]

Y para extender eso, puedo tener muchas columnas con nombres almacenados en una variable (digamos col_names) y me gustaría hacer:

x[ , col_names := another_f(), by = "id", with = FALSE]

¿Cuál es la forma correcta de hacer algo como esto?

Alex
fuente
1
Parece que se ha respondido: stackoverflow.com/questions/11308754/…
Alex
Alex, esa respuesta está cerca, pero no parece funcionar en combinación con bycomo @Christoph_J es correcto decir. Enlace a su pregunta agregada al FR # 2120 "Elimine la necesidad con = FALSO para LHS de: =", para que no se olvide de volver a visitar.
Matt Dowle
Para ser claros, f()es una función que devuelve múltiples valores, uno para cada una de sus columnas.
smci

Respuestas:

161

Esto ahora funciona en v1.8.3 en R-Forge. ¡Gracias por resaltarlo!

x <- data.table(a = 1:3, b = 1:6) 
f <- function(x) {list("hi", "hello")} 
x[ , c("col1", "col2") := f(), by = a][]
#    a b col1  col2
# 1: 1 1   hi hello
# 2: 2 2   hi hello
# 3: 3 3   hi hello
# 4: 1 4   hi hello
# 5: 2 5   hi hello
# 6: 3 6   hi hello

x[ , c("mean", "sum") := list(mean(b), sum(b)), by = a][]
#    a b col1  col2 mean sum
# 1: 1 1   hi hello  2.5   5
# 2: 2 2   hi hello  3.5   7
# 3: 3 3   hi hello  4.5   9
# 4: 1 4   hi hello  2.5   5
# 5: 2 5   hi hello  3.5   7
# 6: 3 6   hi hello  4.5   9 

mynames = c("Name1", "Longer%")
x[ , (mynames) := list(mean(b) * 4, sum(b) * 3), by = a]
#     a b col1  col2 mean sum Name1 Longer%
# 1: 1 1   hi hello  2.5   5    10      15
# 2: 2 2   hi hello  3.5   7    14      21
# 3: 3 3   hi hello  4.5   9    18      27
# 4: 1 4   hi hello  2.5   5    10      15
# 5: 2 5   hi hello  3.5   7    14      21
# 6: 3 6   hi hello  4.5   9    18      27


x[ , get("mynames") := list(mean(b) * 4, sum(b) * 3), by = a][]  # same
#    a b col1  col2 mean sum Name1 Longer%
# 1: 1 1   hi hello  2.5   5    10      15
# 2: 2 2   hi hello  3.5   7    14      21
# 3: 3 3   hi hello  4.5   9    18      27
# 4: 1 4   hi hello  2.5   5    10      15
# 5: 2 5   hi hello  3.5   7    14      21
# 6: 3 6   hi hello  4.5   9    18      27

x[ , eval(mynames) := list(mean(b) * 4, sum(b) * 3), by = a][]   # same
#    a b col1  col2 mean sum Name1 Longer%
# 1: 1 1   hi hello  2.5   5    10      15
# 2: 2 2   hi hello  3.5   7    14      21
# 3: 3 3   hi hello  4.5   9    18      27
# 4: 1 4   hi hello  2.5   5    10      15
# 5: 2 5   hi hello  3.5   7    14      21
# 6: 3 6   hi hello  4.5   9    18      27

Versión anterior que usa el withargumento (desaconsejamos este argumento cuando sea posible):

x[ , mynames := list(mean(b) * 4, sum(b) * 3), by = a, with = FALSE][] # same
#    a b col1  col2 mean sum Name1 Longer%
# 1: 1 1   hi hello  2.5   5    10      15
# 2: 2 2   hi hello  3.5   7    14      21
# 3: 3 3   hi hello  4.5   9    18      27
# 4: 1 4   hi hello  2.5   5    10      15
# 5: 2 5   hi hello  3.5   7    14      21
# 6: 3 6   hi hello  4.5   9    18      27
Matt Dowle
fuente
Gracias por esta respuesta y los ejemplos. ¿Cómo debo modificar la siguiente línea para obtener dos columnas para cada objectName de la salida tenue, en lugar de una columna con dos filas? data.table(objectName=ls())[,c("rows","cols"):=dim(get(objectName)),by=objectName](Estoy usando data.table1.8.11)
dnlbrky
@dnlbrky dimdevuelve un vector, por lo que convertirlo para escribir listdebería rotarlo; por ej [,c("rows","cols"):=as.list(dim(get(objectName))),by=objectNa‌​me]. El problema es que as.listtiene sobrecarga de llamadas y también copia el vector pequeño. Si la eficiencia es un problema a medida que aumenta el número de grupos, háganoslo saber.
Matt Dowle
1
Hola Matt. El primer ejemplo en su segundo bloque de código (es decir x[,mynames:=list(mean(b)*4,sum(b)*3),by=a,with=FALSE][]) ahora arroja una advertencia, ¿entonces tal vez lo elimine? En una nota relacionada, ¿alguien ha sugerido que, con options(datatable.WhenJisSymbolThenCallingScope=TRUE), una tarea como x[,mynames:=list(mean(b)*4,sum(b)*3),by=a]debería funcionar? Parece que eso sería consistente con los otros cambios, aunque supongo que podría romper demasiado código de usuario existente (?).
Josh O'Brien el
1
@PanFrancisco Sin by=afuncionará, pero devuelve una respuesta diferente. Los agregados mean(a)y sum(a)se reciclan dentro de cada grupo cuando by=a. Sin by=aél, simplemente pega el meany sumpara la columna completa en cada celda (es decir, números diferentes).
Matt Dowle
1
@MattDowle, ¿qué pasa si mi función ya devuelve una lista con nombre? ¿Hay alguna forma de agregar las columnas al dt sin tener que nombrarlas nuevamente? por ejemplo, f <- function (x) {list ("c" = "hi", "d" = "hello")} imprimirá los resultados con cols con nombre con x [, f (), by = a] []. No sé cómo agregar el resultado al dt.
Jfly