首页 文章

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

提问于
浏览
9

我有一个相对较大的数据帧(~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 回答

  • 4

    使用 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 'subcolumn'(该组),而 .x 是你正在循环的元素 .

    a base solution

    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
    
  • 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
    

    该解决方案比为观察基准提出的替代方案快了~2501587次 . 它可以在不到一分钟的时间内完成最多 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)
    )
    
  • 3

    使用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
    
  • 8

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

    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
    

相关问题