Вопрос по r-faq, split, r, string – Разделите запятую строки в столбце на отдельные строки

81

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

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 колонка.

Все эти люди - номинанты на премию Оскар, что вряд ли является секретом =) RoyalTS
Просто чтобы спросить очевидное: эти данные вы должны публиковать на веб-сайтах? Ricardo Saporta
Oни "weren»Т все фильмы B ", Кажется достаточно безобидным. Matthew Lundberg

Ваш Ответ

4   ответа
59

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 ответвопрос который был закрыт как дубликат этого вопроса.

@Ferroao Это 'неправильно, подходы data.tables изменяют "Таблица" на месте, все столбцы сохраняются, конечно, если вы неВ этом случае вы получаете отфильтрованную копию только того, о чем просили. Вкратце, подход data.table заключается в том, чтобы не создавать результирующий набор данных, а обновить его, что 'Реальная разница между data.table и dplyr. Tensibai
@Frank Спасибо за ваши предложения по улучшению тестов и за проверку влияния на результаты. Подниму это при обновлении после выпуска следующих версий,data.tabledplyr, так далее. Uwe
Я думаю, что подходы несопоставимы, по крайней мере, не во всех случаях, потому что подходы, основанные на данных, производят только таблицы с "выбран» столбцы, в то время как dplyr выдает результат со всеми столбцами (включая столбцы, не участвующие в анализе и не записывающие их имена в функцию). Ferroao
Ницца! Похоже, есть место для улучшений в cSplit и Отдельные_ строки (которые специально предназначены для этого). Кстати, cSplit также принимает fixed = arg и является базой данных на основе data.table, поэтому может также дать ему DT вместо DF. Кроме того, я неЯ думаю, что преобразование из множителя в символ относится к эталону (так как для начала это должен быть символ). Я проверил, и ни одно из этих изменений не влияет на результаты качественно. Frank
0

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

Интересно, может ли `AB = rep (v $ AB, unlist (sapply (s, FUN = length)))` легче понять, чем более непонятнымvapply? Есть что-нибудь, что делаетvapply здесь уместнее? 42-
Настоящее времяsapply(s, length) можно заменить на.lengths(s) Rich Scriven
29

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

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))
вау только что понял, что он уже работает для нескольких столбцов одновременно - это удивительно! Reilstein
Есть ли способ сделать это для нескольких столбцов одновременно? Например, 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")) Reilstein

Похожие вопросы