resetting cumsum if value goes to negative in r
Asked Answered
T

5

5
ve <- c(17, -9, 9, -17, 17, -17, 11, -9, 16, -18, 17, 0, 0, -18, 17, 0, 0, -17, 14, -14, 17, -2, 0, -15, 9, -9, 17, -16, 16, -17, 17, -17, 17, -17, 17, -17, 17, -8, 7, -16, 17, -14, 14, -10, 10, -16, 16, -10, 10, -12, 12, -11, 11, -17, 17, -17, 17, -9, 8, -17, 17, -17, 17, -16, 16, -17, 17, -8, 8, -9, 9, -17, 17, -17, 17, -13, 13, -10, 7, -10, 13, -16, 17, -13, 13, -13, 13, -9, 8, -17, 17, -10, 9, -17, 17, -17, 17, -16, 16, -10, 10, -15, 15, -14, 14, -14, 15, -13, 13, -9, 9, -13, 13, -12, 12, -10, 9, -11, 12, -8, 7, -10, 10, -9, 9, -11, 11, -9, 9, -7, 7, -12, 11, -11, 12, -11, 11, -14, 14, -13, 13, -10, 10, -13, 13, -17, 17, -7, 7, -17, 17, -17, 17, -14, 14, NA)

df <- data.frame(ve = ve, calc = 0)

I need to calculate cumsum in column calc, but it needs to reset to zero and start again whenever its value goes negative.. I've tried several conditions but it's not really working...

Also, is it possible to achieve this in dplyr? I'm new to dplyr and find it somewhat difficult whenever I need to use dependent value..

Thank you for your help!

it should go as..

     ve calc
1    17    17
2    -9    8
3     9    17
4   -17    0
5    17    17
6   -17    0
7    11    11
8    -9    2
9    16    18
10  -18    0
11   17    17
12    0    17
13    0    17
14  -18    0
15   17    17

If you see row 14 and 15, with the normal cumsum it would be -1 and 16 but I want it to reset to 0 instead of -1 and continue cumsum, hence the next would be 17

Tritheism answered 13/6, 2017 at 3:52 Comment(3)
do you want to omit NAs?Luci
oh yes, NA needs to be omitted pleaseTritheism
Can you provide example output? I'm not sure exactly how the cumsum works when it hits a negative (e.g. starting from 0 at the next positive value, including the negative in the cumsum).Bite
B
3

We can replace the NA values with 0 and use cumsum

library(dplyr)
df1 <- df %>%
      group_by(grp = cumsum(lag(cumsum(replace(ve, is.na(ve), 0)) < 0, default = TRUE))) %>%
     mutate(calc = cumsum(replace(ve, is.na(ve), 0)), calc = replace(calc, calc < 0, 0)) %>%
      ungroup() %>%
      select(-grp)
head(df1, 15)
# A tibble: 15 x 2
#      ve  calc
#   <dbl> <dbl>
# 1    17    17
# 2    -9     8
# 3     9    17
# 4   -17     0
# 5    17    17
# 6   -17     0
# 7    11    11
# 8    -9     2
# 9    16    18
#10   -18     0
#11    17    17
#12     0    17
#13     0    17
#14   -18     0
#15    17    17
Breath answered 13/6, 2017 at 4:0 Comment(0)
C
2

base R's Reduce or purrr::accumulate are designed for these scenarios

df$calc <- Reduce(\(.x, .y) ifelse(.x + .y < 0, 0, .x + .y), df$ve, accumulate = TRUE)
df
#>      ve calc
#> 1    17   17
#> 2    -9    8
#> 3     9   17
#> 4   -17    0
#> 5    17   17
#> 6   -17    0
#> 7    11   11
#> 8    -9    2
#> 9    16   18
#> 10  -18    0
#> 11   17   17
#> 12    0   17
#> 13    0   17
#> 14  -18    0
#> 15   17   17
.
.
.

or in purrr

library(purrr)
library(dplyr)

df %>% mutate(calc = accumulate(ve,  ~ ifelse(.x + .y < 0, 0, .x + .y)))

Cleodel answered 11/7, 2021 at 8:8 Comment(0)
L
1

Not using dplyr, but this should work:

ve = as.data.frame(ve)
ve = na.omit(ve)
ve$cumS = 0
ve$cumS[1] = ve$ve[1]

for (i in 2 : length(ve$ve)) {

ve$cumS[i] = ifelse((ve$cumS[i - 1] + ve$ve[i]) < 0,
                     0, (ve$cumS[i - 1] + ve$ve[i]))
}
Luci answered 13/6, 2017 at 4:18 Comment(1)
This looks about right, but I think it fails because your ifelse statement doesn't actually do anything, you would need to assign the result to ve$cumS[i] I think.Bite
B
1

Here is an iterative solution. I can't think of how to do this vectorized/using dplyr without multiple passes over the data, but I'm sure someone else will:

ve_csum = numeric(length(ve))

current_total = 0
for (i in 1:length(ve)) {
    if (is.na(ve[i])) {
        ve_csum[i] = current_total
        next
    }
    current_total = current_total + ve[i]
    if (current_total < 0) {
        current_total = 0
    }
    ve_csum[i] = current_total
}

result = data.frame(ve, ve_csum)
Bite answered 13/6, 2017 at 4:34 Comment(0)
F
-1
> df$calc=ifelse(cumsum(df$ve)<0,0,cumsum(df$ve))
Fourgon answered 13/6, 2017 at 6:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.