我正在努力学习R并且我在SAS工作了10多年的一些事情,我无法弄清楚在R中做的最好的方法。拿这个数据:
id class t count desired
-- ----- ---------- ----- -------
1 A 2010-01-15 1 1
1 A 2010-02-15 2 3
1 B 2010-04-15 3 3
1 B 2010-09-15 4 4
2 A 2010-01-15 5 5
2 B 2010-06-15 6 6
2 B 2010-08-15 7 13
2 B 2010-09-15 8 21
我想通过id,class和4个月的滚动窗口计算所需的列作为滚动总和。请注意,每个id和class组合都不存在所有月份。
在SAS中,我通常会采用以下两种方式之一:
RETAIN
加上一个id&类。 PROC SQL
左侧从df连接为df1到df为id,class上的df2和相应窗口中的df1.d-df2.d 解决此类问题的最佳方法是什么?
t <- as.Date(c("2010-01-15","2010-02-15","2010-04-15","2010-09-15",
"2010-01-15","2010-06-15","2010-08-15","2010-09-15"))
class <- c("A","A","B","B","A","B","B","B")
id <- c(1,1,1,1,2,2,2,2)
count <- seq(1,8,length.out=8)
desired <- c(1,3,3,4,5,6,13,21)
df <- data.frame(id,class,t,count,desired)
答案 0 :(得分:18)
以下是一些解决方案:
1)动物园使用ave
,为每个组创建一个月度系列m
,方法是将原始系列z
与网格合并, g
。然后计算滚动总和并仅保留原始时间点:
library(zoo)
f <- function(i) {
z <- with(df[i, ], zoo(count, t))
g <- zoo(, seq(start(z), end(z), by = "month"))
m <- merge(z, g)
window(rollapplyr(m, 4, sum, na.rm = TRUE, partial = TRUE), time(z))
}
df$desired <- ave(1:nrow(df), df$id, df$class, FUN = f)
给出:
> df
id class t count desired
1 1 A 2010-01-15 1 1
2 1 A 2010-02-15 2 3
3 1 B 2010-04-15 3 3
4 1 B 2010-09-15 4 4
5 2 A 2010-01-15 5 5
6 2 B 2010-06-15 6 6
7 2 B 2010-08-15 7 13
8 2 B 2010-09-15 8 21
注意我们假设时间是在每个组中排序的(如问题所示)。如果不是这样,那么先排序df
。
2) sqldf
library(sqldf)
sqldf("select id, class, a.t, a.'count', sum(b.'count') desired
from df a join df b
using(id, class)
where a.t - b.t between 0 and 100
group by id, class, a.t")
给出:
id class t count desired
1 1 A 2010-01-15 1 1
2 1 A 2010-02-15 2 3
3 1 B 2010-04-15 3 3
4 1 B 2010-09-15 4 4
5 2 A 2010-01-15 5 5
6 2 B 2010-06-15 6 6
7 2 B 2010-08-15 7 13
8 2 B 2010-09-15 8 21
注意:如果合并太大而无法放入内存中,请使用sqldf("...", dbname = tempfile())
使中间结果存储在动态创建的数据库中并自动销毁之后。
3)基础R sqldf解决方案激发了这个基本R解决方案,它只是将SQL转换为R:
m <- merge(df, df, by = 1:2)
s <- subset(m, t.x - t.y >= 0 & t.x - t.y <= 100)
ag <- aggregate(count.y ~ t.x + class + id, s, sum)
names(ag) <- c("t", "class", "id", "count", "desired")
结果是:
> ag
t class id count desired
1 2010-01-15 A 1 1 1
2 2010-02-15 A 1 2 3
3 2010-04-15 B 1 3 3
4 2010-09-15 B 1 4 4
5 2010-01-15 A 2 5 5
6 2010-06-15 B 2 6 6
7 2010-08-15 B 2 7 13
8 2010-09-15 B 2 8 21
注意:这会在内存中进行合并,如果数据集非常大,可能会出现问题。
更新:第一个解决方案的简化,并添加了第二个解决方案。
更新2:添加了第三个解决方案。
答案 1 :(得分:5)
我几乎不好意思发布这个。我通常都很优秀,但必须有更好的方法。
首先使用zoo
的{{1}}来获取月份和年份的日期,然后重新整形以获得每个as.yearmon
/ id
的一列组合,然后在错误的月份之前,之后和之后用零填充,然后使用class
获得滚动总和,然后拉出所需的月份并与原始数据框合并。
zoo
答案 2 :(得分:4)
使用data.table库可以找到这个问题的一个非常有效的答案。
##Utilize the data.table package
library("data.table")
data <- data.table(t,class,id,count,desired)[order(id,class)]
##Assign each customer an ID
data[,Cust_No:=.GRP,by=c("id","class")]
##Create "list" of comparison dates and values
Ref <- data[,list(Compare_Value=list(I(count)),Compare_Date=list(I(t))), by=c("id","class")]
##Compare two lists and see of the compare date is within N days
data$Roll.Val <- mapply(FUN = function(RD, NUM) {
d <- as.numeric(Ref$Compare_Date[[NUM]] - RD)
sum((d <= 0 & d >= -124)*Ref$Compare_Value[[NUM]])
}, RD = data$t,NUM=data$Cust_No)
##Print out data
data <- data[,list(id,class,t,count,desired,Roll.Val)][order(id,class)]
data
id class t count desired Roll.Val
1: 1 A 2010-01-15 1 1 1
2: 1 A 2010-02-15 2 3 3
3: 1 B 2010-04-15 3 3 3
4: 1 B 2010-09-15 4 4 4
5: 2 A 2010-01-15 5 5 5
6: 2 B 2010-06-15 6 6 6
7: 2 B 2010-08-15 7 13 13
8: 2 B 2010-09-15 8 21 21
答案 3 :(得分:0)
使用runner软件包,可以计算滚动窗口上的所有内容。下面的示例使用sum_run
library(runner)
df %>%
group_by(id) %>%
mutate(
output = sum_run(count, k = 30*4, idx = t)
)
# <dbl> <fct> <date> <dbl> <dbl> <dbl>
# 1 A 2010-01-15 1 1 1
# 1 A 2010-02-15 2 3 3
# 1 B 2010-04-15 3 3 6
# 1 B 2010-09-15 4 4 4
# 2 A 2010-01-15 5 5 5
# 2 B 2010-06-15 6 6 6
# 2 B 2010-08-15 7 13 13
# 2 B 2010-09-15 8 21 21