2013-05-20 10 views
6

Ho un grande frame di dati con una colonna fattoriale che ho bisogno di dividere in tre colonne fattore suddividendo i nomi dei fattori con un delimitatore. Qui è il mio approccio attuale, che è molto lento con una grande cornice di dati (a volte diversi milioni di righe):Accelerare `strsplit` quando l'output possibile è noto

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 

Esiste un modo per velocizzare questa operazione? Prendo atto che c'è un piccolo numero (< 5) di ciascuna delle categorie "Specie", "SizeClass" e "Infected", e so cosa sono in anticipo.

Note:

  • stringr::str_split_fixed esegue questo compito, ma non più velocemente
  • Il frame di dati viene effettivamente generato inizialmente chiamando reshape::melt su un array in cui Class e suoi livelli associati sono una dimensione. Se c'è un modo più veloce per arrivare da lì a qui, fantastico.
  • data.rds a http://dl.getdropbox.com/u/3356641/data.rds

risposta

5

Questo dovrebbe probabilmente offrire piuttosto un aumento:

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


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

Le ragioni per l'aumento:

  1. data.table pre alloca la memoria per le colonne
  2. ogni assegnazione di colonne in data.frame riassegna la totalità dei dati (a differenza di data.table)
  3. l'istruzione by consente di implementare l'attività strsplit una volta per ciascun valore univoco.

Ecco un metodo rapido piacevole per l'intero processo.

# 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

Questo è veloce! Sebbene tu abbia bisogno di usare 'as.character (Class)'. Puoi restituire le colonne come fattori nello stesso comando? –

+0

È possibile convertire in fattore, ma farlo come una seconda riga dopo. L'utilizzo di as.factor nella stessa chiamata che include un argomento 'by' rallenta necessariamente il processo. –

+0

@NoamRoss, bella cattura su 'as.character'. Codice aggiornato più alcuni passaggi aggiuntivi –

3

Si potrebbe ottenere un aumento decente in termini di velocità, semplicemente estraendo le parti della stringa avete bisogno utilizzando gsub invece di spaccare tutto e cercando di mettere di nuovo insieme:

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 bel metodo! –

2

Sembra che tu avere un fattore, quindi lavorare sui livelli e poi tornare indietro. Utilizzare fixed=TRUE in strsplit, regolando su 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

In realtà una grande risposta: semplice e non aggiunge nuove dipendenze. Ma il join può essere lento con un grande data.frame, quindi il costrutto 'data.frame' di Ricardo è una soluzione migliore per me. –

+0

Oops, significava il costrutto 'data.table', ma non riesco a modificare il commento dopo tanto tempo. –

+0

Questo suggerimento ha velocizzato il mio codice di un ordine di grandezza! Grazie! – CephBirk