Найти последовательную последовательность нулей в 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