Найдите распределение последовательных нулей
У меня есть вектор, скажем x
который содержит только целые числа 0
, 1
и 2
. Например;
x <- c(0,1,0,2,0,0,1,0,0,1,0,0,0,1,0)
Из этого я хотел бы извлечь, сколько раз ноль происходит в каждом "шаблоне". В этом простом примере это происходит три раза по собственному усмотрению, дважды в 00
и ровно один раз в 000
, поэтому я хотел бы выводить что-то вроде:
0 3
00 2
000 1
Мой фактический набор данных довольно большой (1000-2000 элементов в векторе) и, по крайней мере теоретически, максимальное число последовательных нулей - это length(x)
Ответы
Ответ 1
1) Мы можем использовать rleid
из data.table
data.table(x)[, strrep(0, sum(x==0)) ,rleid(x == 0)][V1 != "",.N , V1]
# V1 N
#1: 0 3
#2: 00 2
#3: 000 1
2), или мы можем использовать tidyverse
library(tidyverse)
tibble(x) %>%
group_by(grp = cumsum(x != 0)) %>%
filter(x == 0) %>%
count(grp) %>%
ungroup %>%
count(n)
# A tibble: 3 x 2
# n nn
# <int> <int>
#1 1 3
#2 2 2
#3 3 1
3) Или мы можем использовать tabulate
с rleid
tabulate(tabulate(rleid(x)[x==0]))
#[1] 3 2 1
Ориентиры
При проверке с system.time
на @SymbolixAU наборе данных
system.time({
tabulate(tabulate(rleid(x2)[x2==0]))
})
# user system elapsed
# 0.03 0.00 0.03
Сравнивая с функцией Rcpp
, вышесказанное не так уж плохо
system.time({
m <- zeroPattern(x2)
m[m[,2] > 0, ]
})
# user system elapsed
# 0.01 0.01 0.03
С помощью microbenchmark
удалены методы, которые потребляют больше времени (на основе сравнения @SymbolixAU) и инициировали новое сравнение. Обратите внимание, что здесь также не яблоки для яблок, но они все еще намного более похожи, так как в предыдущем сравнении есть накладные расходы data.table
вместе с некоторым форматированием для тиражирования ожидаемого вывода OP
microbenchmark(
akrun = {
tabulate(tabulate(rleid(x2)[x2==0]))
},
G = {
with(rle(x2), table(lengths[values == 0]))
},
sym = {
m <- zeroPattern(x2)
m[m[,2] > 0, ]
},
times = 5, unit = "relative"
)
#Unit: relative
# expr min lq mean median uq max neval cld
# akrun 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 5 a
# G 6.049181 8.272782 5.353175 8.106543 7.527412 2.905924 5 b
# sym 1.385976 1.338845 1.661294 1.399635 3.845435 1.211131 5 a
Ответ 2
1) rle Используйте rle
и table
как это. Пакетов не требуется.
tab <- with(rle(x), table(lengths[values == 0]))
давая:
> tab
1 2 3
3 2 1
или же
> as.data.frame(tab)
Var1 Freq
1 1 3
2 2 2
3 3 1
То есть, существует 3 пробега одного нуля, 2 пробега из двух нулей и 1 пробег из трех нулей.
Формат вывода в вопросе не является реально выполнимым, если есть очень длинные пробежки, но просто для развлечения здесь:
data.frame(Sequence = strrep(0, names(tab)), Freq = as.numeric(tab))
давая:
Sequence Freq
1 0 3
2 00 2
3 000 1
2) gregexpr. Другая возможность - использовать регулярное выражение:
tab2 <- table(attr(gregexpr("0+", paste(x, collapse = ""))[[1]], "match.length"))
давая:
> tab2
1 2 3
3 2 1
Другие форматы вывода могут быть получены как в (1).
Заметка
Я проверил скорость с length(x)
2000 года и (1) занял около 1,6 мс на моем ноутбуке и (2) занял около 9 мс.
Ответ 3
Вы упоминаете в "довольно большой" набор данных, так что вы можете использовать C++ через Rcpp
, чтобы ускорить этот процесс (однако, сравнительный анализ показывает базовый rle
решение достаточно быстро в любом случае)
Функция может быть
library(Rcpp)
cppFunction('Rcpp::NumericMatrix zeroPattern(Rcpp::NumericVector x) {
int consecutive_counter = 0;
Rcpp::IntegerVector iv = seq(1, x.length());
Rcpp::NumericMatrix m(x.length(), 2);
m(_, 0) = iv;
for (int i = 0; i < x.length(); i++) {
if (x[i] == 0) {
consecutive_counter++;
} else if (consecutive_counter > 0) {
m(consecutive_counter-1, 1)++;
consecutive_counter = 0;
}
}
if (consecutive_counter > 0) {
m(consecutive_counter-1, 1)++;
}
return m;
}')
Что дает вам матрицу отсчетов последовательных нулей
x <- c(0,1,0,2,0,0,1,0,0,1,0,0,0,1,0)
zeroPattern(x)
m <- zeroPattern(x)
m[m[,2] > 0, ]
# [,1] [,2]
# [1,] 1 3
# [2,] 2 2
# [3,] 3 1
В большем наборе данных мы замечаем улучшения скорости
set.seed(20180411)
x2 <- sample(x, 1e6, replace = T)
m <- zeroPattern(x2)
m[m[,2] > 0, ]
library(microbenchmark)
library(data.table)
microbenchmark(
akrun = {
data.table(x2)[, strrep(0, sum(x2==0)) ,rleid(x2 == 0)][V1 != "",.N , V1]
},
G = {
with(rle(x2), table(lengths[values == 0]))
},
sym = {
m <- zeroPattern(x2)
m[m[,2] > 0, ]
},
times = 5
)
# Unit: milliseconds
# expr min lq mean median uq max neval
# akrun 3727.66899 3782.19933 3920.9151 3887.6663 4048.2275 4158.8132 5
# G 236.69043 237.32251 258.4320 246.1470 252.1043 319.8956 5
# sym 97.54988 98.76986 190.3309 225.2611 237.5781 292.4955 5
Замечания:
Функции Mine и G возвращают ответ "table" -style. Akrun отформатировал его, чтобы включить проложенные нули, так что понесут небольшую стоимость.