Weisen Sie mehrere Spalten mit: = in data.table nach Gruppe zu

130

Was ist der beste Weg, um mehrere Spalten mit zuzuweisen data.table? Beispielsweise:

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

Ich würde gerne so etwas machen (natürlich ist diese Syntax falsch):

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

Und um das zu erweitern, habe ich möglicherweise viele Spalten mit Namen, die in einer Variablen gespeichert sind (z. B. col_names), und ich möchte Folgendes tun:

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

Was ist der richtige Weg, um so etwas zu tun?

Alex
quelle
1
Dies sieht so aus, als ob es beantwortet wurde: stackoverflow.com/questions/11308754/…
Alex
Alex, diese Antwort ist nah, aber sie scheint nicht in Kombination mit by@Christoph_J zu funktionieren . Link zu Ihrer Frage hinzugefügt zu FR # 2120 "Drop muss mit = FALSE für LHS von: =", damit es nicht vergessen wird, erneut zu besuchen.
Matt Dowle
Um es klar auszudrücken, f()gibt eine Funktion mehrere Werte zurück, einen für jede Ihrer Spalten.
smci

Antworten:

161

Dies funktioniert jetzt in Version 1.8.3 unter R-Forge. Vielen Dank für die Hervorhebung!

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

Ältere Version mit dem withArgument (wir raten von diesem Argument ab, wenn möglich):

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
quelle
Danke für diese Antwort und die Beispiele. Wie soll ich die folgende Zeile ändern, um zwei Spalten für jeden Objektnamen aus der Dim-Ausgabe zu erhalten, anstatt eine Spalte mit zwei Zeilen? data.table(objectName=ls())[,c("rows","cols"):=dim(get(objectName)),by=objectName](Ich benutze data.table1.8.11)
dnlbrky
@dnlbrky dimgibt einen Vektor zurück, sodass die Konvertierung in einen Typ diesen listdrehen sollte. zB [,c("rows","cols"):=as.list(dim(get(objectName))),by=objectNa‌​me]. Das Problem ist, dass as.listder Anrufaufwand hoch ist und auch der kleine Vektor kopiert wird. Wenn Effizienz ein Problem ist, wenn die Anzahl der Gruppen steigt, lassen Sie es uns bitte wissen.
Matt Dowle
1
Hallo Matt. Das erste Beispiel in Ihrem zweiten Codeblock (dh x[,mynames:=list(mean(b)*4,sum(b)*3),by=a,with=FALSE][]) gibt jetzt eine Warnung aus. Entfernen Sie sie also möglicherweise? Hat jemand in options(datatable.WhenJisSymbolThenCallingScope=TRUE)einem ähnlichen Zusammenhang vorgeschlagen, dass eine Aufgabe wie diese x[,mynames:=list(mean(b)*4,sum(b)*3),by=a]tatsächlich funktionieren sollte? Scheint so, als würde dies mit den anderen Änderungen übereinstimmen, obwohl ich denke, dass es zu viel vorhandenen Benutzercode (?) Brechen könnte.
Josh O'Brien
1
@PanFrancisco Ohne by=aes wird funktionieren, aber eine andere Antwort zurückgeben. Die mean(a)und sum(a)Aggregate werden innerhalb jeder Gruppe recycelt, wenn by=a. Ohne by=aes klebt einfach das meanund sumfür die gesamte Spalte in jede Zelle (dh unterschiedliche Zahlen).
Matt Dowle
1
@MattDowle Was ist, wenn meine Funktion bereits eine benannte Liste zurückgibt? Kann ich die Spalten trotzdem zum dt hinzufügen, ohne sie erneut benennen zu müssen? Beispiel: f <- function (x) {list ("c" = "hi", "d" = "hello")} druckt Ergebnisse mit benannten Spalten mit x [, f (), by = a] []. Ich weiß nicht, wie ich das Ergebnis an das dt anhängen soll.
Jfly
47

Die folgende Kurzschreibweise kann hilfreich sein. Alle Ehre gebührt Andrew Brooks, insbesondere diesem Artikel .

dt[,`:=`(avg=mean(mpg), med=median(mpg), min=min(mpg)), by=cyl]
Gerry
quelle