跨越几列删除重复值但保留行

3

我有一个看起来像这样的数据框:

dat <- data.frame(id=1:6,
                  z_1=c(100,290,38,129,0,290),
                  z_2=c(20,0,0,0,0,290),
                  z_3=c(0,0,38,0,0,98),
                  z_4=c(0,0,38,127,38,78),
                  z_5=c(23,0,25,0,0,98),
                  z_6=c(100,0,25,127,0,9))

dat

  id z_1 z_2 z_3 z_4 z_5 z_6
1  1 100 20  0   0   23  100
2  2 290  0  0   0   0   0
3  3  38  0  38  38  25  25
4  4 129  0  0   127 0   127
5  5   0  0  0   38  0   0
6  6 290 290 98  78  98  9

我希望能够删除每一行中z_x的重复值,并用0NA替换任何重复值,但保留原始行和列(即不删除)。这里的0不算作重复值,它们是缺失值。列内的重复值是可以的。我期望的输出如下:

   id z_1 z_2 z_3 z_4 z_5 z_6
1  1  100 20  0   0   23  0
2  2  290 0   0   0   0   0
3  3  38  0   0   0   25  0
4  4  129 0   0   127 0   0
5  5   0  0   0   38  0   0
6  6  290 0   98  78  0   9

我并不关心在 z_x 中值的顺序,所以如果它们被移动了也没关系。有没有一种有效的方法来实现这一点,最好是使用 tidyverse 的某种方式?我知道我可以把数据长表格化并删除重复行,但我的数据集非常大,我正在寻找一种无需长表格化就能完成的方法。


我正在努力想到比旋转更快且去重的方法。即使您没有明确地进行旋转,您仍然需要拆分/循环每个行标识符,如果您有大量行,则通常会非常慢。 - thelatemail
2个回答

6

使用Base R和apply函数的方法:

cols <- grep('z_\\d+', names(dat))
dat[cols] <- t(apply(dat[cols], 1, function(x)  replace(x, duplicated(x), 0)))

#  id z_1 z_2 z_3 z_4 z_5 z_6
#1  1 100  20   0   0  23   0
#2  2 290   0   0   0   0   0
#3  3  38   0   0   0  25   0
#4  4 129   0   0 127   0   0
#5  5   0   0   0  38   0   0
#6  6 290   0  98  78   0   9

tidyverse 的方式可以通过使用 pmap 实现而无需进行重塑:

library(tidyverse)

dat %>%
  mutate(result = pmap(select(., matches('z_\\d+')), ~{
    x <- c(...)
    replace(x, duplicated(x), 0)
    })) %>%
  select(id, result) %>%
  unnest_wider(result)

由于@thelatemail所执行的测试表明,改变数据形状比逐行处理数据更好,因此您可能需要考虑这种方法。

dat %>%
  pivot_longer(cols = matches('z_\\d+')) %>%
  group_by(id) %>%
  mutate(value = replace(value, duplicated(value), 0)) %>%
  pivot_wider()

这正是我一直在寻找的,谢谢!我的数据框有数百万行,但只有大约30列需要去重 - 你知道其中哪种方法会比另一种更快吗? - linkspan
1
@Ian - 经过快速测试,pmap解决方案比基本的apply循环慢得多。 - thelatemail
2
从一个快速测试中可以得出,在2M条记录上进行长宽比再次操作少于1秒钟,apply需要15秒钟,pmap我在一分钟后杀掉了它,但是它还没有完成。 - thelatemail
我尝试使用稍微不同的方法来获取不同的 id/value 对 - 我认为这应该比需要分组更快,例如:dat %>% pivot_longer(starts_with("z_")) %>% distinct(id, value, .keep_all=TRUE) %>% pivot_wider(values_fill=0) - thelatemail

1
这个解决方案不是tidyverse,但希望足够简单。

duplicated()函数可以实现您想要的功能。您可以使用apply()函数按行提供数据给duplicated()

dat[t(apply(dat, MARGIN = 1, duplicated))] <- 0

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