在data.table列中拆分文本字符串


86

我有一个脚本,可将CSV文件中的数据读入a data.table,然后将一列中的文本拆分为几个新列。我目前正在使用lapplystrsplit函数来执行此操作。这是一个例子:

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 

在上面的示例中,该列PREFIX分为两个新列,PXPY在“ _”字符上。

即使此方法工作正常,我仍想知道是否有更好(更有效)的方法来执行此操作data.table。我的真实数据集有> = 10M +行,因此时间/内存效率变得非常重要。


更新:

遵循@Frank的建议,我创建了一个更大的测试用例并使用了建议的命令,但是stringr::str_split_fixed比原始方法花费的时间长得多。

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 

因此,该str_split_fixed方法花费的时间大约是原来的20倍。


我认为先在data.table之外进行操作可能会更好。如果您使用该stringr软件包,则此命令为:str_split_fixed(PREFIX,"_",2)。我没有回答,因为我还没有测试过提速速度...或者,一步就可以了:dt[,c("PX","PY"):=data.table(str_split_fixed(PREFIX,"_",2))]
弗兰克(Frank)

Answers:


122

更新:从版本1.9.6(从9月15日开始在CRAN上),我们可以使用该函数tstrsplit()直接获取结果(并且以更有效的方式):

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()基本上是的包装transpose(strsplit()),其中transpose()最近也实现的功能会转置列表。请参阅?tstrsplit()?transpose()示例。

查看历史以获取旧答案。


谢谢阿伦 我没有想到先创建列表,然后再创建索引,然后再创建列的方法,如“ a_spl”所述。我一直认为,单行执行所有操作都是最好的方法。只是出于好奇,为什么索引方式会这么快地工作?
Derric Lewis

与这个问题相关的@Arun,您在类似我在此处编写的函数中会看到哪些陷阱:gist.github.com/mrdwab/6873058基本上,我已经使用了fread,但是这样做是,我不得不使用tempfile(似乎是一个瓶颈),因为它似乎没有fread等效于text参数的内容。使用此样本数据进行测试,其性能介于您a_spla_sub方法之间。
A5C1D2H2I1M1N2O1R2T1 2014年

4
我想知道如何猜测LHS的列数==并根据grep tstrsplit的出现动态地创建列的名称
amonk '17

15

我为不使用data.table v1.9.5并且也想要单行解决方案的人添加了答案。

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

7

使用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

4

我们可以尝试:

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

使用tidyr,解决方案是:

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

该问题专门要求提供data.table解决方案。在这个领域工作的人们已经选择了data.table解决方案,而不是tidyr解决方案,这是他们面临挑战的充分理由。
Michael Tuchman

其他用户也提供了其他库的解决方案,我刚刚给出了一种有效的替代方法,简便,快速。
skan
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.