Dividir la cadena de texto en columnas de una tabla de datos

86

Tengo una secuencia de comandos que lee datos de un archivo CSV en data.tableay luego divide el texto en una columna en varias columnas nuevas. Actualmente estoy usando las funciones lapplyy strsplitpara hacer esto. He aquí un ejemplo:

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 

En el ejemplo anterior, la columna PREFIXse divide en dos columnas nuevas PXy PYen el carácter "_".

Aunque esto funciona bien, me preguntaba si hay una forma mejor (más eficiente) de hacer esto usando data.table. Mis conjuntos de datos reales tienen> = 10M + filas, por lo que la eficiencia de tiempo / memoria se vuelve realmente importante.


ACTUALIZAR:

Siguiendo la sugerencia de @ Frank, creé un caso de prueba más grande y usé los comandos sugeridos, pero stringr::str_split_fixedlleva mucho más tiempo que el método 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 

Por tanto, el str_split_fixedmétodo tarda unas 20 veces más.

Derric Lewis
fuente
Creo que hacer la operación fuera de data.table primero podría ser mejor. Si se utiliza el stringrpaquete, este es el comando: str_split_fixed(PREFIX,"_",2). No respondo porque no he probado la aceleración ... O, en un solo paso:dt[,c("PX","PY"):=data.table(str_split_fixed(PREFIX,"_",2))]
Frank

Respuestas:

122

Actualización: desde la versión 1.9.6 (en CRAN a partir de septiembre de 2015), podemos usar la función tstrsplit()para obtener los resultados directamente (y de una manera mucho más 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()básicamente es un contenedor para transpose(strsplit()), donde la transpose()función, también implementada recientemente, transpone una lista. Consulte ?tstrsplit()y ?transpose()para ver ejemplos.

Consulte el historial para obtener respuestas antiguas.

Arun
fuente
Gracias Arun. No había pensado en el método de crear primero la lista, luego el índice y luego las columnas como se describe en "a_spl". Siempre pensé que hacer todo en una sola línea era la mejor manera. Solo por curiosidad, ¿por qué la forma de índice funciona mucho más rápido?
Derric Lewis
@Arun, en relación con esta pregunta, ¿cuáles son algunas de las trampas que vería en una función como la que escribí aquí: gist.github.com/mrdwab/6873058 Básicamente, he utilizado fread, pero para hacerlo, Tuve que usar un tempfile(que parecería ser un cuello de botella) ya que no parece que freadtenga un equivalente a un textargumento. Al probar con estos datos de muestra, su rendimiento está entre sus enfoques a_sply a_sub.
A5C1D2H2I1M1N2O1R2T1
4
Me preguntaba cómo se puede adivinar el número de columnas en el LHS de: = y crear dinámicamente los nombres de las nuevas columnas en función de las ocurrencias de grep tstrsplit
amonk
15

Agrego una respuesta para alguien que no usa data.table v1.9.5 y también quiere una solución de una línea.

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

Usando el splitstackshapepaquete:

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
fuente
4

Podemos intentarlo:

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
usuario2657469
fuente
1

Con tidyr la solución es:

separate(df,col = "PREFIX",into = c("PX", "PY"), sep = "_")
skan
fuente
La pregunta pedía específicamente soluciones data.table. Las personas que trabajan en este dominio ya han elegido las soluciones data.table en lugar de las soluciones tidyr por una buena razón en relación con sus desafíos.
Michael Tuchman
Otros usuarios también han proporcionado soluciones con otras bibliotecas, acabo de dar una alternativa válida, fácil y rápida.
skan