2013-05-20 11 views
6

Mam dużą ramkę danych z kolumną czynnikową, którą muszę podzielić na trzy kolumny o współczynniku, dzieląc nazwy czynników przez separator. Oto moje obecne podejście, które jest bardzo powolny z dużym ramki danych (czasami kilka milionów wierszy):Przyspieszenie `strsplit` gdy możliwe wyjścia są znane

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 

Czy istnieje jakiś sposób, aby przyspieszyć tę operację? Zauważam, że istnieje niewielka (< 5) liczba każdej z kategorii "Gatunki", "SizeClass" i "Infected", i wiem, co to jest z góry.

Uwagi:

  • stringr::str_split_fixed realizuje to zadanie, ale nie szybciej
  • ramka danych jest faktycznie początkowo generowane przez wywołanie reshape::melt na tablicy, w której Class i związane z nią poziomy są wymiarem. Jeśli jest szybszy sposób, aby dostać się stąd, świetnie.
  • data.rds na http://dl.getdropbox.com/u/3356641/data.rds

Odpowiedz

5

Powinno to prawdopodobnie zaoferować całkiem wzrost:

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


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

Powody wzrostu:

  1. data.table wstępnie przydziela pamięć dla kolumn
  2. każde przypisanie do kolumny w data.frame powoduje ponowne przypisanie całych danych (w przeciwieństwie do danych, tab. Danych)
  3. Instrukcja by umożliwia wykonanie zadania strsplit raz na każdą unikalną wartość.

Oto miły szybka metoda dla całego procesu.

# 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

To szybko! Mimo że użyłeś 'as.character (Class)'. Czy możesz zwrócić kolumny jako czynniki w tym samym poleceniu? –

+0

Można przekonwertować na czynnik, ale zrobić to jako drugą linię po. Użycie as.factor w tym samym wywołaniu, które zawiera argument "by", koniecznie spowalnia proces. –

+0

@NoamRoss, ładny połów na znaku 'as.character'. Zaktualizowany kod plus kilka dodatkowych kroków –

3

Można dostać przyzwoity wzrost prędkości tylko o wyodrębnieniu części łańcucha, czego potrzebujesz za pomocą gsub zamiast dzielenia wszystkiego i próbuje umieścić go z powrotem razem:

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 bardzo dobra metoda! –

2

Wygląda na to, mieć czynnik, więc pracuj na poziomach, a potem odwróć mapę. Użyj fixed=TRUE w strsplit, dostosowując do 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

Właściwie świetna odpowiedź - prosta i nie dodaje nowych zależności. Ale sprzężenie może być powolne z dużą strukturą data.frame, więc konstrukcja 'data.frame 'Ricarda jest lepszym rozwiązaniem dla mnie. –

+0

Ups, chodziło o konstrukcję 'data.table', ale nie mogę edytować komentarza po tak długim czasie. –

+0

Ta sugestia przyspieszyła mój kod o rząd wielkości! Dzięki! – CephBirk