У меня есть маленький орешек, который нужно расколоть.
У меня есть data.frame
, где прогоны TRUE
разделены прогонами одного или нескольких FALSE
или NA
:
group criterium
1 A NA
2 A TRUE
3 A TRUE
4 A TRUE
5 A FALSE
6 A FALSE
7 A TRUE
8 A TRUE
9 A FALSE
10 A TRUE
11 A TRUE
12 A TRUE
13 B NA
14 B FALSE
15 B TRUE
16 B TRUE
17 B TRUE
18 B FALSE
structure(list(group = structure(c(1L, 1L, 1L, 1L, 1L, 1L, 1L,
1L, 1L, 1L, 1L, 1L, 2L, 2L, 2L, 2L, 2L, 2L), .Label = c("A",
"B"), class = "factor"), criterium = c(NA, TRUE, TRUE, TRUE,
FALSE, FALSE, TRUE, TRUE, FALSE, TRUE, TRUE, TRUE, NA, FALSE,
TRUE, TRUE, TRUE, FALSE)), class = "data.frame", row.names = c(NA,
-18L))
Я хочу ранжировать группы TRUE
в столбце criterium
в порядке возрастания, игнорируя FALSE
и NA
. Цель состоит в том, чтобы иметь уникальный последовательный идентификатор для каждого запуска TRUE
внутри каждого group
.
Итак, результат должен выглядеть так:
group criterium goal
1 A NA NA
2 A TRUE 1
3 A TRUE 1
4 A TRUE 1
5 A FALSE NA
6 A FALSE NA
7 A TRUE 2
8 A TRUE 2
9 A FALSE NA
10 A TRUE 3
11 A TRUE 3
12 A TRUE 3
13 B NA NA
14 B FALSE NA
15 B TRUE 1
16 B TRUE 1
17 B TRUE 1
18 B FALSE NA
Я уверен, что есть относительно простой способ сделать это, я просто не могу придумать ни одного. Я экспериментировал с dense_rank()
и другими оконными функциями dplyr
, но безрезультатно.
это действительно забавное решение. Очень хорошая работа!
В вашем примере сначала идет вся группа A, затем группа B. Нам не нужно обрабатывать случаи с группой = A, критерием = ИСТИНА, перемежающимися с группой = B, критерием = ИСТИНА?
Нет, когда группа А останавливается, останавливается и последовательность для группы А.
Но я предлагаю, если вы создадите пример с группой = A, критерием = TRUE, за которым следует группа = B, критерий = TRUE (без промежуточных FALSE), получит ли он новый «целевой» номер или нет? Некоторые из ответов здесь потерпят неудачу, потому что они не группируются по group
или не учитывают разрыв в group
.
На самом деле этого не может быть, потому что, судя по тому, как построен мой data.frame, каждая группа начинается с NA.
Может быть, я слишком усложнил это, но один из способов с dplyr
library(dplyr)
df %>%
mutate(temp = replace(criterium, is.na(criterium), FALSE),
temp1 = cumsum(!temp)) %>%
group_by(temp1) %>%
mutate(goal = +(row_number() == which.max(temp) & any(temp))) %>%
group_by(group) %>%
mutate(goal = ifelse(temp, cumsum(goal), NA)) %>%
select(-temp, -temp1)
# group criterium goal
# <fct> <lgl> <int>
# 1 A NA NA
# 2 A TRUE 1
# 3 A TRUE 1
# 4 A TRUE 1
# 5 A FALSE NA
# 6 A FALSE NA
# 7 A TRUE 2
# 8 A TRUE 2
# 9 A FALSE NA
#10 A TRUE 3
#11 A TRUE 3
#12 A TRUE 3
#13 B NA NA
#14 B FALSE NA
#15 B TRUE 1
#16 B TRUE 1
#17 B TRUE 1
#18 B FALSE NA
Мы сначала replace
NA
s в столбце criterium
до FALSE
и берем кумулятивную сумму по ее отрицанию (temp1
). Мы group_by
temp1
и присваиваем 1 каждому первому TRUE
значению в группе. Наконец, группируя по group
, мы берем кумулятивную сумму для значений TRUE
или возвращаем NA
для значений FALSE
и NA
.
Вариант data.table
с использованием rle
library(data.table)
DT <- as.data.table(dat)
DT[, goal := {
r <- rle(replace(criterium, is.na(criterium), FALSE))
r$values <- with(r, cumsum(values) * values)
out <- inverse.rle(r)
replace(out, out == 0, NA)
}, by = group]
DT
# group criterium goal
# 1: A NA NA
# 2: A TRUE 1
# 3: A TRUE 1
# 4: A TRUE 1
# 5: A FALSE NA
# 6: A FALSE NA
# 7: A TRUE 2
# 8: A TRUE 2
# 9: A FALSE NA
#10: A TRUE 3
#11: A TRUE 3
#12: A TRUE 3
#13: B NA NA
#14: B FALSE NA
#15: B TRUE 1
#16: B TRUE 1
#17: B TRUE 1
#18: B FALSE NA
шаг за шагом
Когда мы вызываем r <- rle(replace(criterium, is.na(criterium), FALSE))
, мы получаем объект класса rle
r
#Run Length Encoding
# lengths: int [1:9] 1 3 2 2 1 3 2 3 1
# values : logi [1:9] FALSE TRUE FALSE TRUE FALSE TRUE ...
Мы манипулируем компонентом values
следующим образом.
r$values <- with(r, cumsum(values) * values)
r
#Run Length Encoding
# lengths: int [1:9] 1 3 2 2 1 3 2 3 1
# values : int [1:9] 0 1 0 2 0 3 0 4 0
То есть мы заменили TRUE
s на совокупную сумму values
и установили FALSE
s на 0
. Теперь inverse.rle
возвращает вектор, в котором values
будет повторяться lenghts
раз
out <- inverse.rle(r)
out
# [1] 0 1 1 1 0 0 2 2 0 3 3 3 0 0 4 4 4 0
Это почти то, что хочет OP, но нам нужно заменить 0
s на NA
replace(out, out == 0, NA)
Это делается для каждого group
.
данные
dat <- structure(list(group = structure(c(1L, 1L, 1L, 1L, 1L, 1L, 1L,
1L, 1L, 1L, 1L, 1L, 2L, 2L, 2L, 2L, 2L, 2L), .Label = c("A",
"B"), class = "factor"), criterium = c(NA, TRUE, TRUE, TRUE,
FALSE, FALSE, TRUE, TRUE, FALSE, TRUE, TRUE, TRUE, NA, FALSE,
TRUE, TRUE, TRUE, FALSE)), class = "data.frame", row.names = c(NA,
-18L))
Спасибо! Я анализировал ваш ответ именно так. Ваш ответ научил меня больше всего. Но chinsoon12 — это всего лишь Teufelskerl. ^^
Чистое решение Base R, мы можем создать пользовательскую функцию через rle
и использовать ее для каждой группы, т.е.
f1 <- function(x) {
x[is.na(x)] <- FALSE
rle1 <- rle(x)
y <- rle1$values
rle1$values[!y] <- 0
rle1$values[y] <- cumsum(rle1$values[y])
return(inverse.rle(rle1))
}
do.call(rbind,
lapply(split(df, df$group), function(i){i$goal <- f1(i$criterium);
i$goal <- replace(i$goal, is.na(i$criterium)|!i$criterium, NA);
i}))
Конечно, если вы хотите, вы можете применить его через dplyr
, т.е.
library(dplyr)
df %>%
group_by(group) %>%
mutate(goal = f1(criterium),
goal = replace(goal, is.na(criterium)|!criterium, NA))
который дает,
# A tibble: 18 x 3 # Groups: group [2] group criterium goal <fct> <lgl> <dbl> 1 A NA NA 2 A TRUE 1 3 A TRUE 1 4 A TRUE 1 5 A FALSE NA 6 A FALSE NA 7 A TRUE 2 8 A TRUE 2 9 A FALSE NA 10 A TRUE 3 11 A TRUE 3 12 A TRUE 3 13 B NA NA 14 B FALSE NA 15 B TRUE 1 16 B TRUE 1 17 B TRUE 1 18 B FALSE NA
Другой data.table
подход:
library(data.table)
setDT(dt)
dt[, cr := rleid(criterium)][
(criterium), goal := rleid(cr), by=.(group)]
вы можете просто взять то, что вам нужно, с этим произведением красоты;
as.numeric(as.factor(cumsum(is.na(d$criterium^NA)) + d$criterium^NA))
-- просто нужно подать заявку группой