Attribuez plusieurs colonnes en utilisant: = in data.table, par groupe


130

Quelle est la meilleure façon d'attribuer à plusieurs colonnes à l'aide de data.table? Par exemple:

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

Je voudrais faire quelque chose comme ça (bien sûr, cette syntaxe est incorrecte):

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

Et pour étendre cela, je peux avoir de nombreuses colonnes avec des noms stockés dans une variable (par exemple col_names) et je voudrais faire:

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

Quelle est la bonne façon de faire quelque chose comme ça?


1
On dirait qu'il a été répondu: stackoverflow.com/questions/11308754/…
Alex

Alex, cette réponse est proche mais elle ne semble pas fonctionner en combinaison avec by@Christoph_J a raison de le dire. Lien vers votre question ajoutée à FR # 2120 "Supprimez le besoin avec = FALSE pour LHS de: =", donc il ne sera pas oublié de revenir.
Matt Dowle

Pour être clair, f()une fonction renvoie plusieurs valeurs, une pour chacune de vos colonnes.
smci

Réponses:


161

Cela fonctionne maintenant dans la v1.8.3 sur R-Forge. Merci de l'avoir souligné!

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

Ancienne version utilisant l' withargument (nous déconseillons cet argument lorsque cela est possible):

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

Merci pour cette réponse et les exemples. Comment dois-je modifier la ligne suivante afin d'obtenir deux colonnes pour chaque objectName à partir de la sortie dim, plutôt qu'une colonne avec deux lignes? data.table(objectName=ls())[,c("rows","cols"):=dim(get(objectName)),by=objectName](J'utilise data.table1.8.11)
dnlbrky

@dnlbrky dimrenvoie un vecteur donc la conversion en type listdoit le faire pivoter; par exemple [,c("rows","cols"):=as.list(dim(get(objectName))),by=objectNa‌​me]. Le problème est qu'il y as.lista une surcharge d'appel et copie également le petit vecteur. Si l'efficacité est un problème alors que le nombre de groupes augmente, veuillez nous en informer.
Matt Dowle

1
Salut Matt. Le premier exemple de votre deuxième bloc de code (c'est-à-dire x[,mynames:=list(mean(b)*4,sum(b)*3),by=a,with=FALSE][]) jette maintenant un avertissement, alors peut-être le supprimer? Sur une note connexe, quelqu'un a-t-il suggéré que, avec options(datatable.WhenJisSymbolThenCallingScope=TRUE), une affectation comme x[,mynames:=list(mean(b)*4,sum(b)*3),by=a]devrait en fait fonctionner? Il semble que cela soit cohérent avec les autres changements, même si je suppose que cela pourrait casser trop de code utilisateur existant (?).
Josh O'Brien

1
@PanFrancisco Sans by=acela fonctionnera, mais renvoie une réponse différente. Les granulats mean(a)et sum(a)sont recyclés dans chaque groupe quand by=a. Sans by=acela, colle simplement le meanet sumpour toute la colonne dans chaque cellule (c'est-à-dire des nombres différents).
Matt Dowle

1
@MattDowle et si ma fonction renvoie déjà une liste nommée, est-ce que je peux quand même ajouter les colonnes au dt sans avoir à les nommer à nouveau? par exemple, f <- function (x) {list ("c" = "hi", "d" = "hello")} affichera les résultats avec des colonnes nommées avec x [, f (), by = a] []. Je ne sais pas comment ajouter le résultat au dt.
Jfly

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.