Chia các chuỗi được phân tách bằng dấu phẩy trong một cột thành các hàng riêng biệt


109

Tôi có một khung dữ liệu, như sau:

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

Như bạn có thể thấy, một số mục trong directorcột là nhiều tên được phân tách bằng dấu phẩy. Tôi muốn chia các mục này thành các hàng riêng biệt trong khi vẫn giữ nguyên các giá trị của cột khác. Ví dụ: hàng đầu tiên trong khung dữ liệu ở trên phải được chia thành hai hàng, mỗi hàng có một tên trong directorcột và 'A' trong ABcột.


2
Chỉ để hỏi rõ ràng: Đây có phải là dữ liệu bạn nên đăng trên các trang liên mạng không?
Ricardo Saporta

1
Họ "không phải tất cả đều là phim B". Có vẻ vô thưởng vô phạt.
Matthew Lundberg

24
Tất cả những người này là giải Oscar đề cử, mà tôi hầu như không nghĩ là một bí mật =)
RoyalTS

Câu trả lời:


79

Câu hỏi cũ này thường được sử dụng làm mục tiêu dupe (được gắn thẻ với r-faq). Tính đến ngày hôm nay, nó đã được trả lời ba lần đưa ra 6 cách tiếp cận khác nhau nhưng thiếu tiêu chuẩn để hướng dẫn cách tiếp cận nào là nhanh nhất 1 .

Các giải pháp chuẩn bao gồm

Nhìn chung, 8 phương pháp khác nhau đã được đánh giá trên 6 kích thước khác nhau của khung dữ liệu bằng cách sử dụng microbenchmarkgói (xem mã bên dưới).

Dữ liệu mẫu do OP đưa ra chỉ bao gồm 20 hàng. Để tạo khung dữ liệu lớn hơn, 20 hàng này được lặp lại đơn giản 1, 10, 100, 1000, 10000 và 100000 lần để cung cấp kích thước vấn đề lên đến 2 triệu hàng.

Kết quả điểm chuẩn

nhập mô tả hình ảnh ở đây

Kết quả điểm chuẩn cho thấy rằng đối với khung dữ liệu đủ lớn, tất cả các data.tablephương pháp đều nhanh hơn bất kỳ phương pháp nào khác. Đối với các khung dữ liệu có hơn 5000 hàng, data.tablephương pháp 2 của Jaap và biến thể DT3là nhanh nhất, phóng đại nhanh hơn các phương pháp chậm nhất.

Đáng chú ý, thời gian của hai tidyversephương pháp và splistackshapegiải pháp giống nhau đến mức rất khó để phân tích các đường cong trong biểu đồ. Chúng là phương pháp chậm nhất trong số các phương pháp được chuẩn hóa trên tất cả các kích thước khung dữ liệu.

Đối với các khung dữ liệu nhỏ hơn, giải pháp R cơ sở của Matt và data.tablephương pháp 4 dường như có chi phí thấp hơn các phương pháp khác.

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)

Xác định chức năng cho các lần chạy điểm chuẩn có kích thước vấ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
  )
}

Chạy điểm chuẩn cho các kích thước vấn đề khác nhau

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

Chuẩn bị dữ liệu để vẽ biểu đồ

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

Tạo biểu đồ

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

Thông tin phiên và phiên bản gói (phần trích dẫn)

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 Sự tò mò của tôi đã được khơi dậy bởi nhận xét thú vị này Brilliant! Đơn đặt hàng của cường độ nhanh hơn! cho một tidyversecâu trả lời của một câu hỏi đã được đóng lại là một bản sao của câu hỏi này.


Đẹp! Có vẻ như cần cải tiến trong cSplit và own_rows (được thiết kế đặc biệt để làm việc này). Btw, cSplit cũng lấy một fixed = arg và là một gói dựa trên data.table, vì vậy cũng có thể cung cấp cho nó DT thay vì DF. Ngoài ra, tôi không nghĩ rằng việc chuyển đổi từ hệ số sang ký tự thuộc về tiêu chuẩn (vì nó phải là ký tự bắt đầu). Tôi đã kiểm tra và không có thay đổi nào trong số này ảnh hưởng đến kết quả một cách định tính.
Frank

1
@Frank Cảm ơn bạn đã góp ý để cải thiện điểm chuẩn và kiểm tra ảnh hưởng của kết quả. Sẽ nhặt này khi thực hiện một bản cập nhật sau khi phát hành các phiên bản tiếp theo của data.table, dplyrvv
Uwe

Tôi nghĩ rằng các phương pháp tiếp cận không thể so sánh được, ít nhất là không phải trong tất cả các trường hợp, bởi vì các phương pháp tiếp cận dữ liệu chỉ tạo ra các bảng với các cột "đã chọn", trong khi dplyr tạo ra một kết quả với tất cả các cột (bao gồm cả những cột không liên quan đến phân tích và không có để viết tên của chúng trong hàm).
Ferroao

5
@Ferroao Đó là sai, các phương pháp tiếp cận data.tables sửa đổi "bảng" tại chỗ, tất cả các cột được giữ nguyên, tất nhiên nếu bạn không sửa đổi tại chỗ, bạn sẽ chỉ nhận được một bản sao đã lọc của những gì bạn yêu cầu. Trong cách tiếp cận data.table ngắn gọn là không tạo ra một tập dữ liệu kết quả mà để cập nhật tập dữ liệu, đó là sự khác biệt thực sự giữa data.table và dplyr.
Tensibai

1
So sánh thực sự tốt đẹp! Có thể bạn có thể thêm matt_modjaap_dplyr , khi thực hiện strsplit fixed=TRUE. Như những người khác có nó và điều này sẽ ảnh hưởng đến thời gian. Kể từ R 4.0.0 , mặc định, khi tạo data.frame, là stringsAsFactors = FALSE, as.charactercó thể bị xóa.
GKi

94

Một số lựa chọn thay thế:

1) hai cách với :

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 / sự phối hợp:

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

3) với only: Với tidyr 0.5.0(và sau này), bạn cũng có thể chỉ sử dụng separate_rows:

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

Bạn có thể sử dụng convert = TRUEtham số để tự động chuyển đổi số thành cột số.

4) với cơ sở 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))

Có cách nào để làm điều này cho nhiều cột cùng một lúc không? Ví dụ: 3 cột mà mỗi cột có các chuỗi được phân tách bằng ";" với mỗi cột có cùng số chuỗi. tức là data.table(id= "X21", a = "chr1;chr1;chr1", b="123;133;134",c="234;254;268")trở thành data.table(id = c("X21","X21",X21"), a=c("chr1","chr1","chr1"), b=c("123","133","134"), c=c("234","254","268"))?
Reilstein

1
wow mới nhận ra rằng nó đã hoạt động cho nhiều cột cùng một lúc - điều này thật tuyệt vời!
Reilstein

@Reilstein bạn có thể chia sẻ cách bạn điều chỉnh điều này cho nhiều cột không? Tôi có cùng một trường hợp sử dụng, nhưng không chắc chắn về cách xử lý nó.
Moon_Watcher

1
@Moon_Watcher Phương pháp 1 trong câu trả lời ở trên đã hoạt động cho nhiều cột, đó là điều tôi nghĩ là tuyệt vời. setDT(dt)[,lapply(.SD, function(x) unlist(tstrsplit(x, ";",fixed=TRUE))), by = ID]là những gì đã làm việc cho tôi.
Reilstein

51

Đặt tên data.frame ban đầu của bạn v, chúng tôi có cái này:

> 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

Lưu ý việc sử dụng repđể xây dựng cột AB mới. Tại đây, sapplytrả về số lượng tên trong mỗi hàng ban đầu.


1
Tôi đang tự hỏi liệu `AB = rep (v $ AB, unlist (sapply (s, FUN = length)))` có thể dễ nắm bắt hơn là khó hiểu hơn không vapply? Có điều gì làm cho vapplythích hợp hơn ở đây không?
IRTFM

7
Ngày nay sapply(s, length)có thể được thay thế bằng lengths(s).
Rich Scriven

31

Đến muộn, nhưng một giải pháp thay thế tổng quát khác là sử dụng cSplittừ gói "splitstackshape" của tôi có một directionđối số. Đặt điều này thành "long"để nhận được kết quả bạn chỉ định:

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

Một Điểm chuẩn khác do sử dụng strsplittừ cơ sở hiện có thể được đề xuất để Chia các chuỗi được phân tách bằng dấu phẩy trong cột thành các hàng riêng biệt , vì nó là nhanh nhất trên một loạt các kích thước:

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

Lưu ý rằng việc sử dụng fixed=TRUEcó tác động đáng kể đến thời gian.

Đường cong hiển thị thời gian tính toán theo số hàng

Phương pháp so sánh:

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

Thư viện:

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

Dữ liệu:

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

Kết quả tính toán và thời gian:

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

Lưu ý, các phương pháp như

(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

trả về strsplitcho unique giám đốc và có thể được so sánh với

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

nhưng theo sự hiểu biết của tôi, điều này đã không được hỏi.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.