按组为 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
的运行被一个或多个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))
我想按升序排列criterium
列中的TRUE
组,而忽略FALSE
和NA
。目标是在每个 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
我们首先将replace
NA
s 在criterium
列中添加到FALSE
,然后对其取反(temp1
)。我们 group_by
temp1
并为组中的每个第一个 TRUE
值分配 1。最后按group
分组,我们对TRUE
值进行累积总和,或者为FALSE
和NA
值返回NA
。
【讨论】:
【参考方案3】:使用rle
的data.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 <- 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。 ^^【参考方案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 运行创建计数器的主要内容,如果未能解决你的问题,请参考以下文章
Error in if (x[i] == NA) { : missing value where TRUE/FALSE needed
Error in if (x[i] == NA) { : missing value where TRUE/FALSE needed