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?

Alex
la source
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
Matt Dowle
la source
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