对于分组数据帧(dplyr)R中的每个元素,值的总和大于或等于

时间:2018-03-22 20:16:04

标签: r dplyr data.table

我有一个相对较大的数据帧(~2,000,000行),对于每一行,我需要在该观察组中计算大于或等于当前行值的每个值的总和。

以下是一个示例数据框:

sample_df = data.frame(
  group_id = c(1,1,1,1,2,2,2,2),
  value = c(10,12,14,12,8,8,21,10)
)

我目前有一个非常缓慢的解决方案,使用循环和一些过滤来做到这一点,但是,更好的解决方案是更优选的。我一直在尝试使用dplyr,但我无法弄清楚如何在数据分组后得到其他观察值的总和。

使用上面的玩具示例,这将是所需的输出:

desired_output = data.frame(
  group_id = c(1,1,1,1,2,2,2,2),
  value = c(10,12,14,12,8,8,21,10),
  output = c(38,26,0,26,39,39,0,21)
)

为了寻找已经发布的解决方案,我没有看到一个明确的答案,它解释了如何将一组中的每个观察结果与其他观察结果进行比较,并在该组中对某些标准进行过滤。我更喜欢基于dplyr的解决方案,但如果有高效的base-R或data.table解决方案,我会同样感激不尽!

4 个答案:

答案 0 :(得分:8)

使用tidyverse。诀窍是使用map_dbl循环每个value

library(tidyverse)
sample_df %>%
  group_by(group_id) %>%
  mutate(output= map_dbl(value,~sum(value[value>=.x]))-value) %>%
  ungroup

# A tibble: 8 x 3
  group_id value output
     <dbl> <dbl>  <dbl>
1        1    10     38
2        1    12     26
3        1    14      0
4        1    12     26
5        2     8     39
6        2     8     39
7        2    21      0
8        2    10     21
mutate行中的

value是您的value&#39;子列&#39; (小组),而.x是你正在循环的元素。

基础解决方案

within(sample_df,output <- unlist(tapply(
  value,group_id,function(x) sapply(x,function(y) sum(x[x>=y])-y))))
#   group_id value output
# 1        1    10     38
# 2        1    12     26
# 3        1    14      0
# 4        1    12     26
# 5        2     8     39
# 6        2     8     39
# 7        2    21      0
# 8        2    10     21

答案 1 :(得分:8)

不太紧凑,有点棘手,但速度更快,仅使用data.table

诀窍在于,一旦您的数据按每个group_id的值按降序排序,您需要计算的是group_id的累计总和,这非常快。

每当value在群组中多次出现时,您希望保留最后一次考虑所有先前出现的累积总和。

library(data.table)
DT=as.data.table(sample_df)[order(group_id,-value),]
DT[,output:=cumsum(value)-value,keyby=.(group_id)]
temp=DT[, .SD[.N], by=.(group_id,value)]  # Keep the last row by group and value
DT=merge(setDF(sample_df)[,.(group_id,value)],temp,by=c("group_id","value"),sort=F) 

#    group_id value output
# 1:        1    10     38
# 2:        1    12     26
# 3:        1    12     26
# 4:        1    14      0
# 5:        2     8     39
# 6:        2     8     39
# 7:        2    10     21
# 8:        2    21      0

此解决方案比4000观测基准提出的替代解决方案快10^6倍。它可以在不到一分钟的时间内完成10^8次观测。

#       N data.table.trick             dplyr          sapply              base
#1: 1e+06 0.067678928 secs 261.32966185 secs 282.639625 secs 275.08949995 secs
#2: 1e+05 0.013036013 secs   3.55517507 secs   5.356922 secs   3.36490607 secs
#3: 1e+04 0.007019043 secs   0.09926391 secs   0.312326 secs   0.04562092 secs

我使用以下基准计算了sys.Time()的时间:

N=10^8 # observation
G=20 # group
V=100 # values
sample_df = data.table(
  group_id = sample(1:G,N,replace=T),
  value = sample(1:V,V,replace=T)
)

答案 2 :(得分:4)

使用R base *apply函数。不像@Moody_Mudskipper那样可读,但是没有任何额外包的输出相同。

sample_df$output <- unlist(lapply(split(sample_df, sample_df$group_id), function(x){
  sapply(1:nrow(x), function(i){
    sum(x$value[x$value >= x$value[i]])-x$value[i];
  })
}))

sample_df

  group_id value output
1        1    10     38
2        1    12     26
3        1    14      0
4        1    12     26
5        2     8     39
6        2     8     39
7        2    21      0
8        2    10     21

答案 3 :(得分:3)

这是一个简单的非等连接问题:

library(data.table)
dt = as.data.table(sample_df)

dt[dt, on = .(group_id, value >= value), by = .EACHI,
   .(output = sum(x.value) - i.value)]
#   group_id value output
#1:        1    10     38
#2:        1    12     26
#3:        1    14      0
#4:        1    12     26
#5:        2     8     39
#6:        2     8     39
#7:        2    21      0
#8:        2    10     21