【问题标题】:Efficient way to perform running total in the last 365 day window在过去 365 天窗口中执行运行总计的有效方法
【发布时间】:2015-08-16 11:33:00
【问题描述】:

这是我的数据框的样子:

库(data.table)

df <- fread('
                Name  EventType  Date  SalesAmount RunningTotal Runningtotal(prior365Days)
                John    Email      1/1/2014      0          0            0
                John    Sale       2/1/2014     10          10           10
                John    Sale       7/1/2014     20          30           30
                John    Sale       4/1/2015     30          60           50 
                John    Webinar    5/1/2015      0          60           50
                Tom     Email      1/1/2014      0          0            0
                Tom     Sale       2/1/2014     15          15           15
                Tom     Sale       7/1/2014     10          25           25
                Tom     Sale       4/1/2015     25          50           35 
                Tom     Webinar    5/1/2015      0          50           35
                ')
    df[,Date:= as.Date(Date, format="%m/%d/%Y")]

最后一列是我想要的列,它是过去 365 天滚动窗口中 SalesAmount(每个名称)的累积总和,我在 @6pool 的帮助下执行了此操作。他的解决方案是:

df$EventDate <- as.Date(df$EventDate, format="%d/%m/%Y")
df <- df %>%
   group_by (Name) %>%
   arrange(EventDate) %>% 
   mutate(day = EventDate - EventDate[1])

f <- Vectorize(function(i)
    sum(df[df$Name[i] == df$Name & df$day[i] - df$day >= 0 & 
             df$day[i] - df$day <= 365, "SalesAmount"]), vec="i")
df$RunningTotal365 <- f(1:nrow(df))

但是,df$RunningTotal365

【问题讨论】:

  • 我想知道你是否尝试过实现this
  • @DavidArenburg 嗨,大卫,我将您的解决方案(来自链接)用于另一个问题。给定名称在过去 365 天窗口中的累积运行总数是一个完全独立的问题。
  • 是的,我的意思是我想知道您是否尝试修改它以解决您的新问题。这可能是迄今为止最快的解决方案
  • @DavidArenburg 谢谢大卫,我从来没想过。我想知道在这种情况下我可以稍后加入的两个临时数据集是什么。非常感谢任何帮助!
  • 我觉得你很快就接受了答案。 30 秒的运行时间很棒,但还有一个 Rcpproll 包;熟悉其(可能更快)方法的人现在可能不愿回答。 (不是我;我只是通过谷歌搜索找到的。)

标签: r vectorization dplyr zoo rollapply


【解决方案1】:

在 data.table 中使用较新的非 equi 连接功能:

    df1 = df[.(iName=Name,start = Date - 365L, end = Date),
    on=.(Name=iName,Date >= start, Date <= end),nomatch = 0, allow.cart=TRUE][,
  .(MyTotal = sum(SalesAmount)), by=.(Name,Date = Date.1)]


    df[df1, on = .(Name,Date)]

【讨论】:

    【解决方案2】:

    这是使用 data.table 包中的 foverlaps 函数的方法:

    require(data.table)
    setDT(df)[, end := as.Date(EventDate, format="%d/%m/%Y")
            ][, start := end - 365L]
    setkey(df, Name, start, end)
    olaps = foverlaps(df, df, nomatch=0L, which=TRUE)
    olaps = olaps[xid >= yid, .(ans = sum(dt$SalesAmount[yid])), by=xid]
    
    df[olaps$xid, Runningtotal := olaps$ans]
    

    如有必要,您可以删除 start 和 end 列,方法是:

    df[, c("start", "end") := NULL]
    

    很高兴知道它有多快/多慢..

    【讨论】:

    • 速度非常快。我用了不到 45 秒。谢谢你,阿伦。但是,我正在努力将解决方案扩展到一个小问题。假设我还有一个名为 Fund 的专栏。它有 Fund A 和 B,仅在 EventType ==Sale 时出现。例如,如果我只想要基金 A 的累计金额,我该如何修改 Roland 的解决方案或您的解决方案?
    • 1.您能否提供更大数据的链接(如果您无法共享,您能否生成具有类似设置的人工数据)? 2. 你在纠结什么扩展?
    • 请用这个编辑你的帖子,并显示所需的输出(或者更好的是,作为一个新帖子,因为它看起来与这个 Q 不同)。你能分享更大的数据吗?我想看看是否有任何改进。
    • @Frank 我从 Frank 那里得到了解决方案。非常感谢你们。
    • @gibbz00 那么,Arun 的解决方案(根据您的说法不到 45 秒)比我的解决方案(根据您的说法是 30 秒)慢?你能提供准确的时间(使用system.time)吗?您能告诉我们您的数据中有多少个唯一名称吗?
    【解决方案3】:

    试试这个:

    DF <- read.table(text = "Name  EventType  EventDate  SalesAmount RunningTotal Runningtotal(prior365Days)
    John    Email      1/1/2014      0          0            0
    John    Sale       2/1/2014     10          10           10
    John    Sale       7/1/2014     20          30           30
    John    Sale       4/1/2015     30          60           50 
    John    Webinar    5/1/2015      0          60           50
    Tom     Email      1/1/2014      0          0            0
    Tom     Sale       2/1/2014     15          15           15
    Tom     Sale       7/1/2014     10          25           25
    Tom     Sale       4/1/2015     25          50           35 
    Tom     Webinar    5/1/2015      0          50           35", header = TRUE)
    
    
    fun <- function(x, date, thresh) {
      D <- as.matrix(dist(date)) #distance matrix between dates
      D <- D <= thresh
      D[lower.tri(D)] <- FALSE #don't sum to future
      R <- D * x #FALSE is treated as 0
      colSums(R)
    }
    
    
    library(data.table)
    setDT(DF)
    DF[, EventDate := as.Date(EventDate, format = "%m/%d/%Y")]
    setkey(DF, Name, EventDate)
    
    DF[, RT365 := fun(SalesAmount, EventDate, 365), by = Name]
    
    #    Name EventType  EventDate SalesAmount RunningTotal Runningtotal.prior365Days. RT365
    # 1: John     Email 2014-01-01           0            0                          0     0
    # 2: John      Sale 2014-02-01          10           10                         10    10
    # 3: John      Sale 2014-07-01          20           30                         30    30
    # 4: John      Sale 2015-04-01          30           60                         50    50
    # 5: John   Webinar 2015-05-01           0           60                         50    50
    # 6:  Tom     Email 2014-01-01           0            0                          0     0
    # 7:  Tom      Sale 2014-02-01          15           15                         15    15
    # 8:  Tom      Sale 2014-07-01          10           25                         25    25
    # 9:  Tom      Sale 2015-04-01          25           50                         35    35
    #10:  Tom   Webinar 2015-05-01           0           50                         35    35
    

    【讨论】:

    • 150万行可行吗?
    • @ExperimenteR 1.5MM 是一个非常小的数据集。任何矢量化代码都可以轻松处理。我所说的“矢量化”并不是指“矢量化”功能。
    • @DavidArenburg D
    • @DavidArenburg 非常感谢!这就像魔术般的罗兰。花了不到30秒。你们中的哪一位可以解释一下为什么我采用的初始解决方案如此缓慢吗?
    • @ExperimenteR 该函数显然不适合非常长的输入向量。但是,如果数据集中每个用户的时间序列长于几年,并且销售频率通常比每天少得多,我会感到非常惊讶。
    猜你喜欢
    • 1970-01-01
    • 2020-06-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-30
    • 2014-06-14
    相关资源
    最近更新 更多