Создать счетчик запусков TRUE среди FALSE и NA по группам

У меня есть маленький орешек, который нужно расколоть.

У меня есть 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, но безрезультатно.

вы можете просто взять то, что вам нужно, с этим произведением красоты; as.numeric(as.factor(cumsum(is.na(d$criterium^NA)) + d$criterium^NA)) -- просто нужно подать заявку группой

user20650 10.04.2019 10:45

это действительно забавное решение. Очень хорошая работа!

Humpelstielzchen 10.04.2019 10:49

В вашем примере сначала идет вся группа A, затем группа B. Нам не нужно обрабатывать случаи с группой = A, критерием = ИСТИНА, перемежающимися с группой = B, критерием = ИСТИНА?

smci 10.04.2019 10:50

Нет, когда группа А останавливается, останавливается и последовательность для группы А.

Humpelstielzchen 10.04.2019 10:51

Но я предлагаю, если вы создадите пример с группой = A, критерием = TRUE, за которым следует группа = B, критерий = TRUE (без промежуточных FALSE), получит ли он новый «целевой» номер или нет? Некоторые из ответов здесь потерпят неудачу, потому что они не группируются по group или не учитывают разрыв в group.

smci 10.04.2019 10:53

На самом деле этого не может быть, потому что, судя по тому, как построен мой data.frame, каждая группа начинается с NA.

Humpelstielzchen 10.04.2019 10:56
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
11
6
844
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Может быть, я слишком усложнил это, но один из способов с 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

Мы сначала replaceNAs в столбце criterium до FALSE и берем кумулятивную сумму по ее отрицанию (temp1). Мы group_bytemp1 и присваиваем 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 

То есть мы заменили TRUEs на совокупную сумму values и установили FALSEs на 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, но нам нужно заменить 0s на 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. ^^

Humpelstielzchen 10.04.2019 10:36

Чистое решение 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)]

Другие вопросы по теме