【问题标题】:Find sum of previous n rows in dataframe查找数据框中前 n 行的总和
【发布时间】:2020-03-10 01:02:01
【问题描述】:

我想在数据框中找到之前的 n 行的总和。例如:

id = 1:10
vals = c(4,7,2,9,7,0,4,6,1,8)
test = data.frame(id,vals)

所以,对于n=3,我想将下一列计算为:

test$sum = c(NA, NA, 13,18,18,16,11,10,11,15)

我最接近的方法是使用以下方法创建一个新列:

test$valprevious = c(NA, head(test$vals,-1)

然后使用循环重复此n 次,然后sum 跨列。我确定这不是最有效的方法,是否有任何函数可以访问n 前几行?或者其他方式来做到这一点?

【问题讨论】:

    标签: r dataframe


    【解决方案1】:

    您可以为此使用 zoo 包中的 rollsumr 函数:

    library(zoo)
    test$sums <- rollsumr(test$vals, k = 3, fill = NA)
    

    给出:

    > test
       id vals sums
    1   1    4   NA
    2   2    7   NA
    3   3    2   13
    4   4    9   18
    5   5    7   18
    6   6    0   16
    7   7    4   11
    8   8    6   10
    9   9    1   11
    10 10    8   15
    

    这与使用带有align = 'right' 参数的rollsum 函数相同:

    rollsum(test$vals, k = 3, fill = NA, align = 'right')
    

    作为替代方案,您可以将Reduce 包中的shift 一起使用:

    library(data.table)
    setDT(test)[, sums := Reduce(`+`, shift(vals, 0:2))]
    

    给出相同的结果:

    > test
        id vals sums
     1:  1    4   NA
     2:  2    7   NA
     3:  3    2   13
     4:  4    9   18
     5:  5    7   18
     6:  6    0   16
     7:  7    4   11
     8:  8    6   10
     9:  9    1   11
    10: 10    8   15
    

    最近, 增加了快速滚动功能。因此,另一种选择是:

    setDT(test)[, sums := frollsum(vals, 3)]
    

    @alexis_laz 在 cmets 中提出的一个不错的基本 R 替代方案:

    n <- 3
    cs <- cumsum(test$vals)
    test$sums <- c(rep_len(NA, n - 1), tail(cs, -(n - 1)) - c(0, head(cs, -n)))
    

    @Khashaa 在 cmets 中提出的另外两个选项:

    # with base R
    n <- 3
    test$sums <- c(rep_len(NA, n - 1), rowSums(embed(test$vals, n)))
    
    # with RcppRoll
    library(RcppRoll)
    test$sums <- roll_sumr(test$vals, 3)
    

    基准测试:

    作为@alexis_laz noted in the comments,某些解决方案可能会在重新计算总和和重新创建length-向量时产生开销。这可能会导致计算速度的差异。由于对如此小的数据集进行基准测试并没有真正意义,我将在模拟示例数据集的大型数据集上对不同的解决方案进行基准测试:

    # window size
    n <- 3
    
    # creating functions of the different solutions:
    alexis_laz <- function(test) {cs <- cumsum(test$vals); test$sums <- c(rep_len(NA, n - 1), tail(cs, -(n - 1)) - c(0, head(cs, -n)))}
    khashaa <- function(test) {test$sums <- c(rep_len(NA, n - 1), rowSums(embed(test$vals, n)))}
    rcpp_roll <- function(test) test$sums <- roll_sumr(test$vals, n)
    zoo_roll <- function(test) test$sums <- rollsumr(test$vals, k=n, fill=NA)
    dt_reduce <- function(test) setDT(test)[, sums := Reduce(`+`, shift(vals, 0:(n-1)))]
    dt_froll <- function(test) setDT(test)[, sums := frollsum(vals, n)]
    
    # load the 'bench' package
    library(bench)
    
    # create a big test dataset
    test <- data.frame(id=rep(1:10,1e7), vals=sample(c(4,7,2,9,7,0,4,6,1,8),1e7,TRUE))
    
    # run the benchmark
    big_bm <- mark(alexis_laz(test),
                   khashaa(test),
                   rcpp_roll(test), 
                   zoo_roll(test), 
                   dt_reduce(test), 
                   dt_froll(test),
                   iterations = 1,
                   check = FALSE)
    
    # extract some core measures and sort them
    big_bm %>% select(expression, median, mem_alloc) %>% arrange(median)
    

    给出:

      expression         median mem_alloc
      <bch:expr>       <bch:tm> <bch:byt>
    1 dt_froll(test)   776.35ms    1.49GB
    2 rcpp_roll(test)     1.23s  762.94MB
    3 dt_reduce(test)     2.12s    4.47GB
    4 alexis_laz(test)    3.68s    4.47GB
    5 khashaa(test)       8.35s    5.21GB
    6 zoo_roll(test)     33.32s   22.63GB
    

    如您所见,-package 中的新 frollsum-function 在速度方面无疑是赢家。在考虑内存分配时,roll_sumr 需要的内存最少。

    【讨论】:

    • 为了避免重新计算sums 和创建length(vals) 向量,可以使用n = 3; cs = cumsum(test$vals); c(rep_len(NA, n - 1), tail(cs, -(n - 1)) - c(0, head(cs, -n)))
    • @alexis_laz 谢谢!这是一个非常好的基础 R 替代方案。将其添加到答案中。
    • rowSums(embed(test$vals, 3)) 曾经是RcppRoll 之前最高效的日子。
    • @Khashaa 也是不错的选择!也添加了它们。
    • @alexis_laz 的 Base 实现可能失败的地方是向量中间缺少数据。虽然并不完美,但您可以像这样修改基本 R 实现:lagsum &lt;- function(x, n) { x[is.na(x)] &lt;- 0; cs &lt;- cumsum(x); suml &lt;- c(rep_len(NA, n - 1), tail(cs, -(n - 1)) - c(0, head(cs, -n))); suml[n] &lt;- NA; suml }。它只是通过将它们设置为零来从 cumsum() 中排除 NA 值。
    猜你喜欢
    • 2016-03-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-19
    • 1970-01-01
    • 1970-01-01
    • 2021-10-06
    • 1970-01-01
    相关资源
    最近更新 更多