2013-05-20 13 views
6

Ich habe einen großen Datenrahmen mit einer Faktorspalte, die ich in drei Faktorspalten aufteilen muss, indem ich die Faktornamen durch ein Trennzeichen aufspalte. Hier ist mein aktueller Ansatz, der mit einem großen Datenrahmen ist sehr langsam (manchmal mehrere Millionen Zeilen):Beschleunigen Sie `strsplit`, wenn die mögliche Ausgabe bekannt ist

data <- readRDS("data.rds") 
data.df <- reshape2:::melt.array(data) 
head(data.df) 
## Time Location Class Replicate Population 
##1 1  1 LIDE.1.S   1 0.03859605 
##2 2  1 LIDE.1.S   1 0.03852957 
##3 3  1 LIDE.1.S   1 0.03846853 
##4 4  1 LIDE.1.S   1 0.03841260 
##5 5  1 LIDE.1.S   1 0.03836147 
##6 6  1 LIDE.1.S   1 0.03831485 

Rprof("str.out") 
cl <- which(names(data.df)=="Class") 
Classes <- do.call(rbind, strsplit(as.character(data.df$Class), "\\.")) 
colnames(Classes) <- c("Species", "SizeClass", "Infected") 
data.df <- cbind(data.df[,1:(cl-1)],Classes,data.df[(cl+1):(ncol(data.df))]) 
Rprof(NULL) 

head(data.df) 
## Time Location Species SizeClass Infected Replicate Population 
##1 1  1 LIDE   1  S   1 0.03859605 
##2 2  1 LIDE   1  S   1 0.03852957 
##3 3  1 LIDE   1  S   1 0.03846853 
##4 4  1 LIDE   1  S   1 0.03841260 
##5 5  1 LIDE   1  S   1 0.03836147 
##6 6  1 LIDE   1  S   1 0.03831485 

summaryRprof("str.out") 

$by.self 
       self.time self.pct total.time total.pct 
"strsplit"   1.34 50.00  1.34  50.00 
"<Anonymous>"   1.16 43.28  1.16  43.28 
"do.call"    0.04  1.49  2.54  94.78 
"unique.default"  0.04  1.49  0.04  1.49 
"data.frame"   0.02  0.75  0.12  4.48 
"is.factor"   0.02  0.75  0.02  0.75 
"match"    0.02  0.75  0.02  0.75 
"structure"   0.02  0.75  0.02  0.75 
"unlist"    0.02  0.75  0.02  0.75 

$by.total 
         total.time total.pct self.time self.pct 
"do.call"     2.54  94.78  0.04  1.49 
"strsplit"     1.34  50.00  1.34 50.00 
"<Anonymous>"    1.16  43.28  1.16 43.28 
"cbind"      0.14  5.22  0.00  0.00 
"data.frame"     0.12  4.48  0.02  0.75 
"as.data.frame.matrix"  0.08  2.99  0.00  0.00 
"as.data.frame"    0.08  2.99  0.00  0.00 
"as.factor"     0.08  2.99  0.00  0.00 
"factor"      0.06  2.24  0.00  0.00 
"unique.default"    0.04  1.49  0.04  1.49 
"unique"      0.04  1.49  0.00  0.00 
"is.factor"     0.02  0.75  0.02  0.75 
"match"      0.02  0.75  0.02  0.75 
"structure"     0.02  0.75  0.02  0.75 
"unlist"      0.02  0.75  0.02  0.75 
"[.data.frame"    0.02  0.75  0.00  0.00 
"["       0.02  0.75  0.00  0.00 

$sample.interval 
[1] 0.02 

$sampling.time 
[1] 2.68 

Gibt es eine Möglichkeit, diesen Vorgang zu beschleunigen? Ich stelle fest, dass es eine kleine (< 5) Anzahl von jeder der Kategorien "Species", "SizeClass" und "Infiziert" gibt, und ich weiß, was diese im Voraus sind.

Anmerkungen:

  • stringr::str_split_fixed führt diese Aufgabe, aber nicht schneller
  • Der Datenrahmen zunächst durch Aufruf reshape::melt auf einem Array erzeugt tatsächlich in der Class und seine zugehörigen Ebene eine Dimension sind. Wenn es einen schnelleren Weg gibt, von hier nach hier zu kommen, großartig.
  • data.rds bei http://dl.getdropbox.com/u/3356641/data.rds

Antwort

5

Dies sollte wahrscheinlich bieten durchaus eine Steigerung:

library(data.table) 
DT <- data.table(data.df) 


DT[, c("Species", "SizeClass", "Infected") 
     := as.list(strsplit(Class, "\\.")[[1]]), by=Class ] 

Die Gründe für den Anstieg:

  1. data.table pre reserviert Speicher für Spalten
  2. Jede Spaltenzuordnung in data.frame weist die Gesamtheit der Daten neu zu (data.table dagegen nicht)
  3. Die by-Anweisung ermöglicht die einmalige Implementierung der strsplit-Aufgabe für jeden eindeutigen Wert.

Hier ist ein schönes schnelles Verfahren für den gesamten Prozess.

# Save the new col names as a character vector 
newCols <- c("Species", "SizeClass", "Infected") 

# split the string, then convert the new cols to columns 
DT[, c(newCols) := as.list(strsplit(as.character(Class), "\\.")[[1]]), by=Class ] 
DT[, c(newCols) := lapply(.SD, factor), .SDcols=newCols] 

# remove the old column. This is instantaneous. 
DT[, Class := NULL] 

## Have a look: 
DT[, lapply(.SD, class)] 
#  Time Location Replicate Population Species SizeClass Infected 
# 1: integer integer integer numeric factor factor factor 

DT 
+0

Das ist schnell! Obwohl du 'as.character (Class)' verwenden musst. Können Sie die Spalten als Faktoren in demselben Befehl zurückgeben? –

+0

Sie können in Faktor konvertieren, aber tun Sie es als zweite Zeile nach. Die Verwendung von as.factor in demselben Aufruf, der ein "by" -Argument enthält, verlangsamt notwendigerweise den Prozess. –

+0

@NoamRoss, schöner Fang auf dem 'as.character'. Aktualisierter Code plus ein paar zusätzliche Schritte –

3

Sie könnten eine anständige Erhöhung der Geschwindigkeit erhalten, indem nur die Teile der Zeichenfolge extrahieren Sie gsub verwenden müssen, anstatt alles zu spalten und versuchen, sie wieder zusammen zu setzen:

data <- readRDS("~/Downloads/data.rds") 
data.df <- reshape2:::melt.array(data) 

# using `strsplit` 
system.time({ 
cl <- which(names(data.df)=="Class") 
Classes <- do.call(rbind, strsplit(as.character(data.df$Class), "\\.")) 
colnames(Classes) <- c("Species", "SizeClass", "Infected") 
data.df <- cbind(data.df[,1:(cl-1)],Classes,data.df[(cl+1):(ncol(data.df))]) 
}) 

user system elapsed 
3.349 0.062 3.411 

#using `gsub` 
system.time({ 
data.df$Class <- as.character(data.df$Class) 
data.df$SizeClass <- gsub("(\\w+)\\.(\\d+)\\.(\\w+)", "\\2", data.df$Class, 
    perl = TRUE) 
data.df$Infected <- gsub("(\\w+)\\.(\\d+)\\.(\\w+)", "\\3", data.df$Class, 
    perl = TRUE) 
data.df$Class <- gsub("(\\w+)\\.(\\d+)\\.(\\w+)", "\\1", data.df$Class, 
    perl = TRUE) 
}) 

user system elapsed 
0.812 0.037 0.848 
+0

+1 sehr nette Methode! –

2

Sieht aus wie Sie habe einen Faktor, also arbeite auf den Ebenen und mappe dann zurück. Verwenden Sie fixed=TRUE in strsplit, Anpassung an split=".".

Classes <- do.call(rbind, strsplit(levels(data.df$Class), ".", fixed=TRUE)) 
colnames(Classes) <- c("Species", "SizeClass", "Infected") 
df0 <- as.data.frame(Classes[data.df$Class,], row.names=NA) 
cbind(data.df, df0) 
+1

Eigentlich eine gute Antwort - einfach und fügt keine neuen Abhängigkeiten hinzu. Aber der Join kann auch mit einem großen data.frame langsam sein, daher ist Ricardos "data.frame" -Konstrukt eine bessere Lösung für mich. –

+0

Hoppla, gemeint ist das 'data.table' Konstrukt, aber ich kann den Kommentar nach dieser langen Zeit anscheinend nicht bearbeiten. –

+0

Dieser Vorschlag beschleunigte meinen Code um eine Größenordnung! Vielen Dank! – CephBirk