按组为 FALSE 和 NA 之间的 TRUE 运行创建计数器

Posted

技术标签:

【中文标题】按组为 FALSE 和 NA 之间的 TRUE 运行创建计数器【英文标题】:Create counter for runs of TRUE among FALSE and NA, by group 【发布时间】:2019-08-31 13:04:15 【问题描述】:

我有一个小问题要破解。

我有一个data.frame,其中TRUE 的运行被一个或多个FALSENA 的运行分隔:

   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))

我想按升序排列criterium 列中的TRUE 组,而忽略FALSENA。目标是在每个 group 内为每次运行 TRUE 提供一个唯一的、连续的 ID。

所以结果应该是这样的:

    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)) -- 只需要按组申请 这是一个非常有趣的解决方案。干得好! 在您的示例中,首先是 A 组,然后是 B 组。我们不需要处理 group=A、criterium=TRUE 和 group=B、criterium=TRUE 的情况?跨度> 不,当 A 组停止时,停止 A 组的序列。 但是我建议如果你用 group=A, criterium=TRUE 后跟 group=B, criterium=TRUE (中间没有 FALSE)来构建一个示例,那会得到一个新的 '目标数字与否?这里的一些答案会失败,因为它们没有按group 分组或考虑group 中的不连续性。 【参考方案1】:

另一个data.table 方法:

library(data.table)
setDT(dt)
dt[, cr := rleid(criterium)][
    (criterium), goal := rleid(cr), by=.(group)]

【讨论】:

【参考方案2】:

也许我过于复杂了,但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_by temp1 并为组中的每个第一个 TRUE 值分配 1。最后按group 分组,我们对TRUE 值进行累积总和,或者为FALSENA 值返回NA

【讨论】:

【参考方案3】:

使用rledata.table 选项

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 &lt;- 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。 ^^【参考方案4】:

纯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

【讨论】:

以上是关于按组为 FALSE 和 NA 之间的 TRUE 运行创建计数器的主要内容,如果未能解决你的问题,请参考以下文章

按组将函数应用于整个数据表

在 data.table 中按组划分的分位数

Error in if (x[i] == NA) { : missing value where TRUE/FALSE needed

Error in if (x[i] == NA) { : missing value where TRUE/FALSE needed

在 dplyr 中按组过滤多个条件的条件 IF

如何从 R 中的数据帧的开头和结尾删除 NA?