正确使用 dplyr 函数在滑动 window 中计算每个产品的销售额,而不需要额外的传递或加入?
Correct use of dplyr functions to compute per-product sales in sliding-window, without needing extra pass or join?
给定一个由以下定义的数据框:
set.seed(1)
date <- sample(seq(as.Date('2016/01/01'), as.Date('2016/12/31'), by="day"), 12)
vals <- data.frame(x = rep(1:3, 4), date = date, cost = rnorm(12, 100))
vals
# x date cost
# 1 1 2016-04-07 100.48743
# 2 2 2016-05-15 100.73832
# 3 3 2016-07-27 100.57578
# 4 1 2016-11-25 99.69461
# 5 2 2016-03-14 101.51178
# 6 3 2016-11-20 100.38984
# 7 1 2016-12-06 99.37876
# 8 2 2016-08-25 97.78530
# 9 3 2016-08-13 101.12493
# 10 1 2016-01-23 99.95507
# 11 2 2016-12-27 99.98381
# 12 3 2016-03-03 100.94384
我想添加一个新列,其中第 i 行的新值是所有成本值的总和:
- 日期小于等于第i天且大于第i天减90天
- 并且x值等于第i行的x值。 (在这个例子中,x 和 date 的组合是唯一的,但通常它们可能不是。)
我可以通过两种不同的方式做到这一点:
tmp <- vals %>% group_by(date, x) %>%
summarise(total = sum(vals$cost[vals$date <= date[1] & vals$date > (date[1] - 90) & vals$x == x[1]]))
vals %>% left_join(tmp)
和
vals %>% rowwise() %>%
mutate(total = sum(vals$cost[vals$date <= date[1] & vals$date > (date[1] - 90) & vals$x == x]))
在我的大数据上两者都非常慢,大概是因为所有的子集化。我正在将数据框传回计算中,这对我来说有点像 hack。
有没有办法在 dplyr
内做到这一点 "properly"?
我的意思是,无需传入数据框并进行缓慢的子集设置。
或者如果没有,是否至少有更有效的方法来做到这一点?
喜欢vals %>% arrange(x, date) %>% group_by(x) %>% mutate(new = cumsum(cost))
?
解决每天多条记录的问题。我想你必须先做一个 per-day 计算?
vals %>%
arrange(x, date) %>%
group_by(x, date) %>%
mutate(cost = cumsum(cost)) %>%
ungroup() %>%
group_by(x) %>%
mutate(new = cumsum(cost))
基本上,(按日期排序时)您总是计算 sum(cost[index_start : index_end])
,其中 index_start
和 index_end
在行上滑动。使用成本的累计总和可以更有效地完成此操作:sum(cost[index_start : index_end]) = cumsum(cost[index_end]) - cumsum(cost[index_start - 1])
。对于您的数据框,代码一种可能的实现如下。
# arrange by date so all relevant cost come after each other
vals <- arrange(vals, x, date)
group_by(vals, x) %>%
mutate(
cumsum_cost = cumsum(cost),
index_start = map_dbl(
date,
function(cur_date, date) {
min(which(cur_date - days(90) <= date))
},
date = date),
cumsum_cost_90_days_ago = map_dbl(
index_start,
function(index_start, cumsum_cost) {
if (index_start - 1 <= 0) {
return(0)
} else {
cumsum_cost[index_start - 1]
}
},
cumsum_cost = cumsum_cost),
cost_90_days = cumsum_cost - cumsum_cost_90_days_ago
)
如果能更聪明地获得 index_start
(例如,通过使用数据框按 date
排序的知识),可以进一步加快速度。索引的一种简单方法是滚动连接,例如在 data.table
.
给定一个由以下定义的数据框:
set.seed(1)
date <- sample(seq(as.Date('2016/01/01'), as.Date('2016/12/31'), by="day"), 12)
vals <- data.frame(x = rep(1:3, 4), date = date, cost = rnorm(12, 100))
vals
# x date cost
# 1 1 2016-04-07 100.48743
# 2 2 2016-05-15 100.73832
# 3 3 2016-07-27 100.57578
# 4 1 2016-11-25 99.69461
# 5 2 2016-03-14 101.51178
# 6 3 2016-11-20 100.38984
# 7 1 2016-12-06 99.37876
# 8 2 2016-08-25 97.78530
# 9 3 2016-08-13 101.12493
# 10 1 2016-01-23 99.95507
# 11 2 2016-12-27 99.98381
# 12 3 2016-03-03 100.94384
我想添加一个新列,其中第 i 行的新值是所有成本值的总和:
- 日期小于等于第i天且大于第i天减90天
- 并且x值等于第i行的x值。 (在这个例子中,x 和 date 的组合是唯一的,但通常它们可能不是。)
我可以通过两种不同的方式做到这一点:
tmp <- vals %>% group_by(date, x) %>%
summarise(total = sum(vals$cost[vals$date <= date[1] & vals$date > (date[1] - 90) & vals$x == x[1]]))
vals %>% left_join(tmp)
和
vals %>% rowwise() %>%
mutate(total = sum(vals$cost[vals$date <= date[1] & vals$date > (date[1] - 90) & vals$x == x]))
在我的大数据上两者都非常慢,大概是因为所有的子集化。我正在将数据框传回计算中,这对我来说有点像 hack。
有没有办法在 dplyr
内做到这一点 "properly"?
我的意思是,无需传入数据框并进行缓慢的子集设置。
或者如果没有,是否至少有更有效的方法来做到这一点?
喜欢vals %>% arrange(x, date) %>% group_by(x) %>% mutate(new = cumsum(cost))
?
解决每天多条记录的问题。我想你必须先做一个 per-day 计算?
vals %>%
arrange(x, date) %>%
group_by(x, date) %>%
mutate(cost = cumsum(cost)) %>%
ungroup() %>%
group_by(x) %>%
mutate(new = cumsum(cost))
基本上,(按日期排序时)您总是计算 sum(cost[index_start : index_end])
,其中 index_start
和 index_end
在行上滑动。使用成本的累计总和可以更有效地完成此操作:sum(cost[index_start : index_end]) = cumsum(cost[index_end]) - cumsum(cost[index_start - 1])
。对于您的数据框,代码一种可能的实现如下。
# arrange by date so all relevant cost come after each other
vals <- arrange(vals, x, date)
group_by(vals, x) %>%
mutate(
cumsum_cost = cumsum(cost),
index_start = map_dbl(
date,
function(cur_date, date) {
min(which(cur_date - days(90) <= date))
},
date = date),
cumsum_cost_90_days_ago = map_dbl(
index_start,
function(index_start, cumsum_cost) {
if (index_start - 1 <= 0) {
return(0)
} else {
cumsum_cost[index_start - 1]
}
},
cumsum_cost = cumsum_cost),
cost_90_days = cumsum_cost - cumsum_cost_90_days_ago
)
如果能更聪明地获得 index_start
(例如,通过使用数据框按 date
排序的知识),可以进一步加快速度。索引的一种简单方法是滚动连接,例如在 data.table
.