2013-06-05 14 views
25

Ich versuche, eine elegante Möglichkeit zu finden, := Zuweisung zu verwenden, um viele Spalten auf einmal in data.table durch Anwenden einer gemeinsamen Funktion zu ersetzen. Eine typische Verwendung davon könnte sein, eine Zeichenfolgenfunktion (z. B. gsub) auf alle Zeichenspalten in einer Tabelle anzuwenden. Es ist nicht schwierig, die data.frame Weise, dies zu einer data.table zu tun, aber ich bin auf der Suche nach einer Methode im Einklang mit der data.table Art und Weise, Dinge zu tun.Elegant zuweisen mehrere Spalten in data.table mit lapply()

Zum Beispiel:

library(data.table) 

m <- matrix(runif(10000), nrow = 100) 
df <- df1 <- df2 <- df3 <- as.data.frame(m) 
dt <- as.data.table(df) 
head(names(df)) 
head(names(dt)) 

## replace V20-V100 with sqrt 

# data.frame approach 
# by column numbers 
df1[20:100] <- lapply(df1[20:100], sqrt) 
# by reference to column numbers 
v <- 20:100 
df2[v] <- lapply(df2[v], sqrt) 
# by reference to column names 
n <- paste0("V", 20:100) 
df3[n] <- lapply(df3[n], sqrt) 

# data.table approach 
# by reference to column names 
n <- paste0("V", 20:100) 
dt[, n] <- lapply(dt[, n, with = FALSE], sqrt) 

Ich verstehe es eine Schleife über einen Vektor von Spaltennamen effizienter ist := mit zuzuweisen:

for (col in paste0("V", 20:100)) dt[, col := sqrt(dt[[col]]), with = FALSE] 

Ich mag das nicht, weil ich don‘ t wie Referenz data.table in einem j Ausdruck. Ich weiß auch, dass ich := können mit lapply gegeben zuweisen, dass ich die Spaltennamen kennen: (. Sie könnten dies erweitern, indem Sie einen Ausdruck mit unbekannten Spaltennamen Bau)

dt[, c("V20", "V30", "V40", "V50", "V60") := lapply(list(V20, V30, V40, V50, V60), sqrt)] 

Im Folgenden sind die Ideen Ich habe es versucht, aber ich konnte sie nicht zur Arbeit bringen. Mache ich einen Fehler, oder gibt es einen anderen Ansatz, den ich vermisse?

# possible data.table approaches? 
# by reference to column names; assignment works, but not lapply 
n <- paste0("V", 20:100) 
dt[, n := lapply(n, sqrt), with = FALSE] 
# by (smaller for example) list; lapply works, but not assignment 
dt[, list(list(V20, V30, V40, V50, V60)) := lapply(list(V20, V30, V40, V50, V60), sqrt)] 
# by reference to list; neither assignment nor lapply work 
l <- parse(text = paste("list(", paste(paste0("V", 20:100), collapse = ", "), ")")) 
dt[, eval(l) := lapply(eval(l), sqrt)] 

Antwort

30

Ja, Sie hier in Frage sind:

Ich verstehe, dass es effizienter ist, einen Vektor von Spaltennamen zu durchlaufen, indem := zugewiesen wird:

for (col in paste0("V", 20:100)) dt[, col := sqrt(dt[[col]]), with = FALSE]

Abgesehen: beachten Sie, dass die neue Art und Weise, dies zu tun ist:

for (col in paste0("V", 20:100)) 
    dt[ , (col) := sqrt(dt[[col]])] 

weil die with = FALSE nicht leicht zu lesen war, ob er den LHS oder die RHS von := bezeichnet. Ende beiseite.

Wie Sie wissen, ist das effizient, weil das jede Spalte einzeln ausführt, so dass Arbeitsspeicher nur für jeweils eine Spalte benötigt wird.Das kann einen Unterschied machen, ob es funktioniert und es mit dem gefürchteten Fehler wegen mangelndem Speicher ausfällt.

Das Problem mit lapply auf der RHS von := ist, dass die RHS (lapply) zuerst ausgewertet wird; h. das Ergebnis für die 80 Spalten wird erzeugt. Das sind 80 Spalten neuer Speicher, die zugewiesen und gefüllt werden müssen. Sie benötigen also 80 Spalten freien RAM für diesen Vorgang, um erfolgreich zu sein. Das RAM-Nutzung dominiert gegen die anschließend sofort den Betrieb der Zuordnung (plonking) jene 80 neue Spalten in die Spaltenzeiger Slots data.table.

Wie @Frank zeigte, wenn Sie viele Spalten (sagen wir 10.000 oder mehr) haben, beginnt sich der kleine Overhead des Versands an die [.data.table Methode zu addieren). Um diesen Aufwand zu vermeiden, dass es data.table::set die unter ?set wird als ein „Endlos-Film“ beschrieben :=. Ich verwende eine for Schleife für diese Art von Operation. Es ist der schnellste Weg und es ist ziemlich einfach zu schreiben und zu lesen.

for (col in paste0("V", 20:100)) 
    set(dt, j = col, value = sqrt(dt[[col]])) 

Obwohl mit nur 80 Spalten, ist es unwahrscheinlich Rolle zu spielen. (Beachten Sie es häufiger set über eine große Anzahl von Zeilen als eine große Anzahl von Spalten in einer Schleife sein kann.) Jedoch set geschleift löst nicht das Problem des wiederholten Verweises auf die dt Symbolnamen, die Sie in der Frage erwähnt:

Ich mag das nicht, weil ich nicht gerne die data.table in aj Ausdruck.

Einverstanden. So das Beste, was ich tun kann, ist zu Ihrem Looping von := zurückkehren, aber get stattdessen verwenden.

for (col in paste0("V", 20:100)) 
    dt[, (col) := sqrt(get(col))] 

Ich befürchte allerdings, dass get in j mit ineffizient sein kann. Benchmarking benötigt, #1380. Auch ist es vielleicht verwirrend, get() auf der RHS zu verwenden, aber nicht auf der LHS. Anzusprechen, wir konnten die LHS Zucker und erlauben get() auch, #1381:

for (col in paste0("V", 20:100)) 
    dt[, get(col) := sqrt(get(col))] 

Auch vielleicht value von set im Rahmen von DT ausgeführt werden könnten, #1382.

for (col in paste0("V", 20:100)) 
    set(dt, j = col, value = sqrt(get(col)) 
+1

Vielen Dank für die Klammern um "col". Bis ich mich an diesen Trick erinnerte, bekam ich eine Kolumne namens "Col". – Farrel

7

Ist das wonach Sie suchen?

dt[ , names(dt)[20:100] :=lapply(.SD, function(x) sqrt(x)) , .SDcols=20:100] 

ich gehört habe, sagen, dass .SD Verwendung ist nicht so effizient, weil sie eine Kopie der Tabelle vorher macht, aber wenn Ihr Tisch nicht riesige ist (natürlich relativ, das ist auf Ihrem System-Spezifikationen abhängig) Ich bezweifle, Es wird einen großen Unterschied machen.

+4

Ich habe gesagt, dass [ 'Set' auch Operationen wie diese beschleunigen kann] (http://stackoverflow.com/questions/16846380/how-to-apply-same-function-to-every -specified-column-in-a-datentabelle/16846530 # 16846530). – Frank

+0

@Frank +1 für diese Antwort, und ich habe Buch-markiert für zukünftige Referenz. Ich würde nicht denken, hier eine 'for'-Schleife zu verwenden. Klug. –

+0

@Frank, wusste ich nicht über die 'for' loop +' set' Ansatz. Ich werde darüber nachdenken müssen, das in Zukunft zu verwenden. –

13

Diese sollten funktionieren, wenn Sie durch string name den Spalten beziehen möchten:

n = paste0("V", 20:100) 
dt[, (n) := lapply(n, function(x) {sqrt(get(x))})] 

oder

dt[, (n) := lapply(n, function(x) {sqrt(dt[[x]])})] 
+2

Eine zusätzliche) wird in der letzten Zeile benötigt:' dt [, (n): = lapply (n, Funktion (x) {sqrt (dt [[x]])})] ' – HywelMJ

+0

@HywelMJ danke, behoben – eddi

+0

Vielleicht fügen Sie auch' .SDcols' Optionen, wie zum Beispiel 'dt [, (n): = lapply (.SD, sqrt), .SDcols = n] '? Hmm ..Auf den zweiten Gedanken hat Simon vielleicht schon etwas Ähnliches gemacht. –

Verwandte Themen