【问题标题】:R: fast/parallel multicolumn multirow lookupR:快速/并行多列多行查找
【发布时间】:2019-10-04 07:32:15
【问题描述】:

我有许多大型(300k - 1M 行)数据帧,我试图通过循环遍历数据帧 (df_i) 来附加值,并且对于每个数据帧,循环遍历行并询问第二个中的值是什么数据框是 (do2) 在匹配的纬度、经度、月份和深度。 Lat/lon/month 将完全匹配,深度比较棘手,因为 do2 有 57 列用于增加深度箱中的值:

我的逐行循环代码的核心是一个 3-ands 行子集和一个列子集:

for (j in 1:nrow(df_i)) {
  df_i[j,"DO2"] <- do2[do2$Latitude == df_i[j,"latbin"] &
                       do2$Longitude == df_i[j,"lonbin"] &
                       do2$Month == month(df_i[j,"DateTimeUTCmin5"]),
                       which.min(abs(depthbins - df_i[j, "Depth.m."])) + 3]
}

这可行,但速度很慢。我知道它可以加快速度,但是我的并行化工作一直在碰壁,而且并行调试/回溯要困难得多。我在阅读this 后尝试了 FBM,但得到了

值必须是唯一的或者是 x[i, j] 的维度

大约有 200k 行。我了解 data.table indexes are fast,所以可能像 Frank's comment here 这样的东西可能会起作用,也许是 data.table 中的多行子集?但大概这与我现有的解决方案是相同的方法(因为我还需要子集/查找列),只是可能更快一点?

有人知道更聪明的方法吗?我以前对 apply 函数感到困惑,但如果那里有什么有用的东西我不会感到惊讶?

提前致谢。

可重现(简化月份,添加深度箱,之前省略):

depthbins <- c(0,5,10,15,20,25,50,75,100,125,150,200,250,300,350,400)
df_i <- data.frame(latbin = c(-77.5, -78, -78.5),
                   lonbin = c(-178.5, -177.5, -176.5),
                   month = c(1,2,3),
                   Depth.m. = c(130,120,110))
do2 <- tibble(Month = c(1,1,1),
              Latitude = c(-78,-78,-79),
              Longitude = c(-178.5, -177.5, -177.5),
              "0" = c(214, 223, 345),
              "5" = c(123,234,345),
              "10" = c(345,456,567))

最终编辑:对 Marius 代码的一些调整:

do2 %<>% gather(.vars = colnames(do2)[4:length(colnames(do2))],
                key = "depbin", value = "DO2")
do2$depbin <- as.numeric(do2$depbin)
depthbins <- sort(unique(do2$depbin))
df_i$depbin = sapply(df_i$Depth.m., function(d) depthbins[which.min(abs(depthbins - d))])

df_i %<>% left_join(do2, by = c("Month" = "Month",
                                "latbin" = "Latitude",
                                "lonbin" = "Longitude",
                                "depbin" = "depbin")) %>%
          select(-Month, -latbin, -lonbin, -depbin)

【问题讨论】:

  • 您能否提供df_ido2depthbins 的可重现示例?它们只需每行几行,并带有几个深度箱。我认为这绝对可以加快速度,使用诸如合并之类的东西(由于Depth.m的匹配可能是模糊连接),而不必使用任何并行处理。
  • 另外,与Depth.m 匹配,您是否只是通过与bin 的中点进行比较来寻找Depth.m 值所在的bin?那么5 &lt;= Depth.m &lt; 10 的任何值都与5 bin 匹配吗?
  • 谢谢马吕斯。补充道。 Depth.m 匹配:我使用了从 df_i 深度到 depthbin 值的最小距离来生成索引,但现在考虑那条线,它与 bin 值有关,它是 bin 的开始而不是中点,所以可能我需要改善这一点!
  • 编辑:depthbins 代码确实有效,假设值取自这些 depthbins 深度点,代码将 df_i 深度与最近的 depthbins 深度对齐,这是正确的。应该知道是对的,因为不是我自己写的!!
  • 更多想法:可能我可以将多个深度列收集到 1 (r4ds.had.co.nz/tidy-data.html#gathering),将 do2 从 49128*60 更改为 2800296*4。这将使其大​​ 3.8 倍,但可能会促进标准的仅列查找/匹配/索引/连接操作。此外,我可以事先用 depthbin 查找替换深度,以从循环中删除计算...

标签: r indexing parallel-processing match lookup


【解决方案1】:

我认为通过一些重组,您可以将其作为合并来完成。合并部分应该比你的 for 循环方法快得多,这将被do2 的增加的大小和准备时间稍微抵消。请注意,我不得不稍微修改您的示例数据,以便每一行实际上都有一些要匹配的内容:

depthbins <- c(0,5,10,15,20,25,50,75,100,125,150,200,250,300,350,400)
df_i <- data.frame(latbin = c(-77.5, -78, -78.5),
                   lonbin = c(-178.5, -177.5, -176.5),
                   month = c(1,2,3),
                   Depth.m. = c(130,120,110))
do2 <- tibble(Month = c(1,2,3),
              Latitude = c(-77.5,-78,-78.5),
              Longitude = c(-178.5, -177.5, -176.5),
              "100" = c(214, 223, 345),
              "125" = c(123,234,345),
              "150" = c(345,456,567))


library(tidyverse)
# Precalculate closest bin for each row
df_i$bin = sapply(df_i$Depth.m., function(d) depthbins[which.min(abs(depthbins - d))])

# Convert do2 to long
do2_long = do2 %>%
    gather(bin, DO2, -Month, -Latitude, -Longitude) %>%
    mutate(bin = as.numeric(bin))

# Now everything can just be done as a merge
# The merge syntax would be a bit cleaner if you give the two df's
#   matching column names to start with
df_i %>%
    left_join(do2_long, by = c("month" = "Month", "latbin" = "Latitude", 
                               "lonbin" = "Longitude", "bin" = "bin"))

【讨论】:

  • 非常感谢! sapply 是一个非常优雅的解决方案,这就是为什么我需要了解应用程序的原因!对您的收集方法感兴趣 - 我发现这导致所有 do2_long$bin 值的 NA。此外,行数似乎是我预期的两倍。我将在问题中粘贴我的方法。干杯!!
  • 我试图 gather 除了 MonthLatitudeLongitude 列之外的所有内容,因为这意味着我正在收集所有深度 bin 列。如果do2 中有其他列,您可能需要调整gather 命令。 as.numeric 可能会生成所有的 binNA,如果它们不是像 "5""10" 等简单的字符串。
  • Cheers Marius - 现在使用我正在处理的收集代码进行排序,并使用调整后的代码更新问题。您节省了原本没有效率的一天 - 再次感谢。
  • 不用担心。我很想知道这能提高多少速度,因为当你试图解释 R 中 for 循环的缺点时,这样的例子总是很好的。
  • 定性回复:大量。并且,为更简洁的脚本大幅减少了代码行。并且,使用 *apply 解决应该允许使用(例如)parSapply 进一步加速,这是一个(接近)直接替代品,与(例如)foreach 需要各种结构重新排列(并且我不知道如何进行工作)相比!)。非常特别的是,这个任务导致最大的文件从 410 缩小到 380mb,添加了 1 列 160 万行。所有文件的总大小相同,所以这可能只是压缩优化。看起来还是很奇怪!
猜你喜欢
  • 2013-06-23
  • 1970-01-01
  • 1970-01-01
  • 2014-05-04
  • 2019-11-30
  • 1970-01-01
  • 2012-06-09
  • 2017-02-17
  • 1970-01-01
相关资源
最近更新 更多