열의 쉼표로 구분 된 문자열을 별도의 행으로 분할


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
명백한 질문을하기 위해 :이 데이터를 인터넷에 게시해야합니까?
Ricardo Saporta 2012

1
그들은 "모든 B 영화를 보지 않는다". 충분히 무해한 것 같습니다.
매튜 런드 버그

24
이 모든 사람들은 내가 거의) 비밀 = 생각하지 아카데미 상 후보입니다
RoyalTS

답변:


79

이 오래된 질문은 자주 속이는 대상으로 사용됩니다 (태그가 붙음 r-faq). 현재까지 6 가지 접근 방식을 제공하는 3 번의 답변을 받았지만 어떤 접근 방식이 가장 빠른지 지침 으로 벤치 마크가 부족합니다 1 .

벤치 마크 솔루션에는 다음이 포함됩니다.

microbenchmark패키지를 사용하여 6 가지 크기의 데이터 프레임에서 전체 8 가지 방법을 벤치마킹했습니다 (아래 코드 참조).

OP가 제공하는 샘플 데이터는 20 개의 행으로 만 구성됩니다. 더 큰 데이터 프레임을 만들기 위해이 20 개의 행이 1, 10, 100, 1000, 10000 및 100000 번 반복되어 문제 크기가 최대 2 백만 행에 이릅니다.

벤치 마크 결과

여기에 이미지 설명 입력

벤치 마크 결과는 충분히 큰 데이터 프레임의 경우 모든 data.table방법이 다른 방법보다 빠르다 는 것을 보여줍니다 . 행이 약 5000 개 이상인 데이터 프레임의 경우 Jaap의 data.table방법 2와 변형 DT3이 가장 느린 방법보다 빠르고 크기가 빠릅니다.

놀랍게도 두 tidyverse방법과 splistackshape솔루션 의 타이밍 이 너무 비슷해서 차트에서 곡선을 구별하기가 어렵습니다. 모든 데이터 프레임 크기에서 벤치 마크 된 방법 중 가장 느립니다.

더 작은 데이터 프레임의 경우 Matt의 기본 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 이 생동감 넘치는 댓글 에 호기심이 사로 잡혔습니다 . Brilliant! 더 빠른 주문! A와 tidyverse의 대답은 질문 이 질문의 중복으로 폐쇄되었다.


좋은! cSplit 및 separate_rows (이 작업을 위해 특별히 설계됨)에서 개선 할 여지가있는 것 같습니다. Btw, cSplit은 fixed = arg도 취하고 data.table 기반 패키지이므로 DF 대신 DT를 제공하는 것이 좋습니다. 또한 fwiw, 요소에서 char 로의 변환이 벤치 마크에 속한다고 생각하지 않습니다 (시작하려면 char이어야하므로). 확인한 결과 이러한 변경 사항 중 어떤 것도 결과에 질적으로 영향을주지 않았습니다.
Frank

1
@Frank 벤치 마크를 개선하고 결과에 미치는 영향을 확인하기위한 제안에 감사드립니다. 의 다음 버전의 릴리스 이후에 업데이 트를 수행 할 때이 최대를 선택할 것인가 data.table, dplyr
우베

데이터 테이블 접근 방식은 "선택된"열이있는 테이블 만 생성하는 반면 dplyr은 모든 열 (분석에 포함되지 않고 포함되지 않은 열 포함)으로 결과를 생성하기 때문에 접근 방식이 모든 경우에 비교할 수는 없다고 생각합니다. 함수에 이름을 쓰려면).
Ferroao

5
@Ferroao 틀 렸습니다. data.tables 접근 방식은 "테이블"을 제자리에서 수정하고 모든 열은 유지합니다. 물론 제자리에서 수정하지 않으면 요청한 내용 만 필터링 된 복사본을 얻게됩니다. 간단히 말해 data.table 접근 방식은 결과 데이터 세트를 생성하지 않고 데이터 세트를 업데이트하는 것입니다. 이것이 data.table과 dplyr의 실제 차이점입니다.
Tensibai

1
정말 좋은 비교! 아마도 당신은 할 때 matt_modjaap_dplyr를 추가 할 수 있습니다 strsplit fixed=TRUE. 다른 사람이 가지고 있고 이것은 타이밍에 영향을 미칠 것입니다. R 4.0.0 이후로를 만들 때 기본값 data.framestringsAsFactors = 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) a / 콤비네이션:

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"))?
Reilstein

1
와우 이미 한 번에 여러 열에 대해 작동한다는 것을 깨달았습니다-이것은 놀랍습니다!
Reilstein

@Reilstein을 여러 열에 적용한 방법을 공유 할 수 있습니까? 동일한 사용 사례가 있지만 어떻게해야할지 잘 모르겠습니다.
Moon_Watcher

1
위 답변의 @Moon_Watcher 방법 1은 이미 여러 열에 대해 작동하므로 놀랍습니다. setDT(dt)[,lapply(.SD, function(x) unlist(tstrsplit(x, ";",fixed=TRUE))), by = ID]나를 위해 일한 것입니다.
Reilstein

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 2013 년

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

basestrsplit 에서 사용 하는 또 다른 벤치 마크 는 현재 열의 쉼표로 구분 된 문자열을 별도의 행 으로 분할하는 데 권장 될 수 있습니다 . 다양한 크기에서 가장 빠르기 때문입니다.

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

그러나 내 이해에 이것은 요청되지 않았습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.