R Plyr Sapply 似乎真的很慢

R Plyr Sapply seems to be really slow

我以为我有一个非常简单的数据帧转换,但出于某种原因我无法理解它似乎需要永恒,这让我怀疑它可能没有按照我的希望进行。任何人都可以解释一下吗?

第 1 部分 - 将源数据转换为单独的列(实际 df 有 260 万行)。

鉴于...

> V1 <- c("E11 2286 1", "ECAT 2286 1", "M11 2286 1", "M12 2286 1", "MCAT 2286 1", "C24 2287 1")
> df <- data.frame(V1)
> df
           V1
1  E11 2286 1
2 ECAT 2286 1
3  M11 2286 1
4  M12 2286 1
5 MCAT 2286 1
6  C24 2287 1

我想创建两个新列(itemID 和主题)并用 V1 中相应行的子字符串填充每个列。

这个我可以用;

> require(stringr)
> df$itemID <- sapply(1:nrow(df), function(i) str_split(df[i,"V1"]," ")[[1]][[2]] )
> df$topic  <- sapply(1:nrow(df), function(i) str_split(df[i,"V1"]," ")[[1]][[1]] )

但这需要几分钟时间,而且似乎应该有更有效的方法。所以首先我尝试使用 sapply;

> sapply(1:nrow(df), function(i) {
                                    t <- str_split(df[i,"V1"]," ")
                                    df$itemID <- t[[1]][[2]] 
                                    df$topic  <- t[[1]][[1]] 
                                  })

一个多小时后,什么也没有。所以我保释了,因为当单个命令花费不到 20 分钟时,这显然毫无进展。

下一个选项是尝试在单个任务上使用 ddply,但也失败了。

> require(plyr)
> require(stringr)
> df$itemID <- ddply(df, .(V1), str_split(df$V1," ")[[1]][[2]], .progress="text"  )

Error in get(as.character(FUN), mode = "function", envir = envir) : 
  object '2286' of mode 'function' was not found

所以对于这个任务的第一部分,任何人都可以;


第 2 部分 - 收集项目 ID 的所有主题 为了加分...我需要的任务的第二部分是获取 2.6M 行(现在在 3 列中)并折叠每一行以获得 itemID,以便所有主题都保存在一个单元格中。

输出应该看起来像...

  itemID    topic
1 2286      E11,ECAT,M11,M12,MCAT
2 2287      C24

任何人都可以建议一个简单的方法来将这样的行聚集到一个单元格中吗?

我们可以使用几个选项来提高速度。

1. stringi

stringi 包中的函数通常更快。我们可以使用 stri_extract_all_regex 和适当的 regex 来提取字母数字字符。在这里,我根据显示的示例使用 [[:alnum:]]{2,}rbind 列表元素 (do.call(rbind.data.frame,..)),使用 setNames 更改列名称,将 'data.frame' 转换为 'data.table' (setDT),以及 paste 按 'itemID' 分组的 'topic' 元素(toString- 是 paste(., collapse=', ') 的包装器)。

library(stringi)
library(data.table)
setDT(setNames(do.call(rbind.data.frame,stri_extract_all_regex(df$V1,
       '[[:alnum:]]{2,}')), c('topic', 'itemID')))[, 
          list(topic=toString(topic)), itemID]
#   itemID                     topic
#1:   2286 E11, ECAT, M11, M12, MCAT
#2:   2287                       C24

2。 dplyr/tidyr

我们可以使用 tidyr 中的 extract 通过指定适当的正则表达式将单列转换为多列,并且 paste 'topic' 由 [=43] 分组的元素=]

library(dplyr)
library(tidyr)
 extract(df, V1, into= c('topic', 'itemID'), '([^ ]+) ([^ ]+).*', 
                        convert=TRUE) %>% 
           group_by(itemID) %>% 
           summarise(topic=toString(topic))
#  itemID                     topic
#1   2286 E11, ECAT, M11, M12, MCAT
#2   2287                       C24

这个怎么样?使用 data.table v1.9.5:

require(data.table)
cols = c("topic", "itemID", "tmp")
setDT(df)[, c(cols) := tstrsplit(V1, " ", fixed=TRUE, type.convert=TRUE)]
df[, .(topic=paste(topic, collapse=", ")), by=itemID]
#    itemID                     topic
# 1:   2286 E11, ECAT, M11, M12, MCAT
# 2:   2287                       C24

260 万行的基准:

N = 2.6e6L
x = paste(rep(letters, length.out=N), sample(1e4, N, TRUE), "1", sep=" ")
dat = data.frame(x, stringsAsFactors=FALSE)
nrow(dat) # 2.6 million

# dplyr+tidyr
system.time({ans1 <- extract(dat, x, into= c('topic', 'itemID'), 
         '([^ ]+) ([^ ]+).*', convert=TRUE) %>% 
          group_by(itemID) %>% 
         summarise(topic=toString(topic))})
#    user  system elapsed 
#  45.643   0.854  46.777 

# data.table
system.time({
    cols = c("topic", "itemID", "tmp")
    setDT(dat)[, c(cols) := tstrsplit(x, " ", fixed=TRUE, type.convert=TRUE)]
    ans2 <- dat[, .(topic=paste(topic, collapse=", ")), by=itemID]
})    
#    user  system elapsed 
#   1.906   0.064   1.981 

identical(as.data.frame(ans1), setDF(ans2[order(itemID)]))
# [1] TRUE

加速为~24x


更新: 运行 data.table先回答再dplyr回答结果运行次7s44s,产生 ~6.3x 的加速。在 dplyr.

之后 运行 时,data.table 方法似乎有一些缓存效率