【问题标题】:Rolling Sum by Another Variable in RR中的另一个变量的滚动总和
【发布时间】:2014-08-15 08:17:13
【问题描述】:

我想通过 ID 获得滚动的 7 天总和。假设我的数据如下所示:

data<-as.data.frame(matrix(NA,42,3))
data$V1<-seq(as.Date("2014-05-01"),as.Date("2014-09-01"),by=3)
data$V2<-rep(1:6,7)
data$V3<-rep(c(1,2),21)
colnames(data)<-c("Date","USD","ID")

         Date USD ID
1  2014-05-01   1  1
2  2014-05-04   2  2
3  2014-05-07   3  1
4  2014-05-10   4  2
5  2014-05-13   5  1
6  2014-05-16   6  2
7  2014-05-19   1  1
8  2014-05-22   2  2
9  2014-05-25   3  1
10 2014-05-28   4  2

如何添加一个包含按 ID 计算的 7 天滚动总和的新列?

【问题讨论】:

  • 这可能会让你开始:library(xts); lapply(split(data, data$ID), function(x) apply.weekly(xts(x[, 2:3], x$Date), sum))
  • @jbaums apply.weekly(它是 period.apply 的包装器)将函数应用于非重叠周期,这与滚动周期不同。

标签: r data.table xts


【解决方案1】:

1)假设您的意思是该 ID 的每个连续重叠的 7 行:

library(zoo)

transform(data, roll = ave(USD, ID, FUN = function(x) rollsumr(x, 7, fill = NA)))

2) 如果您确实是指 7 天而不是 7 行,那么试试这个:

library(zoo)

z <- read.zoo(data)
z0 <- merge(z, zoo(, seq(start(z), end(z), "day")), fill = 0) # expand to daily
roll <- function(x) rollsumr(x, 7, fill = NA)
transform(data, roll = ave(z0$USD, z0$ID, FUN = roll)[time(z)])

更新添加 (2) 并进行了一些改进。

【讨论】:

  • 这是一个不错、简单且(我发现)相当快速的解决方案
【解决方案2】:

如果您的数据很大,您可能需要查看这个使用data.table 的解决方案。它非常快。如果您需要更快的速度,您可以随时将mapply 更改为mcmapply 并使用多个内核。

#Load data.table and convert to data.table object
require(data.table)
setDT(data)[,ID2:=.GRP,by=c("ID")]

#Build reference table
Ref <- data[,list(Compare_Value=list(I(USD)),Compare_Date=list(I(Date))), by=c("ID2")]

#Use mapply to get last seven days of value by id
data[,Roll.Val := mapply(RD = Date,NUM=ID2, function(RD, NUM) {
                  d <- as.numeric(Ref$Compare_Date[[NUM]] - RD)
                  sum((d <= 0 & d >= -7)*Ref$Compare_Value[[NUM]])})]

【讨论】:

  • @Mike.Gahan 这行得通,但我不具备理解它所需的条件。如果您删除了 ID 的复杂性,我想看看代码会是什么。如果您只是想要滚动 7 天的总和或滚动的 9 天总和,您会怎么做?
  • 如果每个观察的 ID 都相同,那么它会按照您的意愿工作。因此,该解决方案可以推广到您的问题。我想 rollapply 也适用于这个问题的简化版本。
  • @Mike.Gahan 我见过 rollapply,人们只取最后 7 行,但你的不同,因为你实际上是在计算范围,因为每天可能没有一行。我迷路的地方(因为我不是真正的程序员)是计算与每个范围的跨度相对应的索引位置的方式。你能写出省略ID复杂性的代码吗?我也许可以遵循...我希望。
  • 如果有d &lt;- as.integer(Ref$Compare_Date[[NUM]]) - as.integer(RD)会更快
【解决方案3】:

OP 提供的数据集不会暴露任务的复杂性。到目前为止,就解决 OP 问题而言,只有 Mike 的答案是正确的。
实际上,由于@G 的d &lt;= 0 &amp; d &gt;= -7.
zoo 解决方案,因此需要 8 个滚动天,而不是 7 个滚动天。格洛腾迪克几乎是有效的,只有当merge 被分配到ID 的每一组时。
在第二个 data.table 解决方案下方,这次是有效结果,使用允许na.rm=TRUE 的 dev RcppRoll。
并稍微格式化了 Mike 的解决方案输出。

data<-as.data.frame(matrix(NA,42,3))
data$V1<-seq(as.Date("2014-05-01"),as.Date("2014-09-01"),by=3)
data$V2<-rep(1:6,7)
data$V3<-rep(c(1,2),21)
colnames(data)<-c("Date","USD","ID")

library(microbenchmark)
library(RcppRoll) # install_github("kevinushey/RcppRoll")
library(data.table) # install_github("Rdatatable/data.table")
correct_jan_dt = function(n, partial=TRUE){
  DT = as.data.table(data) # this can be speedup by setDT()
  date.range = DT[,range(Date)]
  all.dates = seq.Date(date.range[1],date.range[2],by=1)
  setkey(DT,ID,Date)
  r = DT[CJ(unique(ID),all.dates)][, c("roll") := as.integer(roll_sumr(USD, n, normalize = FALSE, na.rm = TRUE)), by="ID"][!is.na(USD)]
  # This could be simplified when `partial` arg will be implemented in [kevinushey/RcppRoll](https://github.com/kevinushey/RcppRoll)
  if(isTRUE(partial)){
    r[is.na(roll), roll := cumsum(USD), by="ID"][]
  }
  return(r[order(Date,ID)])
}
correct_mike_dt = function(){
  data = as.data.table(data)[,ID2:=.GRP,by=c("ID")]
  #Build reference table
  Ref <- data[,list(Compare_Value=list(I(USD)),Compare_Date=list(I(Date))), by=c("ID2")]
  #Use mapply to get last seven days of value by id
  data[, c("roll") := mapply(RD = Date,NUM=ID2, function(RD, NUM){
    d <- as.numeric(Ref$Compare_Date[[NUM]] - RD)
    sum((d <= 0 & d >= -7)*Ref$Compare_Value[[NUM]])})][,ID2:=NULL][]
}
identical(correct_mike_dt(), correct_jan_dt(n=8,partial=TRUE))
# [1] TRUE
microbenchmark(unit="relative", times=5L, correct_mike_dt(), correct_jan_dt(8))
# Unit: relative
#               expr      min       lq     mean   median       uq      max neval
#  correct_mike_dt() 274.0699 273.9892 267.2886 266.6009 266.2254 256.7296     5
#  correct_jan_dt(8)   1.0000   1.0000   1.0000   1.0000   1.0000   1.0000     5

期待来自@Khashaa 的更新。

编辑 (20150122.2):低于基准不回答 OP 问题。

在更大(仍然很小)的数据集上计时,5439 行:

library(zoo)
library(data.table)
library(dplyr)
library(RcppRoll)
library(microbenchmark)
data<-as.data.frame(matrix(NA,5439,3))
data$V1<-seq(as.Date("1970-01-01"),as.Date("2014-09-01"),by=3)
data$V2<-sample(1:6,5439,TRUE)
data$V3<-sample(c(1,2),5439,TRUE)
colnames(data)<-c("Date","USD","ID")
zoo_f = function(){
    z <- read.zoo(data)
    z0 <- merge(z, zoo(, seq(start(z), end(z), "day")), fill = 0) # expand to daily
    roll <- function(x) rollsumr(x, 7, fill = NA)
    transform(data, roll = ave(z0$USD, z0$ID, FUN = roll)[time(z)])
}
dt_f = function(){
    DT = as.data.table(data) # this can be speedup by setDT()
    date.range = DT[,range(Date)]
    all.dates = seq.Date(date.range[1],date.range[2],by=1)
    setkey(DT,Date)
    DT[.(all.dates)
       ][order(Date), c("roll") := rowSums(setDT(shift(USD, 0:6, NA, "lag")),na.rm=FALSE), by="ID"
         ][!is.na(ID)]
}
dp_f = function(){
  data %>% group_by(ID) %>% 
    mutate(roll=roll_sum(c(rep(NA,6), USD), 7))
} 
dt2_f = function(){
  # this can be speedup by setDT()
  as.data.table(data)[, c("roll") := roll_sum(c(rep(NA,6), USD), 7), by="ID"][]
}
identical(as.data.table(zoo_f()),dt_f())
# [1] TRUE
identical(setDT(as.data.frame(dp_f())),dt_f())
# [1] TRUE
identical(dt2_f(),dt_f())
# [1] TRUE
microbenchmark(unit="relative", times=20L, zoo_f(), dt_f(), dp_f(), dt2_f())
# Unit: relative
#     expr        min         lq       mean     median         uq        max neval
#  zoo_f() 140.331889 141.891917 138.064126 139.381336 136.029019 137.730171    20
#   dt_f()  14.917166  14.464199  15.210757  16.898931  16.543811  14.221987    20
#   dp_f()   1.000000   1.000000   1.000000   1.000000   1.000000   1.000000    20
#  dt2_f()   1.536896   1.521983   1.500392   1.518641   1.629916   1.337903    20

但我不确定我的 data.table 代码是否已经优化。

以上函数没有回答 OP 问题。阅读帖子顶部以获取更新。 Mike 的解决方案是正确的。

【讨论】:

    【解决方案4】:
    library(data.table)
    
    data <- data.table(Date = seq(as.Date("2014-05-01"),
                                  as.Date("2014-09-01"),
                                  by = 3),
                       USD = rep(1:6, 7),
                       ID = rep(c(1, 2), 21))
    
    data[, Rolling7DaySum := {
             d <- data$Date - Date
             sum(data$USD[ID == data$ID & d <= 0 & d >= -7])
           },
         by = list(Date, ID)]
    

    【讨论】:

      【解决方案5】:

      我发现Mike.Gahan的建议代码有问题,测试后改正如下。

      require(data.table)
      setDT(data)[,ID2:=.GRP,by=c("ID")]
      Ref <-data[,list(Compare_Value=list(I(USD)),Compare_Date=list(I(Date))),by=c("ID2")]
      data[,Roll.Val := mapply(RD = Date,NUM=ID2, function(RD, NUM) {
      d <- as.numeric(Ref[ID2 == NUM,]$Compare_Date[[1]] - RD)
      sum((d <= 0 & d >= -7)*Ref[ID2 == NUM,]$Compare_Value[[1]])})]
      

      【讨论】:

        猜你喜欢
        • 2020-12-06
        • 2021-11-25
        • 2023-03-17
        • 1970-01-01
        • 2014-04-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多