id date goal date_followup_3month cumulative_sum
1 2004-12-31 1 2005-03-31 3
1 2005-01-21 2 2005-04-21 6
1 2005-04-04 3 2005-07-03 4
1 2005-04-04 1 2005-07-03 1
2 2001-01-05 4 2001-04-05 4
2 2002-02-05 3 2002-05-06 5
cumulative_sum
列是每个ID从日期到三个月的目标累计值。
我认为的代码如下,结果没有出来。
错误代码:参数“是”缺失,没有默认值
for(i in 1:length(id)){
cumulative_sum[i] <- for(j in 1:length(id))
{max(cumsum(ifelse(id[i] == id[j]
& date[j] >= date[i]
& date[j] <= date_followup_3month[i])
,goal[j],0))}
}
我想知道是否还有其他好的代码。非常感谢。
答案 0 :(得分:4)
这里有几种可能性。基于SQL的解决方案(1)似乎具有很高的可读性,并且由于SQL可以优化联接,因此可能节省空间。 data.table解决方案(2)会产生较大的中间结果,但请参阅@Frank的注释来避免这种情况。在(3)和(4)中会创建更大的中间结果,如果数据足够大,这可能是不可行的。基于循环的解决方案(5)节省空间,但是使用R中通常不使用的样式。(5)可以按照我们在(6)中所示的直接方式将其转换为C ++(使用Rcpp)。
1)sqldf 可以在复杂的逻辑条件下使用自联接在SQL中表示该问题:
library(sqldf)
sqldf("select a.*, sum(b.goal) cumulative_sum
from DF a
join DF b on a.id = b.id and b.rowid >= a.rowid and b.date <= a.date_followup_3month
group by a.rowid")
给予:
id date goal date_followup_3month cumulative_sum
1 1 2004-12-31 1 2005-03-31 3
2 1 2005-01-21 2 2005-04-21 6
3 1 2005-04-04 3 2005-07-03 4
4 1 2005-04-04 1 2005-07-03 1
5 2 2001-01-05 4 2001-04-05 4
6 2 2002-02-05 3 2002-05-06 3
2)data.table 这也可以在data.table中完成,尽管请注意,这涉及到创建具有大量行的中间对象,而sql可能会优化它。
library(data.table)
DT <- as.data.table(DF)
DT[, seq:=.I][
DT, on = .(id == id, seq <= seq, date_followup_3month >= date)][
, list(id = id[1],
date = date[1],
date_followup_3month = date_followup_3month[1],
cumulative_sum = sum(i.goal)), by = seq]
3)基本R 这是一个基本解决方案,该解决方案仅在id上显式执行自连接,然后将条件中其他项的行向下子集化。最后,它使用tapply
进行求和。它涉及显式生成s
,这是更大的中间结果。
DF0 <- cbind(seq = 1:nrow(DF), DF)
s <- subset(merge(DF0, DF0, by = "id"),
seq.x <= seq.y & date_followup_3month.x >= date.y)
transform(DF, cumulative_sum = tapply(s$goal.y, s$seq.x, sum))
4)dplyr 这使用了dplyr,像(3)一样,由于它仅对id执行自联接,因此涉及潜在的非常大的中间结果。
library(dplyr)
DF %>%
mutate(seq = 1:n()) %>%
inner_join(., ., by = "id", suffix = c("", ".x")) %>%
filter(seq.x >= seq & date.x <= date_followup_3month) %>%
group_by(seq, date, goal, date_followup_3month) %>%
summarize(cumulative_sum = sum(goal.x)) %>%
ungroup %>%
select(-seq)
5)循环-基本R 在R中不建议使用显式循环,该循环可能很慢,但另一方面,它相对简单且节省空间。这可以用作将代码转换为C ++的模型,我们将在此之后的解决方案中进行此操作。请注意,我们包括了一些优化。由于对输入进行了排序,因此j循环可以从i开始,而不是从1开始,并且一旦j循环中的条件失败,我们就可以立即退出j循环,因为令人满意的行必定同时出现。
n <- nrow(DF)
Sum <- numeric(n)
for(i in 1:n) {
for(j in i:n) {
if (with(DF, id[i] == id[j] && date[j] <= date_followup_3month[i])) {
Sum[i] <- Sum[i] + DF$goal[j]
} else break
}
}
transform(DF, cumulative_sum = Sum)
6)Rcpp 我们可以将(5)转换为C ++。假设我们有一个名为cum_sum.cpp的文件,其中包含以下内容:
#include <Rcpp.h>
using namespace Rcpp;
// [[Rcpp::export]]
NumericVector cum_sum(NumericVector id, IntegerVector date,
IntegerVector date_followup_3month, NumericVector goal) {
auto n = id.size();
NumericVector Sum(n);
for(auto i = 0; i < n; i++) {
Sum[i] = 0.0;
for(auto j = i; j < n; j++) {
if (id[i] == id[j] && date[j] <= date_followup_3month[i]) {
Sum[i] = Sum[i] + goal[j];
} else break;
}
}
return Sum;
}
然后运行:
library(Rcpp)
sourceCpp("cum_sum.cpp")
transform(DF, cumulative_sum =
cum_sum(id, date, date_followup_3month, as.numeric(goal)))
可重复形式的输入DF
为:
Lines <- "id date goal date_followup_3month
1 2004-12-31 1 2005-03-31
1 2005-01-21 2 2005-04-21
1 2005-04-04 3 2005-07-03
1 2005-04-04 1 2005-07-03
2 2001-01-05 4 2001-04-05
2 2002-02-05 3 2002-05-06"
DF <- read.table(text = Lines, header = TRUE)
DF$date <- as.Date(DF$date)
DF$date_followup_3month <- as.Date(DF$date_followup_3month)
答案 1 :(得分:1)
对于满足日期和id条件的行,您可以仅使用sum来代替max(cumsum)。为了避免嵌套循环,可以使用函数。简化示例如下:
goalsum <- function(date, i){
start <- date$date[i]
end <- date$date_followup_3month[i]
ind <- date$id[i]
tot_goal <- date%>%
filter(date>=start & date<=end & id==ind)%>%
summarise(sum(goal))
return(tot_goal[1,1])
}
for(i in 1:length(date)){date$res[i] <-goalsum(date, i)}