Dividir seqüências separadas por vírgula em uma coluna em linhas separadas

Eu tenho um quadro de dados, assim:

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')) 

Como você pode ver, algumas inputs na coluna do director são vários nomes separados por vírgulas. Eu gostaria de dividir essas inputs em linhas separadas, mantendo os valores da outra coluna. Como exemplo, a primeira linha no quadro de dados acima deve ser dividida em duas linhas, com um único nome na coluna do director e ‘A’ na coluna AB .

Essa pergunta antiga está sendo usada frequentemente como alvo do dupe (marcado com r-faq ). A partir de hoje, foram respondidas três vezes oferecendo 6 abordagens diferentes, mas faltando um ponto de referência para orientar qual das abordagens é a mais rápida 1 .

As soluções comparadas incluem

  • Abordagem de R base de Matthew Lundberg, mas modificada de acordo com o comentário de Rich Scriven ,
  • Os dois methods de data.table Jaap e duas abordagens tidyr / tidyr ,
  • A solução do tipo splitstackshape de Ananda ,
  • e duas variantes adicionais dos methods data.table de Jaap.

No total, 8 methods diferentes foram comparados em 6 tamanhos diferentes de frameworks de dados usando o pacote microbenchmark (veja o código abaixo).

Os dados da amostra fornecidos pelo OP consistem apenas em 20 linhas. Para criar frames de dados maiores, essas 20 linhas são simplesmente repetidas 1, 10, 100, 1000, 10000 e 100000 vezes, o que dá tamanhos de problema de até 2 milhões de linhas.

Resultados de referência

insira a descrição da imagem aqui

Os resultados do benchmark mostram que, para frameworks de dados suficientemente grandes, todos os methods de dados são mais rápidos do que qualquer outro método. Para frameworks de dados com mais de cerca de 5000 linhas, o método data.table 2 de Jaap e a variante DT3 são as magnitudes mais rápidas, mais rápidas do que os methods mais lentos.

Notavelmente, os timings dos dois methods tidyverse e a solução splistackshape são tão semelhantes que é difícil distinguir as curvas no gráfico. Eles são os mais lentos dos methods comparados em todos os tamanhos de frameworks de dados.

Para frames de dados menores, a solução base R de Matt e o método 4 do data.table parecem ter menos sobrecarga do que os outros methods.

Código

 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) 

Definir function para execuções de benchmark do tamanho do problema 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 ) } 

Execute o benchmark para diferentes tamanhos de problemas

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

Prepare dados para plotagem

 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)] 

Crie um gráfico

 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() 

Informações da session e versões do pacote (trecho)

 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 Minha curiosidade foi despertada por este comentário exuberante Brilliant! Ordens de magnitude mais rápidas! para uma resposta tidyverse de uma questão que foi encerrada como uma duplicata desta questão.

Várias alternativas:

1) duas maneiras com 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) uma combinação tidyr / tidyr : Alternativamente, você também pode usar a combinação tidyr / tidyr :

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

3) apenas com o tidyr : Com o tidyr 0.5.0 (e posterior), você também pode apenas usar o separate_rows :

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

Você pode usar o parâmetro convert = TRUE para converter automaticamente números em colunas numéricas.

4) com base 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)) 

Nomeando seu data.frame original v , nós temos isto:

 > 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 

Observe o uso de rep para construir a nova coluna AB. Aqui, sapply retorna o número de nomes em cada uma das linhas originais.

Tarde para a festa, mas outra alternativa generalizada é usar o cSplit do meu pacote “splitstackshape” que tem um argumento de direction . Configure isso para "long" para obter o resultado especificado:

 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