Reverse count vector elements when sequence occurs

Viewed 1171

I have a vector with values NA, 0, and 1:

x <- c(NA, 0, 0, 1, 1, 1, 1, NA, 0, 0, 0, 0, NA, NA, 1, 1, 1, NA)
#> x
#[1] NA  0  0  1  1  1  1 NA  0  0  0  0 NA NA  1  1  1 NA

Whenever the sequence switches from 1 to NA, I would like to count the positions of non-NAs before that event and replace the elements with that number. I expect this output:

#> x_output
#[1] NA  6  5  4  3  2  1 NA  0  0  0  0 NA NA  3  2  1 NA

Does anybody have a solution for this? A vectorised approach is preferred because the vectors are long and the dataset is fairly big.

3 Answers

Using rle to define run lengths and ave to create the sequences:

x <- c(NA, 0, 0, 1, 1, 1, 1, NA, 0, 0, 0, 0, NA, NA, 1, 1, 1, NA)

fun <- function(x) {
  x <- rev(x)
  y <- rle(!is.na(x))
  y$values[y$values] <- seq_along(y$values[y$values])
  y <- inverse.rle(y)

  x[!is.na(x)] <- ave(x[!is.na(x)], y[!is.na(x)], FUN = function(x) {
    if (x[1] == 0L) return(x)
    seq_along(x)
  })
  rev(x)
}

fun(x)
#[1] NA  6  5  4  3  2  1 NA  0  0  0  0 NA NA  3  2  1 NA

Here is an option with data.table. Create an 'indx', of TRUE/FALSE column to identify the switching of 1 to NA. Then, grouped by run-length-id of logical vector (rleid(is.na(x))), if there are any TRUE in 'indx', then get the reverse of sequence of rows or else return 'x' and extract the column 'V1'

library(data.table)
data.table(x)[, indx := shift(shift(x,  fill = 0) %in% 1 & is.na(x), 
   type = 'lead', fill = FALSE)][, if(any(indx)) rev(seq_len(.N)) else 
             as.integer(x) ,rleid(is.na(x))]$V1
#[1] NA  6  5  4  3  2  1 NA  0  0  0  0 NA NA  3  2  1 NA

Another approach

library(dplyr)
start_inds <- which(x == 1 & is.na(lead(x)))
na_inds <- which(is.na(x))
sapply(start_inds, function(x) {
   sub_ind = x - na_inds
   end_inds = (x - min(sub_ind[sub_ind > 0]) + 1) : x
   x[end_inds] <<- rev(seq_along(end_inds))
})

x
#[1] NA  6  5  4  3  2  1 NA  0  0  0  0 NA NA  3  2  1 NA

We find out the intersection where x is equal to 1 and the next element is NA using lead from dplyr which gives us the indices from where we need the change the value backwards. (start_inds). We calculate all the indices in the vector where NA occurs in na_inds so that we can use it to get the closest NA value. Now for each of the start_inds we subtract it's value with na_inds and calculate the closest NA value till where we need to change the value (end_inds). To select end_inds the difference between start_ind and na_inds has to be greater than 0 as we need to NA values which are before start_ind and we use min to get the recent index of NA value. Update the values by generating a sequence seq_along using global assignment operator (<<-).

Related