Розділити текстовий рядок у стовпцях таблиці даних


86

У мене є сценарій, який зчитує дані з файлу CSV у a, 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. Мої реальні набори даних мають> = 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))]
Френк,

Відповіді:


122

Оновлення: З версії 1.9.6 (на CRAN станом на 15 вересня) ми можемо використовувати функцію 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

4
Мені було цікаво, як можна вгадати кількість стовпців на LHS: = і динамічно створювати імена нових стовпців на основі випадків grep tstrsplit
amonk

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, з поважної причини щодо своїх проблем.
Майкл Тухман,

Інші користувачі також надали рішення з іншими бібліотеками, я щойно дав дійсну альтернативу, легко та швидко.
скан
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.