Bir sütundaki virgülle ayrılmış dizeleri ayrı satırlara bölme


109

Bir veri çerçevem ​​var, şöyle:

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

Gördüğünüz gibi, directorsütundaki bazı girişler virgülle ayrılmış birden çok addır. Diğer sütunun değerlerini korurken bu girişleri ayrı satırlara bölmek istiyorum. Örnek olarak, yukarıdaki veri çerçevesindeki ilk satır, her biri directorsütunda tek bir ad ve sütunda "A" olacak şekilde iki satıra bölünmelidir AB.


2
Sadece açık olanı sormak için: Bu veriler internette yayınlamanız gerekiyor mu?
Ricardo Saporta

1
Hepsi "B filmi" değildi. Yeterince zararsız görünüyor.
Matthew Lundberg

24
Tüm bu insanlar Akademi Ödülü adayları, ki bunun sır olduğunu pek düşünmüyorum =)
RoyalTS

Yanıtlar:


79

Bu eski soru sıklıkla dupe hedef olarak kullanılmaktadır (ile etiketlenmiştir r-faq). Bugün itibariyle, 6 farklı yaklaşım önerilerek üç kez yanıtlanmış , ancak yaklaşımlardan hangisinin en hızlı olduğu konusunda rehberlik edecek bir kriter eksiktir 1 .

Karşılaştırmalı çözümler şunları içerir:

Genel olarak, microbenchmarkpaket kullanılarak 6 farklı boyuttaki veri çerçevesi üzerinde 8 farklı yöntem karşılaştırıldı (aşağıdaki koda bakın).

OP tarafından verilen örnek veriler sadece 20 satırdan oluşmaktadır. Daha büyük veri çerçeveleri oluşturmak için, bu 20 satır basitçe 1, 10, 100, 1000, 10000 ve 100000 kez tekrarlanır ve bu da 2 milyon satıra kadar problem boyutları verir.

Karşılaştırma sonuçları

görüntü açıklamasını buraya girin

Karşılaştırma sonuçları, yeterince büyük veri çerçeveleri için tüm data.tableyöntemlerin diğer yöntemlerden daha hızlı olduğunu göstermektedir. Yaklaşık 5000'den fazla satır içeren veri çerçeveleri için, Jaap'ın data.table2. yöntemi ve varyantı DT3, en hızlı yöntemdir , en yavaş yöntemlerden daha hızlıdır.

Dikkat çekici bir şekilde, iki tidyverseyöntemin zamanlamaları ve splistackshapeçözüm o kadar benzerdir ki, grafikteki eğrileri ayırt etmek zordur. Tüm veri çerçevesi boyutlarında karşılaştırılan yöntemlerin en yavaş olanıdır.

Daha küçük veri çerçeveleri için, Matt'in temel R çözümü ve data.tableyöntem 4, diğer yöntemlerden daha az ek yüke sahip görünüyor.

Kod

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)

Problem büyüklüğünde kıyaslama çalışmaları için fonksiyon tanımlayın 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
  )
}

Farklı sorun boyutları için kıyaslama çalıştırın

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

Verileri çizim için hazırlayın

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

Grafik oluştur

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

Oturum bilgileri ve paket sürümleri (alıntı)

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 Bu coşkulu yorum merakımı artırdı. Harika! Büyüklükler daha hızlı! Bir üzere bir cevap bir soru bu soruya bir kopyası olarak kapatıldı. tidyverse


Güzel! Görünüşe göre cSplit ve ayrık_ satırlarda (özellikle bunu yapmak için tasarlanmıştır) iyileştirme için yer var. Btw, cSplit ayrıca bir sabit = arg alır ve data.table tabanlı bir pakettir, bu yüzden ona DF yerine DT verebilir. Ayrıca fwiw, faktörden karaktere dönüşümün karşılaştırmalı değerlendirmeye ait olduğunu düşünmüyorum (çünkü başlamak için char olması gerekir). Kontrol ettim ve bu değişikliklerin hiçbiri sonuçlara niteliksel olarak bir şey yapmıyor.
Frank

1
@Frank Karşılaştırmaları iyileştirme önerileriniz ve sonuçlar üzerindeki etkisini kontrol ettiğiniz için teşekkür ederiz. Sonraki sürümlerinde yayımlanmasından sonra bir güncelleme yaparken bu kadar alacak mısın data.table, dplyrvb
Uwe

Yaklaşımların karşılaştırılabilir olmadığını düşünüyorum, en azından her durumda değil, çünkü verilebilir yaklaşımlar yalnızca "seçilen" sütunlarla tablolar üretirken, dplyr tüm sütunlarla bir sonuç üretir (analize dahil olmayanlar dahil ve isimlerini fonksiyona yazmak için).
Ferroao

5
@Ferroao Bu yanlış, data.tables yaklaşımları "tabloyu" yerinde değiştirir, tüm sütunlar tutulur, tabii ki yerinde değiştirmezseniz, sadece istediğiniz şeyin filtrelenmiş bir kopyasını alırsınız. Özetle data.table yaklaşımı, sonuçta ortaya çıkan bir veri seti üretmek değil, veri setini güncellemektir, data.table ve dplyr arasındaki gerçek fark budur.
Tensibai

1
Gerçekten güzel karşılaştırma! Belki yaparken matt_mod ve jaap_dplyr ekleyebilirsiniz strsplit fixed=TRUE. Diğerinde olduğu gibi ve bu zamanlamalar üzerinde etkili olacaktır. Yana R 4.0.0 bir oluştururken, varsayılan data.frame, bir stringsAsFactors = FALSEnedenle as.characterçıkarılabilir.
GKi

94

Birkaç alternatif:

1) iki yolla :

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) bir / kombinasyon:

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

3) ile yalnızca: ile tidyr 0.5.0(ve daha sonra), şunları da kullanabilirsiniz separate_rows:

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

convert = TRUESayıları otomatik olarak sayısal sütunlara dönüştürmek için parametreyi kullanabilirsiniz .

4) R tabanı ile:

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

Bunu aynı anda birden çok sütun için yapmanın bir yolu var mı? Örneğin, her biri ";" ile ayrılmış dizelere sahip 3 sütun her sütun aynı sayıda dizeye sahip. yani data.table(id= "X21", a = "chr1;chr1;chr1", b="123;133;134",c="234;254;268")olma data.table(id = c("X21","X21",X21"), a=c("chr1","chr1","chr1"), b=c("123","133","134"), c=c("234","254","268"))?
Reilstein

1
vay, daha önce aynı anda birden çok sütun için çalıştığını fark ettim - bu harika!
Reilstein

@Reilstein, bunu birden fazla sütun için nasıl uyarladığınızı paylaşabilir misiniz? Aynı kullanım senaryosuna sahibim, ancak bunun nasıl yapılacağından emin değilim.
Moon_Watcher

1
Yukarıdaki cevapta @Moon_Watcher Yöntem 1 zaten birden fazla sütun için çalışıyor, ben de harika olduğunu düşündüm. setDT(dt)[,lapply(.SD, function(x) unlist(tstrsplit(x, ";",fixed=TRUE))), by = ID]benim için işe yarayan şey.
Reilstein

51

Orijinal data.frame'inizi adlandırırken, şuna vsahibiz:

> 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

repYeni AB sütununu oluşturmak için kullanımına dikkat edin. Burada, sapplyorijinal satırların her birindeki adların sayısını döndürür.


1
Merak ediyorum, `` AB = rep (v $ AB, unlist (sapply (s, FUN = length))) 'kavraması daha belirsiz olandan daha kolay olabilir vapplymi? vapplyBurada daha uygun kılan herhangi bir şey var mı?
IRTFM

7
Günümüzde sapply(s, length)ile değiştirilebilir lengths(s).
Rich Scriven

31

Partiye geç, ancak başka bir genelleştirilmiş alternatif, argümanı cSplitolan "splitstackshape" paketimden kullanmaktır direction. "long"Belirttiğiniz sonucu almak için bunu ayarlayın :

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

Oluşan başka Deney strsplitgelen taban , şu anda tavsiye edilebilir ayrı satırlara kolonu Split virgülle ayrılmış dizeleri bu boyutlarda hızlı üzerinde geniş olarak:

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

Kullanmanın fixed=TRUEzamanlamalar üzerinde önemli etkisi olduğunu unutmayın .

Satır sayısı üzerinden hesaplama süresini gösteren eğriler

Karşılaştırılan Yöntemler:

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

Kitaplıklar:

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

Veri:

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

Hesaplama ve Zamanlama sonuçları:

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

Not, gibi yöntemler

(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

yönetmenstrsplit için bir iadeunique ve karşılaştırılabilir olabilir

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

ama anladığım kadarıyla bu sorulmadı.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.