【问题标题】:Sum of values greater than or equal too for each element in grouped dataframe (dplyr) R分组数据帧(dplyr)R中每个元素大于或等于的值总和
【发布时间】:2018-09-01 10:13:20
【问题描述】:

我有一个相对较大的数据框(约 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 解决方案,我将不胜感激!

【问题讨论】:

  • 第 1 组中的值 12 为什么会输出 24?我希望 26
  • 我很好奇 data.table 解决方案!!
  • ...因为我刚刚在创建输出时搞砸了二年级数学:(。为那个错误道歉!
  • 我允许自己编辑它;)

标签: r dplyr data.table


【解决方案1】:

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

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

【讨论】:

【解决方案2】:

不太紧凑,有点棘手,但速度更快,并且只使用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

此解决方案比为10^6 观察基准提出的替代解决方案快4000 倍。它可以在不到一分钟的时间内完成多达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)
)

【讨论】:

  • 使用cumsum 的好技巧!你能包括你的基准吗?
  • 值得一提的是,您正在丢失行顺序,这可能对 OP 很重要。
  • 我将编辑我的答案。基准是指我用来进行时间比较的模拟数据集吗?
  • 是的,可重复的比较,在SOmicrobenchmark 上经常使用,如果你在搜索工具中查找,你会发现很多。
  • 我没有考虑保留行顺序。谢谢!
【解决方案3】:

使用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 是您要循环的元素。

基本解决方案

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

【讨论】:

  • 这是一个非常优雅的解决方案。如果您不介意我问,map_dbl 函数中的“价值”是什么?它是 tidyverse 的关键短语吗?编辑:没关系,我在我的玩具示例变量名称上隔开。
  • 这对于 10^6 行的数据框来说可能非常慢吗?
  • 值是我认为数据集中的变量
  • 正如 Max Ft 所说:)。我更新了一个小解释,告诉我它是否仍然令人困惑。
【解决方案4】:

使用 R 基础 *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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-01-12
    • 1970-01-01
    • 2022-10-12
    • 2016-04-02
    • 2020-06-02
    相关资源
    最近更新 更多