8

I have a vector of numbers in a data.frame such as below.

df <- data.frame(a = c(1,2,3,4,2,3,4,5,8,9,10,1,2,1))

I need to create a new column which gives a running count of entries that are greater than their predecessor. The resulting column vector should be this:

0,1,2,3,0,1,2,3,4,5,6,0,1,0

My attempt is to create a "flag" column of diffs to mark when the values are greater.

df$flag <- c(0,diff(df$a)>0)
> df$flag
0 1 1 1 0 1 1 1 1 1 1 0 1 0

Then I can apply some dplyr group/sum magic to almost get the right answer, except that the sum doesn't reset when flag == 0:

df %>% group_by(flag) %>% mutate(run=cumsum(flag))
    a flag run
1   1    0   0
2   2    1   1
3   3    1   2
4   4    1   3
5   2    0   0
6   3    1   4
7   4    1   5
8   5    1   6
9   8    1   7
10  9    1   8
11 10    1   9
12  1    0   0
13  2    1  10
14  1    0   0

I don't want to have to resort to a for() loop because I have several of these running sums to compute with several hundred thousand rows in a data.frame.

cottontail
  • 10,268
  • 18
  • 50
  • 51
nsymms
  • 115
  • 1
  • 1
  • 7

3 Answers3

20

Here's one way with ave:

ave(df$a, cumsum(c(F, diff(df$a) < 0)), FUN=seq_along) - 1
 [1] 0 1 2 3 0 1 2 3 4 5 6 0 1 0

We can get a running count grouped by diff(df$a) < 0. Which are the positions in the vector that are less than their predecessors. We add c(F, ..) to account for the first position. The cumulative sum of that vector creates an index for grouping. The function ave can carry out a function on that index, we use seq_along for a running count. But since it starts at 1, we subtract by one ave(...) - 1 to start from zero.


A similar approach using dplyr:

library(dplyr)
df %>% 
  group_by(cumsum(c(FALSE, diff(a) < 0))) %>% 
  mutate(row_number() - 1)
Pierre L
  • 28,203
  • 6
  • 47
  • 69
  • 1
    Or with `dplyr` (as per the tag) `df %>% group_by(cumsum(c(FALSE, diff(a) < 0))) %>% mutate(row_number() - 1)` – David Arenburg Oct 07 '15 at 13:59
  • 2
    Wow thanks. Works for me. I kept trying to do this with ave or rle, but couldn't put it together. – nsymms Oct 07 '15 at 14:01
  • Thanks @DavidArenburg. added : ) Although I think the tag was for suggestive purposes as opposed to required method. – Pierre L Oct 07 '15 at 14:04
  • Yeah I'm not married to dplyr, I just thought it was the logical direction to go. Both answers look great. I'll do some timing benchmarks to see what works best in my situation. Thanks again; been stuck on this many hours since yesterday. – nsymms Oct 07 '15 at 14:09
12

You don't need dplyr:

fun <- function(x) {
  test <- diff(x) > 0
  y <- cumsum(test)
  c(0, y - cummax(y * !test))
}

fun(df$a)
[1] 0 1 2 3 0 1 2 3 4 5 6 0 1 0
Roland
  • 127,288
  • 10
  • 191
  • 288
  • 2
    I've seen something like this on SO, but can't find it anymore. I would really like to give proper credit. – Roland Oct 07 '15 at 14:41
3
a <- c(1,2,3,4,2,3,4,5,8,9,10,1,2,1)
f <- c(0, diff(a)>0)
ifelse(f, cumsum(f), f)

that it is without reset.
with reset:

unlist(tapply(f, cumsum(c(0, diff(a) < 0)), cumsum))
jogo
  • 12,469
  • 11
  • 37
  • 42