1. ホーム
  2. r

[解決済み] カラム内のカンマ区切り文字列を別の行に分割する

2022-06-22 06:18:31

質問

このようなデータフレームがあります。

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 つの行に分割し、それぞれ単一の名前で director カラムにそれぞれ単一の名前を、'A' は AB カラムに

どのように解決するのですか?

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

ベンチマークされたソリューションは以下の通りです。

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

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 私の好奇心を刺激したのは というコメントで興味をそそられました。 素晴らしい! 桁外れに速い! tidyverse の答えになります。 質問 の回答で、この質問と重複しているとして閉じられました。