Wählen Sie data.table aus / weisen Sie sie zu, wenn Variablennamen in einem Zeichenvektor gespeichert sind

88

Wie verweisen Sie auf Variablen in a, data.tablewenn die Variablennamen in einem Zeichenvektor gespeichert sind? Dies funktioniert beispielsweise für data.frame:

df <- data.frame(col1 = 1:3)
colname <- "col1"
df[colname] <- 4:6
df
#   col1
# 1    4
# 2    5
# 3    6

Wie kann ich denselben Vorgang für eine Datentabelle mit oder ohne :=Notation ausführen ? Das Offensichtliche dt[ , list(colname)]funktioniert nicht (und ich habe es auch nicht erwartet).

frankc
quelle

Antworten:

130

Zwei Möglichkeiten zur programmgesteuerten Auswahl von Variablen:

  1. with = FALSE::

    DT = data.table(col1 = 1:3)
    colname = "col1"
    DT[, colname, with = FALSE] 
    #    col1
    # 1:    1
    # 2:    2
    # 3:    3
  2. ..Präfix 'dot dot' ( ):

    DT[, ..colname]    
    #    col1
    # 1:    1
    # 2:    2
    # 3:    3

Weitere Informationen zur.. Notation 'dot dot' ( ) finden Sie unter Neue Funktionen in 1.10.2 (derzeit nicht im Hilfetext beschrieben).

Um Variablen zuzuweisen , setzen Sie die LHS von :=in Klammern:

DT[, (colname) := 4:6]    
#    col1
# 1:    4
# 2:    5
# 3:    6

Letzteres wird als Spalten- Plonk bezeichnet , da Sie den gesamten Spaltenvektor durch Referenz ersetzen. Wenn eine Teilmenge ivorhanden wäre, würde sie durch Referenz untergeordnet. Die Parens (colname)sind eine Abkürzung, die in Version v1.9.4 auf CRAN Oct 2014 eingeführt wurde. Hier ist die Nachricht :

Die Verwendung with = FALSEmit :=ist jetzt in allen Fällen veraltet, da das Umschließen der LHS :=mit Klammern seit einiger Zeit bevorzugt wird.

colVar = "col1"
DT[, colVar := 1, with = FALSE]                 # deprecated, still works silently
DT[, (colVar) := 1]                             # please change to this
DT[, c("col1", "col2") := 1]                    # no change
DT[, 2:4 := 1]                                  # no change
DT[, c("col1","col2") := list(sum(a), mean(b)]  # no change
DT[, `:=`(...), by = ...]                       # no change

Siehe auch Abschnitt Details in ?`:=`:

DT[i, (colnamevector) := value]
# [...] The parens are enough to stop the LHS being a symbol

Und um weitere Fragen im Kommentar zu beantworten, gibt es eine Möglichkeit (wie üblich gibt es viele Möglichkeiten):

DT[, colname := cumsum(get(colname)), with = FALSE]
#    col1
# 1:    4
# 2:    9
# 3:   15 

oder es ist möglicherweise einfacher, nur evaleine zu lesen, zu schreiben und zu debuggen paste, ähnlich wie beim Erstellen einer dynamischen SQL-Anweisung, die an einen Server gesendet werden soll:

expr = paste0("DT[,",colname,":=cumsum(",colname,")]")
expr
# [1] "DT[,col1:=cumsum(col1)]"

eval(parse(text=expr))
#    col1
# 1:    4
# 2:   13
# 3:   28

Wenn Sie das häufig tun, können Sie eine Hilfsfunktion definieren EVAL:

EVAL = function(...)eval(parse(text=paste0(...)),envir=parent.frame(2))

EVAL("DT[,",colname,":=cumsum(",colname,")]")
#    col1
# 1:    4
# 2:   17
# 3:   45

Nun , da data.table1.8.2 automatisch optimiert jfür Effizienz, kann es vorteilhaft sein , die verwenden evalMethode. Das get()In jverhindert beispielsweise einige Optimierungen.

Oder gibt es set(). Eine funktionale Form mit geringem Overhead, :=die hier in Ordnung wäre. Siehe ?set.

set(DT, j = colname, value = cumsum(DT[[colname]]))
DT
#    col1
# 1:    4
# 2:   21
# 3:   66
Matt Dowle
quelle
1
Danke für die Antwort Matthew. Das with = FALSE löst definitiv einen Teil meines Problems. In Wirklichkeit möchte ich die Spalte durch das Cumsum der Spalte ersetzen. Kann ich den Spaltennamen durch eine Variable auf der rechten Seite der Zuweisung irgendwie referenzieren?
Frankc
Eigentlich habe ich das Cumsum nur extern mit einem anderen Namen versehen, der im dt nicht vorhanden ist und der gut funktioniert.
Frankc
1
Aber das wäre eine ganze zusätzliche Zeile! Nicht sehr elegant :) Aber ok, manchmal ist es nützlich. In diesen Fällen ist es am besten, den Variablennamen mit zu beginnen .oder ..eine mögliche Maskierung zu vermeiden, falls DTdieses Symbol in Zukunft jemals als Spaltenname enthalten sein sollte (und sich an die Konvention zu halten, mit der Spaltennamen nicht beginnen .). Es gibt einige Feature-Anforderungen, um es robuster zu machen, solche Probleme wie das Hinzufügen von .()und ..().
Matt Dowle
Ich habe geantwortet, bevor ich bemerkt habe, dass Sie Ihre Antwort bearbeitet haben. Mein erster Gedanke war eval (parse ()), aber aus irgendeinem Grund hatte ich Probleme, es zum Laufen zu bringen, als mir klar wurde, dass ich es nur extern tun sollte. Dies ist eine großartige Antwort mit vielen Dingen, über die ich nicht nachgedacht habe. Vielen Dank für data.table im Allgemeinen, es ist ein großartiges Paket.
Frankc
2
Beachten Sie, dass Sie die String-Interpolation vom Typ Quasi-Perl fn$aus dem Paket gsubfn verwenden können, um die Lesbarkeit der EVAL-Lösung zu verbessern : library(gsubfn); fn$EVAL( "DT[,$colname:=cumsum($colname)]" ).
G. Grothendieck
8

* Dies ist eigentlich keine Antwort, aber ich habe nicht genug Straßenguthaben, um Kommentare zu posten: /

Für jeden, der tatsächlich eine neue Spalte in einer Datentabelle mit einem in einer Variablen gespeicherten Namen erstellen möchte, muss Folgendes funktionieren. Ich habe keine Ahnung, wie es funktioniert. Verbesserungsvorschläge? Ist es sicher anzunehmen, dass eine namenlose neue Spalte immer den Namen V1 erhält?

colname <- as.name("users")
# Google Analytics query is run with chosen metric and resulting data is assigned to DT
DT2 <- DT[, sum(eval(colname, .SD)), by = country]
setnames(DT2, "V1", as.character(colname))

Beachten Sie, dass ich in der Summe () ganz gut darauf verweisen kann, es aber nicht dazu bringen kann, es im selben Schritt zuzuweisen. Übrigens, der Grund, warum ich dies tun muss, ist, dass colname auf Benutzereingaben in einer Shiny-App basiert.

efh0888
quelle
+1 für nur arbeiten: Ich stimme zu, dass dies nicht "der Weg" sein muss, aber nachdem ich gerade 45 Minuten damit verbracht habe, jeden SO-Beitrag zu diesem Thema zu überfluten, ist dies die einzige Lösung, die ich tatsächlich erreichen konnte Arbeit - danke, dass Sie sich die Zeit genommen haben, darauf hinzuweisen!
Neuropsych
Froh, dass ich helfen konnte! Leider habe ich nie eine elegantere Lösung direkt mit data.tables gefunden, obwohl dieser 3-Liner nicht schrecklich ist. In meinem Szenario wurde mir klar, dass eine einfachere Alternative darin bestand, Tidyr zu verwenden, um meine Daten nur "lang" statt "breit" zu machen, da ich basierend auf Benutzereingaben immer nach einer einzelnen Spalte filtern konnte, anstatt aus einem Satz auszuwählen von Spalten.
efh0888
2
Es ist nicht sicher anzunehmen, dass V1es sich um den neuen Namen handelt. Wenn Sie beispielsweise csv mit lesen freadund eine unbenannte Spalte vorhanden ist, hat diese einen V1Namen (und read.csvgibt sie an X). Es ist also möglich, dass Ihr Tisch bereits einen hat V1. Vielleicht bekommen Sie einfach den Namen vonnames(DT)[length(names(DT))]
dracodoc
2

Für mehrere Spalten und eine Funktion, die auf Spaltenwerte angewendet wird.

Wenn Sie die Werte einer Funktion aktualisieren, muss die RHS ein Listenobjekt sein. Wenn Sie also eine Schleife .SDmit verwenden lapply, reicht dies aus.

Im folgenden Beispiel werden ganzzahlige Spalten in numerische Spalten konvertiert

a1 <- data.table(a=1:5, b=6:10, c1=letters[1:5])
sapply(a1, class)  # show classes of columns
#         a           b          c1 
# "integer"   "integer" "character" 

# column name character vector
nm <- c("a", "b")

# Convert columns a and b to numeric type
a1[, j = (nm) := lapply(.SD, as.numeric ), .SDcols = nm ]

sapply(a1, class)
#         a           b          c1 
# "numeric"   "numeric" "character" 
Sathish
quelle
1

Sie könnten dies versuchen

colname <- as.name ("COL_NAME")

DT2 <- DT [, Liste (COL_SUM = Summe (eval (colname, .SD))), by = c (Gruppe)]

Shrilata Murthy
quelle
1
Es wird immer empfohlen, eine Erklärung mit Ihrem Code hinzuzufügen, anstatt nur Code zu veröffentlichen.
MBorg
1

Abrufen mehrerer Spalten aus data.table über Variable oder Funktion:

library(data.table)

x <- data.table(this=1:2,that=1:2,whatever=1:2)

# === explicit call
x[, .(that, whatever)]
x[, c('that', 'whatever')]

# === indirect via  variable
# ... direct assignment
mycols <- c('that','whatever')
# ... same as result of a function call
mycols <- grep('a', colnames(x), value=TRUE)

x[, ..mycols]
x[, .SD, .SDcols=mycols]

# === direct 1-liner usage
x[, .SD, .SDcols=c('that','whatever')]
x[, .SD, .SDcols=grep('a', colnames(x), value=TRUE)]

was alle ergeben

   that whatever
1:    1        1
2:    2        2

Ich finde den .SDcolsWeg am elegantesten.

CK
quelle