"foreach" 并行循环返回<NA>

6
我正在尝试并行处理多个列表项。
我的目标是:对每一列运行一些标记函数,基于其值。然后返回带有节点名称、列名称和处理后标签的数据框。
使用普通for循环工作流程正常。但是,当我尝试在foreach循环中执行相同的操作时,返回的结果如下: (请注意:以下内容仅为原始数据集的抽象表示)
我不确定到底发生了什么问题。如果您能帮我解决这个问题,那就太好了 :-)
set.seed(12345)
options(stringsAsFactors = F)


# I. Random data generation (Original data is in data frame format)
random.data = list()
random.data[["one"]] = as.data.frame(matrix(data = runif(n = 15), ncol = 3))
random.data[["two"]] = as.data.frame(matrix(data = runif(n = 15), ncol = 3))
random.data[["three"]] = as.data.frame(matrix(data = runif(n = 15), ncol = 3))



# II. Some function applied to each column to label/classify the values
valslabel = function(DataCOlumn) {
  if(mean(DataCOlumn) < 0.5) return("low")
  return("high")
}



# III. Generating the desired output in a regular for loop : 

desiredOutput = list()

for(frame.i in seq_along(random.data)) {

  frame = random.data[[frame.i]]
  frame.name = names(random.data)[frame.i]
  frame.results = data.frame(frame.name = character(0), 
                  mappedField = character(0), label = character(0) )

  for(col.i in 1:ncol(frame)) {
    frame.results[col.i, "frame.name"] = frame.name
    frame.results[col.i, "mappedField"] = colnames(frame)[col.i]
    frame.results[col.i, "label"] = valslabel(frame[,col.i])  
  }

  desiredOutput[[frame.name]] = frame.results
}


print(desiredOutput)

# $one
# frame.name mappedField label
# 1        one          V1  high
# 2        one          V2  high
# 3        one          V3   low
# 
# $two
# frame.name mappedField label
# 1        two          V1   low
# 2        two          V2  high
# 3        two          V3   low
# 
# $three
# frame.name mappedField label
# 1      three          V1   low
# 2      three          V2  high
# 3      three          V3  high




# IV. Using the "foreach" parallel execution

library(foreach)
library(doParallel)

cl = makeCluster(6)
registerDoParallel(cl)

output = foreach(frame.i = seq_along(random.data), .verbose = T) %dopar% {

  frame = random.data[[frame.i]]
  frame.name = names(random.data)[frame.i]
  frame.results = data.frame(frame.name = character(0), mappedField = character(0), label = character(0) )

  for(col.i in 1:ncol(frame)) {
    frame.results[col.i, "frame.name"] = frame.name
    frame.results[col.i, "mappedField"] = colnames(frame)[col.i]
    frame.results[col.i, "label"] = valslabel(frame[,col.i])  
  }

  return(frame.results)
}


print(output)

# [[1]]
# frame.name mappedField label
# 1       <NA>        <NA>  <NA>
# 2       <NA>        <NA>  <NA>
# 3       <NA>        <NA>  <NA>
#   
# [[2]]
# frame.name mappedField label
# 1       <NA>        <NA>  <NA>
# 2       <NA>        <NA>  <NA>
# 3       <NA>        <NA>  <NA>
#   
# [[3]]
# frame.name mappedField label
# 1       <NA>        <NA>  <NA>
# 2       <NA>        <NA>  <NA>
# 3       <NA>        <NA>  <NA>

谢谢!

1个回答

3
问题与您初始化数据框的方式有关,以及在 foreach 环境中,选项 stringsAsFactors 没有设置为 FALSE。在每个 foreach 循环中发生的情况类似于这样。
options(stringsAsFactors = FALSE)
d <- data.frame(x =character(0))
d[1, "x"] <- "a"
#Warning message:
#In `[<-.factor`(`*tmp*`, iseq, value = "a") :
#  invalid factor level, NA generated
d
#     x
#1 <NA>

请注意,这仅仅是一个警告而非错误,因此循环不会停止。如果您首先将 stringsAsFactors 设置为 FALSE,则没有问题(就像在未并行运行时所做的那样)。
options(stringsAsFactors = FALSE)
d <- data.frame(x =character(0))
d[1, "x"] <- "a"
d
#  x
#1 a

在您的全局环境中,您已经设置了options(stringsAsFactors = FALSE),所以%do%循环可以工作。然而,此选项不会传递到每个并行作业的本地环境中,因此%dopar%循环会遇到上述问题。
例如,查看以下输出。
options(stringsAsFactors = FALSE)
.Options$stringsAsFactors
#[1] FALSE
foreach(i = 1:3) %dopar% .Options$stringsAsFactors
#[[1]]
#[1] TRUE
#
#[[2]]
#[1] TRUE
#
#[[3]]
#[1] TRUE

因此,解决方案是在foreach循环中设置选项stringsAsFactors = FALSE

另外,如果可能的话,最好使用整个列向量而不是逐行创建数据框。在你的示例中,你可以替换

frame.results = data.frame(frame.name = character(0), mappedField = character(0), label = character(0))
for(col.i in 1:ncol(frame)) {
    frame.results[col.i, "frame.name"] = frame.name
    frame.results[col.i, "mappedField"] = colnames(frame)[col.i]
    frame.results[col.i, "label"] = valslabel(frame[,col.i])  
}

使用

frame.results <- data.frame( 
    frame.name = frame.name, 
    mappedField = colnames(frame), 
    label = valslabel1(colMeans(frame)))

其中valslabel函数已被替换为矢量化版本。

valslabel1 <- function(x) {
    ifelse(x < 0.5, "low", "high")
}

谢谢Konvas。非常非常好的捕捉!! - user5363218
2
顺便提一下,我注意到如果你使用参数cores注册并行后端,即registerDoParallel(cores = 6),该选项会被传递到每个作业的本地环境中...不确定为什么会这样,以及它是否与平台有关。@Deena @cryo111 - konvas
2
@konvas 很有趣。我在我的Linux Mint 17.1机器上检查了一下,就像你说的那样,“registerDoParallel(cores = 6)”会使所有进程的“stringsAsFactors”选项设置为“FALSE”。 - cryo111
2
@konvas 刚刚查看了 registerDoParallel 的源代码。非 Windows 操作系统的区别似乎在于,当 missing(cl)==TRUE 时,会调用 setDoPar(doParallelMC, cores, mcinfo),而当 missing(cl)==FALSE 时,则会执行 setDoPar(doParallelSNOW, cl, snowinfo) 这一行。因此,似乎 doParallelMC 导出了选项,而 doParallelSNOW 则没有。 - cryo111

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接