【问题标题】:add columns to data frame using foreach and %dopar%使用 foreach 和 %dopar% 向数据框添加列
【发布时间】:2011-12-16 02:36:15
【问题描述】:

在 Windows 7 和 Ubuntu 64 位 11.04 上的 Revolution R 2.12.2 中,我有一个包含超过 100K 行和超过 100 列的数据框,并且我为每一列派生了大约 5 列(sqrt、log、log10 等)原始列并将它们添加到同一数据框中。如果没有使用 foreach 和 %do% 的并行性,这可以正常工作,但速度很慢。当我尝试将它与 foreach 和 %dopar% 并行化时,它不会访问全局环境(以防止竞争条件或类似情况),因此我无法修改数据框,因为“找不到数据框对象”。

我的问题是我怎样才能加快速度?换句话说,如何并行化列或转换?

简化示例:

require(foreach)    
require(doSMP)
w <- startWorkers()
registerDoSMP(w)

transform_features <- function()
{    
    cols<-c(1,2,3,4) # in my real code I select certain columns (not all)

    foreach(thiscol=cols, mydata) %dopar% { 
        name <- names(mydata)[thiscol]
        print(paste('transforming variable ', name))
        mydata[,paste(name, 'sqrt', sep='_')] <<- sqrt(mydata[,thiscol])
            mydata[,paste(name, 'log', sep='_')] <<- log(mydata[,thiscol])
    }
}


n<-10 # I often have 100K-1M rows
mydata <- data.frame(
    a=runif(n,1,100),
    b=runif(n,1,100),
    c=runif(n,1,100),
    d=runif(n,1,100)
    )

ncol(mydata) # 4 columns

transform_features()

ncol(mydata) # if it works, there should be 8

请注意,如果您将 %dopar% 更改为 %do%,它可以正常工作

【问题讨论】:

    标签: performance r foreach parallel-processing


    【解决方案1】:

    尝试data.table 中的:= 运算符以通过引用添加列。你需要with=FALSE,这样你就可以在:=的LHS上拨打paste

    When should I use the := operator in data.table?

    【讨论】:

      【解决方案2】:

      如果你做类似的事情可能会更容易

      n<-10
      mydata <- data.frame(
          a=runif(n,1,100),
          b=runif(n,1,100),
          c=runif(n,1,100),
          d=runif(n,1,100)
          )
      
      mydata_sqrt <- sqrt(mydata)  
      colnames(mydata_sqrt) <- paste(colnames(mydata), 'sqrt', sep='_')
      
      mydata <- cbind(mydata, mydata_sqrt)
      

      产生类似的东西

      > mydata
                 a         b         c        d   a_sqrt   b_sqrt   c_sqrt   d_sqrt
      1  29.344088 47.232144 57.218271 58.11698 5.417018 6.872565 7.564276 7.623449
      2   5.037735 12.282458  3.767464 40.50163 2.244490 3.504634 1.940996 6.364089
      3  80.452595 76.756839 62.128892 43.84214 8.969537 8.761098 7.882188 6.621340
      4  39.250277 11.488680 38.625132 23.52483 6.265004 3.389496 6.214912 4.850240
      5  11.459075  8.126104 29.048527 76.17067 3.385126 2.850632 5.389669 8.727581
      6  26.729365 50.140679 49.705432 57.69455 5.170045 7.081008 7.050208 7.595693
      7  42.533937  7.481240 59.977556 11.80717 6.521805 2.735186 7.744518 3.436157
      8  41.673752 89.043099 68.839051 96.15577 6.455521 9.436265 8.296930 9.805905
      9  59.122106 74.308573 69.883037 61.85404 7.689090 8.620242 8.359607 7.864734
      10 24.191878 94.059012 46.804937 89.07993 4.918524 9.698403 6.841413 9.438217
      

      【讨论】:

      • 感谢您的想法。我没有那样想。这段代码更简单但不是并行的(我有 8 个真正的 CPU 内核),所以我必须考虑如何让它并行。同样在我的真实案例中,我只将转换(sqrt、log 等)应用于数据框中大约一半的列,因此完成这可能会消除简单性。
      【解决方案3】:

      有两种方法可以解决这个问题:

      1. 遍历每一列(或者,更好的是,列的子集)并应用转换来创建一个临时数据框,返回它,然后对数据框列表执行cbind,如@Henry 建议。

      2. 循环转换,将每个转换应用于数据帧,然后返回转换数据帧cbind,然后继续。

      就我个人而言,我倾向于这样做的方式是创建一个bigmatrix 对象(在内存中或磁盘上,使用bigmemory 包),您可以访问共享内存中的所有列。只需预先分配您将填写的列,您无需事后处理cbind。我倾向于在磁盘上进行。只需确保运行flush(),以确保所有内容都写入磁盘。

      【讨论】:

      • 最初我没有考虑循环转换。这可能更容易。我会考虑你的建议。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-23
      • 1970-01-01
      • 2018-10-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多