【问题标题】:Recursively sum data frames for matching rows递归求和匹配行的数据帧
【发布时间】:2018-07-06 17:30:16
【问题描述】:

我想通过对具有匹配变量的列求和(而不是附加列)将一组数据框组合成一个数据框。

例如,给定

df1 <- data.frame(A = c(0,0,1,1,1,2,2), B = c(1,2,1,2,3,1,5), x = c(2,3,1,5,3,7,0))
df2 <- data.frame(A = c(0,1,1,2,2,2), B = c(1,1,3,2,4,5), x = c(4,8,4,1,0,3))
df3 <- data.frame(A = c(0,1,2), B = c(5,4,2), x = c(5,3,1))

我想通过"A""B" 进行匹配并对"x" 的值求和。对于这个例子,我可以得到想要的结果如下:

library(plyr)
library(dplyr)
# rename columns so that join_all preserves them all:
colnames(df1)[3] <- "x1"
colnames(df2)[3] <- "x2"
colnames(df3)[3] <- "x3"
# join the data frames by matching "A" and "B" values:
res <- join_all(list(df1, df2, df3), by = c("A", "B"), type = "full")
# get the sums and drop superfluous columns:
arrange(res, A, B) %>% 
  rowwise() %>% 
  mutate(x = sum(x1, x2, x3, na.rm = TRUE)) %>% 
  select(A, B, x)

结果:

       A     B     x
   <dbl> <dbl> <dbl>
 1     0     1     6
 2     0     2     3
 3     0     5     5
 4     1     1     9
 5     1     2     5
 6     1     3     7
 7     1     4     3
 8     2     1     7
 9     2     2     2
10     2     4     0
11     2     5     3

更通用的解决方案是

library(dplyr)
# function to get the desired result for two data frames:
my_merge <- function(df1, df2)
{
  m1 <- merge(df1, df2, by = c("A", "B"), all = TRUE)
  m1 <- rowwise(res) %>% 
    mutate(x = sum(x.x, x.y, na.rm = TRUE)) %>%
    select(A, B, x)
  return(m1)
}
l1 <- list(df2, df3) # omit the first data frame
res <- df1 # initial value of the result
for(df in l1) res <- my_merge(res, df) # call the function repeatedly

有没有更有效的方法来组合大量数据框?理想情况下,它应该是递归的(即,在计算总和之前,最好不要将所有数据帧加入一个庞大的数据帧)。

【问题讨论】:

  • 如果您说mergefull_join 内存效率更高,那没关系,但我认为rowwise 和后来的sum 效率低下。我会使用rowSumsreduce+
  • 很好,谢谢!所以我可以将my_merge 中的第二行替换为res &lt;- res %&gt;% mutate(x = rowSums(select(., x.x, x.y), na.rm = TRUE)) %&gt;% select(A, B, x)(根据stackoverflow.com/questions/27354734/…)。

标签: r dplyr plyr


【解决方案1】:

更简单的选择是绑定数据集的行,然后按感兴趣的列分组,并通过获取“x”的sum 来获得汇总输出

library(tidyverse)
bind_rows(df1, df2, df3) %>% 
        group_by(A, B) %>%
        summarise(x = sum(x))
# A tibble: 11 x 3
# Groups:   A [?]
#       A     B     x
#   <dbl> <dbl> <dbl>
# 1     0     1     6
# 2     0     2     3
# 3     0     5     5
# 4     1     1     9
# 5     1     2     5
# 6     1     3     7
# 7     1     4     3
# 8     2     1     7
# 9     2     2     2
#10     2     4     0
#11     2     5     3

如果全局环境中有很多对象,模式"df"后跟一些数字

mget(ls(pattern= "^df\\d+")) %>%
        bind_rows %>%
        group_by(A, B) %>% 
        summarise(x = sum(x))

正如OP提到的memory约束,如果我们先执行join,然后使用rowSums+reduce,效率会更高

mget(ls(pattern= "^df\\d+")) %>% 
      reduce(full_join, by = c("A", "B")) %>%
      transmute(A, B, x = rowSums(.[3:5], na.rm = TRUE)) %>%
      arrange(A, B)
#   A B x
#1  0 1 6
#2  0 2 3
#3  0 5 5
#4  1 1 9
#5  1 2 5
#6  1 3 7
#7  1 4 3
#8  2 1 7
#9  2 2 2
#10 2 4 0
#11 2 5 3

这也可以通过data.table 完成

library(data.table)
rbindlist(mget(ls(pattern= "^df\\d+")))[, .(x = sum(x)), by = .(A, B)]

【讨论】:

  • 谢谢!我考虑过bind_rows,但它不会导致暂时在内存中保存一个(可能很大的)组合数据帧吗?
  • @rob 我在考虑效率部分
  • 很公平。我想很难避免在速度和内存使用之间进行权衡。
【解决方案2】:

理想情况下,它应该是递归的(即,在计算总和之前,最好不要将所有数据帧加入一个庞大的数据帧)。

如果您的内存受限并且愿意牺牲速度(与 @akrun 的 data.table 方法相比),请在循环中一次使用一个表:

library(data.table)
tabs = c("df1", "df2", "df3")

# enumerate all combos for the results table
# initializing sum to 0
res = CJ(A = 0:2, B = 1:5, x = 0)
# loop over tabs, adding on
for (i in seq_along(tabs)){
  tab = get(tabs[[i]])
  res[tab, on=.(A, B), x := x + i.x][]
  rm(tab)
}

如果您需要从磁盘读取表,请将tabs 更改为文件名,将get 更改为fread 或任何函数。

我怀疑您是否可以将所有表格放在内存中,但也不能将它们的rbind-ed 副本放在一起。


同样(感谢@akrun 的评论),成对使用他的方法:

res = data.table(get(tabs[[1]]))[0L]

for (i in seq_along(tabs)){
  tab = get(tabs[[i]])
  res = rbind(res, tab)[, .(x = sum(x)), by=.(A,B)]
  rm(tab)
}

【讨论】:

  • 谢谢!事实上,我正在从一个单独的文件中读取每个数据帧,所以循环选项很诱人。很高兴有您和@akrun 提供的各种选项来测试最佳内存/速度。
  • @rob Cool :) 如果您想出另一种方法,请告诉我们(例如,通过另一个答案)。我不习惯关注 RAM,所以真的不知道最好的方法。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-02-26
  • 2021-10-16
相关资源
最近更新 更多