Разделить текстовую строку на столбцы data.table

86

У меня есть сценарий, который считывает данные из файла CSV в, data.tableа затем разбивает текст в одном столбце на несколько новых столбцов. Я в настоящее время с помощью lapplyи strsplitфункции , чтобы сделать это. Вот пример:

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разделен на два новых столбца PXи PYсимвол «_».

Несмотря на то, что это работает нормально, мне было интересно, есть ли лучший (более эффективный) способ сделать это с помощью data.table. Мои настоящие наборы данных содержат> = 10 млн + строк, поэтому эффективность использования времени / памяти становится действительно важной.


ОБНОВИТЬ:

Следуя предложению @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))]
Фрэнк

Ответы:

122

Обновление: начиная с версии 1.9.6 (на CRAN по состоянию на сентябрь 2015 г.), мы можем использовать функцию 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». Я всегда считал, что лучше всего делать все в одну строчку. Просто из любопытства, почему индексный способ работает намного быстрее?
Деррик Льюис,
@Arun, в связи с этим вопросом, какие подводные камни вы могли бы увидеть в функции, подобной написанной здесь: gist.github.com/mrdwab/6873058 В принципе, я использовал fread, но для этого Мне пришлось использовать tempfile(что, казалось бы, было бы узким местом), поскольку не похоже, что он freadимеет эквивалент textаргументу. При тестировании с этими образцами данных его производительность находится между вашими подходами a_splи a_subподходами.
A5C1D2H2I1M1N2O1R2T1 02
4
Мне было интересно, как можно угадать количество столбцов в LHS: = и динамически создать имена новых столбцов на основе вхождений grep tstrsplit
amonk 05
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
zx8754
источник
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
user2657469
источник
1

Решение с tidyr:

separate(df,col = "PREFIX",into = c("PX", "PY"), sep = "_")
скан
источник
Вопрос специально задан для решений data.table. Люди, работающие в этой области, уже выбрали решения data.table вместо решений tidyr по уважительной причине, связанной с их проблемами.
Майкл Тачман,
Другие пользователи также предоставили решения с другими библиотеками, я только что дал действительную альтернативу, простую и быструю.
skan