dplyr如何覆盖组内除第一个出现的值以外的所有值?

5
我有一个分组数据框,其中"tag"列的值为"0"和"1"。在每个组中,我需要找到第一次出现的"1",并将其余的所有出现更改为"0"。有没有办法在dplyr中实现?
例如,让我们以"鸢尾花"数据为例,并添加额外的"tag"列:
data(iris)
set.seed(1)
iris$tag <- sample( c(0, 1), 150, replace = TRUE, prob = c(0.8, 0.2))
giris <- iris %>% group_by(Species)

在“giris”数据集中,“setosa”组中,我需要仅保留“1”的第一次出现(即在第4行),并将其余的设置为“0”。这似乎有点像应用掩码或类似的东西...
有没有办法做到这一点?我一直在尝试使用“which”和“duplicated”,但我没有成功。我一直在考虑仅过滤“1”,保留它们,然后与剩余集合连接,但这似乎很笨拙,特别是对于一个12GB的数据集。
2个回答

3

dplyr选项:

mutate(giris, newcol = as.integer(tag & cumsum(tag) == 1))

或者
mutate(giris, newcol = as.integer(tag & !duplicated(tag)))

或者使用 data.table,同样的方法,但通过引用修改:

library(data.table)
setDT(giris)
giris[, newcol := as.integer(tag & cumsum(tag) == 1), by = Species]

非常好的cumsum使用技巧 :-). 只是出于好奇:如果我有"a"和"b"而不是"0"和"1",该如何推广它呢?或者如果有三个级别怎么办?再次强调,这只是好奇。 - rpl
对于 "a" 和 "b",可以使用 as.integer(tag == "a" & cumsum(tag == "a") == 1)。不同之处在于0/1被解释为TRUE/FALSE,并且可以简单地求和或用于逻辑比较,而对于 "a" 和 "b",我们必须明确地编写检查条件。 - talat
啊,确实如此。这个设计真是简单而巧妙。非常感谢! - rpl
@rpl,我添加了一个使用duplicated的方法。如果你将其修改为:mutate(giris, newcol = as.integer(tag == "a" & !duplicated(tag))),你也可以轻松地将其用于其他情况。 - talat

2

我们可以尝试

res <- giris %>%
         group_by(Species) %>% 
         mutate(tag1 = ifelse(cumsum(c(TRUE,diff(tag)<0))!=1, 0, tag))

table(res[c("Species", "tag1")])
#            tag1
#Species      0  1
# setosa     49  1
# versicolor 49  1
# virginica  49  1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接