列内のカンマ区切りの文字列を個別の行に分割する


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列の一部のエントリは、コンマで区切られた複数の名前です。他の列の値を維持しながら、これらのエントリを別々の行に分割したいと思います。例として、上のデータフレームの最初の行を2つの行に分割し、それぞれ1つの名前をdirector列に、「A」をAB列に入れます。


2
明白な質問をするだけです:このデータはインターウェブに投稿する必要がありますか?
Ricardo Saporta

1
彼らは「すべてのB映画ではない」。無害なようです。
マシュー・ランドバーグ、

24
これらの人々はすべて、アカデミー賞の候補者であり、私は秘密だとは思いません=)
RoyalTS

回答:


79

この古い質問は頻繁に(でタグ付けされたr-faq)重複ターゲットとして使用されています。今日のところ、3つの回答があり、6つの異なるアプローチが提供されていますが、どちらのアプローチが最も速いかについてのガイダンスとしてベンチマークが欠けています1

ベンチマークソリューションには、

microbenchmarkパッケージを使用して、全体で8つの異なるメソッドを6つの異なるサイズのデータ​​フレームでベンチマークしました(以下のコードを参照)。

OPによって提供されるサンプルデータは、20行のみで構成されています。より大きなデータフレームを作成するには、これらの20行を1、10、100、1000、10000、および100000回繰り返すだけで、最大200万行の問題サイズが生じます。

ベンチマーク結果

ここに画像の説明を入力してください

ベンチマークの結果は、十分に大きいデータフレームの場合、すべてのdata.table方法が他のどの方法よりも高速であることを示しています。約5000行を超えるデータフレームの場合、Jaapのdata.table方法2とバリアントDT3が最も速く、最も遅い方法よりも速度が速くなります。

驚くべきことに、2つの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!桁違いに速い!tidyverse答え質問この質問の重複として閉鎖されました。


いいね!cSplitとSeparate_rows(特にこれを行うように設計されています)には改善の余地があるようです。ところで、cSplitはfixed = argも取り、data.tableベースのパッケージであるため、DFではなくDTを指定することもできます。また、fwiw、factorからcharへの変換はベンチマークに含まれるとは思いません(最初はcharである必要があるため)。私はチェックしましたが、これらの変更はいずれも結果的に質的には何もしません。
フランク

1
@フランクベンチマークを改善し、結果への影響を確認するための提案をありがとうございます。次期バージョンのリリース後に更新を行う場合、このアップを選ぶだろうdata.tabledplyrなど
ウーヴェ

データテーブルアプローチは「選択された」列を含むテーブルのみを生成し、dplyrはすべての列(分析に関与しない列を含む、関数にそれらの名前を書き込むため)。
Ferroao 2017年

5
@Ferroao不正解です。data.tablesアプローチは「テーブル」を適切に変更します。すべての列は保持されます。もちろん、適切な場所で変更しない場合は、要求したもののみのフィルタリングされたコピーが得られます。簡単に言うと、data.tableのアプローチは、結果のデータセットを生成するのではなく、データセットを更新することです。これが、data.tableとdplyrの本当の違いです。
Tensibai 2017年

1
本当にいい比較!たぶん、行うときに、matt_modjaap_dplyrを追加できますstrsplit fixed=TRUE。他の人が持っているように、これはタイミングに影響を与えます。R 4.0.0以降、を作成するときのデフォルトdata.framestringsAsFactors = FALSEなので、as.character削除できました。
GKi

94

いくつかの選択肢:

1)2つの方法

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)
リッチスクリーベン2017

31

パーティーには遅れますが、もう1つの一般化された代替案はcSplitdirection引数を持つ「splitstackshape」パッケージから使用することです。これを"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.