【问题标题】:R Plyr Sapply seems to be really slowR Plyr Sapply 似乎真的很慢
【发布时间】:2015-03-27 10:11:29
【问题描述】:

我以为我有一个非常简单的数据框转换,但由于某种原因,我无法理解它似乎需要很长时间,这让我怀疑它可能没有达到我的预期。有人能解释一下吗?

第 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 中相应行的 substr 填充每一列。

这个我可以用;

> 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

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

  • i) 告诉我哪种方法可能更快(sapply 或 ddply),并且
  • ii) 使用该方法提供了关于如何将列 V1 拆分为两个必需的组件列的解决方案?

第 2 部分 - 为 itemID 收集所有主题 对于奖励积分...我需要的任务的第二部分是获取 260 万行(现在为 3 列)并折叠每一行以获取 itemID,以便所有主题都保存在一个单元格中。

输出最终应该看起来像...

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

谁能建议一种简单的方法将这样的行聚集到一个单元格中?

【问题讨论】:

    标签: r plyr sapply


    【解决方案1】:

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

    1.字符串i

    stringi 包中的函数通常更快。我们可以使用stri_extract_all_regex 和适当的regex 来提取字母数字字符。在这里,我根据显示的示例使用[[:alnum:]]{2,}rbind列表元素(do.call(rbind.data.frame,..)),用setNames更改列名,将'data.frame'转换为'data.table'(setDT),paste将'topic'元素分组通过 'itemID'(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按“itemID”分组的“主题”元素将单列转换为多列

    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
    

    【讨论】:

    • 哇...我只是试图理解第一个答案(这对我的问题的两个部分都有效)。所以 stringi 和 stringr 大致相等,使用 str_extract_all 没有执行速度差异。我喜欢使用 dplyr 和 tidyr 的第二个示例,因为我一直在尝试掌握项目中其他地方的示例,因此更多示例会更好。超级小伙子,非常非常有帮助。
    • @BarneyC stringi 函数非常快。你也可以试试extract,它也应该很快。 (虽然没有测试)
    • 我会好好看看 stringi 包,因为它确实似乎包含了更多方法。在这种情况下,没有速度优势(在 2.6M 行上都在 0.2 秒内执行!)但它可能在其他地方更加灵活。
    • 我的立场是正确的...@akrun 你是个天才!为所有 260 万行在第一种方法中运行适当的时间。 Stringr 5.37 分钟 vs Stringi 1.73 分钟(快 3 倍)。现在检查 dlypr/tidyr 方法
    • @BarneyC 感谢您返回基准测试。创建这些很棒的软件包的人是天才,而不是我 :-)
    【解决方案2】:

    这个怎么样?使用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 answer 然后运行 ​​dplyr answer 会导致运行时间分别为 7s44s,从而产生加速~6.3 倍。在dplyr 之后运行时,data.table 方法似乎有一些缓存效率。

    【讨论】:

    • 我最初有tstrsplit 的想法,但后来我认为stringi 会很快。感谢您的基准测试。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-11
    • 1970-01-01
    • 1970-01-01
    • 2015-02-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多