Найти последовательную последовательность нулей в R

У меня есть data.frame действительно большой (на самом деле data.table). Теперь, чтобы упростить вещи, допустим, что мой data.frame выглядит следующим образом:

x <- c(1, 1, 0, 0, 1, 0, 0, NA, NA, 0) 
y <- c(1 ,0 ,NA, NA, 0, 0, 0, 1, 1, 0)
mydf <- data.frame(rbind(x,y))

Я хотел бы определить, в какой строке (если таковая имеется) последняя последовательность формируется тремя последовательными нулями, не считая NA. Итак, в приведенном выше примере первая строка имеет три последовательных нуля в последней последовательности, но не вторую.

Я знаю, как это сделать, если только у меня есть вектор (а не data.frame):

runs <-  rle(x[is.na(x)==F])

runs$lengths[length(runs$lengths)] > 2 & runs$values[length(runs$lengths)]==0

Я, очевидно, могу сделать цикл, и у меня будет то, что я хочу. Но это будет невероятно неэффективно, и мой фактический data.frame довольно большой. Итак, какие-либо идеи о том, как это сделать самым быстрым способом?

Я предполагаю, что это применимо, но я не могу думать об использовании его прямо сейчас. Кроме того, возможно, есть способ data.table сделать это?

ps: На самом деле, этот data.frame является измененной версией моей исходной таблицы данных. Если каким-то образом я смогу выполнить работу с data.frame в исходном формате, это нормально. Чтобы узнать, как мой исходный файл data.frame, просто подумайте об этом как:

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

myOriginalDf <- data.frame(value=c(x,y), id=rep(c('x','y'), c(length(x), length(y))))

Ответ 1

Используя data.table, поскольку ваш вопрос предполагает, что вы действительно хотите, насколько я могу видеть, это делает то, что вы хотите

DT <- data.table(myOriginalDf)

# add the original order, so you can't lose it
DT[, orig := .I]

# rle by id, saving the length as a new variables

DT[, rleLength := {rr <- rle(value); rep(rr$length, rr$length)}, by = 'id']

# key by value and length to subset 

setkey(DT, value, rleLength)

# which rows are value = 0 and length > 2

DT[list(0, unique(rleLength[rleLength>2])),nomatch=0]

##    value rleLength id orig
## 1:     0         3  x    6
## 2:     0         3  x    7
## 3:     0         3  x    8
## 4:     0         4  y   10
## 5:     0         4  y   11
## 6:     0         4  y   12
## 7:     0         4  y   13

Ответ 2

Вот выражение приложения, основанного на вашем решении для вектора. Это может сделать то, что вы хотите.

z <- apply(mydf,1, function(x) {
runs <-  rle(x[is.na(x)==FALSE]) ;
runs$lengths[length(runs$lengths)] > 2 & runs$values[length(runs$lengths)]==0 })

mydf[z,]

#   X1 X2 X3 X4 X5 X6 X7 X8 X9 X10
# x  1  1  0  0  1  0  0 NA NA   0

Ответ 3

isMidPoint ниже будет идентифицировать средний 0, если он есть.

library(data.table)
myOriginalDf <- data.table(myOriginalDf, key="id")

myOriginalDf[, isMidPoint := FALSE]
myOriginalDf <- myOriginalDf[!is.na(value)][(c(FALSE, !value[-(1:2)], FALSE) & c(!value[-(length(value))], FALSE) & c(FALSE, !value[-length(value)])), isMidPoint := TRUE, by=id]

Объяснение:

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

Так как ваши значения 0 / 1, они эффективно T / F, и это делает его чрезвычайно простым для оценки (при условии отсутствия НС).

Если v - ваши значения (без NA), то !v & !v[-1] будет TRUE в любом месте где элемент и его преемник равны 0. Добавьте в & !v[-(1:2)], и это будет быть верным везде, где у вас есть середина серии из трех 0s. Обратите внимание, что это также захватывает серию из 4+ 0s!

Тогда остается только (1) рассчитать выше, удаляя (и учитывая!) любые NA, и (2) отделяет значение id. К счастью, data.table делает из них легкий ветерок.

Результаты:

  > myOriginalDf

    row value id isMidPoint
 1:   1     1  x      FALSE
 2:   2     1  x      FALSE
 3:   3     0  x      FALSE
 4:   4     0  x      FALSE
 5:   5     1  x      FALSE
 6:   6     0  x      FALSE
 7:   7     0  x       TRUE  <~~~~
 8:   9     0  x      FALSE
 9:  10     1  x      FALSE
10:  11     0  x      FALSE
11:  12     0  x       TRUE  <~~~~
12:  13     0  x       TRUE  <~~~~
13:  14     0  x       TRUE  <~~~~
14:  15     0  x      FALSE
15:  16     1  y      FALSE
16:  17     0  y      FALSE
17:  18     0  y       TRUE  <~~~~
18:  20     0  y      FALSE
19:  21     1  y      FALSE
20:  22     1  y      FALSE
21:  23     0  y      FALSE
22:  25     0  y       TRUE  <~~~~
23:  27     0  y       TRUE  <~~~~
24:  29     0  y      FALSE
    row value id isMidPoint

ИЗМЕНИТЬ НА КОММЕНТАРИИ:

Если вы хотите найти последнюю последовательность, которая истинна, используйте:

    max(which(myOriginalDf$isMidpoint))

Если вы хотите знать, если используется последняя последовательность:

  # Will be TRUE if last possible sequence is 0-0-0
  #   Note, this accounts for NA as well
  myOriginalDf[!is.na(value), isMidpoint[length(isMidpoint)-1]

Ответ 4

Решение Base R на основе rle, которое повторяет каждый счетчик длины столько раз:

rle_lens <- rle(myOriginalDf$value)$lengths
myOriginalDf$rle_len <- unlist(lapply(1:length(rle_lens), function(i) rep(rle_lens[i], rle_lens[i])))

Затем вы можете value == 0 & rle_len >= 3 строки, в которых value == 0 & rle_len >= 3 (при желании номера строк сохраняются как новые столбцы)

> myOriginalDf
   value id rle_len
1      1  x       2
2      1  x       2
3      0  x       2
4      0  x       2
5      1  x       1
6      0  x       3
7      0  x       3
8      0  x       3
9      1  y       1
10     0  y       4
11     0  y       4
12     0  y       4
13     0  y       4
14     1  y       2
15     1  y       2
16     0  y       1

Чтобы получить индекс первой/последней строки каждой группы, мы можем сложить длины cumsum используя cumsum:

last_ind <- cumsum(rle(myOriginalDf$value)$lengths)
# 2  4  5  8  9 13 15 16
first_ind <- last_ind - rle(myOriginalDf$value)$lengths + 1 
# 1  3  5  6  9 10 14 16