【问题标题】:Compute rolling sum by id variables, with missing timepoints通过 id 变量计算滚动总和,缺少时间点
【发布时间】:2023-03-17 17:41:01
【问题描述】:

我正在尝试学习 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

我想在 4 个月的滚动窗口内按 id、类和滚动总和计算所需的列。请注意,并非每个 id 和 class 组合都存在所有月份。

在 SAS 中,我通常会采用以下两种方式之一:

  1. RETAIN 加上 id 和类。
  2. PROC SQL 在相应窗口中的 id、class 和 df1.d-df2.d 上从 df as df1 到 df as df2 进行左连接

解决此类问题的最佳 R 方法是什么?

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)

【问题讨论】:

  • d 是什么?它的定义不包含在您的设置代码中。
  • 看看zoo 包。它可以相当容易地对时间对齐的数据进行这些滚动摘要。如果您对sql 感到满意,您可以使用sqldf 包。
  • @MatthewPlourde,我认为@ADJ 的意思是df &lt;- data.frame(t,class, id, count ,desired)
  • 我已经在使用 sqldf 包了。一方面,我还没有完全弄清楚如何在 sqldf 查询中使用 R 函数,就像我可以在 PROC SQL 中使用许多 SAS 函数之一一样。在这种情况下,我首选的解决方案将涉及使用 SAS 的 INTCK 函数的 R 版本以比我原始示例中描述的更复杂的方式进行日期运算。但由于我是从零开始学习 R,所以我更喜欢学习如何以 R 方式做事(参见 Joe 的评论)
  • 老实说,对于这样的东西,我会继续使用 SAS。 :)

标签: r sas plyr zoo


【解决方案1】:

这里有一些解决方案:

1) zoo 使用ave,通过将原始系列z 与网格g 合并,为每个组创建一个月度系列m。然后计算rolling sum,只保留原来的时间点:

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) Base R sqldf 解决方案激发了这种将 SQL 转换为 R 的基本 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:添加了第三个解决方案。

【讨论】:

  • 不错!很好地使用ave,我可能不会经常使用它,还有一些使用zoo 的方法对我来说是新的。谢谢!
  • 也感谢您在 zoo 软件包上所做的工作——非常感谢!
  • 赏金奖励,当之无愧的答案。谢谢!
【解决方案2】:

我几乎不好意思发布这个。我通常和这些一样好,但必须有更好的方法。

这首先使用zoo 的as.yearmon 来获取月份和年份的日期,然后对其进行整形以获取每个id/class 组合的一列,然后在之前用零填充, 之后和缺失的月份,然后使用 zoo 获取滚动总和,然后只提取所需的月份并与原始数据框合并。

library(reshape2)
library(zoo)
df$yearmon <- as.yearmon(df$t)
dfa <- dcast(id + class ~ yearmon, data=df, value.var="count")
ida <- dfa[,1:2]
dfa <- t(as.matrix(dfa[,-c(1:2)]))
months <- with(df, seq(min(yearmon)-3/12, max(yearmon)+3/12, by=1/12))
dfb <- array(dim=c(length(months), ncol(dfa)), 
             dimnames=list(paste(months), colnames(dfa)))
dfb[rownames(dfa),] <- dfa
dfb[is.na(dfb)] <- 0
dfb <- rollsumr(dfb,4, fill=0)
rownames(dfb) <- paste(months)
dfb <- dfb[rownames(dfa),]
dfc <- cbind(ida, t(dfb))
dfc <- melt(dfc, id.vars=c("class", "id"))
names(dfc)[3:4] <- c("yearmon", "desired2")
dfc$yearmon <- as.yearmon(dfc$yearmon)
out <- merge(df,dfc)

> out
  id class  yearmon          t count desired desired2
1  1     A Feb 2010 2010-02-15     2       3        3
2  1     A Jan 2010 2010-01-15     1       1        1
3  1     B Apr 2010 2010-04-15     3       3        3
4  1     B Sep 2010 2010-09-15     4       4        4
5  2     A Jan 2010 2010-01-15     5       5        5
6  2     B Aug 2010 2010-08-15     7      13       13
7  2     B Jun 2010 2010-06-15     6       6        6
8  2     B Sep 2010 2010-09-15     8      21       21

【讨论】:

    【解决方案3】:

    使用 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
    

    【讨论】:

    • 这是一个 124 天的滚动时间段。显然这不是 4 个月,但代码可以轻松修改。
    【解决方案4】:

    使用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
    

    【讨论】:

      猜你喜欢
      • 2021-11-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-09
      • 2018-03-11
      • 2018-03-01
      相关资源
      最近更新 更多