3
votes

The question looks simple but I didn't figure out how it can done in R. I want to modify a logical vector depending on patterns of its values. There are two modification steps:

  1. If there is a single FALSE surrounded by TRUE values, switch it to TRUE.
  2. If there are less then 3 successive TRUE values, switch them to FALSE.

Everything else should remain as it is. Here's an example:

# input  
x = c(FALSE, TRUE, FALSE, FALSE, TRUE, TRUE, FALSE, FALSE, TRUE, TRUE, TRUE,
    FALSE, TRUE, TRUE, FALSE, FALSE, TRUE,  TRUE,  TRUE,  TRUE, FALSE)

# output
xo = c(FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, TRUE, TRUE, 
   TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, TRUE,  TRUE,  TRUE,  TRUE, FALSE)

cbind(x,xo) is

          x    xo
 [1,] FALSE FALSE
 [2,]  TRUE FALSE
 [3,] FALSE FALSE
 [4,] FALSE FALSE
 [5,]  TRUE FALSE
 [6,]  TRUE FALSE
 [7,] FALSE FALSE
 [8,] FALSE FALSE
 [9,]  TRUE  TRUE
[10,]  TRUE  TRUE
[11,]  TRUE  TRUE
[12,] FALSE  TRUE
[13,]  TRUE  TRUE
[14,]  TRUE  TRUE
[15,] FALSE FALSE
[16,] FALSE FALSE
[17,]  TRUE  TRUE
[18,]  TRUE  TRUE
[19,]  TRUE  TRUE
[20,]  TRUE  TRUE
[21,] FALSE FALSE

I dont want to use a for loop because its slow and I would have to do a lot of if statements.

Is there a better way to get this working?

3
This is a general problem that seems to come up a lot on r-SO---applying, in a vectorized rather than looped-way, operations on a vector/matrix where the function to apply to one element depends on other elements identified by indexes, so it's not just a simple use of the apply family of functions or something. I'm not enough of an R ninja to write a tutorial on how to do this myself, but someone oughta... - Paul Gowder

3 Answers

3
votes

Here is an approach:

#sample data 
x <- c(FALSE, TRUE, FALSE, FALSE, TRUE, TRUE, FALSE, FALSE, TRUE, TRUE, TRUE,
    FALSE, TRUE, TRUE, FALSE, FALSE, TRUE,  TRUE,  TRUE,  TRUE, FALSE)

First, find the indices where FALSE values need to be changed to TRUE values, by looking for FALSE values that follow and are followed by TRUE values

tochange <- 
  intersect(
    intersect(
     which(x == FALSE),   # not strictly necessary
     which(diff(x) == 1)  # FALSEs followed by a TRUE
     ),
    which(diff(x) == -1) + 1 # FALSEs that follow a TRUE
    )

Change the values

x[tochange] <- TRUE

Next, look for runs of TRUE (and FALSE) that are less than 3 in length, and set them to FALSE.

xrle <- rle(x)

xrle$values[xrle$lengths < 3] <-  FALSE

newx <- inverse.rle(xrle) # thanks to Frank for pointing out inverse.rle!

# [1] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE
#[10]  TRUE  TRUE  TRUE  TRUE  TRUE FALSE FALSE  TRUE  TRUE
#[19]  TRUE  TRUE FALSE
3
votes

You can try rle (thanks to @Frank for the modification)

xtmp <- inverse.rle(within.list(rle(x),{
    n    <- length(values)
    values[lengths == 1 & !values & ! seq_len(n) %in% c(1,n)] <- TRUE
}))

res <- inverse.rle(within.list(rle(xtmp),
    values[lengths < 3 & values] <- FALSE
))

identical(xo,res) # TRUE
1
votes

Try:

make_true <- function(x) {
  string <- paste(as.numeric(x), collapse='')
  ans <- gregexpr('(?=(101))', string, perl=T)
  x[as.numeric(ans[[1]])+1L] <- TRUE
  res <- rle(x)
  res$values[res$lengths < 3] <- FALSE
  inverse.rle(res)
}

The function takes advantage of the fact that T and F can be coerced to numeric. The pattern searched for is "101".