Розділіть рядки, розділені комами, у стовпці на окремі рядки


109

У мене є кадр даних, наприклад:

data.frame(director = c("Aaron Blaise,Bob Walker", "Akira Kurosawa", 
                        "Alan J. Pakula", "Alan Parker", "Alejandro Amenabar", "Alejandro Gonzalez Inarritu", 
                        "Alejandro Gonzalez Inarritu,Benicio Del Toro", "Alejandro González Iñárritu", 
                        "Alex Proyas", "Alexander Hall", "Alfonso Cuaron", "Alfred Hitchcock", 
                        "Anatole Litvak", "Andrew Adamson,Marilyn Fox", "Andrew Dominik", 
                        "Andrew Stanton", "Andrew Stanton,Lee Unkrich", "Angelina Jolie,John Stevenson", 
                        "Anne Fontaine", "Anthony Harvey"), AB = c('A', 'B', 'A', 'A', 'B', 'B', 'B', 'A', 'B', 'A', 'B', 'A', 'A', 'B', 'B', 'B', 'B', 'B', 'B', 'A'))

Як бачите, деякі записи в directorстовпці - це кілька імен, розділених комами. Я б хотів розділити ці записи в окремі рядки, зберігаючи значення іншого стовпця. Як приклад, перший рядок у кадрі даних вище слід розділити на два ряди, по одному ім'я у кожному directorстовпчику та "A" у ABстовпці.


2
Просто для запитання очевидного: чи є ці дані, які ви повинні розміщувати на інтервеї?
Рікардо Сапорта

1
Вони "були не всі фільми". Здається, досить невинно.
Метью Лундберг

24
Усі ці люди є номінантами на премію «Оскар», що я навряд чи вважаю секретом =)
RoyalTS

Відповіді:


79

Це старе запитання часто використовується як цільова дупа (позначена тегами r-faq). На сьогоднішній день на нього було дано відповіді три рази, пропонуючи 6 різних підходів, але не вистачає орієнтиру як орієнтиру, який із підходів є найшвидшим 1 .

Цільові рішення включають:

Загалом 8 різних методів були орієнтовані на 6 різних розмірах фреймів даних за допомогою microbenchmarkпакета (див. Код нижче).

Вибіркові дані, надані ОП, складаються лише з 20 рядків. Щоб створити більші рамки даних, ці 20 рядків просто повторюються в 1, 10, 100, 1000, 10000 і 100000 разів, що дає розміри проблем до 2 мільйонів рядків.

Результати порівняння

введіть тут опис зображення

Результати еталону показують, що для досить великих кадрів даних усі data.tableметоди швидші, ніж будь-який інший метод. Для кадрів даних, що містять понад 5000 рядків, data.tableметод Яапа 2 та варіант DT3- це найшвидші, на величину швидші, ніж найповільніші методи.

Примітно, що терміни двох tidyverseметодів і splistackshapeрішення настільки схожі, що складно відшукати криві на графіку. Вони є найповільнішими із орієнтованих методів у всіх розмірах кадру даних.

Для менших кадрів даних базове рішення R та data.tableметод 4 Метта, здається, мають менші витрати, ніж інші методи.

Код

director <- 
  c("Aaron Blaise,Bob Walker", "Akira Kurosawa", "Alan J. Pakula", 
    "Alan Parker", "Alejandro Amenabar", "Alejandro Gonzalez Inarritu", 
    "Alejandro Gonzalez Inarritu,Benicio Del Toro", "Alejandro González Iñárritu", 
    "Alex Proyas", "Alexander Hall", "Alfonso Cuaron", "Alfred Hitchcock", 
    "Anatole Litvak", "Andrew Adamson,Marilyn Fox", "Andrew Dominik", 
    "Andrew Stanton", "Andrew Stanton,Lee Unkrich", "Angelina Jolie,John Stevenson", 
    "Anne Fontaine", "Anthony Harvey")
AB <- c("A", "B", "A", "A", "B", "B", "B", "A", "B", "A", "B", "A", 
        "A", "B", "B", "B", "B", "B", "B", "A")

library(data.table)
library(magrittr)

Визначте функцію для базових прогонів розміру проблеми n

run_mb <- function(n) {
  # compute number of benchmark runs depending on problem size `n`
  mb_times <- scales::squish(10000L / n , c(3L, 100L)) 
  cat(n, " ", mb_times, "\n")
  # create data
  DF <- data.frame(director = rep(director, n), AB = rep(AB, n))
  DT <- as.data.table(DF)
  # start benchmarks
  microbenchmark::microbenchmark(
    matt_mod = {
      s <- strsplit(as.character(DF$director), ',')
      data.frame(director=unlist(s), AB=rep(DF$AB, lengths(s)))},
    jaap_DT1 = {
      DT[, lapply(.SD, function(x) unlist(tstrsplit(x, ",", fixed=TRUE))), by = AB
         ][!is.na(director)]},
    jaap_DT2 = {
      DT[, strsplit(as.character(director), ",", fixed=TRUE), 
         by = .(AB, director)][,.(director = V1, AB)]},
    jaap_dplyr = {
      DF %>% 
        dplyr::mutate(director = strsplit(as.character(director), ",")) %>%
        tidyr::unnest(director)},
    jaap_tidyr = {
      tidyr::separate_rows(DF, director, sep = ",")},
    cSplit = {
      splitstackshape::cSplit(DF, "director", ",", direction = "long")},
    DT3 = {
      DT[, strsplit(as.character(director), ",", fixed=TRUE),
         by = .(AB, director)][, director := NULL][
           , setnames(.SD, "V1", "director")]},
    DT4 = {
      DT[, .(director = unlist(strsplit(as.character(director), ",", fixed = TRUE))), 
         by = .(AB)]},
    times = mb_times
  )
}

Запустіть орієнтир для різних розмірів проблеми

# define vector of problem sizes
n_rep <- 10L^(0:5)
# run benchmark for different problem sizes
mb <- lapply(n_rep, run_mb)

Підготуйте дані для побудови графіків

mbl <- rbindlist(mb, idcol = "N")
mbl[, n_row := NROW(director) * n_rep[N]]
mba <- mbl[, .(median_time = median(time), N = .N), by = .(n_row, expr)]
mba[, expr := forcats::fct_reorder(expr, -median_time)]

Створення діаграми

library(ggplot2)
ggplot(mba, aes(n_row, median_time*1e-6, group = expr, colour = expr)) + 
  geom_point() + geom_smooth(se = FALSE) + 
  scale_x_log10(breaks = NROW(director) * n_rep) + scale_y_log10() + 
  xlab("number of rows") + ylab("median of execution time [ms]") +
  ggtitle("microbenchmark results") + theme_bw()

Інформація про сесію та версії пакету (витяг)

devtools::session_info()
#Session info
# version  R version 3.3.2 (2016-10-31)
# system   x86_64, mingw32
#Packages
# data.table      * 1.10.4  2017-02-01 CRAN (R 3.3.2)
# dplyr             0.5.0   2016-06-24 CRAN (R 3.3.1)
# forcats           0.2.0   2017-01-23 CRAN (R 3.3.2)
# ggplot2         * 2.2.1   2016-12-30 CRAN (R 3.3.2)
# magrittr        * 1.5     2014-11-22 CRAN (R 3.3.0)
# microbenchmark    1.4-2.1 2015-11-25 CRAN (R 3.3.3)
# scales            0.4.1   2016-11-09 CRAN (R 3.3.2)
# splitstackshape   1.4.2   2014-10-23 CRAN (R 3.3.3)
# tidyr             0.6.1   2017-01-10 CRAN (R 3.3.2)

1 Моя цікавість була викликана цим буйним коментарем Блискуче! Замовлення на величину швидше! на tidyverseвідповідь на запитання, яке було закрите як дублікат цього питання.


Приємно! Схоже, є можливість вдосконалити cSplit і sepa_row (які спеціально розроблені для цього). Btw, cSplit також приймає фіксований = arg і є пакетом на основі даних.tatable, так що може також дати DT замість DF. Крім того, fwiw, я не думаю, що перехід від фактора до char належить до еталону (оскільки це повинно бути символом для початку). Я перевірив, і жодна з цих змін якісно не робить нічого результату.
Френк

1
@Frank Дякуємо за ваші пропозиції щодо покращення показників та перевірки впливу на результати. Буде чи забрати це при виконанні поновлення після випуску наступної версії data.table, dplyrі т.д.
Уве

Я думаю, що підходи не порівнянні, принаймні, не у всіх випадках, тому що підходи до даних можуть створювати лише таблиці з "вибраними" стовпцями, тоді як dplyr створює результат з усіма стовпцями (включаючи ті, що не беруть участь в аналізі та не мають записати їхні імена у функції).
Ферроао

5
@Ferroao Це неправильно, підходи до data.tables змінюють "таблицю" на місці, всі стовпці зберігаються, звичайно, якщо ви не змінюєте їх на місці, ви отримуєте відфільтровану копію лише того, що ви просили. Коротко кажучи, підхід datatable не полягає в створенні отриманого набору даних, а в оновленні набору даних, це справжня різниця між data.table і dplyr.
Тенсібай

1
Дійсно приємне порівняння! Можливо, ви можете додати в matt_mod та jaap_dplyr , коли це робити strsplit fixed=TRUE. Як і інші, це вплине на терміни. Оскільки R 4.0.0 , за замовчуванням, коли створюється data.frame, є stringsAsFactors = FALSE, тому as.characterйого можна видалити.
GKi

94

Кілька альтернатив:

1) двома способами с :

library(data.table)
# method 1 (preferred)
setDT(v)[, lapply(.SD, function(x) unlist(tstrsplit(x, ",", fixed=TRUE))), by = AB
         ][!is.na(director)]
# method 2
setDT(v)[, strsplit(as.character(director), ",", fixed=TRUE), by = .(AB, director)
         ][,.(director = V1, AB)]

2) а / комбінація:

library(dplyr)
library(tidyr)
v %>% 
  mutate(director = strsplit(as.character(director), ",")) %>%
  unnest(director)

3) с тільки: За допомогою tidyr 0.5.0(і пізніших) ви також можете просто використовувати separate_rows:

separate_rows(v, director, sep = ",")

Ви можете використовувати convert = TRUEпараметр для автоматичного перетворення чисел у числові стовпці.

4) з базою R:

# if 'director' is a character-column:
stack(setNames(strsplit(df$director,','), df$AB))

# if 'director' is a factor-column:
stack(setNames(strsplit(as.character(df$director),','), df$AB))

Чи є спосіб зробити це для кількох стовпців одночасно? Наприклад, 3 стовпці, у кожному з яких є рядки, розділені ";" при цьому кожен стовпець має однакову кількість рядків. тобто data.table(id= "X21", a = "chr1;chr1;chr1", b="123;133;134",c="234;254;268")стає data.table(id = c("X21","X21",X21"), a=c("chr1","chr1","chr1"), b=c("123","133","134"), c=c("234","254","268"))?
Рейльштейн

1
Нічого не зрозумів, що він працює відразу для кількох стовпців - це дивовижно!
Рейльштейн

@Reilstein Ви могли б поділитися тим, як ви адаптували це для кількох стовпців? У мене той самий випадок використання, але не знаю, як це зробити.
Moon_Watcher

1
Спосіб @Moon_Watcher 1 у відповіді вище вже працює для декількох стовпців, що, на мою думку, було дивовижним. setDT(dt)[,lapply(.SD, function(x) unlist(tstrsplit(x, ";",fixed=TRUE))), by = ID]це те, що працювало для мене.
Рейльштейн

51

Назвавши свій оригінальний data.frame v, ми маємо це:

> s <- strsplit(as.character(v$director), ',')
> data.frame(director=unlist(s), AB=rep(v$AB, sapply(s, FUN=length)))
                      director AB
1                 Aaron Blaise  A
2                   Bob Walker  A
3               Akira Kurosawa  B
4               Alan J. Pakula  A
5                  Alan Parker  A
6           Alejandro Amenabar  B
7  Alejandro Gonzalez Inarritu  B
8  Alejandro Gonzalez Inarritu  B
9             Benicio Del Toro  B
10 Alejandro González Iñárritu  A
11                 Alex Proyas  B
12              Alexander Hall  A
13              Alfonso Cuaron  B
14            Alfred Hitchcock  A
15              Anatole Litvak  A
16              Andrew Adamson  B
17                 Marilyn Fox  B
18              Andrew Dominik  B
19              Andrew Stanton  B
20              Andrew Stanton  B
21                 Lee Unkrich  B
22              Angelina Jolie  B
23              John Stevenson  B
24               Anne Fontaine  B
25              Anthony Harvey  A

Зверніть увагу на використання repдля створення нового стовпчика AB. Тут sapplyповертається кількість імен у кожному з початкових рядків.


1
Мені цікаво, чи може "AB = rep (v $ AB, unlist (sapply (s, FUN = length))" `легше зрозуміти, ніж більш незрозумілим vapply? Чи є тут щось, що робить vapplyбільш доречним?
IRTFM

7
Нині sapply(s, length)його можна замінити на lengths(s).
Rich Scriven

31

Пізно до партії, але ще одна узагальнена альтернатива - це використовувати cSplitз мого пакету "splitstackshape", який має directionаргумент. Встановіть це, щоб "long"отримати вказаний результат:

library(splitstackshape)
head(cSplit(mydf, "director", ",", direction = "long"))
#              director AB
# 1:       Aaron Blaise  A
# 2:         Bob Walker  A
# 3:     Akira Kurosawa  B
# 4:     Alan J. Pakula  A
# 5:        Alan Parker  A
# 6: Alejandro Amenabar  B

2
devtools::install_github("yikeshu0611/onetree")

library(onetree)

dd=spread_byonecolumn(data=mydata,bycolumn="director",joint=",")

head(dd)
            director AB
1       Aaron Blaise  A
2         Bob Walker  A
3     Akira Kurosawa  B
4     Alan J. Pakula  A
5        Alan Parker  A
6 Alejandro Amenabar  B

0

Інший контрольний показник в результаті , використовуючи strsplitз бази в даний час може бути рекомендовані розділити кому рядків в стовпці в окремі рядки , так як він був найшвидшим в широкому діапазоні розмірів:

s <- strsplit(v$director, ",", fixed=TRUE)
s <- data.frame(director=unlist(s), AB=rep(v$AB, lengths(s)))

Зауважте, що використання fixed=TRUEмає значний вплив на таймінги.

Криві, що показують час обчислення за кількістю рядків

Порівняні методи:

met <- alist(base = {s <- strsplit(v$director, ",") #Matthew Lundberg
   s <- data.frame(director=unlist(s), AB=rep(v$AB, sapply(s, FUN=length)))}
 , baseLength = {s <- strsplit(v$director, ",") #Rich Scriven
   s <- data.frame(director=unlist(s), AB=rep(v$AB, lengths(s)))}
 , baseLeFix = {s <- strsplit(v$director, ",", fixed=TRUE)
   s <- data.frame(director=unlist(s), AB=rep(v$AB, lengths(s)))}
 , cSplit = s <- cSplit(v, "director", ",", direction = "long") #A5C1D2H2I1M1N2O1R2T1
 , dt = s <- setDT(v)[, lapply(.SD, function(x) unlist(tstrsplit(x, "," #Jaap
   , fixed=TRUE))), by = AB][!is.na(director)]
#, dt2 = s <- setDT(v)[, strsplit(director, "," #Jaap #Only Unique
#  , fixed=TRUE), by = .(AB, director)][,.(director = V1, AB)]
 , dplyr = {s <- v %>%  #Jaap
    mutate(director = strsplit(director, ",", fixed=TRUE)) %>%
    unnest(director)}
 , tidyr = s <- separate_rows(v, director, sep = ",") #Jaap
 , stack = s <- stack(setNames(strsplit(v$director, ",", fixed=TRUE), v$AB)) #Jaap
#, dt3 = {s <- setDT(v)[, strsplit(director, ",", fixed=TRUE), #Uwe #Only Unique
#  by = .(AB, director)][, director := NULL][, setnames(.SD, "V1", "director")]}
 , dt4 = {s <- setDT(v)[, .(director = unlist(strsplit(director, "," #Uwe
   , fixed = TRUE))), by = .(AB)]}
 , dt5 = {s <- vT[, .(director = unlist(strsplit(director, "," #Uwe
   , fixed = TRUE))), by = .(AB)]}
   )

Бібліотеки:

library(microbenchmark)
library(splitstackshape) #cSplit
library(data.table) #dt, dt2, dt3, dt4
#setDTthreads(1) #Looks like it has here minor effect
library(dplyr) #dplyr
library(tidyr) #dplyr, tidyr

Дані:

v0 <- data.frame(director = c("Aaron Blaise,Bob Walker", "Akira Kurosawa", 
                        "Alan J. Pakula", "Alan Parker", "Alejandro Amenabar", "Alejandro Gonzalez Inarritu", 
                        "Alejandro Gonzalez Inarritu,Benicio Del Toro", "Alejandro González Iñárritu", 
                        "Alex Proyas", "Alexander Hall", "Alfonso Cuaron", "Alfred Hitchcock", 
                        "Anatole Litvak", "Andrew Adamson,Marilyn Fox", "Andrew Dominik", 
                        "Andrew Stanton", "Andrew Stanton,Lee Unkrich", "Angelina Jolie,John Stevenson", 
                        "Anne Fontaine", "Anthony Harvey"), AB = c('A', 'B', 'A', 'A', 'B', 'B', 'B', 'A', 'B', 'A', 'B', 'A', 'A', 'B', 'B', 'B', 'B', 'B', 'B', 'A'))

Результати обчислень та термінів:

n <- 10^(0:5)
x <- lapply(n, function(n) {v <- v0[rep(seq_len(nrow(v0)), n),]
  vT <- setDT(v)
  ti <- min(100, max(3, 1e4/n))
  microbenchmark(list = met, times = ti, control=list(order="block"))})

y <- do.call(cbind, lapply(x, function(y) aggregate(time ~ expr, y, median)))
y <- cbind(y[1], y[-1][c(TRUE, FALSE)])
y[-1] <- y[-1] / 1e6 #ms
names(y)[-1] <- paste("n:", n * nrow(v0))
y #Time in ms
#         expr     n: 20    n: 200    n: 2000   n: 20000   n: 2e+05   n: 2e+06
#1        base 0.2989945 0.6002820  4.8751170  46.270246  455.89578  4508.1646
#2  baseLength 0.2754675 0.5278900  3.8066300  37.131410  442.96475  3066.8275
#3   baseLeFix 0.2160340 0.2424550  0.6674545   4.745179   52.11997   555.8610
#4      cSplit 1.7350820 2.5329525 11.6978975  99.060448 1053.53698 11338.9942
#5          dt 0.7777790 0.8420540  1.6112620   8.724586  114.22840  1037.9405
#6       dplyr 6.2425970 7.9942780 35.1920280 334.924354 4589.99796 38187.5967
#7       tidyr 4.0323765 4.5933730 14.7568235 119.790239 1294.26959 11764.1592
#8       stack 0.2931135 0.4672095  2.2264155  22.426373  289.44488  2145.8174
#9         dt4 0.5822910 0.6414900  1.2214470   6.816942   70.20041   787.9639
#10        dt5 0.5015235 0.5621240  1.1329110   6.625901   82.80803   636.1899

Зауважте, такі методи, як

(v <- rbind(v0[1:2,], v0[1,]))
#                 director AB
#1 Aaron Blaise,Bob Walker  A
#2          Akira Kurosawa  B
#3 Aaron Blaise,Bob Walker  A

setDT(v)[, strsplit(director, "," #Jaap #Only Unique
  , fixed=TRUE), by = .(AB, director)][,.(director = V1, AB)]
#         director AB
#1:   Aaron Blaise  A
#2:     Bob Walker  A
#3: Akira Kurosawa  B

повертати strsplitдля unique директора і може бути порівнянна з

tmp <- unique(v)
s <- strsplit(tmp$director, ",", fixed=TRUE)
s <- data.frame(director=unlist(s), AB=rep(tmp$AB, lengths(s)))

але наскільки я зрозумів, про це не питали.

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