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

У меня есть фрейм данных, вот так:

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 столбец и «А» вAB колонка.

 Matthew Lundberg08 дек. 2012 г., 05:37
Oни "weren»Т все фильмы B ", Кажется достаточно безобидным.
 Ricardo Saporta08 дек. 2012 г., 05:22
Просто чтобы спросить очевидное: эти данные вы должны публиковать на веб-сайтах?
 RoyalTS08 дек. 2012 г., 11:44
Все эти люди - номинанты на премию Оскар, что вряд ли является секретом =)

Ответы на вопрос(4)

Решение Вопроса

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

Тестовые решения включают в себя

Мэтью Лундбергбазовый подход R но модифицируется в соответствии сРич Скривенкомментарий,Яп-х дваdata.table методы и два /dplyrtidyr подходы,Анандаsplitstackshapeрешение,и два дополнительных варианта Jaap'sdata.table методы.

В целом 8 различных методов были сопоставлены с 6 различными размерами фреймов данных с использованиемmicrobenchmark пакет (см. код ниже).

Данные выборки, представленные OP, состоят только из 20 строк. Для создания больших фреймов данных эти 20 строк просто повторяются 1, 10, 100, 1000, 10000 и 100000 раз, что дает размер проблемы до 2 миллионов строк.

Результаты тестов

Результаты тестов показывают, что для достаточно больших фреймов данных всеdata.table методы быстрее, чем любой другой метод. Для фреймов данных с более чем 5000 строк, Jaap'sdata.table способ 2 и вариантDT3 являются самыми быстрыми, величины быстрее, чем самые медленные методы.

Примечательно, что время двухtidyverse методы иsplistackshape решение настолько похоже, чтоСложно выделить кривые на графике. Это самый медленный из тестируемых методов для всех размеров фреймов данных.

Для небольших кадров данных, МэттS Base 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()
Информация о сеансе версии пакета (отрывок) 1
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)

Мое любопытство было задетоэтот обильный комментарий Brilliant! На порядок быстрее! кtidyverse ответвопрос который был закрыт как дубликат этого вопроса.

 Ferroao26 июн. 2017 г., 22:08
Я думаю, что подходы несопоставимы, по крайней мере, не во всех случаях, потому что подходы, основанные на данных, производят только таблицы с "выбран» столбцы, в то время как dplyr выдает результат со всеми столбцами (включая столбцы, не участвующие в анализе и не записывающие их имена в функцию).
 Uwe16 апр. 2017 г., 01:49
@Frank Спасибо за ваши предложения по улучшению тестов и за проверку влияния на результаты. Подниму это при обновлении после выпуска следующих версий,data.tabledplyr, так далее.
 Tensibai27 июн. 2017 г., 10:37
@Ferroao Это 'неправильно, подходы data.tables изменяют "Таблица" на месте, все столбцы сохраняются, конечно, если вы неВ этом случае вы получаете отфильтрованную копию только того, о чем просили. Вкратце, подход data.table заключается в том, чтобы не создавать результирующий набор данных, а обновить его, что 'Реальная разница между data.table и dplyr.
 Frank16 апр. 2017 г., 00:59
Ницца! Похоже, есть место для улучшений в cSplit и Отдельные_ строки (которые специально предназначены для этого). Кстати, cSplit также принимает fixed = arg и является базой данных на основе data.table, поэтому может также дать ему DT вместо DF. Кроме того, я неЯ думаю, что преобразование из множителя в символ относится к эталону (так как для начала это должен быть символ). Я проверил, и ни одно из этих изменений не влияет на результаты качественно.

но другой обобщенной альтернативой является использование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

1) два способа с:data.table

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) а /dplyrtidyr сочетание: Кроме того, вы также можете использовать /dplyrtidyr сочетание:

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

3) сtidyr только: С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))
 Reilstein19 янв. 2019 г., 02:50
вау только что понял, что он уже работает для нескольких столбцов одновременно - это удивительно!
 Reilstein19 янв. 2019 г., 02:39
Есть ли способ сделать это для нескольких столбцов одновременно? Например, 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"))
Решение Вопроса

Называя ваш оригинальный data.framevу нас есть это:

> 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 возвращает количество имен в каждой из исходных строк.

 42-03 янв. 2013 г., 21:44
Интересно, может ли `AB = rep (v $ AB, unlist (sapply (s, FUN = length)))` легче понять, чем более непонятнымvapply? Есть что-нибудь, что делаетvapply здесь уместнее?
 Rich Scriven21 февр. 2017 г., 02:06
Настоящее времяsapply(s, length) можно заменить на.lengths(s)

Ваш ответ на вопрос