'Apply ifelse without applying a for loop using R

I have data with 2 columns like this :

v1 = c(0, 29, 32, 29, 32, 28, -59, 30)
v2 = c(0, 0, 0, 0, 1, 1, 0, 1)

data = data.frame(v1, v2)

   v1 v2
1   0  0
2  29  0
3  32  0
4  29  0
5  32  1
6  28  1
7 -59  0
8  30  1

I want to change the values of the column v2 as follows: if the value of v1 is negative, then change all previous 1s of v2 to 0. I can do this using applying a for loop using R. Is there a way to do the same thing without applying a for loop (probably using dplyr package)?

Update My actual problem is more general than the situation I explained earlier. I apologize for the confusion. My data can have more than one negative values like in the given example: enter image description here



Solution 1:[1]

A dplyr solution with cumsum() determining the position where v1 < 0:

library(dplyr)

data %>%
  mutate(v2 = ifelse(row_number() < which.max(cumsum(v1 < 0)), 0, v2))

   v1 v2
1   0  0
2  29  0
3  32  0
4  29  0
5  32  0
6  28  0
7 -59  0
8  30  1

If the data have more than one negative values:

data <- data.frame(v1 = c(0, 29, 32, 60, -30, 31, -31, 31),
                   v2 = c(0,  0,  0,  1,   0,  1,   0,  1))

My code gives (result column):

   v1 v2 result
1   0  0      0
2  29  0      0
3  32  0      0
4  60  1      0
5 -30  0      0
6  31  1      0
7 -31  0      0
8  31  1      1

Solution 2:[2]

Last Update:

v1= c(0 , 29,  32 , 60, -30 , 31, -31,  31  )
v2=c(0, 0, 0, 1, 0, 1,0,1)
data=data.frame(v1,v2)
data %>% 
  group_by(group_id = lag(cumsum(v1<0), default=0)) %>% 
  mutate(v2 = lag(cumsum(v1<0), default = min(v2))) %>% 
  ungroup() %>% 
  select(-group_id)
    v1    v2
  <dbl> <dbl>
1     0     0
2    29     0
3    32     0
4    60     0
5   -30     0
6    31     0
7   -31     0
8    31     1

First answer: We could use lag together with cumsum:

library(dplyr)
data %>% 
  mutate(v2 = lag(cumsum(v1<0), default = 0))
   v1 v2
1   0  0
2  29  0
3  32  0
4  29  0
5  32  0
6  28  0
7 -59  0
8  30  1

Solution 3:[3]

No need for something fancy, find the last 1 and overwrite all prior 1's as zero in 2 steps

# Find last one
lastO <- max(which(data$v1))
if(length(last0)){
  # Overwrite all v2 where v2 == 1 and the row is prior to the last row in v1
  data$v2[data$v2 == 1 & seq.int(lastO) < lastO] <- 0
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2
Solution 3 Oliver