在序列中删除/合并连续重复的值

23

我有以下的数据框

a a a b c c d e a a b b b e e d d

需要的结果应该是

a b c d e a b e d 

这意味着相邻的两行不能有相同的值。如何在不使用循环的情况下完成。

由于我的数据集非常庞大,循环执行需要大量时间。

数据框的结构如下所示:

a 1 
a 2
a 3
b 2
c 4
c 1
d 3
e 9
a 4
a 8
b 10
b 199
e 2
e 5
d 4
d 10

结果:

a 1 
b 2
c 4
d 3
e 9
a 4
b 10
e 2
d 4

应该删除整行。

5个回答

28

有一种简单的方法是使用rle

以下是你的样本数据:

x <- scan(what = character(), text = "a a a b c c d e a a b b b e e d d")
# Read 17 items

rle函数返回一个list,其中包含两个值:重复的次数("lengths")和这段重复的值("values")。

rle(x)$values
# [1] "a" "b" "c" "d" "e" "a" "b" "e" "d"

更新:针对 data.frame

如果您正在使用 data.frame,请尝试以下方法:

## Sample data
mydf <- data.frame(
  V1 = c("a", "a", "a", "b", "c", "c", "d", "e", 
         "a", "a", "b", "b", "e", "e", "d", "d"),
  V2 = c(1, 2, 3, 2, 4, 1, 3, 9, 
         4, 8, 10, 199, 2, 5, 4, 10)
)

## Use rle, as before
X <- rle(mydf$V1)
## Identify the rows you want to keep
Y <- cumsum(c(1, X$lengths[-length(X$lengths)]))
Y
# [1]  1  4  5  7  8  9 11 13 15
mydf[Y, ]
#    V1 V2
# 1   a  1
# 4   b  2
# 5   c  4
# 7   d  3
# 8   e  9
# 9   a  4
# 11  b 10
# 13  e  2
# 15  d  4

更新2

“data.table”包有一个函数rleid,可以让您轻松地执行此操作。使用上面的mydf,尝试:

library(data.table)
as.data.table(mydf)[, .SD[1], by = rleid(V1)]
#    rleid V2
# 1:     1  1
# 2:     2  2
# 3:     3  4
# 4:     4  3
# 5:     5  9
# 6:     6  4
# 7:     7 10
# 8:     8  2
# 9:     9  4

我该如何在data.frame中使用它? 如果我在列表中使用它,那么我必须再次将这些唯一值与旧的data.frame映射,但由于长度较短,无法进行映射。 我的目的是,每当我在特定列的连续行中获得相同的值时,删除数据框的整行。 - Amarjeet
很好的回答!对于你的第一个“data.frame”解决方案,我发现我需要“X <- rle(as.numeric(mydf$V1))”,因为“V1”是一个因子。另外一点:我发现在某些情况下,“cumsum(X$lengths)”可以完成任务,具体取决于您希望保留哪些重复行(从上到下还是从下到上),您确认吗? - PatrickT

13
library(dplyr)
x <- c("a", "a", "a", "b", "c", "c", "d", "e", "a", "a", "b", "b", "b", "e", "e", "d", "d")
x[x!=lag(x, default=1)]
#[1] "a" "b" "c" "d" "e" "a" "b" "e" "d"

编辑: 对于data.frame

  mydf <- data.frame(
    V1 = c("a", "a", "a", "b", "c", "c", "d", "e", 
         "a", "a", "b", "b", "e", "e", "d", "d"),
    V2 = c(1, 2, 3, 2, 4, 1, 3, 9, 
         4, 8, 10, 199, 2, 5, 4, 10),
   stringsAsFactors=FALSE)

dplyr 的解决方案只需要一行代码:

mydf %>% filter(V1!= lag(V1, default="1"))
#  V1 V2
#1  a  1
#2  b  2
#3  c  4
#4  d  3
#5  e  9
#6  a  4
#7  b 10
#8  e  2
#9  d  4

附言

lead(x,1) ,由@Carl Witthoft 提出,是反向迭代。

leadit<-function(x) x!=lead(x, default="what")
rows <- leadit(mydf[ ,1])
mydf[rows, ]

#   V1  V2
#3   a   3
#4   b   2
#6   c   1
#7   d   3
#8   e   9
#10  a   8
#12  b 199
#14  e   5
#16  d  10

这里您是否在使用 dplyr::lag?我尝试了一下您的代码,使用 stats::lag 在一个干净的会话中并不奏效。 - talat

6

使用基础 R,我喜欢有趣的算法:

x <- c("a", "a", "a", "b", "c", "c", "d", "e", "a", "a", "b", "b", "b", "e", "e", "d", "d")

x[x!=c(x[-1], FALSE)]
#[1] "a" "b" "c" "d" "e" "a" "b" "e" "d"

2
同样可以使用索引而不是 tail,类似于 x[x != c(x[-1], FALSE)] - David Arenburg
我很难将其适应到“data.frame”内的因子上。在数据框中,rhs的“FALSE”将向量限制为“integer”类型,因此在lhs中,“x”必须受到这种限制才能进行比较,并使用“as.integer()”。如果我错了,请纠正我! - PatrickT
抱歉,但这非常不清楚...如果您在此类主题上遇到问题,可以在SO上提出问题,附上您的输入、输出和迄今为止所做的工作。 - Colonel Beauvel

3

虽然我非常喜欢...嗯,热爱rle,但是这里还有一个问题:

编辑:无法确定dplyr出了什么问题,所以我使用了dplyr::lead。我在OSX、R3.1.2以及从CRAN上获取的最新dplyr版本。

xlet<-sample(letters,1e5,rep=T)
rleit<-function(x) rle(x)$values
lagit<-function(x) x[x!=lead(x, default=1)]
tailit<-function(x) x[x!=c(tail(x,-1), tail(x,1))]



  microbenchmark(rleit(xlet),lagit(xlet),tailit(xlet),times=20)
Unit: milliseconds
         expr      min       lq   median       uq      max neval
  rleit(xlet) 27.43996 30.02569 30.20385 30.92817 37.10657    20
  lagit(xlet) 12.44794 15.00687 15.14051 15.80254 46.66940    20
 tailit(xlet) 12.48968 14.66588 14.78383 15.32276 55.59840    20

lagit 对你有用吗?它对我没有返回任何内容。也许这就是为什么它是最快的原因... - David Arenburg
我认为在加载 dplyr 后需要重新运行此命令,因为我怀疑 lagit 目前没有任何作用,因此非常快速。 - David Arenburg
1
你的基准测试中的 tailit 函数在向量中缺少最后一个 "d"。也许可以更新为答案中的 x[x!=c(x[-1], FALSE)] - talat
似乎leadlag慢一些,为什么会这样呢?https://github.com/hadley/dplyr/blob/master/R/lead-lag.R - Khashaa
在R中是否有任何类似于LAG函数的等效函数??(https://dev59.com/BnrZa4cB1Zd3GeqP1VvM)我已经尝试过了,但没有成功。 - Amarjeet
显示剩余2条评论

0

Tidyverse解决方案:

x <- scan(what = character(), text = "a a a b c c d e a a b b b e e d d")
x <- tibble(x)
x |> 
 mutate(id = consecutive_id(x)) |> 
 distinct(x, id)

此外,如果有另一列与连续值列相关联的列y,则此解决方案允许一定的灵活性:
x <- scan(what = character(), text = "a a a b c c d e a a b b b e e d d")
x <- tibble(x, y = runif(length(x)))
x |> 
    group_by(id = consecutive_id(x)) |> 
    slice_min(y)

我们可以在不同的切片函数之间进行选择,例如slice_max、slice_min、slice_head和slice_tail。

这个Stack Overflow线程出现在R4DS的第二版中,是本书中关于数字章节的一部分。


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