[R] partial cumsum

Tony Plate tplate at acm.org
Wed Nov 11 20:23:55 CET 2009


William Dunlap wrote:
> 
> Bill Dunlap
> Spotfire, TIBCO Software
> wdunlap tibco.com  
> 
>> -----Original Message-----
>> From: r-help-bounces at r-project.org 
>> [mailto:r-help-bounces at r-project.org] On Behalf Of smu
>> Sent: Wednesday, November 11, 2009 7:58 AM
>> To: r-help at r-project.org
>> Subject: [R] partial cumsum
>>
>> Hello,
>>
>> I am searching for a function to calculate "partial" cumsums.
>>
>> For example it should calculate the cumulative sums until a 
>> NA appears,
>> and restart the cumsum calculation after the NA.
>>
>> this:
>>
>> x <- c(1, 2, 3, NA, 5, 6, 7, 8, 9, 10)
>>
>> should become this:
>>
>> 1 3 6 NA 5  11  18  26  35  45
> 
> Perhaps
>    > ave(x, rev(cumsum(rev(is.na(x)))), FUN=cumsum)
>     [1]  1  3  6 NA  5 11 18 26 35 45
> 

Nice simple function!  Here's a different approach I use that's faster for long vectors with many NA values.  Note however, that this approach can suffer from catastrophic round-off error because it does a cumsum over the whole vector (after replacing NA's with zeros) and then subtracting out the cumsum at the most recent NA values.  Most of the body of this function is devoted to allowing (an unreasonable degree of) flexibility in specification of where to reset.

cumsum.reset <- function(x, reset.at=which(is.na(x)), na.rm=F) {
    # compute the cumsum of x, resetting the cumsum to 0 at each element indexed by reset.at
    if (is.logical(reset.at)) {
        if (length(reset.at)>length(x)) {
            if ((length(reset.at) %% length(x))!=0)
                stop("length of reset.at must be a multiple of length of x")
            x <- rep(x, len=length(reset.at))
        } else if (length(reset.at)<length(x)) {
            if ((length(x) %% length(reset.at))!=0)
                stop("length of x must be a multiple of length of reset.at")
            reset.at <- rep(reset.at, len=length(x))
        }
        reset.at <- which(reset.at)
    } else if (!is.numeric(reset.at)) {
        stop("reset.at must be logical or numeric")
    }
    if (length(i <- which(reset.at<=1)))
        reset.at <- reset.at[-i]
    if (any(i <- is.na(x[reset.at])))
        x[reset.at[i]] <- 0
    if (na.rm && any(i <- which(is.na(x))))
        x[i] <- 0
    y <- cumsum(x)
    d <- diff(c(0, y[reset.at-1]))
    r <- rep(0, length(x))
    r[reset.at] <- d
    return(y - cumsum(r))
}



> x <- c(1, 2, 3, NA, 5, 6, 7, 8, 9, 10)
> cumsum.reset(x)
 [1]  1  3  6  0  5 11 18 26 35 45
> ave(x, rev(cumsum(rev(is.na(x)))), FUN=cumsum)
 [1]  1  3  6 NA  5 11 18 26 35 45
> 

The speedup from not breaking the input vector into smaller vectors is (to me) surprisingly small -- only a factor of 3:

> x <- replace(rnorm(1e6), sample(1e6, 10000), NA)
> all.equal(replace(ave(x, rev(cumsum(rev(is.na(x)))), FUN=cumsum), is.na(x), 0), cumsum.reset(x))
[1] TRUE
> system.time(cumsum.reset(x))
   user  system elapsed 
   0.31    0.03    0.35 
> system.time(ave(x, rev(cumsum(rev(is.na(x)))), FUN=cumsum))
   user  system elapsed 
   0.99    0.05    1.15 
> 

So, I'd go with the ave() approach unless this type of cumsum is the core of a long computationally intensive job.  And if that's the case, it would make sense to code it in C.

-- Tony Plate

> Bill Dunlap
> Spotfire, TIBCO Software
> wdunlap tibco.com 
>  
>> any ideas?
>>
>> thank you and best regards,
>>
>>     stefan
>>
>> ______________________________________________
>> R-help at r-project.org mailing list
>> https://stat.ethz.ch/mailman/listinfo/r-help
>> PLEASE do read the posting guide 
>> http://www.R-project.org/posting-guide.html
>> and provide commented, minimal, self-contained, reproducible code.
>>
> 
> ______________________________________________
> R-help at r-project.org mailing list
> https://stat.ethz.ch/mailman/listinfo/r-help
> PLEASE do read the posting guide http://www.R-project.org/posting-guide.html
> and provide commented, minimal, self-contained, reproducible code.
>




More information about the R-help mailing list