Divida a string de texto em colunas data.table


86

Eu tenho um script que lê os dados de um arquivo CSV em um data.tablee divide o texto em uma coluna em várias colunas novas. Atualmente, estou usando as funções lapplye strsplitpara fazer isso. Aqui está um exemplo:

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 

No exemplo acima, a coluna PREFIXé dividida em duas novas colunas PXe PYno caractere "_".

Mesmo que isso funcione bem, eu queria saber se existe uma maneira melhor (mais eficiente) de fazer isso usando data.table. Meus conjuntos de dados reais têm> = 10M + linhas, então a eficiência de tempo / memória torna-se muito importante.


ATUALIZAR:

Seguindo a sugestão de @Frank, criei um caso de teste maior e usei os comandos sugeridos, mas o stringr::str_split_fixedmétodo demora muito mais do que o original.

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 

Portanto, o str_split_fixedmétodo leva cerca de 20 vezes mais.


Acho que fazer a operação fora do data.table primeiro pode ser melhor. Se você usar o stringrpacote, este é o comando: str_split_fixed(PREFIX,"_",2). Não estou respondendo porque não testei a aceleração ... Ou, em uma etapa:dt[,c("PX","PY"):=data.table(str_split_fixed(PREFIX,"_",2))]
Frank

Respostas:


122

Atualização: A partir da versão 1.9.6 (no CRAN a partir de Set'15), podemos usar a função tstrsplit()para obter os resultados diretamente (e de forma muito mais eficiente):

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()basicamente é um wrapper para transpose(strsplit()), onde a transpose()função, também implementada recentemente, transpõe uma lista. Por favor, veja ?tstrsplit()e ?transpose()para exemplos.

Veja a história para respostas antigas.


Obrigado Arun. Não havia pensado no método de criar primeiro a lista, depois o índice e as colunas, conforme descrito em "a_spl". Sempre achei que fazer tudo em uma única linha era o melhor jeito. Só por curiosidade, por que a forma do índice funciona tão mais rápido?
Derric Lewis

@Arun, em relação a esta pergunta, quais são algumas das armadilhas que você veria em uma função como a que escrevi aqui: gist.github.com/mrdwab/6873058 Basicamente, usei fread, mas para fazer isso, Tive de usar um tempfile(o que parece um gargalo), pois não parece freadter um equivalente a um textargumento. Testando com esses dados amostrais, seu desempenho é entre a sua a_sple a_subse aproxima.
A5C1D2H2I1M1N2O1R2T1

4
Eu queria saber como alguém poderia adivinhar o número de colunas no LHS de: = e criar dinamicamente os nomes das novas colunas com base nas ocorrências de grep tstrsplit
amonk

15

Acrescento resposta para quem não usa data.table v1.9.5 e também quer uma solução de uma linha.

dt[, c('PX','PY') := do.call(Map, c(f = c, strsplit(PREFIX, '-'))) ]

7

Usando o splitstackshapepacote:

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

4

Podemos tentar:

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

1

Com o tidyr, a solução é:

separate(df,col = "PREFIX",into = c("PX", "PY"), sep = "_")

A pergunta feita especificamente para soluções data.table. As pessoas que trabalham neste domínio já escolheram as soluções data.table em vez de soluções tidyr por uma boa razão em relação aos seus desafios.
Michael Tuchman

Outros usuários forneceram soluções com outras bibliotecas também, acabei de dar uma alternativa válida, fácil e rápida.
skan
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.