【问题标题】:split column in data.table to multiple rows [duplicate]将data.table中的列拆分为多行[重复]
【发布时间】:2016-04-15 06:07:28
【问题描述】:

我经常有表格,其中单个单元格可能包含多个值(由一些字符分隔符划分),我需要拆分这些记录,例如:

dt1 <- fread("V1 V2 V3
             x b;c;d 1
             y d;ef  2
             z d;ef  3")

应该这样写:

#    V1 V2 V3
# 1:  x  b  1
# 2:  x  c  1
# 3:  x  d  1
# 4:  y  d  2
# 5:  y ef  2
# 6:  z  d  3
# 7:  z ef  3

到目前为止,我做了以下功能:

# I omit all error-checking code here and assume that
# dtInput   is a valid data.table and
# col2split is a name of existing column
splitcol2rows <- function(dtInput, col2split, sep){
  ori.names <- names(dtInput); # save original order of columns
  ori.keys  <-   key(dtInput); # save original keys

  # create new table with 2 columns:
  # one is original "un-splitted" column (will be later used as a key)
  # and second one is result of strsplit:
  dt.split <- dtInput[, 
                    .(tmp.add.col=rep(unlist(strsplit(get(col2split),sep,T)), .N)),
                    by=col2split]
  dt.split <- unique(dt.split, by=NULL);

  # now use that column as a key:
  setkeyv(dt.split, col2split)
  setkeyv(dtInput, col2split)
  dtInput <- dt.split[dtInput, allow.cartesian=TRUE];

  # leave only 'splitted' column
  dtInput[, c(col2split):=NULL]; 
  setnames(dtInput, 'tmp.add.col', col2split); 

  # restore original columns order and keys
  setcolorder(dtInput, ori.names);
  setkeyv(dtInput, ori.keys);

  return(dtInput);
}

它工作正常(检查示例输出为splitcol2rows(dt1, 'V2', ';')[]),但我确信这个解决方案远非最佳,如果有任何建议,我将不胜感激。例如,我查看了 Matt 在对“Applying a function to each row of a data.table”问题的回答中提出的解决方案,我喜欢它在不创建中间表(我的dt.split)的情况下进行管理,但在我的情况下,我需要保留所有其他列并且不知道该怎么做。


UPD。首先,从@RichardScriven 提出的解决方案开始,我开始重写我的函数,让它变得更短更容易阅读:

splitcol2rows_mget <- function(dtInput, col2split, sep){
  dtInput <- dtInput[, .(tmp.add.col = unlist(strsplit(get(col2split),sep,T))), by=names(dtInput)]

  dtInput[, c(col2split):=NULL];
  setnames(dtInput, 'tmp.add.col', col2split); 
  return(dtInput);
}

它仍然有一些难看的部分,比如中间的 'tmp.add.col' 列,如果这些列已经存在于原始表中,可能会导致冲突。此外,这个更短的解决方案比我的第一个代码运行得更慢。并且它们都比来自splitstackshape 包的cSplit() 慢:

require('microbenchmark')
require('splitstackshape')

splitMy1 <- function(input){return(splitcol2rows(input, col2split = 'V2', sep = ';'))}
splitMy2 <- function(input){return(splitcol2rows_mget(input, col2split = 'V2', sep = ';'))}
splitSH  <- function(input){return(cSplit(input, splitCols = 'V2', sep = ';', direction = 'long'))}

# Smaller table, 100 repeats:
set.seed(1)
num.rows <- 1e4;
dt1 <- data.table(V1=seq_len(num.rows),
                  V2=replicate(num.rows,paste0(sample(letters, runif(1,1,6), T), collapse = ";")),
                  V3=rnorm(num.rows))
print(microbenchmark(splitMy1(dt1), splitMy2(dt1), splitSH(dt1), times=100L))
#Unit: milliseconds
#          expr      min       lq     mean   median       uq       max neval
# splitMy1(dt1) 56.34475 58.53842 68.11128 62.51419 79.79727  98.96797   100
# splitMy2(dt1) 61.84215 64.59619 76.41503 69.02970 88.49229 132.43679   100
#  splitSH(dt1) 31.29671 33.14389 38.28108 34.91696 39.31291  83.58625   100    

# Bigger table, 1 repeat:
set.seed(1)
num.rows <- 5e5;
dt1 <- data.table(V1=seq_len(num.rows),
                  V2=replicate(num.rows,paste0(sample(letters, runif(1,1,6), T), collapse = ";")),
                  V3=rnorm(num.rows))
print(microbenchmark(splitMy1(dt1), splitMy2(dt1), splitSH(dt1), times=1L))

#Unit: seconds
#          expr      min       lq     mean   median       uq      max neval
# splitMy1(dt1) 2.955825 2.955825 2.955825 2.955825 2.955825 2.955825     1
# splitMy2(dt1) 3.693612 3.693612 3.693612 3.693612 3.693612 3.693612     1
#  splitSH(dt1) 1.990201 1.990201 1.990201 1.990201 1.990201 1.990201     1

【问题讨论】:

  • 不久前对a question I asked 的回答可能会给您想要的答案。
  • @brittenb,非常感谢,cSplit() 确实完成了这项工作!也许我会做一些基准测试来检查他们的代码是否与大表一样快)
  • 好吧,您可以使用dt1[, c(.(V2 = scan(text = V2, sep = ";", what = "")), mget(names(dt1)[-(1:2)])), by = V1] 获取所有列,但那时我只会使用cSplit() ;-)
  • @RichardScriven,感谢您的意见!但不幸的是,这个代码对于大表来说非常慢。稍后我会添加更多详细信息。
  • @brittenb,也许您发表您对cSplit() 的评论作为答案?我很乐意将其标记为解决方案。我还将添加我的基准测试结果。

标签: r data.table


【解决方案1】:

splitstackshape 中有一个名为cSplit 的函数非常适合此任务。只需通过“;”作为分隔符,“long”作为获取我们需要的方向。

> library(splitstackshape)
> dat <- data.frame(V1 = c("x", "y", "z"), V2 = c("b;c;d", "d;ef", "d;ef"), V3 = 1:3, stringsAsFactors = FALSE)
> cSplit(dat, "V2", sep = ";", direction = "long")
#   V1 V2 V3
# 1:  x  b  1
# 2:  x  c  1
# 3:  x  d  1
# 4:  y  d  2
# 5:  y ef  2
# 6:  z  d  3
# 7:  z ef  3

【讨论】:

    猜你喜欢
    • 2017-03-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-08-24
    • 2018-10-17
    • 1970-01-01
    • 2017-11-08
    • 2016-08-09
    相关资源
    最近更新 更多