我有一个相对较大的数据帧(~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解决方案,我会同样感激不尽!
答案 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