Title: Replace/Modify values in logical vector (Pattern Matching)
Tags: r
Question: 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:
If there is a single FALSE surrounded by TRUE values, switch it to TRUE.
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?
Comment: 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...

Here is the accepted answer: 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
Comment for this answer: Thanks! That's a really beautiful solution. Works perfectly. I will have to learn a lot!

Here is another answer: 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".
Comment for this answer: Also a really nice way. Thanks so much!

Here is another answer: You can try rle (thanks to @Frank for the modification)
xtmp <- inverse.rle(within.list(rle(x),{
n    <- length(values)
values[lengths == 1 &amp; !values &amp; ! seq_len(n) %in% c(1,n)] <- TRUE
res <- inverse.rle(within.list(rle(xtmp),
values[lengths < 3 &amp; values] <- FALSE
identical(xo,res) # TRUE
Comment for this answer: @plafort The credit goes to Frank for modifying it.  I liked your take on `regex`
Comment for this answer: @plafort It's not for here.  I was trying to replace some values in a column with sql command. Not getting it right.  I'll try to figure it out.  Thanks.
Comment for this answer: @plafort Wouldn't just `as.numeric(ans[[1]])+1L` works for you?
Comment for this answer: I had a hard time pulling the first instance of my regex match. It seems to be buried deep into `gregexpr` output with the mess of attributes. Do you know a quicker way?
Comment for this answer: It does work. I was led astray by the bloated output of `ans`. Thank you

{'source': 'StackExchangeDataset', 'subset': 'stackoverflow.com', 'metadata': {'AnswerCount': 3, 'CommentCount': 1, 'ContentLicense': 'CC BY-SA 3.0', 'CreationDate': '2015:00:06/20/15', 'FavoriteCount': None, 'PostTypeId': 1, 'Tags': '<r>', 'Title': 'Replace/Modify values in logical vector (Pattern Matching)', 'ViewCount': 917, 'id': 30955563, 'user_AboutMe': '<p>“No thief, however skillful, can rob one of knowledge, and that is why knowledge is the best and safest treasure to acquire.” ― L. Frank Baum</p>\n', 'user_AccountId': 2220151.0, 'user_DisplayName': 'Stefan', 'user_Reputation': 981.0, 'user_Views': 97.0, 'user_id': 1959724.0}}