0
numbers <- c(1, 0.9, 0.8, 0.7, 0.71, 0.7, 0.72, 0.69, 0.696, 0.697, 0.7, 
0.71, 0.72, 0.55, 0.6, 0.66, 0.55, 0.56, 0.58)

Given numbers, I want to find the first index for which the value in numbers does not decrease for the next n = 5 values. In the above example, the index I'm looking for is 8 because when numbers[8] <= numbers[9:(8 + 5)]. Here's my attempt:

myfun <- function(numbers, n){
  for(i in 1:length(numbers)){
    if(all(numbers[i] <= numbers[(i + 1):(i + n)])){
      return(i)
    }
  }
}

> myfun(numbers, 5)
[1] 8

Is there a quicker way to obtain the answer without writing a for loop?

Adrian
  • 9,229
  • 24
  • 74
  • 132
  • The answer to this question comes close to giving you what you need: https://stackoverflow.com/questions/32994060/r-cumulative-sum-by-condition-with-reset – MrFlick Aug 13 '20 at 01:35
  • A general thought - you can speed up these sorts of processes by only looping over `1:(nextnvalues)` and comparing a lagged vector for each of the 5 look-forwards, rather than looping over each and every row. I did something very similar here the other day - https://stackoverflow.com/a/63311015/496803 – thelatemail Aug 13 '20 at 02:05

1 Answers1

2

EDIT

I think I misunderstood the question earlier (thanks to @thelatemail for bringing that into notice). You want to find out the value which is less than all of next n values.

You can do this with rolling operations.

n <- 5
which(zoo::rollapply(numbers, n, function(x) all(x >= x[1])))[1]
#[1] 8

Earlier answer

This returns the index of continually increasing sequence in numbers.

You can use rle :

n <- 5
with(rle(diff(numbers) > 0), 
      sum(lengths[seq_len(which(lengths >= n & values)[1] - 1)])) + 1
#[1] 8

You can break it down for better understanding :

diff gives difference between consecutive numbers.

diff(numbers)
# [1] -0.100 -0.100 -0.100  0.010 -0.010  0.020 -0.030  0.006  0.001
#[10]  0.003  0.010  0.010 -0.170  0.050  0.060 -0.110  0.010  0.020

We compare it with > 0 to get TRUE for increasing values and FALSE for decreasing.

diff(numbers) > 0
# [1] FALSE FALSE FALSE  TRUE FALSE  TRUE FALSE  TRUE  TRUE  TRUE  TRUE
#[12]  TRUE FALSE  TRUE  TRUE FALSE  TRUE  TRUE

We apply rle over it :

tmp <- rle(diff(numbers) > 0)
tmp
#Run Length Encoding
#  lengths: int [1:10] 3 1 1 1 1 5 1 2 1 2
#  values : logi [1:10] FALSE TRUE FALSE TRUE FALSE TRUE ...

We find a position where the length of increasing sequence is greater than equal to n

tmp$lengths >= n & tmp$values
#[1] FALSE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE FALSE

Use which to get it's index, [1] to select 1st one if there are multiple :

which(tmp$lengths >= n & tmp$values)[1]
[1] 6

sum all the lengths before this index so -1 to above number

sum(tmp$lengths[seq_len(which(tmp$lengths >= n & tmp$values)) - 1])
#[1] 7

Now add +1 to above number get next index.

If you use this step-by-step approach you could handle different edge cases more easily rather than the one-liner at the top.

Ronak Shah
  • 377,200
  • 20
  • 156
  • 213
  • Just curious though - is this assuming there has to be a consistent run of decreasing values? I got the impression from OP that all numbers just have to be less than the current number. E.g. if the current value is 10 and the next 5 are 8 9 8 7 6 then you have an increase via `diff` but they are always less than 10. – thelatemail Aug 13 '20 at 01:55
  • Re-reading the question I think you are right.... _Facepalm_ – Ronak Shah Aug 13 '20 at 02:02