найти и заменить числовую последовательность в r

У меня есть dataframe с последовательностью чисел, подобной приведенной ниже:

data <- c(1,1,1,0,0,1,1,2,2,2,0,0,0,2,1,1,0,1,0,2)

Мне нужно, чтобы найти все экземпляры 1, 2 или 3 повторений 0, где последующие и следующие числа - identical-, то есть оба 1 или оба 2 (например, 1,0,1 или 2,0,0,2 но НЕ 2,0,1).

Затем мне нужно заполнить нули только окружающим значением.

Мне удалось найти и подсчитать последовательные нули

consec <- (!data) * unlist(lapply(rle(data)$lengths, seq_len))

то я нашел строку, где эти последовательные нули начинаются с:

consec <- as.matrix(consec)
first_na <- which(consec==1,arr.ind=TRUE)

Но я смущен процессом замены

Я был бы очень признателен за вашу помощь в этом!

деревенщина

Ответ 1

Поскольку, кажется, большой интерес в ответе на этот вопрос, я думал, что напишу альтернативный метод регулярных выражений для потомков.

Используя функцию "gregexpr", вы можете искать шаблоны и использовать результирующие совпадения местоположения и длины совпадений, чтобы вызывать, какие значения изменить в исходном векторе. Преимущество использования регулярных выражений состоит в том, что мы можем четко указать, какие шаблоны мы хотим сопоставить, и в результате у нас не будет никаких исключений, о которых можно было бы беспокоиться.

Примечание. Следующий пример работает как написанный, потому что мы принимаем одноразрядные значения. Мы могли бы легко адаптировать его для других шаблонов, но мы можем взять небольшой ярлык с одиночными символами. Если бы мы хотели сделать это с возможными знаками с несколькими цифрами, мы хотели бы добавить символ разделения как часть первой функции конкатенации ("вставить").


Код

str.values <- paste(data, collapse="") # String representation of vector
str.matches <- gregexpr("1[0]{1,3}1", str.values) # Pattern 101/1001/10001
data[eval(parse(text=paste("c(",paste(str.matches[[1]] + 1, str.matches[[1]] - 2 + attr(str.matches[[1]], "match.length"), sep=":", collapse=","), ")")))] <- 1 # Replace zeros with ones
str.matches <- gregexpr("2[0]{1,3}2", str.values) # Pattern 202/2002/20002
data[eval(parse(text=paste("c(",paste(str.matches[[1]] + 1, str.matches[[1]] - 2 + attr(str.matches[[1]], "match.length"), sep=":", collapse=","), ")")))] <- 2 # Replace zeros with twos

Шаг 1. Создайте одну строку из всех значений данных.

str.values <- paste(data, collapse="")
# "11100112220002110102"

Это сворачивает данные в одну длинную строку, поэтому мы можем использовать на ней регулярное выражение.

Шаг 2. Примените регулярное выражение, чтобы найти места и длины любых совпадений внутри строки.

str.matches <- gregexpr("1[0]{1,3}1", str.values)
# [[1]]
# [1]  3 16
# attr(,"match.length")
# [1] 4 3
# attr(,"useBytes")
# [1] TRUE

В этом случае мы используем регулярное выражение для поиска первого шаблона, от одного до трех нулей ([0]{2,}) с единицами с каждой стороны (1[0]{1,3}1). Нам нужно будет сопоставить весь шаблон, чтобы не допустить проверки соответствия или двойки на концах. На следующем шаге мы вычтем эти концы.

Шаг 3: Запишите их во все соответствующие местоположения в исходном векторе.

data[eval(parse(text=paste("c(",paste(str.matches[[1]] + 1, str.matches[[1]] - 2 + attr(str.matches[[1]], "match.length"), sep=":", collapse=","), ")")))] <- 1
# 1 1 1 1 1 1 1 2 2 2 0 0 0 2 1 1 1 1 0 2

Мы делаем здесь несколько шагов. Во-первых, мы создаем список числовых последовательностей из чисел, которые соответствуют регулярному выражению. В этом случае есть два совпадения, которые начинаются с индексов 3 и 16 и составляют соответственно 4 и 3 позиции соответственно. Это означает, что наши нули расположены в индексах (3 + 1) :( 3-2 + 4) или 4: 5 и в (16 + 1) :( 16-2 + 3) или 17:17. Мы конкатенируем ("вставляем") эти последовательности, используя параметр "свернуть", если есть несколько совпадений. Затем мы используем вторую конкатенацию, чтобы поместить последовательности внутри функции comb (c()). Используя функции "eval" и "parse", мы превращаем этот текст в код и передаем его в качестве значений индекса в массив [data]. Мы пишем все в эти места.

Шаг x: Повторите для каждого шаблона. В этом случае нам нужно выполнить второй поиск и найти от одного до трех нулей с двумя сторонами с обеих сторон, а затем выполнить то же утверждение, что и в шаге 3, но назначить двойки вместо них.

str.matches <- gregexpr("2[0]{1,3}2", str.values)
# [[1]]
# [1] 10
# attr(,"match.length")
# [1] 5
# attr(,"useBytes")
# [1] TRUE

data[eval(parse(text=paste("c(",paste(str.matches[[1]] + 1, str.matches[[1]] - 2 + attr(str.matches[[1]], "match.length"), sep=":", collapse=","), ")")))] <- 2
# 1 1 1 1 1 1 1 2 2 2 2 2 2 2 1 1 1 1 0 2

Обновление: я понял, что исходная проблема говорит, что она соответствует одному-трем нулям подряд, а не "двум или более", которые я написал в исходном коде. Я обновил регулярные выражения и объяснение, хотя код остается тем же.

Ответ 2

Вот петлевое решение с использованием rle() и inverse.rle().

data <- c(1,1,1,0,0,1,1,2,2,2,0,0,0,2,1,1,0,1,0,2)

local({
  r <- rle(data)
  x <- r$values
  x0 <- which(x==0) # index positions of zeroes
  xt <- x[x0-1]==x[x0+1] # zeroes surrounded by same value
  r$values[x0[xt]] <- x[x0[xt]-1] # substitute with surrounding value
  inverse.rle(r)
})

[1] 1 1 1 1 1 1 1 2 2 2 2 2 2 2 1 1 1 1 0 2

PS. Я использую local() как простой механизм, чтобы не сбивать рабочую область с множеством новых временных объектов. Вы могли бы создать function вместо использования local - я просто нахожу, что я использую local множество в настоящее время для этого типа задач.


PPS. Вам придется изменить этот код, чтобы исключить начальные или конечные нули в исходных данных.

Ответ 3

Может быть решение без цикла for, но вы можете попробовать следующее:

tmp <- rle(data)
val <- tmp$values
for (i in 2:(length(val)-1)) {
  if (val[i]==0 & val[i-1]==val[i+1]) val[i] <- val[i-1]
}
tmp$values <- val
inverse.rle(tmp)  

Который дает:

[1] 1 1 1 1 1 1 1 2 2 2 2 2 2 2 1 1 1 1 0 2