web-dev-qa-db-fra.com

Fractionner une chaîne de texte dans une colonne data.table

J'ai un script qui lit les données d'un fichier CSV dans un data.table et divise ensuite le texte d'une colonne en plusieurs nouvelles colonnes. J'utilise actuellement les fonctions lapply et strsplit pour le faire. Voici un exemple:

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 

Dans l'exemple ci-dessus, la colonne PREFIX est divisée en deux nouvelles colonnes PX et PY sur le caractère "_".

Même si cela fonctionne très bien, je me demandais s'il existait un moyen plus efficace (plus efficace) d'utiliser data.table. Mes véritables ensembles de données ont> = 10 millions de lignes +, l'efficacité en termes de temps/mémoire devient donc vraiment importante.


METTRE À JOUR:

Suivant la suggestion de @ Frank, j'ai créé un scénario de test plus volumineux et utilisé les commandes suggérées, mais le stringr::str_split_fixed prend beaucoup plus de temps que la méthode d'origine.

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 

Donc, la méthode str_split_fixed prend environ 20 fois plus de temps.

62
Derric Lewis

Mise à jour: À partir de la version 1.9.6 (sur CRAN à compter du 15 septembre), nous pouvons utiliser la fonction tstrsplit() pour obtenir les résultats directement (et de manière beaucoup plus efficace):

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() est essentiellement un wrapper pour transpose(strsplit()), où la fonction transpose(), également récemment mise en œuvre, transpose une liste. S'il vous plaît voir ?tstrsplit() et ?transpose() pour des exemples.

Voir l'historique pour les anciennes réponses.

85
Arun

J'ajoute answer pour quelqu'un qui n'utilise pas data.table v1.9.5 et qui veut aussi une solution sur une ligne.

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

Utilisation du package splitstackshape:

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
5
zx8754

Avec tidyr, la solution est la suivante:

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