Podziel ciąg tekstowy na kolumny data.table

86

Mam skrypt, który wczytuje dane z pliku CSV do a, data.tablea następnie dzieli tekst w jednej kolumnie na kilka nowych kolumn. Obecnie używam tych lapplyi strsplitfunkcji, aby to zrobić. Oto przykład:

library("data.table")
df = data.table(PREFIX = c("A_B","A_C","A_D","B_A","B_C","B_D"),
                VALUE  = 1:6)
dt = as.data.table(df)

# split PREFIX into new columns
dt$PX = as.character(lapply(strsplit(as.character(dt$PREFIX), split="_"), "[", 1))
dt$PY = as.character(lapply(strsplit(as.character(dt$PREFIX), split="_"), "[", 2))

dt 
#    PREFIX VALUE PX PY
# 1:    A_B     1  A  B
# 2:    A_C     2  A  C
# 3:    A_D     3  A  D
# 4:    B_A     4  B  A
# 5:    B_C     5  B  C
# 6:    B_D     6  B  D 

W powyższym przykładzie kolumna PREFIXjest podzielona na dwie nowe kolumny PXi PYznak „_”.

Mimo że działa to dobrze, zastanawiałem się, czy istnieje lepszy (bardziej wydajny) sposób na zrobienie tego za pomocą data.table. Moje prawdziwe zestawy danych mają> = 10 mln + wierszy, więc czas / wydajność pamięci stają się naprawdę ważne.


AKTUALIZACJA:

Zgodnie z sugestią @ Franka stworzyłem większy przypadek testowy i użyłem sugerowanych poleceń, ale stringr::str_split_fixedtrwa to znacznie dłużej niż oryginalna metoda.

library("data.table")
library("stringr")
system.time ({
    df = data.table(PREFIX = rep(c("A_B","A_C","A_D","B_A","B_C","B_D"), 1000000),
                    VALUE  = rep(1:6, 1000000))
    dt = data.table(df)
})
#   user  system elapsed 
#  0.682   0.075   0.758 

system.time({ dt[, c("PX","PY") := data.table(str_split_fixed(PREFIX,"_",2))] })
#    user  system elapsed 
# 738.283   3.103 741.674 

rm(dt)
system.time ( {
    df = data.table(PREFIX = rep(c("A_B","A_C","A_D","B_A","B_C","B_D"), 1000000),
                     VALUE = rep(1:6, 1000000) )
    dt = as.data.table(df)
})
#    user  system elapsed 
#   0.123   0.000   0.123 

# split PREFIX into new columns
system.time ({
    dt$PX = as.character(lapply(strsplit(as.character(dt$PREFIX), split="_"), "[", 1))
    dt$PY = as.character(lapply(strsplit(as.character(dt$PREFIX), split="_"), "[", 2))
})
#    user  system elapsed 
#  33.185   0.000  33.191 

Tak więc str_split_fixedmetoda trwa około 20 razy dłużej.

Derric Lewis
źródło
Myślę, że wykonanie operacji poza tabelą data.w pierwszej kolejności mogłoby być lepsze. Jeśli korzystasz z stringrpakietu, to jest polecenie: str_split_fixed(PREFIX,"_",2). Nie odpowiadam, ponieważ nie testowałem przyspieszenia ... Lub w jednym kroku:dt[,c("PX","PY"):=data.table(str_split_fixed(PREFIX,"_",2))]
Frank

Odpowiedzi:

122

Aktualizacja: Od wersji 1.9.6 (na CRAN od września 2015) możemy użyć tej funkcji, tstrsplit()aby uzyskać wyniki bezpośrednio (i znacznie wydajniej):

require(data.table) ## v1.9.6+
dt[, c("PX", "PY") := tstrsplit(PREFIX, "_", fixed=TRUE)]
#    PREFIX VALUE PX PY
# 1:    A_B     1  A  B
# 2:    A_C     2  A  C
# 3:    A_D     3  A  D
# 4:    B_A     4  B  A
# 5:    B_C     5  B  C
# 6:    B_D     6  B  D

tstrsplit()w zasadzie jest opakowaniem dla transpose(strsplit()), gdzie transpose()funkcja, również niedawno zaimplementowana, transponuje listę. Proszę zobaczyć ?tstrsplit()i?transpose() przykłady.

Zobacz historię starych odpowiedzi.

Bieg
źródło
Dzięki Arun. Nie pomyślałem o metodzie tworzenia najpierw listy, potem indeksu, a potem kolumn, jak opisano w „a_spl”. Zawsze uważałem, że robienie wszystkiego w jednej linii to najlepszy sposób. Tylko z ciekawości, dlaczego sposób indeksowania działa o wiele szybciej?
Derric Lewis,
@Arun, w związku z tym pytaniem, jakie są pułapki, które można zobaczyć w funkcji takiej, jak napisałem tutaj: gist.github.com/mrdwab/6873058 Zasadniczo wykorzystałem fread, ale aby to zrobić, Musiałem użyć tempfile(co wydaje się być wąskim gardłem), ponieważ nie wygląda na to, że freadma odpowiednik textargumentu. Podczas testowania z tymi przykładowymi danymi jego wydajność jest między Twoimi a_spla a_subpodejściami.
A5C1D2H2I1M1N2O1R2T1
4
Zastanawiałem się, jak można zgadnąć liczbę kolumn na LHS z: = i dynamicznie utworzyć nazwy nowych kolumn na podstawie wystąpień grep tstrsplit
amonk
15

Dodaję odpowiedź dla kogoś, kto nie używa wersji data.table 1.9.5, a także chce rozwiązania jednowierszowego .

dt[, c('PX','PY') := do.call(Map, c(f = c, strsplit(PREFIX, '-'))) ]
Ha Pham
źródło
7

Korzystanie z splitstackshapepakietu:

library(splitstackshape)
cSplit(df, splitCols = "PREFIX", sep = "_", direction = "wide", drop = FALSE)
#    PREFIX VALUE PREFIX_1 PREFIX_2
# 1:    A_B     1        A        B
# 2:    A_C     2        A        C
# 3:    A_D     3        A        D
# 4:    B_A     4        B        A
# 5:    B_C     5        B        C
# 6:    B_D     6        B        D
zx8754
źródło
4

Możemy spróbować:

cbind(dt, fread(text = dt$PREFIX, sep = "_", header = FALSE))
#    PREFIX VALUE V1 V2
# 1:    A_B     1  A  B
# 2:    A_C     2  A  C
# 3:    A_D     3  A  D
# 4:    B_A     4  B  A
# 5:    B_C     5  B  C
# 6:    B_D     6  B  D
user2657469
źródło
1

W przypadku tidyr rozwiązaniem jest:

separate(df,col = "PREFIX",into = c("PX", "PY"), sep = "_")
skan
źródło
Pytanie dotyczyło rozwiązań typu data.table. Osoby pracujące w tej dziedzinie wybrały już rozwiązania data.table zamiast rozwiązań tidyr z dobrego powodu w odniesieniu do ich wyzwań.
Michael Tuchman
Inni użytkownicy również dostarczali rozwiązania z innymi bibliotekami, właśnie podałem ważną alternatywę, łatwą i szybką.
skan