R - 删除连续(仅)重复项

6

我需要根据给定列中的值的重复性从数据框中删除行,但仅限于连续重复的行。 例如,对于以下数据框:

df = data.frame(x=c(1,1,1,2,2,4,2,2,1))
df$y <- c(10,11,30,12,49,13,12,49,30)
df$z <- c(1,2,3,4,5,6,7,8,9)

x  y z
1 10 1
1 11 2
1 30 3
2 12 4
2 49 5
4 13 6
2 12 7
2 49 8
1 30 9

我需要删除x列中连续重复的行,保留最后一个重复的行,并保持数据框的结构:

x  y z
1 30 3
2 49 5
4 13 6
2 49 8
1 30 9

根据help和其他帖子的指示,我尝试使用duplicated函数:

df[ !duplicated(x,fromLast=TRUE), ] # which gives me this:
      x  y  z
1     1 10  1
6     4 13  6
7     2 12  7
9     1 30  9
NA   NA NA NA
NA.1 NA NA NA
NA.2 NA NA NA
NA.3 NA NA NA
NA.4 NA NA NA
NA.5 NA NA NA
NA.6 NA NA NA
NA.7 NA NA NA
NA.8 NA NA NA

不确定为什么会在末尾出现NA行(之前测试类似的表格并没有发生),但是对于数值只部分起作用。

我还尝试了使用 data.table 包,如下所示:

library(data.table)
dt <- as.data.table(df)           
setkey(dt, x)                    
dt[J(unique(x)), mult ='last'] 

功能很棒,但它会从数据框中删除所有重复项,而不仅仅是连续的重复项,得到的结果类似于:

x  y z
1 30 9
2 49 8
4 13 6

请谅解如果发重复帖子。我尝试了一些建议,但没有一个能够仅消除连续的内容。

非常感谢任何帮助。


2
也许是 inx <- with(df, c(FALSE, diff(x) != 0)); df[c(diff(cumsum(inx)) != 0, TRUE), ]。(我不确定我喜欢它,diff 太多了。) - Rui Barradas
@Rui,也许不够优雅,但能完成任务,谢谢!c(FALSE, diff(x) !=0)是什么意思?抱歉,我是新手,正在努力学习。如果您没有时间详细解释也没关系。谢谢。 - ebb
4个回答

7
怎么样:
df[cumsum(rle(df$x)$lengths),]

解释:

rle(df$x)

给出了变量x中连续重复序列的长度和值。然后:

rle(df$x)$lengths

提取长度。最后:
cumsum(rle(df$x)$lengths)

该函数返回用[可以选择的行索引。

编辑:以下是不同答案的microbenchmark,其中rle为本人提供的答案,consec为@James提供的最基本直接答案,是我会“接受”的答案,dp为@Nik提供的dplyr答案。

#> Unit: microseconds
#>    expr       min         lq       mean     median         uq        max
#>     rle   134.389   145.4220   162.6967   154.4180   172.8370    375.109
#>  consec   111.411   118.9235   136.1893   123.6285   145.5765    314.249
#>      dp 20478.898 20968.8010 23536.1306 21167.1200 22360.8605 179301.213
比我想象的表现要好。

谢谢!我之前见过 rle 用于其他目的。有些人说它可能在大数据集上失败,但在这里它起作用了! - ebb
1
我的电脑可以在大约10秒内完美地运行rle(sample(1:10000, 1e8, repl=TRUE))。因此,我认为rle不会是问题所在。 - ngm
感谢您提供的性能信息和解释,我学到了很多。 - ebb
感谢您使用 microbenchmark。对我来说是一次很有启发性的经历。 - Nik Muhammad Naim

6

您只需要检查是否存在一个数字后面没有重复的,即x [i + 1]!= x [i],请注意最后一个值始终存在。

df[c(df$x[-1] != df$x[-nrow(df)],TRUE),]
  x  y z
3 1 30 3
5 2 49 5
6 4 13 6
8 2 49 8
9 1 30 9

我尝试着做了类似的事情 df[!(x[i] == x[i+1])] 但是我在语法上迷失了。非常整洁简单,谢谢。 - ebb

2

我想到了一个使用dplyr的廉价解决方案:

方法:

library(dplyr)
df %>% 
  mutate(id = lag(x, 1), 
         decision = if_else(x != id, 1, 0), 
         final = lead(decision, 1, default = 1)) %>% 
  filter(final == 1) %>% 
  select(-id, -decision, -final)

输出:

  x  y z
1 1 30 3
2 2 49 5
3 4 13 6
4 2 49 8
5 1 30 9

这甚至适用于数据在底部具有相同的x值的情况。
新输入:
df2 <- df %>% add_row(x = 1, y = 10, z = 12)
df2

   x  y  z
1  1 10  1
2  1 11  2
3  1 30  3
4  2 12  4
5  2 49  5
6  4 13  6
7  2 12  7
8  2 49  8
9  1 30  9
10 1 10 12

使用相同的方法:

df2 %>% 
  mutate(id = lag(x, 1), 
         decision = if_else(x != id, 1, 0), 
         final = lead(decision, 1, default = 1)) %>% 
  filter(final == 1) %>% 
  select(-id, -decision, -final)

新输出:

  x  y  z
1 1 30  3
2 2 49  5
3 4 13  6
4 2 49  8
5 1 10 12

我没想到可以这样使用 dplyr::,这将非常有帮助,谢谢。有个无关的问题,提前抱歉, %>% 是什么意思?我经常看到它,但好像找不到答案。 - ebb

1
这里是一个与编程有关的data.table解决方案。诀窍在于使用shift函数创建x的偏移版本,并将其与x进行比较。请保留HTML标签。
library(data.table)
dattab <- as.data.table(df)
dattab[x != shift(x = x, n = 1, fill = -999, type = "lead")] # edited to add closing )

这样,您将比较每个x的值与其紧随其后的值,并丢弃它们匹配的部分。确保将fill设置为不在x中的内容,以便正确处理最后一个值。

我很好奇如何使用data.table::实现这一点,谢谢。这似乎是@Nik提供的dplyr::响应的data.table::版本。从未想过移动/滞后列。这是一个不错的迂回方法需要记在心里。 - ebb
1
我认为这与@James的逻辑相同。另一个使用运行长度编码(即rle)的data.table解决方案是dattab[, grp:=rleid(x)][,.SD[.N], by=grp][, -"grp"]。它的作用是:基于运行长度组添加名为grp的变量。然后在每个组中选择最后一行。然后删除grp变量。 - ngm
相同的逻辑,是的。我必须承认在发帖之前没有仔细阅读他的代码。我喜欢您的解决方案。我不知道“rleid”这个函数。 - Calbers

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