用doMC替换并联plyr

时间:2017-12-01 16:42:27

标签: r dplyr plyr tidyverse multidplyr

考虑对data.frame进行标准的分组操作:

library(plyr)
library(doMC)
library(MASS) # for example

nc <- 12
registerDoMC(nc)

d <- data.frame(x = c("data", "more data"), g = c("group1", "group2"))
y <- "some global object"

res <- ddply(d, .(g), function(d_group) {
   # slow, complicated operations on d_group
}, .parallel = FALSE)

通过简单地编写.parallel = TRUE来利用多核设置是微不足道的。这是我最喜欢的plyr特色之一。

但是随着plyr被弃用(我认为)并且基本上被dplyr,purrr等取代,并行处理的解决方案变得更加冗长:

library(dplyr)
library(multidplyr)
library(parallel)
library(MASS) # for example

nc <- 12

d <- tibble(x = c("data", "more data"), g = c("group1", "group2"))
y <- "some global object"

cl <- create_cluster(nc)
set_default_cluster(cl)
cluster_library(cl, packages = c("MASS"))
cluster_copy(cl, obj = y)

d_parts <- d %>% partition(g, cluster = cl)
res <- d_parts %>% collect() %>% ungroup()

rm(d_parts)
rm(cl)

您可以想象这个示例可以考虑在循环内需要的每个包和对象需要多长时间才能将自己的cluster_*命令复制到节点上。非并行化的plyr-to-dplyr转换只是一个简单的dplyr::group_by结构,不幸的是,没有简洁的方法来启用并行处理。所以,我的问题是:

  • 这实际上是将我的代码从plyr转换为dplyr的首选方式吗?
  • 在plyr的幕后发生了什么样的魔法,这使得开启并行处理变得如此容易?是否有理由将此功能添加到dplyr特别困难,这就是为什么它还不存在?
  • 我的两个例子在代码执行方式方面有根本的不同吗?

1 个答案:

答案 0 :(得分:3)

  1. 我认为将{plyr}代码翻译为{dplyr}并不存在一种真正的“首选”方法。

  2. 在评论中,@Aurèle在描述{plyr}和{doMC}之间的联系方面做得比我做得更好。发生的一件事是激励措施有所改变。 {doMC}来自Revolution Analytics(自微软购买)。但是开发dplyr的Hadley目前在RStudio工作。这两家公司在IDE领域展开竞争。因此,他们的包装设计不能很好地结合起来也许很自然。我已经看到对RStudio的强烈支持的唯一形式的并行性是{sparklyr},他们已经相对“容易”设置了。但是,我不能真正推荐使用Spark来为一台机器进行并行处理。

  3. @Aurèle再次在解释执行差异方面做得很好。您的新代码使用PSOCK群集和旧代码使用的分叉。 Forks在写入模式下使用副本来访问RAM,因此并行进程可以在fork之后立即访问相同的数据。 PSOCK集群就像产生R的新副本一样 - 它们必须加载库并接收数据的明确副本。

  4. 您可以使用类似......

    的模式
    library(dplyr)
    library(purrr)
    library(future)
    plan(multicore)
    options(mc.cores = availableCores())
    d <- data.frame(x = 1:8, g = c("group1", "group2", "group3", "group4"))
    y <- "some global object"
    
    
    split(d, d$g) %>% 
      map(~ future({Sys.sleep(5);mean(.x$x)})) %>% 
      map_df(~value(.x))
    

    ...在map_df步骤上有一些技巧可以进行一些并行处理。请注意,在{purrr}下,〜是匿名函数语法,其中.x是已映射的值。

    如果您喜欢危险地生活,您可以通过在{purrr}

    中使用私有方法来创建类似的版本而不使用{future}
    mcmap <- function(.x, .f, ...) {
      .f <- as_mapper(.f, ...)
      mclapply(.x, function(.x) {
        force(.f)
        .Call(purrr:::map_impl, environment(), ".x", ".f", "list")
      }) %>%
        map(~ .x[[1]])
    }